第十三章 函数式语言的编译

48
第第第第 第第第第第第第第 第第第第 第第 第第第第第第第第第第第 SFP 第第 第第第第 FAM 第第第第第第第 SFP 第第第第第第第 第第 SFP 第第第第第第第 FAM 第第第

Upload: benicio-yepez

Post on 01-Jan-2016

114 views

Category:

Documents


5 download

DESCRIPTION

第十三章 函数式语言的编译. 本章内容 介绍一种简单的函数式编程语言 SFP 介绍一种抽象机 FAM ,它的机器语言是 SFP 语言的目标语言 介绍 SFP 各种语言构造到 FAM 的编译. 1 3.1 函数式编程语言简介. 1 3.1.1 语言构造 函数是构建程序的基本成分 还提供一些机制用于构造更为复杂的函数 纯函数式语言禁止使用赋值语句,从而不会产生副作用,其优点是具有引用透明性,有助于程序的等式变换和推理 程序设计的任务就是定义函数 计算机按照所定义函数的相应表达式,根据计算规则逐步计算,最后得到所需的结果. 1 3.1 函数式编程语言简介. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第十三章  函数式语言的编译

第十三章 函数式语言的编译

本章内容• 介绍一种简单的函数式编程语言 SFP

• 介绍一种抽象机 FAM ,它的机器语言是 SFP语言的目标语言

• 介绍 SFP 各种语言构造到 FAM 的编译

Page 2: 第十三章  函数式语言的编译

13.1 函数式编程语言简介

13.1.1 语言构造• 函数是构建程序的基本成分

– 还提供一些机制用于构造更为复杂的函数– 纯函数式语言禁止使用赋值语句,从而不会产生

副作用,其优点是具有引用透明性,有助于程序的等式变换和推理

• 程序设计的任务就是定义函数– 计算机按照所定义函数的相应表达式,根据计算

规则逐步计算,最后得到所需的结果

Page 3: 第十三章  函数式语言的编译

13.1 函数式编程语言简介• 语法论域和语法产生式

– B :基值集,如布尔值、整数、 . . . ,用 b 示例– Opbin :二元算符集,如 +, =, and, . . . , 用 opbin 示例– Opun: 一元算符集,如 , not, . . . ,用 opun 示例– V :变量集,用 v 示例– E :表达式集,用 e 示例 e b | v | (opun e) | (e1 opbin e2) | (if e1 then e2 else e3)

| (e1 e2) // 函数应用 | (v.e) // 函数抽象 , 如 x.x+1, 即 f (x) = x+1 | (letrec v1== e1; v2== e2; . . . vn== en in e0)

// 联立递归定义

Page 4: 第十三章  函数式语言的编译

13.1 函数式编程语言简介• 约定

e b | v | (opun e) | … | (e1 e2) | (v.e)

| (letrec v1== e1; v2== e2; . . . vn== en in e0)

– 函数应用有最高优先级并且左结合– 算术和逻辑算符有通常的优先级 抽象选择最大可能的语法表达式作为 v. e 的体 e,

即 e 延伸到表达式的结尾或碰到第一个不能配对的右括号为止

– n 元函数写成 v1 … vn.e 的形式– 把 fe1 … em 实现为一次函数应用,而不是 m 次应用

Page 5: 第十三章  函数式语言的编译

13.1 函数式编程语言简介

13.1.2 参数传递机制对于表达式 e1e2 ,用按需调用的方式传递参数– 值调用– 换名调用– 按需调用

又称惰性计算。从 e1 的计算开始,当第一次需要e2 时,计算它的值,也就计算这一次。其它访问用第一次访问时计算的值。这种方式结合了前两种方式的优点

Page 6: 第十三章  函数式语言的编译

13.1 函数式编程语言简介• 例 1

letrec x == 2;f == y. x + y;F == g x. g2

in F f 1– 静态作用域,结果等于 4– {x :2, f : y. x + y, F : g x. g2} 是表达式 2,

y.x+y, g x. g2 和 F f 1 的计算环境– 表达式 e 和它的计算环境 u 形成二元组 (e, u) ,叫

做闭包。环境 u 用来保证 e 中的自由变量会被正确地解释,因此环境 u 和变元 e 需要一起传递

Page 7: 第十三章  函数式语言的编译

13.1 函数式编程语言简介• 例 2

letrec comp == f .g. x. f (gx);

F == x. …;

G == z. …;

h == comp F G

in h( ... ) + F( ... ) + G( ... ) – 函数可以作为函数的变元– 函数也可以作为函数的结果– n 元函数可作为高阶函数使用 , 当作用于 m (m <

n) 个变元时,结果是一个 ( n m ) 元的函数

Page 8: 第十三章  函数式语言的编译

13.1 函数式编程语言简介

13.1.3 变量的自由出现和约束出现– 在 letrec v1== e1; v2== e2; . . . vn== en in e0 中,等号

左边的 v1, …, vn 是这些变量的定义性出现– 在 v1 … vn.e 的 v1 … vn 中的 v1, …, vn 是这些变量

的定义性出现– 变量出现在其它地方都是引用性出现

– 引用性出现分成自由出现和约束出现在 (x y. (z. x + z) (y + z ) ) x 中,自由出现的变量: x, z约束出现的变量: x, y, z

Page 9: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介• 概述

– 将按需调用语义和静态约束的函数式语言 SFP 的程序编译成 FAM 的机器语言

– FAM 是一种抽象机,它有一个栈,生存期符合栈式管理的所有变量都分配在栈中;它还有一个堆 ,所有其它的变量都存在堆中

– 先用一连串的例子来启发后面从 SFP 程序到FAM 程序的编译描述

Page 10: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.1 几个受启发的例子例 1 1 + 2– 本表达式的结果是基值类型,可以放在栈上– 但是表达式结果也可能是函数,它不能放在栈上

– 统一做法:把程序表达式的结果统一存放在堆中,在栈顶用一个指针指向堆中的结果

Page 11: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.1 几个受启发的例子例 2 letrec x == 1/y; y == 0; z == x in 1 + 2– 由 letrec 或函数抽象引入的变量在 FAM 的栈上分

配单元– x 、 y 和 z 的等式的编译:产生的指令序列不直

接计算它们的右部,将来需要这些值时再计算– 于是,生成的指令序列构造 x 、 y 和 z 的闭包,

并将它们的指针存放在栈中– y 的等式无须构造闭包,因其右部不含自由变量– 让 z 和 x 约束到同一个闭包

Page 12: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.1 几个受启发的例子例 3 if (if 12 then true else false) then 3 else 4– if 1 2 then true else false 的结果在栈上更好,因

为假转指令 jfalse希望在栈顶测试它的值– 由此,表达式的编译方式还依赖于上下文– 由上下文可知,表达式 true 和 false 也应该按照

结果在栈上的方式来编译

Page 13: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.1 几个受启发的例子例 4 letrec f == y z. if z = 0 then 1 else 1/y;

x == 5

in f 1 (x + 1)– 由于 y z. if z = 0 then 1 else 1/y 是函数表达式,需

把它的闭包进一步做成 FUNVAL 对象– FUNVAL 对象和一般闭包的区别仅在于前者还包含存放变元指针的存储空间

– 为保证 1 和 x + 1仅在需要时计算,将它们以闭包 ( 包含一个指令序列和一个约束向量 ) 的形式传递

Page 14: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.1 几个受启发的例子例 5 letrec x == 2 + 1;

f == a b. g a + h b; g == x. . . . h == y. . . .

in f x x– 以闭包或值形式的表达式的指针可以拷贝多份– 总是值的指针和闭包的指针而不是它们本身在传

递,将它们存于约束向量和栈帧中– 每个表达式只有一个实例存在– 表达式对应变量的首次使用引起该表达式闭包的

计算

Page 15: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.1 几个受启发的例子例 6 letrec f == letrec x == 2

in y. x + y in f 5

– 该例可用来说明命令式语言和函数式语言在局部变量生存期上的区别

– 为了把 f 作用于 5 ,需要计算由较内 letrec 构造的函数。若该 letrec已经计算,栈式管理会忘掉属于这个 letrec 的一切东西,包括局部变量 x

– 高阶函数的出现需要延长局部变量的生存期

Page 16: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.2 编译函数表达式在不同的上下文中会编译成不同的指令序列– P :编译完整的程序表达式。结果在堆中,栈顶

有一指针指向它– B :结果必须是基值并且存在栈上– V :结果在堆中,栈顶有一指针指向它。这是计

算的正常情况– C :结果必须是被编译表达式的闭包。函数的变

元和递归等式的右部总是这种情况

Page 17: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.3 环境与约束– 名字的定义性出现总是关联到一个闭包

1 、一个等式被编译时,其左部的名字总是关联到

其右部的闭包2 、抽象中的约束名字是在函数应用时关联到该

次应用的变元的闭包– 名字引用性出现的编译:获得相关联的定义性出

现的值– 符号表在此称为编译环境– 当函数抽象的体或 letrec 中的表达式开始编译时

,新引入的局部变量必须被加入编译环境

Page 18: 第十三章  函数式语言的编译

13.2 函数式语言的编译简介

13.2.3 环境与约束– 编译环境包含

1 、变量的性质:自由 (GLOB) 还是约束 (LOC)2 、变量的位置:相对地址或下标

– 存储分配1 、表达式中的局部变量在栈上分配存储单元 ,

使用栈帧的相对地址 2 、全局变量存储单元的指针分配在约束向量中,依据约束向量中下标可以找到这些指针

Page 19: 第十三章  函数式语言的编译

13.3 抽象机的体系结构– 从本节开始 , 逐步描述 FAM 的体系结构和指令

集 , 以及把 SFP 编译到 FAM 的编译方案– FAM 的存储器包含:

程序存储区 PS 、栈 ST 、堆 HP– FAM 的程序

1 、每条指令占一个单元2 、某些指令含一个运算对象3 、程序计数器 PC总是保留当前指令的地址

Page 20: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.1 抽象机的栈– 和第 6 章的活动记录栈类似,但这里栈向下增长– 基值、各种地址都只占一个单元– 函数应用的栈帧如图– SP 是栈顶指针– FP 是当前栈帧指针– 继续地址就是返回地址– FPold 是老 FP 的值– GPold 是老的全局变量构成的

向量的指针

继续地址FPold

GPold

实在参数

局部变量和

中间结果

FP

SP

Page 21: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.1 抽象机的栈– 和第 6 章的活动记录栈类似,但这里栈向下增长– 基值、各种地址都只占一个单元– 闭包计算的栈帧如图,

无需实在参数域

– 建立和释放栈帧可以用一组固定的指令

继续地址FPold

GPold

局部变量和

中间结果

FP

SP

Page 22: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.2 抽象机的堆堆对象有四类– BASIC :存放基值的单元 b– FUNVAL :对象表示一个函数值。它有三个成分

1 、 cf :指向程序区中函数体开始的地方2 、 fap :指向函数变元向量3 、 fgp :函数各全局变量值的指针所组成的向量的指针后两个向量也存在堆中

Page 23: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.2 抽象机的堆堆对象有四类– BASIC :存放基值的单元 b– FUNVAL :对象表示一个函数值– CLOSURE :对象是一个闭包,有两个成分

1 、 cp :代码指针2 、 gp :全局变量值的指针向量的指针

Page 24: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.2 抽象机的堆堆对象有四类– BASIC :存放基值的单元 b– FUNVAL :对象表示一个函数值– CLOSURE :对象是一个闭包– VECTOR :对象是堆对象指针的向量

1 、存放函数变元的指针,或2 、存放 FUNVAL 对象的全局变量的指针,或3 、存放 CLOSURE 对象的全局变量的指针

Page 25: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.2 抽象机的堆建立堆对象的指令如下– mkbasic :建立基值– mkfunval :建立函数值– mkclos :建立闭包– mkvec n :建立有 n 分量的向量– alloc :建立空闭包

Page 26: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.3 名字的寻址问题– 函数定义的编译必须考虑函数应用允许变元不足

和变元过剩的情况– 编译函数应用 e e1 ... em 时,编译器并非总能知道

被应用函数的变元个数– 这给编译时确定栈帧中变元和局部变量的地址带

来困难

Page 27: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.3 名字的寻址考虑执行函数应用 e e1 ... em 时名字的寻址– 若基于 FP 访问,编译函数定义时,由于不知实参个数,局部变量的寻址困难

继续地址FPold

GPold

实在参数

局部变量和

中间结果

FP

SPe 的 FUNVAL 指针

em 闭包的指针

FP

SP

e1 闭包的指针

Page 28: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.3 名字的寻址考虑执行函数应用 e e1 ... em 时名字的寻址– 以左图中目前 SP 的位置为基准较好,但需要克服SP动态变化带来的困难

继续地址FPold

GPold

实在参数

局部变量和

中间结果

FP

SPe 的 FUNVAL 指针

e1 闭包的指针

FP

SP

em 闭包的指针

Page 29: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.3 名字的寻址考虑执行函数应用 e e1 ... em 时名字的寻址– 选择动态地址 sp0 作为寻址的基地址– SP 的当前值 spa 和 sp0 的值之间的差,对函数体的每点来说是静态可确定的,因为已出现的新局部变量和中间结果数目是已知的– 这个差值在编译时保存在编译参数 sl 中,在程序点的值用sla 表示,则有关系式

spa = sp0 + sla 1

继续地址

e1 闭 包 的 指针

em 闭包的指针┇

FP

SP

sp0

FPold

GPold

Page 30: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.3 名字的寻址考虑执行函数应用 e e1 ... em 时名字的寻址– 选择动态地址 sp0 作为寻址的基地址– spa 和 sp0 的值之间的关系

spa = sp0 + sla 1– 生成的指令可以使用编译时确定的值 sla 和运行时 spa 来计算运行时的值 sp0

继续地址

e1 闭 包 的 指针

em 闭包的指针┇

FP

SP

sp0

FPold

GPold

Page 31: 第十三章  函数式语言的编译

13.3 抽象机的体系结构

13.3.4 约束的建立– 对每个函数定义及表达式,自由变量集静态可知– 这些变量的地址存在一个向量中– 该向量的指针存放在堆中作为 FUNVAL 或

CLOSURE 对象的一部分– 在计算闭包或函数应用时,该指针复写到 GP ,即运算时可以通过 GP去寻找该向量的元素

Page 32: 第十三章  函数式语言的编译

13.4 指令集和编译

本节一步步地描述编译和所需要的 FAM 指令– 使用 4 个编译函数 P_code 、 B_code 、 V_code

和 C_code– 对代码执行结果的不同期望决定使用不同的编译

函数– 这些函数有三个参数:被编译的表达式 e ,变量

环境 和栈标高 sl ( sl 定义了被生成的代码执行前 SP寄存器的值和地址 sp0 的差)课堂上的介绍比较宏观,需课后了解抽象机

各指令后才能完全明白

Page 33: 第十三章  函数式语言的编译

13.4 指令集和编译

13.4.1 表达式1 、程序表达式

P_code e = V_code e [ ] 0 ; stop

– 环境为空 [ ]– 栈标高 sl 是 0

Page 34: 第十三章  函数式语言的编译

13.4 指令集和编译13.4.1 表达式

2 、简单表达式(结果是基值并在栈上,例举)B_code b sl = ldb b

B_code (e1 opbin e2) sl =

B_code e1 sl;

B_code e2 sl+1;

opbin

B_code e sl = // e 不是基值、算符和 if 表达式

V_code e sl ;

getbasic

Page 35: 第十三章  函数式语言的编译

13.4 指令集和编译

13.4.1 表达式2 、简单表达式(结果是基值并在堆上,例举)

V_code b sl = B_code e sl; mkbasic

V_code (if e1 then e2 else e3) sl =

B_code e1 sl;

false l1;

V_code e2 sl;

ujmp l2;

l1 : V_code e3 sl;

l2 :

Page 36: 第十三章  函数式语言的编译

13.4 指令集和编译

13.4.2 变量的引用性出现V_code v sl = getvar v sl;

evalC_code v sl = getvar v sl

getvar v sl = let (p, i) = (v) in if p = LOC then pushloc sl - i

else pushglob i fi

getvar 为局部变量和形式参数产生 pushloc指令,

为全局变量产生 pushglob 指令

Page 37: 第十三章  函数式语言的编译

13.4 指令集和编译

13.4.3 函数定义 V_code (v1 ... vn. e) sl = C_code ( v1 ... vn. e ) sl C_code ( v1 ... vn. e) sl =

pushfree fr sl; // 拷贝全局变量的值的指针 mkvec g; mkvec 0; // 空的变元向量 ldl l1; // 函数代码的地址 mkfunval; ujmp l2; l1 : targ n; // 测试变元个数 V_code e ([vi(LOC, i)][vj (GLOB, j)]) 0; return n; (i = 1, …, n) ( j = 1, …, g)

l2 :

Page 38: 第十三章  函数式语言的编译

13.4 指令集和编译

13.4.3 函数定义fr = [v1, …, vg] = list (freevar ( v1 ... vn. e) )

pushfree [v1, …, vg] sl =

getvar v1 sl;

getvar v2 (sl +1);

. . .

getvar vg (sl + g 1)

1 、 fr 表示 v1 ... vn. e 中的自由变量表2 、 pushfree 生成将这些变量的指针压栈的指

令序列

Page 39: 第十三章  函数式语言的编译

13.4 指令集和编译13.4.4 函数应用

V_code (e e1 ... em ) sl = // e ee

mark l; // 建新栈帧 , 保留当前继续地址 l, FP, GP C_code em (sl + 3); // 为变元在堆上建立闭包 ,

. . . // 并把闭包的指针压栈 C_code e1 (sl + m + 2);

V_code e (sl + m + 3); // 计算 e, 结果指针压栈apply;

l :

Page 40: 第十三章  函数式语言的编译

13.4 指令集和编译apply 指令执行前后情况

FUNVAL 中已有变元 a1, …, an ,本次调用还有变元已先行进栈 PC修改成函数代码起始地址

a1

a2

. . .

执行后

SP

an

PC = cfGP = fgp

SP

执行前

FUNVAL: cf, fap, fgp

VECTOR: [an, …, a1]

Page 41: 第十三章  函数式语言的编译

13.4 指令集和编译

targ n 指令发现变元不足 (n > m) 将栈中已有的 m 个变元打包成向量 重新做成 FUNVAL ,和原先相比,变元多了 本次函数应用到此为止,返回

PC = PColdGP = GPoldFP = FPold

SP执行后

FUNVAL: PC, ,GP

VECTOR: [am, …, a1]

am

GPold

FPold

执行前

FP

PCold

. . .

a1

a2SP

Page 42: 第十三章  函数式语言的编译

13.4 指令集和编译

return n 指令 (SP = FP +1 + n ,没有多余参数 )

函数值拷贝到适当的地方,并释放当前栈帧

an

GPold

FPold

执行前

FP

PCold

. . .

xa1

SP

SP执行后

x

Page 43: 第十三章  函数式语言的编译

13.4 指令集和编译

return 指令 ( 有多余参数 ) 函数应用消费适当个数的变元,其结果是一个函数 再应用到剩余变元,它们的指针仍在栈上 (x.(yz.x + y + z)3)4 5 的执行会出现这种情况

am

执行前FP

. . .a1

SP

FUNVAL: cf, fap, fgp

VECTOR: [xk, …, x1]

PC = cfGP = fgp

an+1

执行后FP

. . .x1

am

SP

xk

. . .

Page 44: 第十三章  函数式语言的编译

13.4 指令集和编译

13.4.5 构造和计算闭包C_code e sl = // 同编译函数类似,无变元部分

pushfree fr sl; // 将全局变量的值压栈mkver g; // 把它们做成一个向量ldl l1; // 闭包代码的地址mkclos;

ujmp l2;

l1 : V_code e [vi (GLOB, i )] 0; (i = 1, …, n)

update;

l2 :

Page 45: 第十三章  函数式语言的编译

13.4 指令集和编译update 指令的效果

用闭包的计算结果去覆盖该闭包对象 以后 eval 指令发现已经不是闭包,则不再计算

GPold

FPold

执行前

FP

PCold

SP y

x

PC = PCold

FP = FPold

GP = GPold

SP

执行后

y

y

Page 46: 第十三章  函数式语言的编译

13.4 指令集和编译13.4.6 letrec 表达式和局部变量V_code (letrec v1 == e1; ...; vn== en in e0) sl =

repeat n alloc; // 在堆上建立 n 个空对象 , 将指针压栈 C_code e1 sl; // 为 ej 建立闭包rewrite n; // 覆盖对应的空闭包对象C_code e2 sl;rewrite n 1;. . . C_code en sl;rewrite 1;V_code e0 sl; // 生成计算 e0 的指令序列slide n // 放弃 e1, ..., en 在栈顶指针,剩下 e0 指针

Page 47: 第十三章  函数式语言的编译

13.4 指令集和编译13.4.6 letrec 表达式和局部变量V_code (letrec v1 == e1; ...; vn== en in e0) sl =

repeat n alloc; // 在堆上建立 n 个空对象 , 将指针压栈 C_code e1 sl; // 为 ej 建立闭包rewrite n; // 覆盖对应的空闭包对象. . . = [vi (LOC, sl + i 1)] (i = 1, …, n) 依据全局变量和 v1, ..., vn ,为 e0, e1, ..., en 建立同样的环境 sl= sl + n n 次 alloc 使 SP 的值增加 n

Page 48: 第十三章  函数式语言的编译

习 题