章节目录

本章总结

本节阅读量:

第四章真正增加的,不只是两种语法,而是“可调用的函数也可以作为普通值流动”:

1
2
(lambda x (+ x 1))
((lambda x (+ x 1)) 41)

前一个表达式创建函数,后一个表达式调用函数。函数可以先放进 let,也可以作为参数传给另一个函数,或者由另一个函数返回。无论写法怎样变化,都要分清创建和调用这两个时刻。

整章可以压缩成下面这张对照表:

源语言动作 解释器 编译器
lambda 创建 Function(param, body) 建立 IrFunction,把代码地址作为值
调用表达式 (callee argument) 建立只含形参绑定的新环境 %rdi 传实参,通过代码地址间接 call
调用结果 函数体求出的 Value 函数体结果放进 %rax
一次调用的局部空间 一份调用环境 一份独立栈帧

语言:保存一段尚未执行的计算

求值 lambda 时,函数体不会运行;求值调用表达式时,才会依次求出 callee 和 argument,并用参数值执行函数体。

第四章的函数体只能使用:

  • 自己的参数;
  • 函数体内部 let 建立的名字。

这条名字边界会同时约束解释器和编译器。源语言没有单独的 return;函数体表达式的值就是调用结果。

解释器:函数值和调用环境

Value 现在既能保存整数,也能保存 Function。本章的 Function 只记录参数名和函数体 AST,不保存创建 lambda 时的环境。

调用时,解释器先确认 callee 确实是函数,再建立一份新的环境,把 argument 绑定到参数名,然后在其中求值函数体。函数体内部的 let 可以继续扩展这份环境;调用结束后,这份环境随之消失。

因此,解释器里的核心对应关系是:

1
2
3
lambda          参数名 + 函数体 AST
call            新调用环境 + 求值函数体
return          函数体的求值结果

编译器:代码地址和独立栈帧

lowering 会把每个 lambda 的函数体登记成独立的 IrFunction。与函数值和调用有关的信息,则通过两类新增操作进入当前 ops:

1
2
function_ref    取得函数标签代表的值
call            通过这个值调用函数

到了 x86-64 汇编层,函数值就是入口标签的代码地址:leaq 取得地址,调用点把地址放进 %rcx、把参数放进 %rdi,再执行 call *%rcx。被调用函数建立自己的栈帧,把参数和临时结果放入自己的栈槽,最后用 %rax 交回结果,并用 retq 回到调用者。

这条编译路径可以压缩成:

1
2
lambda -> IrFunction + function_ref -> 函数标签 + 代码地址
call   -> IR call                  -> %rdi + call *%rcx + %rax

本章的边界

下面的函数需要读取 lambda 外层的 x,第四章还不能表示它:

1
(let x 40 ((lambda y (+ x y)) 2))

解释器的函数值没有保存外层环境,编译后的函数值也只有代码地址。等到闭包章节,函数值才会升级为“代码地址 + 捕获的环境”。本章还没有多参数、可变参数和 letrec 具名递归。

编译器也还没有带标签的运行时值,因此只承诺这样的程序:作用域符合本章规则;实际执行到的 +、eq? 和 call 使用正确种类的值;整数是本章示例中的小值且加法不发生有符号 long 溢出。解释器能报告 (42 1) 这样的错误,裸机器字编译器不能安全执行它。

这条语义边界和退出状态的观察边界是两件事。mini run 可以显示 <function> 或完整整数;手动链接后的 main 只能通过进程退出状态方便地观察 0 到 255 的整数。限制最终结果为小整数只是为了检查 $?,不是解释器与编译器语义一致的前提。

读完本章后,应当能够独立解释四件事:

  1. 为什么求值 lambda 不会执行函数体。
  2. 为什么调用先求 callee,再求 argument,并建立新的参数环境。
  3. 为什么函数值在 IR 中同时对应 function_ref 和独立的 IrFunction。
  4. %rdi、call、新栈帧、%rax 和 retq 怎样完成一次往返。

完成主线后,章末还有一次独立的 C++ runtime 联调:把固定的 (cin)、(cout expr) 编译成外部直接调用,用正常标准输出观察完整整数。它只用于巩固 ABI 和链接,不由后续章节继承。


4.7 汇编:代码地址和间接调用

上一节

4.9 练习

下一节