Skip to content

编译原理

编译原理

2023 - 2024 春夏

Introduction

2024.02.29

compiler: 一个将一种语言翻译为另一种语言的程序 ( 语言与计算机相关 )

image-20240229105604094

Typical Workflow of a Compiler

编译器的工作阶段通常如下:分为前、中、后三端

image-20240229105635243

  • 词法分析 (Lexical Analysis): String \(\rightarrow\) token sequence ( 记号序列 ),删除不必要的部分。通常使用正则表达式定义 ( 例如rm 2024* ),用于匹配不同的 token

  • 语法分析 (Syntactic Analysis): 将记号序列解析为某种语法结构 ( 如语法树 )

  • 语义分析 (Semantic Analysis): 分析语法树节点的属性信息等。得到符号表等。

image-20240229111638563

  • 中间代码生成 (IR): 源语言与目标语言的桥梁。( 三地址 : 每个表达式中至多一个操作符两个操作数 )

image-20240229111840556

  • ** 基于中间表示的优化 ** ( 机器无关代码优化 ): 对中间代码进行优化和变换,降低执行时间,减少资源消耗。

image-20240229112209757

  • 后端 - 目标代码生成 : 把中间代码翻译为目标语言,涉及到指令选择寄存器分配指令调度等。

image-20240229112502564

Tiger 编译器

image-20240229112935252

  • AST ( 抽象语法树 ): 经过语法分析 + "Parsing Actions" 生成。
  • IR Tree ( 树型中间表示 ): 语义分析之后按照一定规则生成。进行了归一化 (Canonicalized) 的操作。
  • Control Flow Analysis ( 控制流分析 ): 判断分支语句的执行路径 ( 可以去除不可达的路径 )
  • CFG (Control Flow Graph) ( 控制流图 ): 方便进行数据流分析–如活跃变量分析 (Liveness Analysis)
  • Interference Graph ( 冲突图 ): 从活跃变量分析的结果构造,用于指导寄存器分配。

Lexical Analysis 词法分析

2024.03.07

image-20240307113231210

Lexical Token

定义:一个字母序列,文法中的基本单元。

常见的 token 有:

image-20240307101433721

多数语言中保留词不作为 identifiers

常见的 non-token 有:

image-20240307101557052

词法分析的示例如下:

image-20240307101803168

Regular Expression

使用正则表达式来描述 token 规则,再使用 DFA 实现上述的正则表达式。

词法分析阶段不用关心 string 的含义,只需要关心字符串是否在语言中。

image-20240307103325675

Note

其中 a | \(\epsilon\) a | 等价。

Note

更多的简写:
image-20240307103937977
简写不会让正则表达式的表达能力增加。

Note

image-20240307104153951
第五行表示的是注释空格、换行等。

Warning

上述的例子中存在歧义,例如 if8 可以解释为 ID 也可以解释为 if + 8
现代的编译器会引入最长匹配机制且按照规则的优先级逐条匹配 (if8 解释为 ID)。

Finite Automata

image-20240307110214618

确定性状态机no two edges leaving from the same state are labeled with the same symbol.

将上述的六个 DFA 拼接成一个 DFA

image-20240307110738639

计算机中 DFA 的实际上通过表格的方式 ( transition matrix ) 实现的。

image-20240307111430774

  • 每一行表示一个状态,每一列表示读入的 symbol,存储的值表示读入后跳转到的状态。
  • 0 表示错误状态。

额外需要一个矩阵存储每一个状态是否为终态 ( "finality" array ),以及其对应的输出标签是什么。

  • 为实现 Longest matching,每次遇到可以终止的节点时,先记录当前的分析结果,继续向后扫描,直到无法扫描为止。
  • 需要 Last-FinalInput-Position-at-Last-Final 两个变量进行记录。
  • image-20240307112415882
  • image-20240307112910258

Nondeterministic Finite Automata

Thompson's Construction

将正则表达式转化为 NFA

image-20240307114418944

Regular Exp NFA
image-20240307115219803 image-20240307115235538

Subset Construction

NFA 转化为 DFA

image-20240307120207165

  • edge(s, c):1.the set of all NFA states reachable by following a single edge with label c from state s.
  • closure(S):the set of states that can be reached from a state in S without consuming any of the input, that is, by going only through ∊-edges.

image-20240307121141938

算法思想:

image-20240307121201189

算法实现:

image-20240307121246802

算法例子:

image-20240307122035132

NFA DFA
image-20240307115235538 image-20240307122243841

上述右图的部分等价状态可以合并,以缩小 DFA。但有部分的等价状态无法消除。

image-20240307122759170

上述的算法总体思想为找到非等价的状态,举例如下:

image-20240307144035741

Lex: A Lexical Analyzer Generator

Lex 是一个程序,

  • 输入:a text file containing regular expressions, together with the actions to be taken when each expression is matched.
  • image-20240314100840000
  • 输出:Contains C source code defining a procedure yylex that is a table-driven implementation of a DFA corresponding to the regular expressions of the input file, and that operates like a getToken procedure.

Syntax analysis 语法分析

2024.03.14

image-20240314101147943

CFG 上下文无关文法

正则表达式的无法表达括号匹配等 Recursive Structure 规则。

CFG 在进行替换时不需要关心 non-terminal symbol 的左右 ( 上下文 ) 如何,根据规则进行替换即可。

一个不含分支语句的程序的 CFG 可以表达如下:

image-20240314103324107

其中 S 表示语句,可以使用分号顺序连接。E 为数字或变量。

在判断程序是否正确时,主要思路为判断程序是否有语法错误。

image-20240314104017211

上述推导遵循了最左推导 left-most derivation,可以根据次生成 parsing tree。某些情况下最左和最右推导等价 ( 当文法没有歧义时 )

image-20240314104507327

一颗 parsing tree 可能对应多个推导关系。

歧义

image-20240314105036328

需要对生成的规则进行更改以消除歧义。规定乘法优先级高于加法,并统一进行左结合。

image-20240314110711697

为识别是否已经读到文件的末尾,引入 $ (EOF)。在规则中加入 $S\rightarrow E $ $

Top-Down Parsing

Recursive Descent Parsing: parses LL(1) grammars : Left-to-right parse; Leftmost-derivation; 1symbol lookahead.

根据读取到的 token,从左到右读取,尝试寻找规则进行匹配进行 parsing tree 的构建。

image-20240314111915045

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
enum token {IF, THEN, ELSE, BEGIN, END, PRINT, SEMI, NUM, EQ};
extern enum token getToken(void);
enum token tok; 
void advance() {tok=getToken();} 
void eat(enum token t) {if (tok==t) advance(); else error();}
void S(void) {
    switch(tok) { 
    case IF: eat(IF); E(); eat(THEN); S(); eat(ELSE); S(); break; 
    case BEGIN: eat(BEGIN); S(); L(); break; 
    case PRINT: eat(PRINT); E(); break; 
    default: error(); }}
void L(void) {
    switch(tok) {
    case END: eat(END); break;
    case SEMI: eat(SEMI); S(); L(); break;
    default: error(); }}
void E(void) { eat(NUM); eat(EQ); eat(NUM); }

上述的实现主要依靠替换后的第一个 symbol 进行规则的选择,但并不适用与所有的语法。

Predictive Parsing

对于无法通过第一个 symbol 进行判断 ( 例如都是 non-terminal),基于一定的规则进行计算首先派生出的 terminal symbol,再进行判断。

对于例如 \(Z\rightarrow XY\) 的规则,首先需要考虑的是 X 在派生之后会产生的第一个 terminal 符号 (First)。但需要考虑到 X 可能派生出空串\(\varepsilon\) (Nullable),所以需要考虑 Y 可能派生出的第一个 terminal。但 Y 若也可能派生出\(\varepsilon\) , 则需要考虑紧跟在 Y 之后的 terminal (Follow)

\(\gamma\rightarrow^* t\beta\),其中 t terminal symbol\(\gamma\)\(\beta\) 表示任意的字符串,\(t\in First(\gamma)\)

\(X \rightarrow^* \alpha Xt \beta\)\(\alpha\)\(\beta\) 表示任意的字符串,\(t\in Follow(X)\)

计算 Nullable 算法:

C
1
2
3
4
5
6
7
for each symbol X:
    Nullable(X) = False
repeat
    for each production X -> Y1 Y2  Yk:
        if Nullable(Yi) = True for all 1 <= i <= k:
            Nullable(X) = True
until Nullable did not change in this iteration

需要注意的是,每一轮都需要遍历所有的规则,每一轮循环结束都需要和前一次的结果进行比较,直到没有变化后结束循环。

image-20240314190711432

First Follow 的算法如下:

image-20240314190942745

举例如下:

First-1 First-2 First-3 First-4
image-20240314191145302 image-20240314191203858 image-20240314191230625 image-20240314191249938
Follow-1 Follow-2 Follow-3
image-20240314191510728 image-20240314191527783 image-20240314191552450

需要额外注意的是 \(X\rightarrow Y\) 规则会使 Follow(Y) Follow(X) 出现重合。最终可以得到如下表格:

image-20240314192039713

需要将上述的表格转化为一下的表格,以指明使用那条规则进行 parsing tree 的构建:

image-20240314192213690

构建的算法如下:

  • if T \(\in\) First(\(\gamma\)) then enter \(X\rightarrow \gamma\) in row X, col T.
  • if \(\gamma\) is Nullable and T \(\in\) Follow(X) then enter \(X\rightarrow \gamma\) in row X, col T.

上述的表格指的是,在自顶向下派生 non-terminal 时,读到词法分析得到的序列中的某个 symbol 时需要选择的规则。若是查表得到了空格,则表示出现了语法错误。

还需要注意上述的表格还存在多条规则一个空格的情况,这可能由两种情况导致:

  • Left-Recursive:\(A\rightarrow A\alpha,\,A\rightarrow\beta\).
  • 修改后:\(A\rightarrow\beta A',\,A'\rightarrow\alpha A', A'\rightarrow \varepsilon\).
  • Left-Left Factoring:修改如下:
  • image-20240314194155167

举例如下:

image-20240314194720103

错误恢复

一般来说,在遇到错误后,需要从错误中恢复过来继续进行语法分析,以分析得到更多的潜在的语法错误。

常见的做法如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int Tprime_follow[] = {PLUS, RPAREN, EOF}; 
void Tprime() { 
    switch (tok) { 
        case PLUS: break; 
        case TIMES: eat(TIMES); F(); Tprime(); break; 
        case RPAREN: break; 
        case EOF: break; 
        default: print("expected +, *, right-paren, or end-of-file"); 
        skipto(Tprime_follow); 
    }
}

LL(k)

k 表示在选择派生规则时需要根据几个 input 才能判定。LL(1) 就表示一个 1 token 就能无歧义的进行规则选择。

every LL(k) grammar is an LL(k+n) grammar, for any n.

所以在例如在证明一个 grammar LL(3) 时,可以先尝试证明其为 LL(1).

LR(k) parsing

相比 LL(k),不急于做匹配,直到 input tokens corresponding to the entire right-hand side of the production.

  • 特征:Left-to-right parse、Rightmost derivation、k-token lookahead。
  • 是一种 bottom-up Parsing
  • 不断将产生式的右部替换为左部。

举例如下:

image-20240321110518813

从以上的过程可以看出以上的过程自下往上看实际上为最右展开。

image-20240321111511601

分为 shift reduce 两个过程。

  • shift: push next input onto top of stack

  • reduce R:

  • top of stack should match RHS of rule R (e.g., X -> A B C)
  • pop the RHS from the top of stack (e.g., pop C B A)
  • push the LHS onto the stack (e.g., push X)

  • error

  • accept: shift $ and can reduce what remains on stack

LR(0) Parsing

只需要在 state stack 中看就可以判断。

可以使用 DFA 来确定是否 reduce

image-20240321112902898

  • . : the current position of the parser

  • start with S’ -> . S $: stack should be empty and the input is expected to be a complete S-sentence followed by $

  • A -> 𝛼 . β: LR(0) item
  • means the parser has processed 𝛼 and expects to see β next.

接下来将 NFA 转化为 DFA

image-20240321113113370

具体算法如下:

image-20240321114549316

应用上述的算法,对于 grammar

S’ -> S$ S -> ( L ) S -> x L -> S L -> L , S

image-20240321115755179

Stack (states) Stack (symbols) Input Action
1 ( x ) $ shift 3
1, 3 ( x ) $ shift 2
1, 3, 2 ( x ) $ reduce 2 S -> x
1, 3 ( S ) $ goto 7
1, 3, 7 ( S ) $ reduce 3 L -> S
1, 3 ( L ) $ goto 5
1, 3, 5 ( L ) $ shift 6
1, 3, 5, 6 ( L ) $ reduce 1 S -> ( L )
1 S $ goto 4
1, 4 S $ accept

可以得到以下的 parsing table

( ) x , $ S L
1 s3 s2 g4
2 r2 r2 r2 r2 r2
3 s3 s2 g7 g5
4 a
5 s6 s8
6 r1 r1 r1 r1 r1
7 r3 r3 r3 r3 r3
8 s3 s2 g9
9 r4 r4 r4 r4 r4

具体的操作规则如下:

  • Shift: For edge labeled with terminal t and from state i to state n:
  • T[i, t] = sn (shift n)

  • Goto: For edge labeled with non-terminal X and from state i to state n:

  • T[i, X] = gn (goto n)

  • Reduce: For item in a state i with dot at the end (e.g., X -> A C .):

  • T[i, each terminal] = rk (reduce k)

  • k is the index of this production

  • Accept: For each state i containing

  • S’ -> S . $:

  • T[i, $] = accept

Simple LR Parsing

可能会存在 shift-reduce 冲突,即无法判断是否需要 reduce

引入 SLR 方法,基本思想是计算 Follow 集合:

C
1
2
3
4
5
6
7
8
R  {} 
for each state I in T 
     for each item A  α. in I 
           for each token X in FOLLOW(A) 
              R  R  {(I, X, A  α)} 
I: the row
X: the column
A  α: the production

LR(1) Parsing

需要往后看一个 symbol 以辅助动作选择。

  • ( \(A \rightarrow \alpha.\beta\), x), x 表示 \(\beta\) 处理完之后,将看到的 symbol
C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Closure(I) = 
    repeat 
        for any item (A  α., z) in I 
           for any production X  γ 
               for any w  FIRST(βz) 
                  I  I  {(X  .γ, w)} 
    until I does not change 
    return I

Goto(I, X) = 
    J  {} 
    for any item (A  α., z) in I    
        add (A  αX. β, z) to J 
    return Closure(J)

// reduce
R  { } 
for each state I in T     
    for each item (A  α., z) in I 
         R  R {(I, z, A  α)} 

Abstract Syntax 抽象语法

2024.04.11

如何构建抽象语法树 AST,如何将语义信息处理加入到加入到语法分析中去。

Note

recursive-descent parser 中,语义动作有 values ( 如算式结果 ),或者 side effects ( 如赋值语句、函数调用 ),都需要记录。

image-20240411103535540

一些情况下需要进行语义值得传递。例如:T → F T′;T′ → *F T′;T′ → / F T′,需要将 F 的值向下传递。

Note

Yacc 中的部分语法:
{ … }: semantic actions
$i: the semantic values of the i_th RHS symbol
$$: the semantic value of the LHS nonterminal symbol
%union: difference possible types for semantic values to carry
<variant>: declares the type of each terminal or nonterminal

Yacc 代码举例如下:

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
%{  %}
%union {int num; string id;}
%token <num> INT
%token <id> ID
%type <num> exp
...
%left UMINUS
%%

exp: INT {$$ = $1;}
   | exp PLUS exp {$$ = $1 + $3;}
   | exp MINUS exp {$$ = $1 - $3;}
   | exp TIMES exp {$$ = $1 * $3;}
   | MINUS exp %prec UMINUS {$$ = -$2;}

Abstract Syntax Tree

image-20240411110554872

相比具体语法,抽象语法略去了一些冗余的信息便于计算机进行处理。

为方便错误恢复,可以记录下每一个 token 的位置信息。

Semantic Analysis 语义分析

2024.04.11

如何将声明与使用联系到一起,检查表达式是否正确。

Symbol Tables 符号表

  • environment:变量与其类型的定义绑定。
  • \(\sigma_0=\{g\rightarrow string, a\rightarrow int\}\)
  • \(\sigma_1=\sigma_0+\{b\rightarrow string, a\rightarrow int\}\)
  • 两个符号表存在一个“冲突”,需要注意到的是局部类型可以覆盖全局的类型。加号右侧的优先级更高。
  • 一个局部语句块结束时,可以抛弃局部的符号表:
    • discard \(\sigma_1\), go back to \(\sigma_0\)

实现符号表有两种方式:

  • Functional Style:保留每一级的符号表。
  • Imperative Style:只是在一个符号表上进行修改。

对于一些面向对象的语言,需要有多个符号表,例如:

Java
1
2
3
class E {   
    static int a = 5;   
}

可以写作:\(\sigma_0 = \{a\rightarrow int\},\,\sigma_1=\{E\rightarrow \sigma_0\}\)

符号表在具体实现时经常使用哈希表以实现快速的插入与删除:

image-20240411113624509

对于函数式的符号表,可以使用如下的指针方式高效实现:

image-20240411115120025

但若是符号表巨大,复制仍然需要较多的时间,可以使用二叉搜索树代替哈希表以加速复制的过程:

image-20240411115338728

语义分析中不在乎字符串是否匹配,可以将字符串转化为 symbol 以加速 Hash 操作。symbol 可以直接作为 hash key,用于匹配,还可以在二叉搜索树中作为索引。

Tiger 语句中有两种绑定:类型绑定 (Typedef)、变量绑定

Type-checking 类型检查

实际上是对于 AST 的一个递归遍历的过程。

需要考虑的情况有:

  • 检查赋值的类型是否一致。

  • 将函数名与其参数表一块放进 environment

  • 结构体中存在大量的递归定义:

C
1
2
3
4
struct temp {
    int a;
    struct temp tmp;
}

如果直接进行检查会出现死锁的情况,此时先检查结构体,使用占位符 ( 如:Ty_Name) 先代替类型。最后进行处理。

但如果存在下述的循环定义情况则不可:

C
1
2
3
4
5
6
7
8
typedef a b;
typedef b a;
/*
line1: a -> Ty_Name{a, b’s Ty_Name}
       b -> Ty_Name{b, NULL}
line2: b -> Ty_Name{b, a’s Ty_Name}
     a -> Ty_Name{a, b’s Ty_Name}
*/

会出现环,无法获取具体的类型。但下述的情况可以:

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typedef a b;
struct b {
    a i;
}
/*
line1: a -> Ty_Name{a, b’s Ty_Name}
       b -> Ty_Name{b, NULL}
line2: b -> Ty_Name{b, Ty_Record}
     a -> Ty_Name{a, b’s Ty_Name}
*/

类型检查也需要检查环的情况。

  • 对于循环函数递归调用:
C
1
2
3
4
5
6
void f() {
    g();
}
void g() {
    f();
}

只需要先计算出一者的返回值和类型、参数表等即可。

Activation Records 活动记录

2024.04.18

image-20240418104824491

此章节主要关心 stack:data structures called activation records that get generated during procedure (i.e. function) calls.

  • Procedure (i.e., function) calls and returns are usually managed by a run-time stack called control stack.

  • Each time a procedure is called, space for its local variables is pushed onto the stack.

  • When the procedure terminates, that space is popped off the stack.

  • Procedure calls are also called activations of procedures.

  • Each live activation has an activation record (sometimes called a frame) on the control stack.

但若可以同时出现 nested function functio-valued variables (e.g. 函数指针 ),栈这种类型的数据结构就不够用了。

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun f(x) =
  let fun g(y) = x+y 
    in g 
  end

val h = f(3) 
val j = f(4)

val z = h(5) 
val w = j(7)

Stack frame

Local variables are pushed/popped in large batches

Local variables are not always initialized right after their creation

We want to continue accessing variables deep within the stack

所以需要将栈视为数组,可以访问非栈顶的元素。

此外还维护了一个 Stack pointer

image-20240418111025073

具体的设计如下:

image-20240418112212377

其中 previous frame caller 函数,current frame callee 函数。

incoming arguments: passed by the caller

outgoing arguments: pass parameters to other functions

可以看到参数是以倒序压在栈中的,这是为了提高指针查询的效率。

针对于之前提到的函数嵌套等问题,此处通过 static link 来进行栈帧与栈帧之间的连接。

可以看到除了栈指针外还维护了一个帧指针。在调用新函数时,会记录下老 FP 的位置,以便回退。

这种双指针的方式对于非连续内存排布或帧大小可变等情况会较为有效。

对于传参,由于寄存器的数量有限,一般情况下只有前 k 个参数通过寄存器进行传参,剩下的参数通过内存传递。但需要注意到由于使用了寄存器进行传参,需要将之前的寄存器都存进内存,似乎会拖慢性能。使用寄存器传参在以下的几个情况有效:

  1. Leaf procedures need not write their incoming arguments to memory.
  2. leaf procedures: the procedures that don't call other procedures

  3. Some optimizing compilers use interprocedural register allocation, analyzing all the functions in an entire program at once.

  4. They assign different procedures different registers to receive parameters and hold local variables

  5. Parameter x is a dead variable at the point where h(z) is called. Then f(x) can overwrite r1 without saving it.

  6. Some architectures have register windows, so that each function invocation can allocate a fresh set of registers without memory traffic.

部分架构不会将寄存器放在栈帧中,会放在指定的寄存器中。针对叶子进程可以使用 interprocedural register allocation 进行优化。

  • Block Structure: In languages allowing nested function declarations (such as Pascal, ML, and Tiger), the inner functions may use variables declared in outer functions.
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
type tree = {key: string, left: tree, right: tree}
function prettyprint(tree: tree) : string =
  let
    var output := “”  
    function write(s: string) = 
      output := concat(output,s)

    function show(n: int, t: tree) =
      let function indent(s: string) =
            (for i := 1 to n
             do write( ));
             output := concat(output, s);
             write("\n"))
      in if t=nil 
         then indent(".")
         else (indent(t.key));
               show(n+1, t.left);
               show(n+1, t.right))
      end
    in show(0, tree); output
  end

此处例如 write 函数需要能够访问 prettyprint 的栈帧。Tiger 语言中需要通过 static link 来实现 Block structure

static link:每当函数 f 被调用时,f 会被传递一个栈帧的指针,指向包含 f 的上一级函数,放置在 f static link 的区域。如果是递归调用则传入自己的 static link

上述代码详细的处理过程如下:

  • in show(0, tree); output

  • passing pretryprint's own frame pointer (FP) as show's static link: prettyprint is the immediate enclosing function of show

  • show's static link = prettyprint's FP

  • function show(n:int, t: tree) =

  • show stores its static link (the address of prettyprint's FP)

  • then indent(".")

  • show calls indent, passing its own FP as indent's static link

  • show(n+1, t.left)

  • show calls show, passing its own static link (not its own FP) as the static link

  • for i := 1 to n

  • fetch n at an appropriate offset from indent's static link (which points at the frame of show)

  • write("\n")

  • indent's static link (show's FP) + offset = show's static link = prettyprint's FP

  • How can indent use output from prettyprint's frame?

    It starts with its own static link, then fetches show's, then fetches output.

Interm. Code 中间代码

2024.04.25

Three Address Code 三地址码

最基本的形式为 x = y op z 一条指令涉及到三个地址 ( 地址 )。对于复杂情况可以进行如下的拆解:

image-20240425101940561

对于例如取负等操作需要特殊处理使其变为三地址码。

image-20240425102313345 image-20240425102322192

需要注意到其中例如 fact *= x 需要拆成两条指令来完成。使用 readwrite 来进行 IO,使用 goto 进行跳转,halt 进行停机。

Intermediate Representation Tree

image-20240425103634297

Expressions

IR AST 进行简化,为翻译汇编代码提供方便。

image-20240425103754153

IR tree 并不是专为 Tiger 语言设计的,具有通用性。每个表达式都有返回值,存在一定的副作用。

image-20240425104525298

其中 EXP(e) 仅用作评估计算表达式的副作用 ( 寄存器改变 ) 等,不关心计算的结果。

语句部分是在评估副作用以及控制流,没有返回值。

Translate into IR tree

image-20240425110132716

表达式分类:

  • Ex:有返回值,适合使用 T_exp 实现。
  • Nx:没有返回值 ( 函数调用等 ),适合使用 T_stm 表达。
  • Cx:特殊类型 ( 由于 Tiger 不支持布尔类型,故特殊处理 ),布尔表达式,使用 conditional jump ( CJUMP ) Label 结合实现,举例如下:
  • image-20240425110808909

举例实现实现如下:

image-20240425111512996

其中 Cx 的第一个参数表示为真执行的语句,toEx 大致实现如下:

image-20240425111658443

另一个例子如下:

image-20240425111822368

Simple Variables

对于变量的调用,过程如下:

image-20240425112649664

其中 fp 指的是帧指针。

L-Values

表示的是内存的一个地址。

与之相对应的是右值,不表示能够被赋值的单元。

Scalar:标量,只用一个字长就能存储。

Tiger 语言中只有标量,但是在 C 等语言中,存在结构化的左值 ( 非标量 ),所以需要对 MEM 语句稍作修改:

C
1
2
T_exp MEM(T_exp, int size);
MEM(+(TEMP fp, CONST kn), s);

Subscripting and Field Selection