High-level Overview of the Native-Code Generation Process

Representation of Pointers

当将代码发射到对象文件时,指针将作为重定位发射。反序列化代码将确保任何指向这些常量之一的对象被重新创建,并包含正确的运行时指针。

否则,它们将作为字面常量被输出。

要发出这些对象之一,请调用 literal_pointer_val。它将处理跟踪 Julia 值和 LLVM 全局,确保它们在当前运行时和反序列化后都是有效的。

当被发射到目标文件中时,这些全局变量作为引用存储在一个大的 gvals 表中。这允许反序列化器通过索引引用它们,并实现类似于全局偏移表(GOT)的自定义手动机制来恢复它们。

函数指针的处理方式类似。它们作为值存储在一个大的 fvals 表中。与全局变量一样,这允许反序列化器通过索引引用它们。

请注意,extern 函数通过链接器中的常规符号解析机制单独处理,使用名称。

请注意,ccall 函数也会通过手动的全局偏移表 (GOT) 和过程链接表 (PLT) 单独处理。

Representation of Intermediate Values

值在 jl_cgval_t 结构中传递。这表示一个 R 值,并包含足够的信息来确定如何将其分配或传递到某个地方。

它们是通过其中一个辅助构造函数创建的,通常是:mark_julia_type(用于即时值)和mark_julia_slot(用于指向值的指针)。

函数 convert_julia_type 可以在任意两种类型之间转换。它返回一个 R 值,cgval.typ 设置为 typ。它会将对象转换为请求的表示形式,必要时创建堆框、分配栈副本,并计算标记联合以更改表示形式。

相比之下,update_julia_type 仅在可以以零成本(即不生成任何代码)的情况下,将 cgval.typ 更改为 typ

Union representation

推断的联合类型可以通过标记类型表示法进行栈分配。

需要能够处理标记联合的原始例程是:

  • 标记类型
  • 加载本地
  • store-local
  • isa
  • emit_typeof
  • emit_sizeof
  • 盒装
  • 开箱
  • 专用 cc-ret

其他一切都应该可以通过使用这些原语来实现联合拆分,从而在推理中处理。

标记联合的表示形式为一对 < void* union, byte selector >。选择器的固定大小为 byte & 0x7f,并将前 126 个 isbits 进行联合标记。它记录了 isbits 对象内部类型联合的基于一的深度优先计数。索引为零表示 union* 实际上是一个标记的堆分配 jl_value_t*,需要作为普通的盒装对象处理,而不是作为标记联合。

选择器的高位(byte & 0x80)可以被测试以确定 void* 是否实际上是一个堆分配的(jl_value_t*)盒子,从而避免重新分配盒子的成本,同时保持根据低位有效处理联合拆分的能力。

保证 byte & 0x7f 是类型的精确测试,如果值可以由标签表示 - 它永远不会被标记为 byte = 0x80。在测试 isa 时,不必再测试类型标签。

union* 内存区域可以以 任何 大小分配。唯一的限制是它必须足够大,以容纳当前由 selector 指定的数据。它可能不足以容纳根据相关的 Union 类型字段可以存储的所有类型的联合体。在复制时请格外小心。

Specialized Calling Convention Signature Representation

一个 jl_returninfo_t 对象描述了任何可调用的调用约定细节。

如果方法的任何参数或返回类型可以以非包装形式表示,并且该方法不是可变参数,它将根据其 specTypesrettype 字段获得优化的调用约定签名。

一般原则是:

  • 原始类型通过整数/浮点寄存器传递。
  • VecElement 类型的元组通过向量寄存器传递。
  • 结构体在栈上传递。
  • 返回值的处理方式与参数类似,存在一个大小阈值,超过该阈值后将通过一个隐藏的 sret 参数返回。

此逻辑的总实现由 get_specsig_functiondeserves_sret 完成。

此外,如果返回类型是一个联合体,它可以作为一对值(一个指针和一个标签)返回。如果联合体的值可以在栈上分配,那么将会作为一个隐藏的第一个参数传递足够的存储空间。返回的指针指向这个空间、一个装箱对象,甚至其他常量内存,取决于被调用者。