IR 与后端:没有 coroutine 操作
本节阅读量:surface-to-core lowering 完成后,编译器面对的是第十章已经能处理的完整语言。本章没有增加 coroutine、yield 或 resume IR op,也没有新增 runtime tag。
|
|
这正是在检验前面建立的抽象能否组合:控制状态由 closure 表示,可变槽由 cell 表示,暂停结果由 record 表示,它们理应进入同一条后端。
从 core 走到 IR
|
|
main 的开头会出现:
|
|
$coro.state.00.cell保存下一步;f.0是 finish closure;f.2是 initial step;store安装 initial step;f.5是 dispatcher;g29.cell是源码let g的位置。
两次 resume 已经只是普通调用:
|
|
resume 复用 load、closure_check、call 和 field。
yield 在 IR 中是什么
initial step 的关键操作是:
|
|
暂停的机器可见效果只有四步:分配 continuation closure、写入 state、分配 (record 0 value)、从当前普通函数返回。下一次 resume 重新进入 dispatcher,再间接调用新 closure。原机器栈已经正常返回,从未被冻结。
自由变量就是保存状态
考虑 coroutine_gc_capture_record.lang 中的结构:
|
|
暂停后的 continuation 还要读取 saved,所以 closure 捕获它的 cell:
|
|
这就是“暂停后仍需保存什么”的可执行答案,无需 coroutine frame descriptor。运行:
|
|
两项都输出 42。第一项先验证 continuation 捕获的外部 record,完成后又触发一轮 GC,并继续检查 done closure 与 completed record 的身份;第二项专门验证 state 中的 next continuation 跨大量分配仍有效。
表示分析继续保守
alloc 不认识“协程变量”。它只应用第十章分类:
| 来源 | 分类 |
|---|---|
| closure、cell、record | root |
| load、field、call | root |
| add、sub1、equal 等确定结果 | integer |
|
|
输出中 dispatcher、state、continuation、captured cell 和调用结果进入 %r15 root slots;恢复后的 (+ value 2) 结果仍可进入整数寄存器。
对象引用可以为了读字段短暂进入 scratch register,但不能未受保护地跨越 allocation 或源语言 call。continuation 本体在堆上,与保存它的 tagged pointer 在 safepoint 时位于哪里,是两个问题。
GC 不增加扫描分支
collector 已经会扫描:
|
|
coroutine lowering 只构造这三种对象。暂停形成的共享或环仍由 forwarding pointer 处理,不需要第四种 header 或 continuation bitmap。
reentry guard 仍是普通 call 检查
dispatcher 生成的 IR 可以概括为:
|
|
若当前 step 重入同一 dispatcher,内层调用从 state 读到 0,已有 closure_check 进入共同运行时错误出口。完成后的 done step 则会在返回 completed record 前把自身重新写回 state,因此 dispatcher 的临时清空不会破坏重复 resume。编译结果状态仍是 70,没有专用错误码。
emitter 与 runtime 保持原样
compile 仍消费同一种 IrProgram:
|
|
Linux、WSL 或 Intel Mac:
|
|
Apple Silicon Mac:
|
|
状态都应为 42。make 已经生成本章自己的 gc_runtime.o,它与 out.s 通过第九章定义的 C ABI 相连。最终汇编只出现普通 allocation、load/store 和 call,而没有“暂停指令”,正说明 surface-to-core 边界发挥了作用。