边界与验收:第七章真正承诺什么
本节阅读量:第七章完成后,不只是 closure.lang 能得到 42。解释器、结构化 IR 和生成代码必须在词法作用域、捕获顺序、递归 self、对象种类与错误路径上互相对齐,同时完整保留第六章已有能力。
这一节把要求集中列出,便于实现和审阅时逐项检查。
语言语义
本章不增加表面语法,必须满足:
- 求值
lambda创建 closure,不执行函数体; - closure 只保存函数体真正需要的外层 Value;
- 变量含义由定义位置决定,不由调用位置决定;
- call 先求 callee,确认它是 closure,再求 argument;
- 调用环境由 captures、可选 self 和参数组成;
- 参数是比 self 更内层的绑定,允许同名遮蔽;
- closure 可以返回 closure,也可以保存或被保存进 record;
- closure 不能参与
eq?。
用这些程序做语义验收:
|
|
结果依次应为 42、42、42、15、42。
前端连续性
AST 和 parser 不新增 closure 专用节点:
- 普通函数仍使用
LambdaExpr; - 调用仍使用
CallExpr; letrec仍使用LetRecExpr;- 普通 identifier 规则保持一个或多个英文字母;
- 第六章的可变长度 record、空 record、
record?、size和非负字面量 indexget全部保留。
closure 是否捕获变量不属于语法判断。相同 LambdaExpr 可以在不同定义环境中创建不同 closure Value。
自由变量分析
front/free_vars.* 必须做到:
- 参数不属于当前
lambda的自由变量; let名字只绑定 body,不绑定 value;letrec的 self 和参数在 function body 中已绑定;- 嵌套 lambda 中仍来自更外层的名字会向外传播;
- 所有继承的 AST kind 都用显式 switch 遍历;
- 同名自由变量只保留一次;
- 结果保持第一次遇到的稳定顺序;
- shadowing 由词法绑定栈判断,不按全局字符串集合猜测。
分析结果同时供解释器捕获环境和 IR lowering 使用,不能让两条路线各自维护一套略有差异的规则。
解释器
解释器 closure 必须保存:
|
|
其中:
- 普通 lambda 的
self_name为空; letrec的self_name是递归名字;- captured environment 只含
free_vars选中的名字; - captured environment 不保存递归 closure 自身;
- call 时用实际 callee 建立 self 绑定;
- call 时不能直接使用调用点环境执行 body。
错误诊断至少覆盖:
|
|
结构化 IR
IR 继续使用 OperandKind、OpKind 和结构体字段,不退回字符串操作列表。第七章新增的关键种类是:
|
|
IrFunction 必须精确区分:
|
|
验收要求:
- closure op 的
target保存代码标签; - closure op 的
fields保存捕获 operands; IrFunction::captures与 fields 等长、同序;- 普通 lambda 的
self为空; - letrec 的
self是独立 local,不出现在 fields; - self、参数和 captures 都做 alpha-renaming;
- lowering 顺序为 callee ops、closure_check、argument ops、call;
- branch 后仍紧跟 then label;
compile先调用lower_to_ir,assembly emitter 只接收IrProgram。
查看代表输出:
|
|
通用堆对象协议
record 和 closure 必须共用:
|
|
kind 分配是:
|
|
不能恢复 closure 专用 pointer tag,也不能只检查 tag 4 就认定对象是 closure 或 record。
closure 布局
布局固定为:
|
|
必须满足:
| capture count | header | size |
|---|---|---|
| 0 | 258 | 16 |
| 1 | 514 | 24 |
| 2 | 770 | 32 |
self 不计入 capture count,也不占 payload。recursive_closure.lang 只捕获 base,所以使用 header 514、大小 24。
机器调用约定
调用约定必须是:
|
|
closure_check 生成:
- low tag 是否为
4; - 清 tag 后 header kind 是否为 closure;
- 失败时跳到状态
70的错误出口。
最终 call op 使用临时 raw address,从 offset 8 取 code pointer,但不能把 %rdi 永久改成 raw pointer。函数入口:
- self 非空时,把 tagged
%rdi保存为 self local; - 把
%rsi保存为 param local; - 用
%rdi的临时 untagged 副本读取 offset16+8i; - 把每个 capture Value 保存为对应 local。
固定栈帧大小向上对齐到 16 字节,外部 malloc、exit 和间接 call 都要满足 System V ABI 对齐。
record 能力必须完整继承
第七章不能退回旧的二字段 record。下面这些程序都必须继续工作:
|
|
预期输出依次是:
|
|
种类交叉测试也必须成立:
|
|
结果为 42、42、0。
生成代码的错误状态
编译后的这些错误统一以状态 70 结束:
| 示例 | 失败原因 |
|---|---|
call_integer_error.lang |
callee 是整数,low tag 不是 heap object |
call_record_error.lang |
callee 是 record,不是 closure |
function_eq_error.lang |
closure 参与 eq? |
record_size_closure_error.lang |
size 收到 closure |
record_get_closure_error.lang |
get 收到 closure |
record_get_bounds_error.lang |
record index 越界 |
以一个例子验证:
|
|
Apple Silicon Mac 的链接命令改为:
|
|
退出状态应为 70。
分支仍然短路
错误路径只有真正执行时才失败:
|
|
解释器和生成代码都必须返回 42,不能因为 else 分支中存在非法调用而提前报错。运行:
|
|
Apple Silicon Mac 仍使用 cc -arch x86_64 out.s -o out 链接。两条执行路径的结果都应是 42。
这同时检查 lowering 没有把分支内的 closure_check 提到 branch 之前。
CLI 与平台边界
本章仍提供:
|
|
compile 只输出 x86-64 System V ABI 的 AT&T 汇编。它不调用 cc,不负责链接,也不执行结果。Linux 和 macOS 的外部符号拼写由 target 选择处理。
非目标
第七章不实现:
- 多参数函数;
- 可变变量和共享 cell;
- closure 相等;
- 逃逸分析或栈上 closure;
- 主动
free、引用计数 runtime 或垃圾回收; - 通用尾调用优化。
这些边界不影响本章目标:闭包已经能在词法作用域下保存自由变量、返回和嵌套,递归函数也能同时使用 self 与真正的外层捕获值。