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
对象描述了任何可调用的调用约定细节。
如果方法的任何参数或返回类型可以以非包装形式表示,并且该方法不是可变参数,它将根据其 specTypes
和 rettype
字段获得优化的调用约定签名。
一般原则是:
- 原始类型通过整数/浮点寄存器传递。
- VecElement 类型的元组通过向量寄存器传递。
- 结构体在栈上传递。
- 返回值的处理方式与参数类似,存在一个大小阈值,超过该阈值后将通过一个隐藏的 sret 参数返回。
此逻辑的总实现由 get_specsig_function
和 deserves_sret
完成。
此外,如果返回类型是一个联合体,它可以作为一对值(一个指针和一个标签)返回。如果联合体的值可以在栈上分配,那么将会作为一个隐藏的第一个参数传递足够的存储空间。返回的指针指向这个空间、一个装箱对象,甚至其他常量内存,取决于被调用者。