Smalltalk:原语是如何实现的?

Smalltalk: How primitives are implemented?

我知道一切皆对象,在 Smalltalk 中,您可以向对象发送消息来完成几乎所有事情。 现在我们如何实现一个对象(内存表示和基本操作)来表示原始数据类型?例如,整数的 + 是如何实现的?

我查看了 Smalltalk 的源代码并在 Smallint.st 中找到了这个。有人可以解释这段代码吗?

 + arg [
    "Sum the receiver and arg and answer another Number"

    <category: 'built ins'>
    <primitive: VMpr_SmallInteger_plus>
    ^self generality == arg generality 
        ifFalse: [self retrySumCoercing: arg]
        ifTrue: [(LargeInteger fromInteger: self) + (LargeInteger fromInteger: arg)]
    ]

这里是上面代码的 link: https://github.com/gnu-smalltalk/smalltalk/blob/62dab58e5231909c7286f1e61e26c9f503b2b3df/kernel/SmallInt.st

从概念上讲,原始 方法是由虚拟机 (VM) 而非常规 Smalltalk 代码实现的行为片段(例程)。

当 Smalltalk 编译器找到语句 <primitive: ...> 时,它会将此解释为一种特殊类型的方法,其参数(在您的例子中为 VMpr_SmallInteger_plus)表示目标例程在 VM 中的整数索引。

从这个意义上说,原语是一个全局例程,不绑定到任何特定 class 的 MethodDictionary。原始逻辑适用于某些 classes 的接收者和参数,这就是为什么它必须检查接收者和参数(如果有)是否符合其要求。如果不是,原语 失败 并且在这种情况下,控制流向 <primitive: ...> 语句之后的 Smalltalk 代码。否则原语 成功 并且下面的 Smalltalk 代码不会被执行。另请注意,编译器不允许在 <primitive:...> 句子上方出现临时声明以外的任何 Smalltalk 代码。

在您的示例中,如果参数 arg 不是预期的 class(大概是 SmallInteger),例程将放弃尝试将其求和给接收者并委托将操作解析为 Smalltalk 代码。

如果参数恰好是 SmallInteger,原语将计算结果(使用 VM 中保存的例程)并用它回答。

我还没有看到这个原语的代码,但是如果总和的结果不适合SmallInteger,原语也可能会失败,在这种情况下,接收者和参数都会失败将转换为 LargeIntegers 并且添加将在适当的 class(LargePositiveIntegerLargeNegativeInteger)的 #+ 方法中进行。

Smalltalk 代码的另一个分支允许在 SmallInteger 和任何其他类型的对象之间实现多态求和。例如,如果您评估 3 + 4.0,Smalltalk 代码的这一部分将发生,因为在这种情况下,参数是 Float。如果您评估 3 + (4 / 3)

,则会发生类似的情况