ch13-ir-control-控制流语句的翻译

本讲内容颇有难度, 需要多多思考

虽然有非常简单的讲法, 教材龙书偏偏采用了让初学者望而生畏的讲法。

为什么要 “创造困难”?

简单的翻译方案会产生大量冗长的代码,选择困难的翻译方案是为了生成更短、更高效的代码

control.g4和bool表达式的文法

主要关注绿框中的控制流语句,assign上节课已经讲过了所以不关注

control.g4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
grammar Control;

@header {
package codegen;
}

prog : stat ;

stat : ID '=' expr ';' # AssignStat
| 'if' '(' bool ')' '{' ifStat = stat '}' 'else' '{' elseStat = stat '}' # IfElseStat
| 'if' '(' bool ')' '{' stat '}' # IfStat
| 'while' '(' bool ')' '{' stat '}' # WhileStat
| 'break' ';' # BreakStat
| first = stat second = stat # SeqStat
;

bool : lhs = expr op = ('>' | '>=' | '==') rhs = expr # RelExpr
| op = '!' bool # NotExpr
| lhs = bool op = '&&' rhs = bool # AndExpr
| lhs = bool op = '||' rhs = bool # OrExpr
| 'true' # TrueExpr
| 'false' # FalseExpr
;

/**
* Section 6.4.1: grammar for expressions
* (array decl and references are not included yet)
*/

expr : '-' expr # NegExpr
| expr '*' expr # MultExpr
| expr '+' expr # ADDExpr
| '(' expr ')' # ParenExpr
| ID # IdExpr
| INT # IntExpr
;

GT : '>' ;
GE : '>=' ;
EE : '==' ;

ID : [a-z] ;
INT : [0-9] ;
WS : [ \t\r\n]+ -> skip;

一种非常简单的做法

分工合作:每个终结符负责且仅负责自己的翻译中间代码,不越俎代庖

  • 为布尔表达式 B 计算逻辑值 (假设最后一条中间代码将结果保存在临时变量t1中)

  • if、while 语句根据 B 的结果改变控制流

例如:if (B) S1

结构:

  • 先为B~1~生成code,计算真假,将最终逻辑值保存在t~1~

  • if用一个branch语句测试t~1~的真假,为真就跳到b.true,为假就跳转到b.false

  • b.true是一个标签,接着递归的为S~1~生成代码

B和if/while的作用是不同的!

控制流语句中间代码翻译代码展示:visitor

  • 不推荐使用AG属性文法:.g4和java完全混杂,模块性不好

  • 不推荐监听器:介绍visitor,visitor的处理更加灵活 ,listener只能在刚进入s或者s的子树全部处理完之后才能做操作(enter和exit),没有提供在两个子节点之间的时机来处理

CodeGenVisitor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
package codegen;

import org.antlr.v4.runtime.Token;

import java.io.FileWriter;
import java.io.IOException;
import java.sql.Array;
import java.util.ArrayDeque;
import java.util.Deque;

public class CodeGenVisitor extends ControlBaseVisitor<String> {
private final Deque<String> breakLabels = new ArrayDeque<>();
private final FileWriter fileWriter;
private int tempCounter = 1;//临时变量的数量
private int labelCounter = 1;//为控制流语句生成label

public CodeGenVisitor(String file) throws IOException {
fileWriter = new FileWriter(file);//中间代码需要写入文件
}

@Override
public String visitProg(ControlParser.ProgContext ctx) {
visit(ctx.stat());

try {
//写文件需要先关闭文件,否则写的内容依然缓存在buffer中
fileWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}

return null;
}

// stat -> break
// 难点在于嵌套结构的break如何选择最终跳转的label
@Override
public String visitBreakStat(ControlParser.BreakStatContext ctx) {
//无条件跳转到栈顶
emit("br " + breakLabels.peek());
return null;
}

// stat -> while (bool) { stat }
@Override
public String visitWhileStat(ControlParser.WhileStatContext ctx) {
String beginLabel = getNewLabel("begin");
emit(beginLabel);

String bool = visit(ctx.bool());

String trueLabel = getNewLabel("b.true");
String falseLabel = getNewLabel("b.false");
emit("br " + bool + " " + trueLabel + " " + falseLabel);

emitLabel(trueLabel);
//label压栈:while的结尾
breakLabels.push(falseLabel);
//访问stat
visit(ctx.stat());
//label出栈
breakLabels.pop();
//true中的无条件跳转指令
emit("br " + beginLabel);

//false
emitLabel(falseLabel);

return null;
}

// stat -> if (bool) { ifStat = stat } else { elseStat = stat}
// 复杂的if语句
@Override
public String visitIfElseStat(ControlParser.IfElseStatContext ctx) {
String bool = visit(ctx.bool());

String trueLabel = getNewLabel("b.true");
String falseLabel = getNewLabel("b.false");
String endLabel = getNewLabel("b.end");
emit("br " + bool + " " + trueLabel + " " + falseLabel);

emitLabel(trueLabel);
visit(ctx.ifStat);
emit("br " + endLabel);

emitLabel(falseLabel);
visit(ctx.elseStat);

emitLabel(endLabel);

return null;
}

// stat -> if (bool) { stat }
// if语句
@Override
public String visitIfStat(ControlParser.IfStatContext ctx) {
String bool = visit(ctx.bool());

//生成label
String trueLabel = getNewLabel("b.true");
String falseLabel = getNewLabel("b.false");
//选择跳转的label
emit("br " + bool + " " + trueLabel + " " + falseLabel);

//为真
emitLabel(trueLabel);
visit(ctx.stat());

//直接跳到false
emitLabel(falseLabel);

return null;
}

// stat -> first = stat second = stat
@Override
public String visitSeqStat(ControlParser.SeqStatContext ctx) {
visit(ctx.first);
visit(ctx.second);
return null;
}

// stat -> ID = expr
@Override
public String visitAssignStat(ControlParser.AssignStatContext ctx) {
String expr = visit(ctx.expr());
emit(ctx.ID().getText() + " = " + expr);
return null;
}

//关系运算符
// bool -> lhs = exp REL rhs = exp
@Override
public String visitRelExpr(ControlParser.RelExprContext ctx) {
//先为操作数生成中间代码
String lhs = visit(ctx.lhs);
String rhs = visit(ctx.rhs);

//根据不同运算符考虑指令
Token op = ctx.op;
String cond = switch (op.getType()) {
case ControlLexer.GT -> "sgt"; // sgt: signed greater than
case ControlLexer.GE -> "sge"; // sge: signed greater than or equal
case ControlLexer.EE -> "eq";
default -> throw new IllegalArgumentException("No such cond: " + op.getText());
};

String temp = getNewTemp();
emit(temp + " = " + "icmp " + cond + " " + lhs + " " + rhs);
return temp;
}

// bool -> lhs = bool && rhs = bool
@Override
public String visitAndExpr(ControlParser.AndExprContext ctx) {
String lhs = visit(ctx.lhs);

//根据B1是true还是false
String trueLabel = getNewLabel("b1.true");
String falseLabel = getNewLabel("b1.false");
String endLabel = getNewLabel("b1.end");
emit("br " + lhs + " " + trueLabel + " " + falseLabel);

//没有短路求值
emitLabel(trueLabel);
String rhs = visit(ctx.rhs);
String temp = getNewTemp();
emit(temp + " = " + "AND " + lhs + " " + rhs);
//用endlabel跳过短路求值的路径,temp就是最终的结果
emit("br " + endLabel);

//有短路求值
emitLabel(falseLabel);
//直接赋值为false
emit(temp + " = " + "false");

emitLabel(endLabel);

return temp;
}

// bool -> lhs = bool || rhs = bool
@Override
public String visitOrExpr(ControlParser.OrExprContext ctx) {
//先拿到bool逻辑值
String lhs = visit(ctx.lhs);

String trueLabel = getNewLabel("or.true");
String falseLabel = getNewLabel("or.false");
String endLabel = getNewLabel("or.end");
emit("br " + lhs + " " + trueLabel + " " + falseLabel);

emitLabel(falseLabel);

String rhs = visit(ctx.rhs);
String temp = getNewTemp();
//or
emit(temp + " = OR " + lhs + " " + rhs);
emit("br " + endLabel);

emitLabel(trueLabel);
emit(temp + " = true");

emitLabel(endLabel);

return temp;
}

// bool -> ! bool
@Override
public String visitNotExpr(ControlParser.NotExprContext ctx) {
String bool = visit(ctx.bool());
String temp = getNewTemp();
emit(temp + " = " + "NOT " + bool);
return temp;
}

@Override
public String visitFalseExpr(ControlParser.FalseExprContext ctx) {
//生成临时变量保存false
String temp = getNewTemp();
emit(temp + " = " + ctx.getText());
return temp;
}

// B -> true
@Override
public String visitTrueExpr(ControlParser.TrueExprContext ctx) {
//生成临时变量保存true
String temp = getNewTemp();
emit(temp + " = " + ctx.getText());
return temp;
}

//expr返回id
@Override
public String visitIdExpr(ControlParser.IdExprContext ctx) {
String temp = getNewTemp();
emit(temp + " = " + ctx.getText());
return temp;
}

@Override
public String visitIntExpr(ControlParser.IntExprContext ctx) {
String temp = getNewTemp();
emit(temp + " = " + ctx.getText());
return temp;
}

private void emitLabel(String label) {
//生成指令产生label
try {
fileWriter.write(label + ":\n");
} catch (IOException ioe) {
System.err.println(ioe.getMessage() + " : " + label);
}
}

private void emit(String code) {
//写文件
try {
fileWriter.write(code + '\n');
} catch (IOException ioe) {
System.err.println(ioe.getMessage() + " : " + code);
}
}

private String getNewTemp() {
//给临时变量起名字
return "t" + tempCounter++;
}

private String getNewLabel(String label) {
//给label取名
return label + labelCounter++;
}
}

关于break的栈保存:

CodeGenVisitorTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package codegen;

import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;

public class CodeGenVisitorTest {
private InputStream is = System.in;
private final String PATH = "src/test/antlr/codegen/control-flow-I/";
private String srcFile;
private String codeFile;

@BeforeMethod
public void setUp() throws IOException {
// bool.txt, if.txt, while.txt, break.txt, bool-short-circuit.txt
// while-if-II.txt, bool-short-circuit-II.txt
srcFile = "while-if-II";
is = new FileInputStream(Path.of(PATH + srcFile + ".txt").toFile());
}

@Test
public void testCodeGenVisitor() throws IOException {
CharStream input = CharStreams.fromStream(is);
ControlLexer lexer = new ControlLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);

ControlParser parser = new ControlParser(tokens);
ParseTree tree = parser.prog();

codeFile = srcFile + "-code";
CodeGenVisitor cg = new CodeGenVisitor(PATH + codeFile + ".txt");
cg.visit(tree);
}
}

短路求值

B -> B~1~ && B~2~:

如果B~1~为false,则正常生成中间代码,但是B~2~不用判断真假,B.value = false,绕过B~2~

修改visitAndExpr

可以自行尝试or的短路求值

直接用布尔表达式改变控制流:比较复杂

(使用控制流跳转间接表名布尔表达式的值)