豫言 / 技术规范

此篇据 4cdeca1 之实现。所据及适用之界,见基线之篇

原生二进制接口

此篇定本基线 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)&15s=(v>>72)&15n=(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 %sstrlen 仍止于零,非诸文本 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]=编码ST[1]=当前代码T[2]=返回号kT[3]=0T[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_MBYY_GC_MAX_HEAP_SIZE_MB 按字节除十六。欲修配置之误,须明非全兼容。