7 中间代码生成Intermediate Code Generation
⭐⭐⭐⭐⭐⭐
Github主页👉https://github.com/A-BigTree
项目链接👉https://github.com/A-BigTree/college_assignment
⭐⭐⭐⭐⭐⭐
文章目录
7.1 语法树的变体
为表达式构建的无环有向图(Directed Acyclic Graph,DAG),其指出了表达式中公共子表达式(多次出现的子表达式)。
7.1.1 表达式的有向无环图
和表达式的语法树类似,一个DAG的叶子节点对应于原子运算分量,而内部结点对应于运算符。与语法树不同的是,如果DAG中的一个结点N表示一个公共子表达式,而N可能有多个父结点。因此,DAG不仅更简洁地表示了表达式,而且可以为最终生成表达式的高效代码提供重要信息。
下图为表达式 a + a ∗ ( b − c ) + ( b − c ) ∗ d a+a*(b-c)+(b-c)*d a+a∗(b−c)+(b−c)∗d的DAG:
7.1.2 构造DAG的值编码方法
语法树或DAG图中的结点通常存放在一个记录数组中,在每个记录中,第一个字段是一个运算符代码,也是该结点的标号。各叶子结点还有还有一个附加字段,它存放了标识符的词法值;内部结点则有两个附加的字段,分别指明其左右子节点。
下图为表达式 i = i + 10 i=i+10 i=i+10的DAG与记录数组:
7.2 三地址代码Three-address Code (TAC)
在三地址代码中,一条指令的右侧最多有一个运算符。三地址代码是一棵语法树或者一个DAG的**线性表示**形式。下图为一个表达式的DAG及其对应的三地址代码:
7.2.1 地址和指令
地址可以具有如下形式之一:
- 名字:实现中,源程序名字被替换为指向符号表条目的指针;
- 常量:不同类型的常量与变量;
- 编译器生成的临时变量;
常见的三地址指令形式:
1
)
x
=
y
o
p
z
双目运算符
2
)
x
=
o
p
y
单目运算符
3
)
x
=
y
复制指令
4
)
g
o
t
o
L
转移指令
5
)
i
f
x
g
o
t
o
L
或
i
f
F
a
l
s
e
x
g
o
t
o
L
条件转移指令
6
)
i
f
x
r
e
l
o
p
y
g
o
t
o
L
条件转移指令
7
)
p
a
r
a
m
x
参数传递
;
c
a
l
l
p
,
n
过程调用
;
y
=
c
a
l
l
p
,
n
函数调用
;
r
e
t
u
r
n
y
返回指令
8
)
x
=
y
[
i
]
,
x
[
i
]
=
y
带下标的复制指令
9
)
x
=
&
y
,
x
=
∗
y
,
∗
x
=
y
地址及指复制指令
\begin{aligned} &1)\ x=y\ op\ z\ 双目运算符\\ &2)\ x = op\ y\ 单目运算符\\ &3)\ x=y\ 复制指令\\ &4)\ goto\ L\ 转移指令\\ &5)\ if\ x\ goto\ L或\ if\ False\ x\ goto\ L\ 条件转移指令\\ &6)\ if\ x\ relop\ y\ goto\ L\ 条件转移指令\\ &7)\ param\ x\ 参数传递; call\ p,n\ 过程调用 ; y=call\ p,n\ 函数调用;return\ y\ 返回指令\\ &8)\ x=y[i],x[i]=y\ 带下标的复制指令\\ &9)\ x=\&y,x=*y,*x=y\ 地址及指复制指令 \end{aligned}
1) x=y op z 双目运算符2) x=op y 单目运算符3) x=y 复制指令4) goto L 转移指令5) if x goto L或 if False x goto L 条件转移指令6) if x relop y goto L 条件转移指令7) param x 参数传递;call p,n 过程调用;y=call p,n 函数调用;return y 返回指令8) x=y[i],x[i]=y 带下标的复制指令9) x=&y,x=∗y,∗x=y 地址及指复制指令
下图为语句do i = i + 1;while(a[i] < v);
的两种翻译:
7.2.2 四元式表示
一个 四元式(quadruple) 有四个字段,我们分别称为 o p , a r g 1 , a r g 2 , r e s u l t op,arg_1,arg_2,result op,arg1,arg2,result。字段op包含一个运算符的内部编码。下面为一些特例:
- 单目运算符指令与复制指令不使用 a r g 2 arg_2 arg2;
- 像 p a r a m param param这样的运算符既不使用 a r g 2 arg_2 arg2,也不适用result;
- 条件或者非条件转移指令将目标标号放入result字段;
下图为语句a = b * -c + b * -c
的三地址代码与对应四元式:
7.2.3 三元式表示
一个 三元式(triple) 只有三个字段,我们分别称为 o p , a r g 1 , a r g 2 op,arg_1,arg_2 op,arg1,arg2。使用三元式时,我们将运用计算结果的位置来表示它的结果,而不是一个显式的临时名字来表示。
- 表达式的DAG表示和三元式表示时等价的;
- 复制语句
a=b
,字段 a r g 1 arg_1 arg1中放置a
,字段 a r g 2 arg_2 arg2放置b
; - 像
x[i]=y
这样的三元运算在三元式中需要两个条目;
下图为语句a = b * -c + b * -c
的DAG与对应三元式:
间接三元式(indirect triple) 包含了一个指向三元式的指针的列表,而不是列出三元式序列本身。如下图:
7.2.4 静态单赋值形式
静态单赋值形式(Static Single-Assignment Form,SSA) 是另一种中间表示形式,它有利于实现某些类型的代码优化。
- SSA中的所有赋值都是针对具有不同名字的变量的;
下图为三地址代码和静态单赋值形式表示的中间程序:
在同一个程序中,同一变量可能在两个不同的控制流路径中被定值,SSA使用一种称为 φ \varphi φ函数的表示规则将变量定值合并起来,如下图:
7.3 类型和声明
可以将类型的应用划分为类型检查和翻译:
- 类型检查(type checking)。类型检查利用一组逻辑规则来推理一个程序在运行时刻的行为。更明确的讲,类型检查保证运算分量的类型和运算符的预期类型相匹配;
- 翻译时的应用(translation application)。根据一个名字的类型,编译器可以确定这个名字在运行时刻需要多大的存储空间;
7.3.1 类型表达式
类型自身也有结构,类型表达式(type expression) 来表示这种结构,可能是基本类型,也可能通过把称为 类型构造算子 的运算符作用于类型表达式而得到.
基本类型的集合和类型构造算子根据被检查的具体语言而定.我们将使用如下的类型表达式的定义:
- 基本类型是一个类型表达式。一个语言的基本类型通常含
boolean, char, integer, floatvoid
; - 类名是一个类型表达式;
- 将类型构造算子
array
作用于一个数字和一个类型表达式可以得到一个类型表达式; - 一个记录是包含有名字段的数据结构。将record类型构造算子应用于字段名和相应的类型可构造得到一个类型表达式;
- 使用类型构造算子→可以构造得到函数类型的类型表达式;
- 如果s和t是类型表达式,则其笛卡尔积 x × y x\times y x×y也是类型表达式,可以用于描述类型的列表或元组;
- 类型表达式可以包含取值为类型表达式的变量;
下图为int[2][3]
的类型表达式:
7.3.2 类型等价
当用图来表示类型表达式的时候,两种类型之间 结构等价(structurally equivalent) 当且仅当下面的某个条件为真:
- 它们是相同的基本类型;
- 它们是将相同的类型构造算子应用于结构等价的类型而构造得到;
- 一个类型是另一个类型表达式的名字;
7.3.3 声明
下面为声明基本数据类型int,float
和对应数组的文法:
D
→
T
i
d
;
D
∣
ε
T
→
B
C
∣
r
e
c
o
r
d
′
{
′
D
′
}
′
B
→
i
n
t
∣
f
l
o
a
t
C
→
ε
∣
[
n
u
m
]
C
\begin{aligned} &D\rightarrow T\ \mathbf{id};D|\varepsilon\\ &T\rightarrow BC|record'\{'D'\}'\\ &B\rightarrow \mathbf{int}|\mathbf{float}\\ &C\rightarrow \varepsilon|[\mathbf{num}]C \end{aligned}
D→T id;D∣εT→BC∣record′{′D′}′B→int∣floatC→ε∣[num]C
7.3.4 局部变量名的存储信息
名字的类型和相对地址信息保存在相应的符号表条目中。
下面图给出的翻译方案(SDT)计算了基本类型和数组宽度以及它们的宽度:
下图展示了类型int[2][3]
的语法分析树与依赖图:
7.3.5 声明的序列
在考虑第一个声明之前,offset=0
,下图为计算被声明变量的相对地址的的SDT:
7.3.6 记录和类的字段
文法加上产生式
T
→
r
e
c
o
r
d
′
{
′
D
′
}
′
T \rightarrow \mathbf{record} '\{' D '\}'
T→record′{′D′}′
这个记录类型中的字段D由生成的声明序列描述,需小心地处理下面两件事:
- 一个记录中各个字段的名字必须互不相同;
- 字段的偏移量是相对于该记录的数据区字段而言的;
为方便起见,记录类型将使用一个专用的符号表,对它们的各个字段的类型和相对地址进行编码.记录类型形如record(t),其中record是类型构造算子,t是一个符号表对象,它保存了有关该记录类型的各个字段的信息。
记录类型SDT如下:
7.4 表达式的翻译
Translation of expressions
7.4.1 表达式的运算
- 一个地址可以是变量名字、常量或者编译器产生的临时变量;
S.code,E.code
分别表示S、E对应的三地址码;top.get
表示返回对应的符号表条目;- 记号
gen(x '=' y '+' z)
来表示三地址指令x = y + z
;
7.4.2 增量翻译
code属性可能是很长的字符串,因此我们不像上式一样构建E.code
,我们可以像下图翻译方案一样只生成新的三地址指令:
7.4.3 数组元素寻址
- 一维数组
假设每个数组元素的宽度是
w
w
w,那么数组A的第i个元素的开始地址为:
b
a
s
e
+
i
×
w
base+i\times w
base+i×w
其中
b
a
s
e
base
base是分配给数组A的内存块的相对地址,即
A
[
0
]
A[0]
A[0]的相对地址;
- 二维数组
假设一行的宽度是
w
1
w_1
w1,同一行中每个元素的宽度是
w
2
w_2
w2。
A
[
i
]
[
j
]
A[i][j]
A[i][j]的相对地址可以使用下面的公式计算:
b
a
s
e
+
i
1
×
w
1
+
i
2
×
w
2
base+i_1\times w_1+i_2\times w_2
base+i1×w1+i2×w2
- k维数组
相应的公式为:
b
a
s
e
+
i
1
×
w
2
+
i
2
×
w
2
+
⋯
+
i
k
×
w
k
base+i_1 \times w_2+i_2\times w_2+\dots+i_k\times w_k
base+i1×w2+i2×w2+⋯+ik×wk
其中,
w
j
(
1
≤
j
≤
k
)
w_j(1\le j\le k)
wj(1≤j≤k)是二维数组中
w
1
,
w
2
w_1,w_2
w1,w2的推广;
二维数组的存储布局:
- 按行存放;
- 按列存放;
7.4.4 数组引用的翻译
令非终结符号L生成一个数组名字再加上一个下标表达式的序列:
L
→
L
[
E
]
∣
i
d
[
E
]
L\rightarrow L[E]\ |\ \mathbf{id}[E]
L→L[E] ∣ id[E]
处理数组引用的语义动作:
非终结符L有三个综合属性:
- L . a d d r L.addr L.addr指示一个临时变量;
- L . a r r a y L.array L.array是一个指向数组名字对应的符号表条目的指针;
- L . t y p e L.type L.type是L生成的子数组的类型;
7.5 类型检查
Type Checking
为了进行类型检查(type checking),编译器需要给源程序的每一个组成部分赋予一个类型表达式。然后,编译器要去确定这些类型表达式是否满足一组逻辑规则。这些规则称为源语言的类型系统(type system)。
一个健全(sound)的类型系统可以消除对动态类型错误检查的需要,因为它可以帮助我们静态地确定这些错误不会在目标程序运行的时候发生。如果编译器可以保证它接受的程序运行时刻不会发生类型错误,那么该语言的这个实现就称为强类型(strongly typed)。
7.5.1 类型检查规则
Rules for Type Synthesis
类型综合(type synthesis)
类型综合根据子表达式的类型构造出表达式的类型。它要求名字先声明再使用。表达式
E
1
+
E
2
E_1+E_2
E1+E2的类型是根据
E
1
E_1
E1和
E
2
E_2
E2的类型定义的。一个典型的类型综合规则具有如下形式:
i
f
f
的类型为
s
→
t
且
x
的类型为
s
t
h
e
n
表达式
f
(
x
)
的类型为
t
\begin{aligned} &\mathbf{if}\ f的类型为s\rightarrow t且x的类型为s\\ &\mathbf{then}\ 表达式f(x)的类型为t \end{aligned}
if f的类型为s→t且x的类型为sthen 表达式f(x)的类型为t
类型推导(type inference)
类型推导**根据一个语言结构的使用方式**来确定该结构的类型。
一个典型的类型推导规则具有下面的形式:
i f f ( x ) 是一个表达式 t h e n 对某些 α 和 β , f 的类型为 α → β 且 x 的类型为 α \begin{aligned} &\mathbf{if}\ f(x)是一个表达式\\ &\mathbf{then}\ 对某些\alpha和\beta,f的类型为\alpha\rightarrow\beta且x的类型为\alpha \end{aligned} if f(x)是一个表达式then 对某些α和β,f的类型为α→β且x的类型为α
7.5.2 类型转换
Type Conversions
对于表达式2*3.14
,我们要先将整数使用单目运算符(float)
转为浮点数,如下:
t1 = (float)2
t2 = t1*3,14
不同语言具有不同的类型转换规则。Java的转换规则区分了拓宽(widening)转换和窄化(narrowing)转换。
拓宽(widening)转换
- 可以保持原有信息,在层次结构中位于底层的类型可以拓宽为较高层次的类型;
窄化(narrowing)转换
- 可能丢失信息,如果存在一条从s到t的路径,则可以将类型s窄化为类型t;
Java中简单类型的转换如下图:
自动类型转换(Coercion)
- 类型转换由编译器自动完成,那么这样的转换就称为隐形转换,即自动类型转换;
强制类型转换(Cast)
- 由程序员写出某些代码来引发类型转换运算,那么这个转换就称为显示转换;
7.5.3 函数和运算符的重载
Overloading of Functions and Operators
依据符号所在的上下文不同,被重载(overloading)的符号会有不同的含义。在这里我们只考虑那些只需要查看函数参数就能解决的函数重载。
针对重载函数的类型转换综合规则:
i
f
f
可能的类型为
s
i
→
t
i
(
1
≤
i
≤
n
)
,
其中
s
i
≠
s
j
(
i
≠
j
)
a
n
d
x
的类型为
s
k
(
1
≤
k
≤
n
)
t
h
e
n
表达式
f
(
x
)
的类型为
t
k
\begin{aligned} &\mathbf{if}\ f可能的类型为s_i\rightarrow t_i(1\le i\le n),其中s_i\not =s_j(i\not=j)\\ &\mathbf{and}\ x的类型为s_k(1\le k\le n)\\ &\mathbf{then}\ 表达式f(x)的类型为t_k \end{aligned}
if f可能的类型为si→ti(1≤i≤n),其中si=sj(i=j)and x的类型为sk(1≤k≤n)then 表达式f(x)的类型为tk
7.5.4 类型推导和多态函数
Type Interface and Polymorphic Functions
多态(polymorphic)
- 任何可以在不同的参数类型上运行的代码片段;
参数多态(parametric polymorphism)
- 这种多态通过参数和类型变量来刻划;
函数length
的描述可以描述为:
“对于任何类型
α
,
l
e
n
g
t
h
函数将元素类型
α
的列表映射为整数。”
“对于任何类型\alpha,length函数将元素类型\alpha的列表映射为整数。”
“对于任何类型α,length函数将元素类型α的列表映射为整数。”
函数length
定义:
f
u
n
l
e
n
g
t
h
(
x
)
=
i
f
n
u
l
l
(
x
)
t
h
e
n
0
e
l
s
e
l
e
n
g
t
h
(
t
l
(
x
)
)
+
1
\begin{aligned} &\mathbf{fun}\ length(x)=\\ &&\mathbf{if}\ null(x)\ \mathbf{then}\ 0\ \mathbf{else}\ length(tl(x))+1 \end{aligned}
fun length(x)=if null(x) then 0 else length(tl(x))+1
- 预定义函数
null(x)
测试一个列表是否为空; - 预定义函数
tl
(tail的缩写)移除列表中的第一个元素,然后返回列表的余下部分;
使用符号
∀
\forall
∀以及类型构造算子list,length的类型可以写作:
∀
α
.
l
i
s
t
(
α
)
→
i
n
t
e
g
e
r
\forall\ \alpha.list(\alpha)\rightarrow integer
∀ α.list(α)→integer
- 其中带有 ∀ \forall ∀符号表达式被称为“多态类型”;
7.6 控制流
Control Flow
- if-else语句;
- while语句;
- 布尔表达式
- 改变控制流:
if(E)S
; - 计算逻辑值:使用带有逻辑运算符的三地址指令进行求值;
- 改变控制流:
7.6.1 布尔表达式
Boolean Expressions
本节中考虑如下文法生成的布尔表达式:
B
→
B
∥
B
∣
B
&
&
B
∣
!
B
∣
(
B
)
∣
E
r
e
l
E
∣
t
r
u
e
∣
f
a
l
s
e
B\rightarrow B\Vert B\ |\ B\&\&B\ |\ !B\ |\ (B)\ |\ E\ \mathbf{rel}\ E\ |\ \mathbf{true}\ |\ \mathbf{false}
B→B∥B ∣ B&&B ∣ !B ∣ (B) ∣ E rel E ∣ true ∣ false
- 通过属性
r
e
l
.
o
p
\mathbf{rel}.op
rel.op指明运算符
<,<=,=,!=,>,>=
中的一种;
7.6.2 短路(跳转)代码
Short-Circuit(or jumping) Code of Boolean Expression
在短路(跳转)代码中,布尔运算符 ∥ , & & , ! \Vert,\&\&,! ∥,&&,!被翻译成跳转指令。运算符本身不出现在代码中,布尔表达式的值是通过代码序列中的位置来表示的。
对于语句
i
f
(
x
<
100
)
∥
x
>
200
&
&
x
!
=
y
)
x
=
0
;
if(x<100)\ \Vert\ x>200\ \&\&\ x!=y)x=0;
if(x<100) ∥ x>200 && x!=y)x=0;
翻译的跳转代码如下:
i
f
x
<
100
g
o
t
o
L
2
g
o
t
o
L
3
L
3
:
i
f
x
>
200
g
o
t
o
L
4
g
o
t
o
L
1
L
4
:
i
f
x
!
=
y
g
o
t
o
L
2
g
o
t
o
L
1
L
2
:
x
=
0
L
1
:
.
.
.
\begin{aligned} &if\ x<100\ goto\ L_2\\ &goto\ L_3\\ L_3:\ &if\ x>200\ goto\ L_4\\ &goto\ L_1\\ L_4:\ &if\ x!=y\ goto\ L_2\\ &goto\ L_1\\ L_2:\ &x=0\\ L_1:\ &... \end{aligned}
L3: L4: L2: L1: if x<100 goto L2goto L3if x>200 goto L4goto L1if x!=y goto L2goto L1x=0...
7.6.3 控制流语句
Flow-of-Control Statements
将布尔表达式翻译成为三地址码:
S
→
i
f
(
B
)
S
1
S
→
i
f
(
B
)
S
1
e
l
s
e
S
2
S
→
w
h
i
l
e
(
B
)
S
1
\begin{aligned} &S\rightarrow \mathbf{if}(B)S_1\\ &S\rightarrow \mathbf{if}(B)S_1\ \mathbf{else}\ S_2\\ &S\rightarrow \mathbf{while}(B)S_1 \end{aligned}
S→if(B)S1S→if(B)S1 else S2S→while(B)S1
翻译过程如下图表示:
语法制导定义如下:
7.6.4 布尔表达式的控制流翻译
Semantic Rules of Boolean Expression
针对布尔表达式的语义规则如下图所示:
实例:
while a < b do
if c < d then
x = y + z
else
x = y - z
翻译结果如下:
L
1
:
i
f
a
<
b
g
o
t
o
L
2
g
o
t
o
L
n
e
x
t
L
2
:
i
f
c
<
d
g
o
t
o
L
3
g
o
t
o
L
4
L
3
:
t
1
=
y
+
z
x
=
t
1
g
o
t
o
L
1
L
4
:
t
2
=
y
−
z
x
=
t
2
g
o
t
o
L
1
L
n
e
x
t
:
.
.
.
\begin{aligned} L_1:\ &if\ a<b\ goto\ L_2\\ &goto\ L_{next}\\ L_2:\ &if\ c<d\ goto\ L_3\\ &goto\ L_4\\ L_3:\ &t_1=y+z\\ &x=t_1\\ &goto\ L_1\\ L_4:\ &t_2=y-z\\ &x=t_2\\ &goto\ L_1\\ L_{next}:\ &... \end{aligned}
L1: L2: L3: L4: Lnext: if a<b goto L2goto Lnextif c<d goto L3goto L4t1=y+zx=t1goto L1t2=y−zx=t2goto L1...
7.7 回填
Backpatching
对于 i f ( B ) S if(B)S if(B)S中的布尔表达式B的翻译结果中包含一条跳转指令。当B为假时,该指令将跳转到紧跟在S的代码之后的指令处。在一趟式的翻译中,B必须在处理S之前就翻译完毕。那么跳过S的goto指令的目标是什么呢?我们解决这个问题的方法是将标号作为继承属性传递到生成相关跳转指令的地方。但是,这样的做法要求在进行一趟处理,将标号和具体地址绑定起来。
本节介绍一种称为回填的补充性技术,它把一个由跳转指令组成的列表以综合属性的形式进行传递。明确的讲,生成一个跳转指令时暂时不指定该跳转指令的目标。这样的指令都被放入一个由跳转指令组成的列表中。等到能确定正确的目标标号时才去填充这些指令的目标标号。同一列表的所有跳转指令具有相同的目标标号。
7.7.1 回填技术目标代码生成
makelist(i)
:创建一个只包含i的列表;merge(p1,p2)
:将列表p1和p2进行合并,返回合并后列表的指针;backpatch(p,i)
:将i作为目标标号插入p列表中;
7.7.2 布尔表达式的回填
Backpatching for Bolean Expressions
布尔表达式文法如下:
B
→
B
∥
M
B
∣
B
&
&
M
B
∣
!
B
∣
(
B
)
∣
E
r
e
l
E
∣
t
r
u
e
∣
f
a
l
s
e
M
→
ε
\begin{aligned} &B\rightarrow B\Vert M B\ |\ B\&\&MB\ |\ !B\ |\ (B)\ |\ E\ \mathbf{rel}\ E\ |\ \mathbf{true}\ |\ \mathbf{false}\\ &M\rightarrow\varepsilon \end{aligned}
B→B∥MB ∣ B&&MB ∣ !B ∣ (B) ∣ E rel E ∣ true ∣ falseM→ε
翻译方案如下:
7.7.3 控制转移语句
Flow-of-Control Statements
控制流语句文法如下:
S
→
i
f
(
B
)
M
S
∣
i
f
(
B
)
M
S
N
e
l
s
e
M
S
∣
w
h
i
l
e
M
(
B
)
M
S
∣
{
L
}
∣
A
M
→
ε
N
→
ε
L
→
L
M
S
∣
S
\begin{aligned} &S\rightarrow \mathbf{if}(B)MS\ |\ \mathbf{if}(B)MSN\ \mathbf{else}\ MS\ |\ \mathbf{while}M(B)MS\ |\ \{L\}\ |\ A\\ &M\rightarrow \varepsilon\\ &N\rightarrow\varepsilon\\ &L\rightarrow LMS\ |\ S \end{aligned}
S→if(B)MS ∣ if(B)MSN else MS ∣ whileM(B)MS ∣ {L} ∣ AM→εN→εL→LMS ∣ S
- S表示一个语句;
- L是一个语句的列表;
- A是一个赋值语句;
- B是一个布尔表达式;
翻译方案如下:
⭐⭐⭐⭐⭐⭐
Github主页👉https://github.com/A-BigTree
项目链接👉https://github.com/A-BigTree/college_assignment
⭐⭐⭐⭐⭐⭐