作业2 要求为java实现常量传播算法,首先先回顾一下常量传播分析,也就是判断每个变量的值是否为常量,是标记为C,如果不是或者不确定标记为NAC;worklist求解器是迭代求解器的优化版本,不会重复计算那些已经得到最终结果(没有变动)的block
实现常量传播 要求 分析范围 在此次作业中,你需要实现针对 int 类型的常量传播。注意,在 Java 中,boolean、byte、char 和 short 类型在运行时实际上都以 int 值的形式进行表示和计算 ,因此你的分析算法也应当能处理这些类型。其他基本数据类型(例如 long、float 和 double)以及引用类型(例如 class types、array types)不在此次作业的考虑范围内,所以你可以在分析时忽略它们的值。
至于语句的处理,你只需要关注等号左侧为变量且右侧只能是如下几类表达式的赋值语句:
常量,如 x = 1
变量,如 x = y
二元运算表达式,如 x = a + b 和 x = a >> b
下表列举出了除逻辑运算符以外所有作用在 int 类型上的二元运算符 。请确保你的算法能正确处理它们:
运算类型
运算符
Arithmetic
+ - * / %
Condition
== != < > <= >=
Shift
<< >> >>>
Bitwise
| & ^
至于等号左侧为变量、等号右侧为其它表达式的赋值语句,例如方法调用(x = m(...))和字段 load(x = o.f),你需要对它们进行保守的近似处理(也许会不够精确),即把它们当作 x = NAC。后续的作业将逐步解锁方法调用和字段访问的精确分析技巧。
对于上面没有提到的其它语句(例如字段存储 o.f = x),我们只需要使用恒等函数作为它们的 transfer 函数。在未来我们引入别名分析后,你就可以对字段存储进行精确处理了。
需要了解的类
1 2 3 4 5 pascal.taie.ir.IR pascal.taie.ir.exp.Exp pascal.taie.ir.exp.Var pascal.taie.ir.exp.IntLiteral pascal.taie.ir.exp.BinaryExp
先进到代码里一个个看看这些子类是负责什么功能的
pascal.taie.ir.IR 这个接口实现了对IR的一系列方法
getMethod() : 返回 JMethod 对象,表示当前IR对应的方法
getThis() : 返回表示this的 Var 对象,静态方法返回null
getParams() : 返回参数 Var 列表
getVars() : 返回所有变量的列表
getStmts() : 返回所有语句的列表
getExceptionEntries() : 返回异常处理表项列表
pascal.taie.ir.exp.Exp 这是IR中表示表达式的接口,可以获取表达式中的元素
getType() :
返回表达式的类型(Type对象)
用于类型检查和分析
getUses() :
返回该表达式中使用的所有RValue表达式列表
默认实现返回空列表
子类可以重写此方法来提供实际使用的表达式
pascal.taie.ir.exp.Var 这是taie中表示变量的类,可以获取某个变量的信息,对于实现常量传播分析至关重要
1 2 3 4 getMethod() getName() getType() getIndex()
1 2 isTempConst() getTempConstValue()
1 2 3 4 5 getLoadFields() getStoreFields() getLoadArrays() getStoreArrays() getInvokes()
pascal.taie.ir.exp.IntLiteral 这是taie中便是证书字面量的一个实现类,
1 2 3 4 public static IntLiteral get (int value) { }
1 2 3 public int getValue () { return value; }
1 2 3 4 @Override public PrimitiveType getType () { return PrimitiveType.INT; }
pascal.taie.ir.exp.BinaryExp BinaryExp是Tai-e框架中表示二元表达式的接口,用于RValue的操作
getOperator() :
返回表达式的操作符(如+, -, *, /, <, > 等)
返回类型为Op接口
getOperand1/2() :
任务 [重点!] 需要完成 ConstantPropagation 的下述 API:
CPFact newBoundaryFact(CFG)
CPFact newInitialFact()
void meetInto(CPFact,CPFact)
boolean transferNode(Stmt,CPFact,CPFact)
还需要实现下面两个辅助方法:
Value meetValue(Value,Value) 该方法对应着格上的 meet 操作(⊓),见第 6 讲课件的第 238 页 。你应当在 meetInto() 方法中调用它。
Value evaluate(Exp,CPFact) 这个方法会计算表达式(Exp)的值(Value)。当然,此处的值是格上的抽象值。你需要参考第 6 讲课件的第 247 页 上的内容来实现它的三种情况。对于其它情况,该方法会像我们在第 2.1 节 提到的那样返回 NAC。你应该在 transferNode() 方法中调用它来进行表达式的求值。
CPFact newBoundaryFact(CFG) 作业提示:本次作业只关注 int 类型的常量传播。为了实现这一点,框架代码在 ConstantPropagation 类中提供了 canHoldInt(Var) 方法来判断一个变量能否储存 int 类型的值。你需要利用这个方法来判断一个变量是否在本次作业的分析范围内,并忽略那些不在范围内的变量(例如 float 类型的变量)。 那么在设置边界条件的时候,我们也用这个方法来过滤掉那些不用设置边界条件的变量 在ConstantPropagation.java中我们可以找到这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static boolean canHoldInt (Var var ) { Type type = var .getType(); if (type instanceof PrimitiveType) { switch ((PrimitiveType) type) { case BYTE: case SHORT: case INT: case CHAR: case BOOLEAN: return true ; } } return false ; }
直接调用即可
1 2 3 4 5 6 7 8 9 10 @Override public CPFact newBoundaryFact (CFG<Stmt> cfg) { CPFact cpf = new CPFact (); \\实例一个用于常量传播分析的cpf对象 for (Var var : cfg.getIR().getParams()) { \\遍历每一个cfg中的节点的变量 if (canHoldInt(var )) { \\如果他们是我们需要关注的变量 cpf.update(var , Value.getNAC()); \\置为NAC } } return cpf; \\返回创建好的常量传播分析对象}
CPFact newInitialFact() 1 2 3 4 @Override public CPFact newInitialFact () { return new CPFact (); }
meetInto() 1 2 3 4 5 6 @Override public void meetInto (CPFact fact, CPFact target) { for (Var var : fact.keySet()){ target.update(var , meetValue(fact.get(var ), target.get(var ))); } }
写了半天才发现下面有个meetValue,可以吧具体规则解耦出来 meetValue按照课上讲的规则即可 NAC П V = NAC UNDEF П v = v c П с = с C1 П C2 = NAC
meetValue(CPFact,CPFact) 实现课上讲的操作:
NAC⌈⌉v=NAC
UNDEF⌈⌉v=v
c⌈⌉v= ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public Value meetValue (Value v1, Value v2) { if (v1.isNAC() || v2.isNAC()) { return Value.getNAC(); } if (v1.isUndef()) { return v2; } if (v2.isUndef()) { return v1; } if (v1.getConstant() == v2.getConstant()) { return v1; } else { return Value.getNAC(); } }
transferNode(Stmt,CPFact,CPFact) 可以看到和之前的作业差不多,只不过操作的是x对应的值的定义 给定一个语句s:x=…我们将其转换函数定义为: F:OUT[s]=gen ∪ (IN[s]−{(x, ‾)}) 下面来看如何针对三类不同的语句生成 gen:
s: x = c,则 gen = {(x, c)} gen={(x,c)} s: x = y,则 gen = {(x, val(y))} gen={(x,val(y))} s: x = y op z,则 gen = {(x, f(y, z))} gen={(x,f(y,z))}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Override public boolean transferNode (Stmt stmt, CPFact in, CPFact out) { CPFact tmpOut = in.copy(); if (stmt.getDef().isPresent()) { if ((stmt.getDef().get() instanceof Va) && canHoldInt((Var)stmt.getDef().get())) { Var var = (Var) stmt.getDef().get(); Value value = evaluate(((DefinitionStmt<Var, RValue>)stmt).getRValue(), in); tmpOut.update(var , value); } } if (!tmpOut.equals(out)) { for (Var var : tmpOut.keySet()) { out.update(var , tmpOut.get(var )); } return true ; } return false ; }
evaluate(Exp exp, CPFact in) 这个方法会计算表达式(Exp)的值(Value)。当然,此处的值是格上的抽象值。需要参考第 6 讲课件的第 247 页 上的内容来实现它的三种情况。对于其它情况,该方法会像我们在第 2.1 节 提到的那样返回 NAC。你应该在 transferNode() 方法中调用它来进行表达式的求值。
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 public static Value evaluate (Exp exp, CPFact in) { if (exp instanceof Var) { return in.get((Var) exp); } if (exp instanceof IntLiteral) { return Value.makeConstant(((IntLiteral) exp).getValue()); } if (exp instanceof BinaryExp) { Value v1 = in.get(((BinaryExp) exp).getOperand1()); Value v2 = in.get(((BinaryExp) exp).getOperand2()); if (v1.isNAC() || v2.isNAC()) { if (exp instanceof ArithmeticExp) { if (((ArithmeticExp) exp).getOperator() == ArithmeticExp.Op.DIV && v2.isConstant() && v2.getConstant() == 0 ) { return Value.getUndef(); } if (((ArithmeticExp) exp).getOperator() == ArithmeticExp.Op.REM && v2.isConstant() && v2.getConstant() == 0 ) { return Value.getUndef(); } } return Value.getNAC(); } if (v1.isUndef() || v2.isUndef()) { return Value.getUndef(); } if (v1.isConstant() && v2.isConstant()) { if (exp instanceof ArithmeticExp) { if (((ArithmeticExp) exp).getOperator() == ArithmeticExp.Op.DIV && v2.getConstant() == 0 ) { return Value.getUndef(); } if (((ArithmeticExp) exp).getOperator() == ArithmeticExp.Op.REM && v2.getConstant() == 0 ) { return Value.getUndef(); } return switch (((ArithmeticExp) exp).getOperator()) { case ADD -> Value.makeConstant(v1.getConstant() + v2.getConstant()); case SUB -> Value.makeConstant(v1.getConstant() - v2.getConstant()); case MUL -> Value.makeConstant(v1.getConstant() * v2.getConstant()); case DIV -> Value.makeConstant(v1.getConstant() / v2.getConstant()); case REM -> Value.makeConstant(v1.getConstant() % v2.getConstant()); }; } if (exp instanceof ConditionExp) { return switch (((ConditionExp) exp).getOperator()) { case EQ -> Value.makeConstant(v1.getConstant() == v2.getConstant() ? 1 : 0 ); case NE -> Value.makeConstant(v1.getConstant() != v2.getConstant() ? 1 : 0 ); case GE -> Value.makeConstant(v1.getConstant() >= v2.getConstant() ? 1 : 0 ); case GT -> Value.makeConstant(v1.getConstant() > v2.getConstant() ? 1 : 0 ); case LE -> Value.makeConstant(v1.getConstant() <= v2.getConstant() ? 1 : 0 ); case LT -> Value.makeConstant(v1.getConstant() < v2.getConstant() ? 1 : 0 ); }; } if (exp instanceof ShiftExp) { return switch (((ShiftExp) exp).getOperator()) { case SHL -> Value.makeConstant(v1.getConstant() << v2.getConstant()); case SHR -> Value.makeConstant(v1.getConstant() >> v2.getConstant()); case USHR -> Value.makeConstant(v1.getConstant() >>> v2.getConstant()); }; } if (exp instanceof BitwiseExp) { return switch (((BitwiseExp) exp).getOperator()) { case OR -> Value.makeConstant(v1.getConstant() | v2.getConstant()); case AND -> Value.makeConstant(v1.getConstant() & v2.getConstant()); case XOR -> Value.makeConstant(v1.getConstant() ^ v2.getConstant()); }; } } return Value.getNAC(); } return Value.getNAC(); }
实现Worklist求解器
任务[重点!] Solver.initializeForward(CFG,DataflowResult) 1 2 3 4 5 6 7 8 9 10 11 12 13 protected void initializeForward (CFG<Node> cfg, DataflowResult<Node, Fact> result) { for (Node node : cfg.getNodes()) { if (cfg.isEntry(node)) { result.setInFact(node, analysis.newBoundaryFact(cfg)); result.setOutFact(node, analysis.newInitialFact()); } else { result.setInFact(node, analysis.newInitialFact()); result.setOutFact(node, analysis.newInitialFact()); } } }
WorkListSolver.doSolveForward(CFG,DataflowResult) 按照课上的伪代码实现即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected void doSolveForward (CFG<Node> cfg, DataflowResult<Node, Fact> result) { ArrayList<Node> nodeArrayList = new ArrayList <Node>(); for (Node node: cfg.getNodes()) { if (cfg.isEntry(node)) { continue ; } nodeArrayList.add(node); } nodeArrayList.add(node); while (!nodeArrayList.isEmpty()) { Node node = nodeArrayList.remove(0 ); for (Node p: cfg.getPredsOf(node)) { analysis.meetInto(result.getOutFact(p), result.getInFact(node)); } boolean changed = analyss.transferNode(node, result.getInFact(node), result.getOutFact(node)); if (changed) { nodeArrayList.addAll(cfg.getSuccsOf(node)); } } }
测试 然后运行测试./gradlew run --args="-cp src/test/resources/dataflow/constprop -m SimpleBranch"
1 2 3 4 5 6 7 8 9 10 11 class SimpleBranch { static void NAC (int p) { int x; if (p > 0 ) { x = 1 ; } else { x = 2 ; } int y = x; } }
输出
1 2 3 4 5 6 7 8 9 10 11 [0@L5] %intconst0 = 0; {%intconst0=0} [1@L5] if (p > %intconst0) goto 3; {%intconst0=0} [2@L5] goto 6; {%intconst0=0} [3@L5] nop; {%intconst0=0} [4@L6] x = 1; {%intconst0=0, x=1} [5@L5] goto 8; {%intconst0=0, x=1} [6@L5] nop; {%intconst0=0} [7@L8] x = 2; {%intconst0=0, x=2} [8@L8] nop; {%intconst0=0, x=NAC} [9@L10] y = x; {%intconst0=0, x=NAC, y=NAC} [10@L10] return; {%intconst0=0, x=NAC, y=NAC}
可以看到已经能进行常量传播分析了
也可以打断点看一下Worklist求解器是如何高效迭代的