原生二进制接口
此篇定本基线 LLVM 原生与 C 运行时之约。整数六十四位,值器一百二十八位;WasmGC 别制。所据见基线与实现依据。
原一:目标与基本类型
语言函数为 void(i128,i128),LLVM 约 ccc,先帧址,后堆分配址;C 为 void 函数(__uint128_t 栈,__uint128_t 堆)。不可擅换为两裸指针,亦不可谓 {i64,i64} 与 i128 必同寄存器之配。宜生 LLVM 而令目标 clang 降之。
今原生以小端、六十四位指针为域;槽步长十六字节。对齐、重定位及参数寄存器从目标工具链,须实机验之。异架构对象不得直链;Mach-O 所显前导 _ 非 LLVM 名之正文。
原二:值之位
常规值 V(p,t,s,n)=uint128(uint64(p))|(t<<64)|(s<<72)|(n<<80),先升一百二十八位而后移。取 p=low64(v)、t=(v>>64)&15、s=(v>>72)&15、n=(v>>80)&0xffffffff。
| 位 | 小端字节偏移 | 义 |
|---|---|---|
| 0–63 | 0–7 | 数、浮点位或地址 |
| 64–71 | 8 | 类型区,今取低四位 |
| 72–79 | 9 | 子类型区,运行时今取低四位 |
| 80–111 | 10–13 | 长度或构造器号 |
| 112–127 | 14–15 | 保留;常规清零,负整有异 |
| 号 | 义 | 长度/子类与所指 |
|---|---|---|
| 0 | 空/单位 | 常规全零 |
| 1 | 元组、闭包、引用、引用数组 | 长为槽数;直指连续值,无独立头 |
| 2 | 整数 | 低六十四位有符号解 |
| 3 | 小数 | 低六十四位作 double |
| 4 | 静态串/字节 | 长含末零;直指字节 |
| 5 | 爻 | 低位常为 0/1 |
| 6 | 函数代码 | 指 void(i128,i128) |
| 7 | 静态对象地址 | 如全局 i128 之址 |
| 8 | 豫言栈址 | 栈不迁 |
| 9 | GC 转发址 | 指新堆对象首槽 |
| 10 | 堆串/字节 | 长含末零,指头槽 |
| 11 | 字节头 | 居堆首,长含末零,后随原字节 |
| 12 | 通用运行指针 | 分配游标等,不得入 GC 根 |
| 13 | 构造器实例 | 子类载槽数,长度区载编号,指载荷;无载则空址 |
| 14 | C 栈/局部游标 | 内用,不存语言持久数据 |
| 15 | 未用 | 无 |
GC 惟迁 1、10、13;11 令扫描越字节,9 惟转发所用。普通对象首项不可伪造 9;堆指针分类断言不见 12。
原二甲:非一形之值
编译器直生阳阴为原始 i128 1/0,不带爻标签;比较及 C 构爻则带 5。条件只取低一位。故不得假定所有语言爻均可过 C 标签断言。
C 构负整先有符号升无符号一百二十八位,再清类型字节而置 2,式为 (uint128(n)&~(255<<64))|(2<<64);负数高位仍多一。LLVM 减法则 i64 运算后零扩展而加标签。同数可异高位,比较须取低六十四位。
构造器前端容二百五十载荷,后端写八位;C 子类却只读四位,setter 限十五。故超过十五载荷,不可许为旧原生 GC 安全之物。改掩码而不易基线,非全兼容;修之须新约或桥接。
原三:诸对象
元组、闭包、引用
非空元组占 m 槽,值为 V(p,1,0,m),第 k 项在 p+16*k,k 自零始。生成器空元组降单位;C 空元组可为类型 1、长度零、空址,不作寻常语言元组互换之约。
闭包亦元组,首项 V(code,6,0,0),余为依本闭包私序所捕之值。调用者惟读首项,并传全闭包为隐藏实参;不得窥他方捕获序。无捕获亦占一槽;类型隐参已擦。
引用为一槽元组。C 写引用次序 (新值,地址);引用数组亦元组,写次序 (新值,序数,地址)。不能因库表层次序而易 C 次序。今无分代写屏障,别名同观其改。
构造器
载荷 m 项,值 V(p,13,m,c)。c 自一起,同文件同返回类型头之构造声明各计;异类型不共计。零参为 V(0,13,0,c),不分配。值不藏编译期类型名,赖静态检查保其用。
缀、罄 依类型构造器之约;C 数组转同构列 则产 [元素元组,整数长度]。二者不可混。
串与字节
逻辑长 L,存储长 N=L+1,末零必有而不入逻辑内容。堆串占 1+ceil(N/16) 槽;头 V(0,11,0,N),后槽始为字节;外值 V(header,10,0,N)。静态串 V(data,4,0,N) 无头且长存。GC 复制整块,不扫描其字节。
内容容零,依长度之术可存之;C %s、strlen 仍止于零,非诸文本 API 悉保内零。复制字节之长不含末零;复制 C 字符串之长含末零而验之。逻辑长须小于 UINT32_MAX。
原四:栈帧
| 槽 | 字节偏移 | 所存 |
|---|---|---|
| 0 | 0 | 祖先帧,类型 8 |
| 1 | 16 | 返回函数代码,类型 6 |
| 2 | 32 | 返回续延号,整数 |
| 3 | 48 | 本帧接收之结果,初调置单位 |
| 4 | 64 | 执行块号,初入为整数 1 |
| 5… | 80… | 运行参数,后为存活局部 |
闭包调则槽五 self,槽六首显参;直接调不传 self,槽五首参。以入口契约定之,不以机器签名猜之。 入口号一,私有续延自编号;表零为分派入口,正常初调不用零。他方所存返回号,须原样传回。
原四甲:源足参,机逐施
源验足数,隐参擦除却把显参脊逐项化一参数应用,显式 λ 亦先为一参数。故 f 于 a 于 b 先以 [f,a] 施旧闭包得 g,再以 [g,b] 施之;不可一帧塞 [f,a,b]。中间闭包在机中,非许源偏应用。
后端 IR 容多参数,实数须取其接口,不猜源箭头。独立器外部宜保逐参包装,内部可合并。减法之例,源两参、机两调,先受被减数而返闭包,后受减数而得差。
普通调用
当前帧 S 占 q 槽,新帧 T=S+16*q。先把调用后活值存于 S;再写 T[0]=编码S、T[1]=当前代码、T[2]=返回号k、T[3]=0、T[4]=整数1,实参依次入 T[5+i]。写前验空间。
超过堆阈值则行原六安全点;迁堆后重读帧中参数与闭包,代码址不迁。末以 musttail call void 目标(i128 编码T,i128 分配值),紧接 ret void;不借 C 栈保存语言续延。
返回
先取 P=S[0]、F=S[1]、K=S[2],写 P[3]=结果、P[4]=K,以 P 与分配值尾调 F,再 ret void。旧注谓写零项,与今生成器不合;误写则毁帧链。恢复块取槽三为结果,重载所存局部。
尾调用
复用当前帧,存槽零、一、二,重置三、四,改实参,行安全点而尾跳。先求并暂存新参数,勿以覆盖毁其来源;多参亦验空间。
原五:入口与公开符号
值通常有零初值 i128 全局;后端 IR 可承接不带 self 的直接代码入口;作函数值时须闭包包装。源身份 P 通常为包上下文所得绝对路径。
E 逐字符把 -、。、.、/、*、%、+ 改 _,余不动。不去后缀,不散列,不作 Unicode 正规化。
M(P)=豫言模块_ + E(P)。- 值名
M(P)+_定义_+E(名)+_+十进制序。 - 直接函数名
M(P)+_函数_+E(名)+_+十进制序。 - 模块入口
M(P)+_计算函数,无 self,无源参。 - 程序入口
豫言_程序入口,亦为void(i128,i128)。
/甲/乙。豫 之减一、序四,得 豫言模块__甲_乙_豫_定义_减一_4。转义非单射,如 甲-乙 与 甲_乙 可同名;不得暗易算法。引号等特殊名另验其 LLVM 转义范围。
定序为检查后与运行时并列定义之零基索引,不是行号或仅运行值之排名。签名先并,类型、空间占位、重诵可改其序。类型内构造号另计。替旧对象宜携依赖接口全序;唯有源码,则须同法展开。匿名名内部计数不可臆猜。
模块入口依次登记根址、求值、写全局;直接声明惟给代码。程序依依赖顺序各调模块一次。模块本身无一次性屏障,不任意重入。
原六:分配与 GC
可见全局 豫言_栈指针、豫言_当前分配指针、豫言_当前堆垃圾回收阈值 皆 C 豫言值*,LLVM external global i128*。函数藏局部分配游标,外调前同步游标与帧至全局,外调后复读游标。
元组于游标顺写,后移槽数;无独立头,不逐次自动回收。C 分配亦然;其局部裸址与 SSA 值未尽登根,故不可在任意 C 原语中迁堆。malloc 外物不可假为旧堆值。
语言调用前,实参及存活值已入帧,游标严格大于阈值则调用 豫言_函数调用前垃圾回收(新帧,整数槽数)。此术暂设全局栈针至新帧末,回收而复至帧首;改根原位,不返回新参数表。
回收之法:先处理所登记值地址及 [栈始,全局栈针),不扫描 C 栈或寄存器。旧堆非空 1/10/13 值须迁;见首槽 9 则循已迁址,否则按头之长复制全块,以旧首槽记转发,并保原标签、子类、长度而改地址。广度扫新堆,普通槽递归;遇 11 则越 1+ceil(N/16) 槽。尽则释旧堆、改游标与阈值。存活比逾 0.45,次轮尝试倍增;阈值容量九成。容量术可异,根更新之约不可异。
不支持任意内部字段指针;根地址须长存,不可登记将亡 C 局部。环与共享以转发保之,不得复制一引用为两身份。所有被扫描槽须初始化为有效值,游标标签十二不得在其中。
原七:C 外调
通常声明 i128 @符号(i128,…),普通 call。源值不自动解箱为 C 整数或 char*,wrapper 自解自装。元容器为零参,单变量一参,元组变量列多参;复杂式先虑成变量。
特例:根注册实为 void(豫言值*),旧 LLVM 却作 i128(i128) 并弃结果;报错中止实返 uint64_t 而不会正常继续。前者依平台,非通用正确签名。新器可按实际签名解静态址为裸指针而注册;不可使用虚构返回寄存器。逐条复刻旧 LLVM 则存错配,须精确目标实测。
诸形参次序、末零之长、返回宽度,见接口表。一符不可各处异参数数目而仍许互通。
原八:异常与启动
当前异常处理器为已登记根之闭包。默认从槽六取串,印而退出一。获取术返全闭包,设置术替之而返单位。续延异常表自一计,以类型八存帧;设置与获取编号用于动态返回。不可用任意 C longjmp 越过豫言帧协议;超帧寿命之恢复无稳定保证。
启动:存命令行、处理运行时参、可选入口初始化(默认堆)、设默认异常处理器并登根、设随机种、分配栈、立根帧、入程序。根帧为 [自身,退出续延,整数1,单位,整数1];退出续延验根与块一而返,正常进程状态零。显退与未捕异常别行。main 仍为 C 签名。
原生默认栈三千二百余万槽,即 512 MiB。原生 YY_GC_INITIAL_STACK_SIZE_MB 实按值乘 1024² 槽,非其名义 MiB;WASI 按 MiB 换算。堆变量 YY_GC_INITIAL_HEAP_SIZE_MB、YY_GC_MAX_HEAP_SIZE_MB 按字节除十六。欲修配置之误,须明非全兼容。