-
Notifications
You must be signed in to change notification settings - Fork 3
/
sccp.rs
367 lines (333 loc) · 13.6 KB
/
sccp.rs
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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use crate::lang::func::{BlockRef, FnRef};
use crate::lang::inst::{BinOp, PhiSrc, UnOp};
use crate::lang::inst::Inst;
use crate::lang::Program;
use crate::lang::util::{ExtRc, WorkList};
use crate::lang::value::{Const, Symbol, SymbolRef, Value};
use crate::pass::{FnPass, Pass};
use crate::pass::graph::{GraphBuilder, SsaGraph, VertRef, VertTag};
/// Sparse Conditional Constant Propagation
pub struct SccpOpt {
/// SSA value graph for the function being optimized
graph: SsaGraph,
/// CFG edge work list
cfg_work: WorkList<CfgEdge>,
/// Value graph edge work list
ssa_work: WorkList<SsaEdge>,
/// Map symbols to lattice values
/// For values that are constants, lattice values are created when used, no lookup is needed.
lat: HashMap<SymbolRef, LatVal>,
/// Visited CFG edges
edge_vis: HashSet<CfgEdge>,
/// Visited instructions
blk_vis: HashSet<BlockRef>,
}
/// Represent an edge in the control flow graph.
/// In the original SCCP algorithm, it is assumed that a basic block contains several phi
/// instructions, following by only one assignment. However, trivial edges (which only contain
/// assignment instruction) can be ignored in the implementation.
#[derive(Eq, PartialEq, Clone, Hash, Debug)]
struct CfgEdge {
from: Option<BlockRef>,
to: BlockRef,
}
/// Represent an edge in SSA value graph.
/// The data flow from definition point (where a value is defined) to one of its use point.
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
struct SsaEdge {
def: VertRef,
us: VertRef, // `use` is a keyword in Rust
}
/// Lattice value for indicating 'constantness' for SSA values
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
enum LatVal {
/// Yet-undetermined value
Top,
/// Determined irc-time constant
Const(Const),
/// Not constant or cannot be determined to be constant
Bottom,
}
impl LatVal {
fn is_const(&self) -> bool {
match self {
LatVal::Const(_) => true,
_ => false
}
}
fn meet(self, rhs: Self) -> Self {
match (self, rhs) {
(LatVal::Top, LatVal::Top) => LatVal::Top,
(LatVal::Top, LatVal::Const(c)) | (LatVal::Const(c), LatVal::Top) => LatVal::Const(c),
(LatVal::Bottom, _) | (_, LatVal::Bottom) => LatVal::Bottom,
(LatVal::Const(l), LatVal::Const(r)) =>
if l == r { LatVal::Const(l) } else { LatVal::Bottom }
}
}
}
impl Pass for SccpOpt {
fn run(&mut self, pro: &mut Program) { FnPass::run(self, pro) }
}
impl FnPass for SccpOpt {
fn run_on_fn(&mut self, func: &FnRef) {
// Create value graph.
let mut builder = GraphBuilder::new();
func.walk_dom(&mut builder);
self.graph = builder.graph;
// Initialize table for all interested values.
self.lat = self.graph.map.iter().map(|(sym, vert)| {
match &vert.tag {
// Symbols defined by constants are sure to be constants.
VertTag::Const(c) => (sym.clone(), LatVal::Const(c.clone())),
// Variable and phi vertices are yet to be determined.
VertTag::Value(_) | VertTag::Phi(_) => (sym.clone(), LatVal::Top),
// Other kind of vertices are not in consideration, do not make any decision about
// their constantness.
_ => (sym.clone(), LatVal::Bottom)
}
}).collect();
// Perform symbolic execution of CFG and SSA.
self.cfg_work.insert(CfgEdge {
from: None,
to: func.ent.borrow().clone(),
});
while !self.cfg_work.is_empty() || !self.ssa_work.is_empty() {
if !self.cfg_work.is_empty() {
self.visit_cfg()
}
if !self.ssa_work.is_empty() {
self.visit_ssa()
}
}
// Apply code replacement
func.dfs().for_each(|block| {
block.inst.borrow_mut().retain(|instr| {
// Remove constant definition
match instr.dst() {
Some(dst) if self.lat_from_sym(dst).is_const() => { return false; }
_ => {}
}
// Possibly replace symbols with constants
instr.src().iter().for_each(|opd| {
if let LatVal::Const(c) = self.lat_from_val(opd) {
opd.replace(Value::Const(c));
}
});
true
});
// Remove unreachable blocks
match block.tail().as_ref() {
Inst::Br { cond, tr, fls } => match self.lat_from_val(cond) {
LatVal::Const(Const::I1(c)) => {
let (tgt, rm) = if c {
(tr.borrow().clone(), fls.borrow().clone())
} else {
(fls.borrow().clone(), tr.borrow().clone())
};
*block.inst.borrow_mut().back_mut().unwrap() = ExtRc::new(
Inst::Jmp { tgt: RefCell::new(tgt) }
);
block.disconnect(&rm);
}
_ => {}
}
_ => {}
}
});
func.remove_unreachable();
// Rebuild dominator tree and scope, since the structure of CFG may have changed
func.build_dom();
func.rebuild_ssa_scope();
// Clear all data structure for this function.
self.lat.clear();
self.edge_vis.clear();
self.blk_vis.clear();
}
}
impl SccpOpt {
pub fn new() -> SccpOpt {
SccpOpt {
graph: SsaGraph::new(),
cfg_work: WorkList::new(),
ssa_work: WorkList::new(),
lat: Default::default(),
edge_vis: Default::default(),
blk_vis: Default::default(),
}
}
fn visit_cfg(&mut self) {
// Possibly visit this edge if it has not been not visited
let edge = self.cfg_work.pick().unwrap();
if self.edge_vis.contains(&edge) { return; }
self.edge_vis.insert(edge.clone());
// Visit all instructions in this block
let block = edge.to;
self.blk_vis.insert(block.clone());
for instr in block.inst.borrow().iter() {
match instr.deref() {
Inst::Phi { src, dst } => self.eval_phi(src, dst),
Inst::Jmp { tgt } => self.cfg_work.insert(CfgEdge {
from: Some(block.clone()),
to: tgt.borrow().clone(),
}),
Inst::Br { cond, tr, fls } => self.eval_br(cond, tr, fls, &block),
Inst::Ret { val: _ } => return,
instr if instr.is_assign() => self.eval_assign(instr),
_ => continue
}
}
}
fn visit_ssa(&mut self) {
// Pick one SSA edge from work list.
let edge = self.ssa_work.pick().unwrap();
let (block, instr) = match edge.us.instr.borrow().clone() {
Some(pair) => pair,
None => return
};
// reject this instruction, if it is not visited in CFG
if !self.blk_vis.contains(&block) { return; }
match instr.deref() {
Inst::Phi { src, dst } => self.eval_phi(src, dst),
Inst::Br { cond, tr, fls } => self.eval_br(cond, tr, fls, &block),
instr if instr.is_assign() => self.eval_assign(instr),
_ => {}
}
}
fn eval_phi(&mut self, src: &Vec<PhiSrc>, dst: &RefCell<SymbolRef>) {
// Evaluate meet operation on operand lattice values
let prev_lat = self.lat_from_sym(dst);
let new_lat = src.iter().map(|(_, val)| self.lat_from_val(val))
.fold(LatVal::Top, LatVal::meet);
// Update lattice value if it has changed
if prev_lat != new_lat {
self.update_sym(dst, new_lat)
}
}
fn eval_br(&mut self, cond: &RefCell<Value>, tr: &RefCell<BlockRef>, fls: &RefCell<BlockRef>,
blk: &BlockRef)
{
let tr_edge = CfgEdge { from: Some(blk.clone()), to: tr.borrow().clone() };
let fls_edge = CfgEdge { from: Some(blk.clone()), to: fls.borrow().clone() };
match self.lat_from_val(cond) {
LatVal::Const(Const::I1(cond)) =>
self.cfg_work.insert(if cond { tr_edge } else { fls_edge }),
_ => {
self.cfg_work.insert(tr_edge);
self.cfg_work.insert(fls_edge);
}
}
}
fn eval_assign(&mut self, instr: &Inst) {
// Decide whether this instruction should be evaluated.
let dst = instr.dst().unwrap();
let prev_lat = self.lat_from_sym(dst);
if prev_lat == LatVal::Bottom { return; } // no point in computing lattice value
// Propagate constant according to instruction type.
let new_lat = match instr {
Inst::Un { op, opd, dst: _ } => self.eval_un(*op, self.lat_from_val(opd)),
Inst::Bin { op, fst, snd, dst: _ } =>
self.eval_bin(*op, self.lat_from_val(fst), self.lat_from_val(snd)),
// Skip move instruction, since their constantness depend on the symbol moved to it.
Inst::Mov { src: _, dst: _ } => return,
// Cannot compute lattice values for other instructions.
_ => LatVal::Bottom
};
// Update lattice value if it has changed
if new_lat != prev_lat {
self.update_sym(dst, new_lat)
}
}
fn update_sym(&mut self, sym: &RefCell<SymbolRef>, lat: LatVal) {
*self.lat.get_mut(sym.borrow().deref()).unwrap() = lat;
let vert = self.graph.find(sym.borrow().deref()).unwrap();
vert.uses.borrow().iter().for_each(
|u| self.ssa_work.insert(SsaEdge { def: vert.clone(), us: u.clone() })
);
}
fn lat_from_sym(&self, sym: &RefCell<SymbolRef>) -> LatVal {
match sym.borrow().as_ref() {
_ if sym.borrow().is_local_var() => *self.lat.get(sym.borrow().deref()).unwrap(),
Symbol::Global(_) => LatVal::Bottom,
_ => unreachable!()
}
}
fn lat_from_val(&self, val: &RefCell<Value>) -> LatVal {
match val.borrow().deref() {
Value::Var(sym) if sym.is_local_var() => self.lat.get(sym).unwrap().clone(),
Value::Var(_) => LatVal::Bottom,
Value::Const(c) => LatVal::Const(c.clone())
}
}
fn eval_un(&self, op: UnOp, opd: LatVal) -> LatVal {
match opd {
LatVal::Top | LatVal::Bottom => opd,
LatVal::Const(c) => LatVal::Const(op.eval(c))
}
}
fn eval_bin(&self, op: BinOp, lhs: LatVal, rhs: LatVal) -> LatVal {
match (lhs, rhs) {
// At lease one is undetermined and the other is constant, still undetermined
(LatVal::Top, LatVal::Top) | (LatVal::Top, LatVal::Const(_))
| (LatVal::Const(_), LatVal::Top) => LatVal::Top,
// Two variables could only produce variables
(LatVal::Bottom, LatVal::Bottom) => LatVal::Bottom,
// An undetermined value with a variable could possibly produce constant, if the
// operator support short circuit evaluation.
// Short circuit evaluation means the result is always a constant when one of two
// operands is a certain constant, no matter what the other value is. Since one
// operand is `Top`, it may be lowered to `Const` and short-circuit this evaluation.
// At present, these five cases are short-circuit evaluations:
// 0 * x = 0, x && false = false, x & 0 = 0, x || true = true, x | -1 = -1.
(LatVal::Top, LatVal::Bottom) | (LatVal::Bottom, LatVal::Top) => match op {
BinOp::Mul | BinOp::And | BinOp::Or => LatVal::Top,
_ => LatVal::Bottom
}
(LatVal::Const(c), LatVal::Bottom) | (LatVal::Bottom, LatVal::Const(c)) => {
match op {
BinOp::Mul => match c {
Const::I64(0) => LatVal::Const(Const::I64(0)),
_ => LatVal::Bottom
}
BinOp::And => match c {
Const::I1(false) => LatVal::Const(Const::I1(false)),
Const::I64(0) => LatVal::Const(Const::I64(0)),
_ => LatVal::Bottom
}
BinOp::Or => match c {
Const::I1(true) => LatVal::Const(Const::I1(true)),
Const::I64(-1) => LatVal::Const(Const::I64(-1)),
_ => LatVal::Bottom
}
_ => LatVal::Bottom
}
}
(LatVal::Const(l), LatVal::Const(r)) => LatVal::Const(op.eval(l, r))
}
}
}
#[test]
fn test_sccp() {
use crate::irc::lex::Lexer;
use crate::irc::parse::Parser;
use crate::irc::build::Builder;
use crate::lang::print::Printer;
use std::io::stdout;
use std::fs::File;
use std::convert::TryFrom;
use std::io::Read;
use std::borrow::BorrowMut;
let mut file = File::open("test/sccp.ir").unwrap();
let lexer = Lexer::try_from(&mut file as &mut dyn Read).unwrap();
let parser = Parser::new(lexer);
let tree = parser.parse().unwrap();
let builder = Builder::new(tree);
let mut pro = builder.build().unwrap();
let mut opt = SccpOpt::new();
Pass::run(&mut opt, &mut pro);
let mut out = stdout();
let mut printer = Printer::new(out.borrow_mut());
printer.print(&pro).unwrap();
}