机器代码生成过程概述
指针的表示
将代码传递到目标文件时,指针将以移动方式传递。 反序列化代码确保指向这些常量之一的任何对象都将被重新创建,并将包含正确的运行时指针。
否则,它们将作为文字常量输出。
要输出其中一个对象,请调用函数’literal_pointer_val'。 它将跟踪Julia和全局LLVM对象的值,确保它们对当前运行时环境和反序列化后都有效。
当您将对象传输到文件时,这些全局对象将作为引用存储在一个大的"gvals"表中。 在这种情况下,解串器可以通过索引引用它们,并为它们的恢复实现自定义手动机制,类似于全局偏移表(GOT)。
函数指针的处理方式类似。 它们作为值存储在一个大的"fvals"表中。 与全局对象一样,反序列化器可以通过索引引用它们。
请注意,'extern’函数是使用链接器中通常的符号解析机制单独处理的,带有名称。
请注意,'ccall’功能也使用手动使用的GOT表和过程布局表(PLT)单独处理。
中间值的表示
这些值在`jl_cgval_t`结构中传递。 它表示一个R值,并包含足够的信息来确定如何分配它或将其转移到某个地方。
值是使用其中一个辅助构造函数创建的,通常如下:mark_julia_type'(用于直接值)和`mark_julia_slot
(用于指向值的指针)。
'Convert_julia_type’函数可以在任何两种类型之间进行转换。 它返回一个带有’cgval的R值。typ’具有值’typ'。 它将通过创建堆指针,分配堆栈副本以及根据需要计算标记连接来更改表示形式,从而将对象带到所需的表示形式。
相反’update_julia_type’函数将更改’cgval。只有当它可以以零成本完成(即不创建代码)时,才键入`to`type`。
协会的代表
联合的输出类型可以通过标记类型表示在堆栈上分配。
以下简单过程用于处理标记连接。
-
标记-类型
-
本地负荷
-
商店-本地
-
伊萨
-
是
-
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*'可以有任何大小。 唯一的限制是它必须足够大,以包含选择器当前指定的数据。 它可能不够大,无法容纳可以根据union类型的关联字段存储在其中的所有类型的union。 复制应该小心。
提供专门的呼叫协议签名
`Jl_returninfo_t’对象描述了任何被调用对象的调用约定的详细信息。
如果方法的任何参数或返回类型可以以扩展形式表示,并且该方法不使用可变数量的参数,则将根据其"specTypes"和"rettype"字段为其提供优化的调用约定签名。
一般原则如下。
-
原始类型在整数或浮点数的寄存器中传递。
-
VecElement类型在向量寄存器中传递。
-
结构在堆栈上传递。
-
返回值的处理类似于参数,其大小限制将使用隐藏的sret参数返回。
一般逻辑使用’get_specsig_function`和`deserves_sret’实现。
此外,如果返回的类型是一个联合,它可以作为一对值(指针和标签)返回。 如果联合值可以放在堆栈上,则足够的存储空间也将作为隐藏的第一个参数传递。 返回的指针将指向什么-这个空间,一个打包的对象,甚至其他永久内存取决于被调用的对象。