diff --git a/ChironCore/ChironAST/ChironAST.py b/ChironCore/ChironAST/ChironAST.py index f86c5da..2f58c3f 100644 --- a/ChironCore/ChironAST/ChironAST.py +++ b/ChironCore/ChironAST/ChironAST.py @@ -221,7 +221,7 @@ class Value(Expression): class Num(Value): def __init__(self, v): - self.val = int(v) + self.val = float(v) def __str__(self): return str(self.val) @@ -233,3 +233,24 @@ def __init__(self, vname): def __str__(self): return self.varname + + +class PhiCommand(Instruction): + def __init__(self, var: str, operands: list): + """ + Represents a φ-function in SSA form + :param var: Variable being assigned (e.g., ":x") + :param operands: List of operand versions from predecessors (e.g., [":x_0", ":x_1"]) + """ + self.var = var + self.operands = operands # Ordered list matching predecessors + + def __str__(self): + return f"{self.var} = φ({', '.join(self.operands)})" + + def __repr__(self): + return f"PhiCommand(var={self.var}, operands={self.operands})" + + def add_operand(self, value: str): + """Add an operand from a predecessor block""" + self.operands.append(value) diff --git a/ChironCore/cfg0_simple.png b/ChironCore/cfg0_simple.png new file mode 100644 index 0000000..2a49903 Binary files /dev/null and b/ChironCore/cfg0_simple.png differ diff --git a/ChironCore/cfg1_old_after_phi_insertion.png b/ChironCore/cfg1_old_after_phi_insertion.png new file mode 100644 index 0000000..6573ac7 Binary files /dev/null and b/ChironCore/cfg1_old_after_phi_insertion.png differ diff --git a/ChironCore/cfg2_old_after_rename.png b/ChironCore/cfg2_old_after_rename.png new file mode 100644 index 0000000..0438791 Binary files /dev/null and b/ChironCore/cfg2_old_after_rename.png differ diff --git a/ChironCore/cfg3_new_post_ssa.png b/ChironCore/cfg3_new_post_ssa.png new file mode 100644 index 0000000..1f36a75 Binary files /dev/null and b/ChironCore/cfg3_new_post_ssa.png differ diff --git a/ChironCore/cfg4_old_out_of_ssa.png b/ChironCore/cfg4_old_out_of_ssa.png new file mode 100644 index 0000000..21a0b2e Binary files /dev/null and b/ChironCore/cfg4_old_out_of_ssa.png differ diff --git a/ChironCore/cfg5_new_out_of_ssa.png b/ChironCore/cfg5_new_out_of_ssa.png new file mode 100644 index 0000000..8224c12 Binary files /dev/null and b/ChironCore/cfg5_new_out_of_ssa.png differ diff --git a/ChironCore/cfg6_new_sscp.png b/ChironCore/cfg6_new_sscp.png new file mode 100644 index 0000000..35b64d8 Binary files /dev/null and b/ChironCore/cfg6_new_sscp.png differ diff --git a/ChironCore/chiron.py b/ChironCore/chiron.py index 2eca801..816c3b4 100755 --- a/ChironCore/chiron.py +++ b/ChironCore/chiron.py @@ -6,7 +6,13 @@ from ChironAST.builder import astGenPass import abstractInterpretation as AI import dataFlowAnalysis as DFA +import ssa.SSATransformation as SSA +import ssa.outOfSSA as OutSSA +import ssa.SSCP as SSCP from sbfl import testsuiteGenerator +from ChironAST.ChironAST import ( + Instruction, PhiCommand, AssignmentCommand, ConditionCommand, BoolExpr, BoolFalse, Var as VarExpr, Num +) sys.path.insert(0, "../Submission/") sys.path.insert(0, "ChironAST/") @@ -134,6 +140,29 @@ def stopTurtle(): help="Run data flow analysis using worklist algorithm on a Chiron Program.", ) + #added by Sarthak Motwani + cmdparser.add_argument( + "-ssa", + "--ssa_transformation", + action="store_true", + help="Run SSA Transformation", + ) + + cmdparser.add_argument( + "-outssa", + "--out_of_ssa", + action="store_true", + help="Run Out of SSA Transformation", + ) + + cmdparser.add_argument( + "-sscp", + "--sparse_simple_const_prop", + action="store_true", + help="Perform SSCP optimization", + ) + + # adding ends here cmdparser.add_argument( "-sbfl", "--SBFL", @@ -219,15 +248,49 @@ def stopTurtle(): # generate control_flow_graph from IR statements. if args.control_flow: - cfg = cfgB.buildCFG(ir, "control_flow_graph", True) + cfg = cfgB.buildCFG(ir, "control_flow_graph") irHandler.setCFG(cfg) else: irHandler.setCFG(None) if args.dump_cfg: - cfgB.dumpCFG(cfg, "control_flow_graph") + cfgB.dumpCFG(cfg, "cfg0_simple") # set the cfg of the program. + #Added by Sarthak Motwani for SSA Transformation + Out-of-SSA + SSCP + if args.ssa_transformation: + # Handling parameters + if args.params: + for var in args.params.keys(): + if not isinstance(args.params[var], (int, float)): + raise ValueError(f"{args.params[var]} is not a number") + rhs_val = Num(args.params[var]) + if not var.startswith(':'): + var = ':'+ var + # print(var, args.params[var]) + lhs_var = VarExpr(var) + assignment = AssignmentCommand(lhs_var, rhs_val) + ir.insert(0, (assignment, 1)) + args.params = {} + + cfg = cfgB.buildCFG(ir, "control_flow_graph") + irHandler.setCFG(cfg) + ssa_cfg = SSA.build_ssa(ir, cfg) + sscp_cfg = ssa_cfg + result_sscp = None + + if args.sparse_simple_const_prop: + sscp_obj = SSCP.SSCP(ssa_cfg) + result_sscp = sscp_obj.get_results() + SSCP.optimize_ir(ssa_cfg, sscp_obj, ir, result_sscp) + sscp_cfg = cfgB.buildCFG(ir, "cfg_sscp") + cfgB.dumpCFG(sscp_cfg, "cfg6_new_sscp") + + if args.out_of_ssa: + OutSSA.out_of_ssa(ir, sscp_cfg, result_sscp) + + #Adding ends + if args.ir: irHandler.pretty_print(irHandler.ir) diff --git a/ChironCore/demo_testcases/1_straightline.tl b/ChironCore/demo_testcases/1_straightline.tl new file mode 100644 index 0000000..c65cff9 --- /dev/null +++ b/ChironCore/demo_testcases/1_straightline.tl @@ -0,0 +1,20 @@ +:x = 50 +:y = 100 +pendown +forward :x +right :y +pendown +backward 20 +:z = :x + :y +left :z +:x = 25 +forward :x +:x = :x + (1/3) +forward :x +:x = :x + (1/3) +forward :x +:x = :x + (1/3) +forward :x +:x = :x + (1/3) +forward :x +goto (:x, :x) \ No newline at end of file diff --git a/ChironCore/demo_testcases/2_ifelse.tl b/ChironCore/demo_testcases/2_ifelse.tl new file mode 100644 index 0000000..ba02583 --- /dev/null +++ b/ChironCore/demo_testcases/2_ifelse.tl @@ -0,0 +1,22 @@ +:x = 5 +:a = 10 +:b = :a - 10 +:c = 10 +:d = 50 +if (:a > :b) [ + if (:c < :d) [ + :x = :c * :d + :c = :d + :d = :c + :c = :d + ] +] else [ + :x = :a - :b + :x = (:a + :b*:b + 3/2 + 8)/:a +] +:b = 5 +repeat :d[ + forward 10/3 + :b = :b + 1/2 +] +forward :x - 50 \ No newline at end of file diff --git a/ChironCore/demo_testcases/3_loops.tl b/ChironCore/demo_testcases/3_loops.tl new file mode 100644 index 0000000..718c667 --- /dev/null +++ b/ChironCore/demo_testcases/3_loops.tl @@ -0,0 +1,9 @@ +:global1 = 10 +repeat 2 [ + :global1 = 20 + if (:global1 > 15) [ + :local = :global1 + :global1 = :local + ] +] +forward :global1 \ No newline at end of file diff --git a/ChironCore/demo_testcases/4_nestedloops.tl b/ChironCore/demo_testcases/4_nestedloops.tl new file mode 100644 index 0000000..b67ea2f --- /dev/null +++ b/ChironCore/demo_testcases/4_nestedloops.tl @@ -0,0 +1,27 @@ +:x = 0 +:y = 100 +:z = 39 +repeat 3 [ + if (:x < :y) [ + :uninit = 10 + :x = :x + 2 + if (:x - 2 == 0) [ + forward :x + ] else [ + backward :y + :t = 90 + left :t + :y = :y - 1 + ] + right 90 + backward :x + ] + forward :uninit + ] +penup +goto (:x, :y) +pendown +repeat 4[ + forward :z + right 90 +] \ No newline at end of file diff --git a/ChironCore/demo_testcases/5_bigprogram_giveparams.tl b/ChironCore/demo_testcases/5_bigprogram_giveparams.tl new file mode 100644 index 0000000..2e55205 --- /dev/null +++ b/ChironCore/demo_testcases/5_bigprogram_giveparams.tl @@ -0,0 +1,37 @@ +pendown +repeat 3 [ + + if (:x > :y) [ + penup + goto (:x, :y) + pendown + repeat 4 [ + forward :x + left 90 + ] + ] else [ + penup + goto (:y, :x) + pendown + repeat 5 [ + forward :p + left 72 + ] + ] + + if (:z >= :p) [ + penup + goto (:p, :x + :z) + pendown + repeat 6 [ + backward :x + left 60 + ] + ] + + :x = :x + 10 + :y = :y + 10 + :z = :z + 10 +] + +penup \ No newline at end of file diff --git a/ChironCore/demo_testcases/6_bigprogram.tl b/ChironCore/demo_testcases/6_bigprogram.tl new file mode 100644 index 0000000..2f9cbc5 --- /dev/null +++ b/ChironCore/demo_testcases/6_bigprogram.tl @@ -0,0 +1,46 @@ +:a = 100 +:b = 50 +:c = 0 +pendown + +repeat 1 [ + :a = :a - 20 + :b = :b + 10 + + repeat 2 [ + :shadow = :a * 2 + if (:shadow > :b) [ + :shadow = :shadow / 2 + forward :shadow + right 45 + ] else [ + :shadow = :shadow + :b + backward :shadow + left 90 + ] + + :c = :c + :shadow + + repeat 1 [ + :a = 5 + forward :a + :a = :a + (:shadow * 3) + left :a + + if (:c > 75) [ + :b = :b - (:a * 2) + penup + goto (0, 0) + pendown + ] + ] + ] + :d=:a* :b * :c + +:a = :a + :b + :b = :b - :c +] +if (:d >10) [ + goto (0, 1) + penup +] \ No newline at end of file diff --git a/ChironCore/demo_testcases/7_verybigprogram.tl b/ChironCore/demo_testcases/7_verybigprogram.tl new file mode 100644 index 0000000..051ea93 --- /dev/null +++ b/ChironCore/demo_testcases/7_verybigprogram.tl @@ -0,0 +1,159 @@ +:basesize = 150 +:sides = 6 +:factor = 8/10 +:angle = 60 +:depth = 3 +:maxdepth = 4 +:shift = 30 +:counter = 0 +:maxcounter = 20 +:direction = 1 +:spiralsize = 5 +:spiralangle = 15 +:spiralgrowth = 108/100 +:branchfactor = 7/10 +:rotationstep = 72 +:flowerpoints = 8 +:flowersize = 120 +:stars = 2 +:starsize = 100 +:starpoints = 7 +:starangle = 144 +:polygons = 4 +:polygonsize = 80 +:spiraliterations = 40 +:spiralmultiple = 3 +:floweriteration = 1 +:specialeffect = 1 +:shapefactor = 2/3 + +pendown + +repeat 5 [ + :currentsize = :basesize + :currentsides = :sides + :currentdepth = 0 + + repeat :currentsides [ + forward :currentsize + right (360 / :currentsides) + + if (:currentdepth < :maxdepth) [ + :currentdepth = :currentdepth + 1 + :branchsize = :currentsize * :branchfactor + :branchsides = :currentsides - 1 + + if (:branchsides < 3) [ + :branchsides = 3 + ] + + repeat :branchsides [ + forward :branchsize + right (360 / :branchsides) + repeat 1[ + forward 1 + ] + ] + + :currentdepth = :currentdepth - 1 + ] + ] + + if (:specialeffect > 0) [ + :spiralrad = :spiralsize + + repeat 20 [ + forward :spiralrad + right :spiralangle + :spiralrad = :spiralrad * :spiralgrowth + ] + + :specialeffect = 0 + ] else [ + :specialeffect = 1 + ] + + :counter = :counter + 1 + + if (:counter >= :maxcounter) [ + :counter = 0 + :direction = :direction * -1 + ] + + :basesize = :basesize * :factor + :sides = :sides + :direction + + if (:sides > 8) [ + :sides = 3 + ] + + if (:sides < 3) [ + :sides = 8 + ] + + penup + goto (0, 0) + pendown + right :rotationstep +] + +if (:floweriteration > 0) [ + repeat :flowerpoints [ + forward :flowersize + backward :flowersize + right (360 / :flowerpoints) + ] +] + +:currentstars = :stars + +repeat :stars [ + :currentstarsize = :starsize + + repeat :starpoints [ + forward :currentstarsize + right :starangle + ] + + :starsize = :starsize * :shapefactor +] + +:currentspiral = :spiralmultiple +:spiralrad = :spiralsize +:spiralstep = :spiralangle + +repeat :spiraliterations [ + forward :spiralrad + right :spiralstep + :spiralrad = :spiralrad * :spiralgrowth + + if (:spiralrad > 200) [ + :currentspiral = :currentspiral - 1 + + if (:currentspiral > 0) [ + penup + goto (0, 0) + pendown + right 90 + :spiralrad = :spiralsize + ] else [ + :spiralgrowth = 100/100 + ] + ] +] + +:currentpolygons = :polygons +:currentpolygonsize = :polygonsize +:polygonsides = 3 + +repeat :polygons [ + repeat :polygonsides [ + forward :currentpolygonsize + right (360 / :polygonsides) + ] + + :currentpolygonsize = :currentpolygonsize * :shapefactor + :polygonsides = :polygonsides + 1 +] + +penup \ No newline at end of file diff --git a/ChironCore/dominanceFrontiers.py b/ChironCore/dominanceFrontiers.py new file mode 100644 index 0000000..66ace81 --- /dev/null +++ b/ChironCore/dominanceFrontiers.py @@ -0,0 +1,84 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" Dominance Frontiers Computation for ChironLang CFG""" + +from typing import Dict, Set, List, Tuple +import networkx as nx +from cfg.cfgBuilder import dumpCFG, buildCFG +from cfg.ChironCFG import BasicBlock, ChironCFG + +# Function to compute dominators set for every basic block +def compute_dominators(cfg: ChironCFG) -> Dict[BasicBlock, Set[BasicBlock]]: + dominators = {} + entry = cfg.entry + all_nodes = set(cfg.nodes()) + + # Initialize dominance sets + for node in cfg.nodes(): + dominators[node] = all_nodes if node != entry else {entry} + + changed = True + while changed: + changed = False + for node in cfg.nodes(): + if node == entry: + continue + preds = list(cfg.predecessors(node)) + if preds: + # Compute intersection of all predecessors' dominators + predecessor_doms = [dominators[p] for p in preds] + new_dom = set.intersection(*predecessor_doms) + else: + new_dom = set() + + new_dom.add(node) # Node always dominates itself + + if new_dom != dominators[node]: + dominators[node] = new_dom + changed = True + + return dominators + +# Function to computer dominator tree using the CFG and immediate dominator +def compute_dominator_tree(dominators: Dict) -> Dict[BasicBlock, List[BasicBlock]]: + dom_tree = {n: [] for n in dominators} + + for node in dominators: + candidates = dominators[node] - {node} + if not candidates: + continue + + # Find the immediate dominator (IDOM) + idom = None + for candidate in candidates: + # Check if this candidate is dominated by all other candidates + if all(other in dominators[candidate] for other in candidates if other != candidate): + idom = candidate + break + + # Fallback: pick the first candidate (shouldn't happen in valid CFGs) + if not idom and candidates: + idom = next(iter(candidates)) + + if idom: + dom_tree[idom].append(node) + + return dom_tree + + +# Function to compute dominance frontiers +def compute_dominance_frontiers( cfg: ChironCFG, dominators: Dict[BasicBlock, Set[BasicBlock]]) -> Dict[BasicBlock, Set[BasicBlock]]: + + frontiers = {n: set() for n in cfg.nodes()} + dom_tree = compute_dominator_tree(dominators) + + for node in cfg.nodes(): + predecessors = list(cfg.predecessors(node)) + if len(predecessors) >= 2: # Merge point + for p in predecessors: + runner = p + idom = next((d for d, children in dom_tree.items() if node in children), None) + while runner != idom and runner is not None: + frontiers[runner].add(node) + runner = next((d for d, children in dom_tree.items() if runner in children), None) # Move to runner's immediate dominator + return frontiers \ No newline at end of file diff --git a/ChironCore/example/control_flow_graph.png b/ChironCore/example/control_flow_graph.png deleted file mode 100644 index 4560280..0000000 Binary files a/ChironCore/example/control_flow_graph.png and /dev/null differ diff --git a/ChironCore/example/example1.tl b/ChironCore/example/example1.tl index 7d15f80..2e55205 100644 --- a/ChironCore/example/example1.tl +++ b/ChironCore/example/example1.tl @@ -1,5 +1,4 @@ pendown - repeat 3 [ if (:x > :y) [ diff --git a/ChironCore/example/example10.tl b/ChironCore/example/example10.tl new file mode 100644 index 0000000..fc82ab6 --- /dev/null +++ b/ChironCore/example/example10.tl @@ -0,0 +1,17 @@ +:x = 0 +:y = 10 + +repeat 3 [ + if (:x < :y) [ + :x = :x + 2 + if (:x - 2 == 0) [ + forward :x + ] else [ + backward :y + ] + ] else [ + :y = :y - 1 + ] +] +penup +goto (:x, :y) \ No newline at end of file diff --git a/ChironCore/example/example11.tl b/ChironCore/example/example11.tl new file mode 100644 index 0000000..5c035c6 --- /dev/null +++ b/ChironCore/example/example11.tl @@ -0,0 +1,16 @@ +:x = 20 +:y = 100 +:z = 60 +repeat 6 [ +if (:x != :y) [ +if ( :x > :y) [ right :x ] +else [ left :y ] +] else [ +if((:x <= :z) || (:y <= :z))[ +:x = :z / :x +:z = :y / :z + ]] + forward :x right :y + forward :z left :z + ] + goto (:x, :y) \ No newline at end of file diff --git a/ChironCore/example/example12.tl b/ChironCore/example/example12.tl new file mode 100644 index 0000000..de34387 --- /dev/null +++ b/ChironCore/example/example12.tl @@ -0,0 +1,7 @@ +:x1 = 3 +:y1 = 4 +repeat :x1 [ + :x1 = :x1 - 1 + :y1 = :y1 - 1 +] +forward (:x1 + 1 + :y1 + 1) \ No newline at end of file diff --git a/ChironCore/example/example13.tl b/ChironCore/example/example13.tl new file mode 100644 index 0000000..bfbbf9e --- /dev/null +++ b/ChironCore/example/example13.tl @@ -0,0 +1,13 @@ +:y = 10 +:x = 2 + if (:y < 2) [ + :x = 30 + :y = 0 + left :y + left :x + ] else [ + :x = 3 + left :x + right 50 + ] + forward :y \ No newline at end of file diff --git a/ChironCore/example/example14.tl b/ChironCore/example/example14.tl new file mode 100644 index 0000000..984f26f --- /dev/null +++ b/ChironCore/example/example14.tl @@ -0,0 +1,8 @@ +:x = 10 +if(:x>3)[ + forward :x + :x = 10 +] +else[ + backward 50 +] \ No newline at end of file diff --git a/ChironCore/example/example15.tl b/ChironCore/example/example15.tl new file mode 100644 index 0000000..d7f213e --- /dev/null +++ b/ChironCore/example/example15.tl @@ -0,0 +1,9 @@ +:x = 10 +repeat 3 [ + :x = 20 + repeat 2 [ + :x = :x + 5 + forward :x + ] + right :x +] \ No newline at end of file diff --git a/ChironCore/example/example16.tl b/ChironCore/example/example16.tl new file mode 100644 index 0000000..befe776 --- /dev/null +++ b/ChironCore/example/example16.tl @@ -0,0 +1,6 @@ +if (2>1) [ + :a = 50 +] else [ + left 10 +] +forward :a \ No newline at end of file diff --git a/ChironCore/example/example17.tl b/ChironCore/example/example17.tl new file mode 100644 index 0000000..fb8bc0e --- /dev/null +++ b/ChironCore/example/example17.tl @@ -0,0 +1,13 @@ +:x = 0 +repeat 2 [ + if (:x < 5) [ + :x = :x + 1 + if (:x - 2 == 0) [ + :x = :x * 2 + repeat :x [ + :x = :x - 1 + forward :x + 20 + ] + ] + ] +] \ No newline at end of file diff --git a/ChironCore/example/example18.tl b/ChironCore/example/example18.tl new file mode 100644 index 0000000..090642d --- /dev/null +++ b/ChironCore/example/example18.tl @@ -0,0 +1,7 @@ +:x = 1 +:y = :x + 2 +repeat :y [ + :x = :x * 2 + :y = :y - 1 + forward :x +] \ No newline at end of file diff --git a/ChironCore/example/example19.tl b/ChironCore/example/example19.tl new file mode 100644 index 0000000..2f9cbc5 --- /dev/null +++ b/ChironCore/example/example19.tl @@ -0,0 +1,46 @@ +:a = 100 +:b = 50 +:c = 0 +pendown + +repeat 1 [ + :a = :a - 20 + :b = :b + 10 + + repeat 2 [ + :shadow = :a * 2 + if (:shadow > :b) [ + :shadow = :shadow / 2 + forward :shadow + right 45 + ] else [ + :shadow = :shadow + :b + backward :shadow + left 90 + ] + + :c = :c + :shadow + + repeat 1 [ + :a = 5 + forward :a + :a = :a + (:shadow * 3) + left :a + + if (:c > 75) [ + :b = :b - (:a * 2) + penup + goto (0, 0) + pendown + ] + ] + ] + :d=:a* :b * :c + +:a = :a + :b + :b = :b - :c +] +if (:d >10) [ + goto (0, 1) + penup +] \ No newline at end of file diff --git a/ChironCore/example/example2.tl b/ChironCore/example/example2.tl index 7d2984e..bacdeb1 100644 --- a/ChironCore/example/example2.tl +++ b/ChironCore/example/example2.tl @@ -1,8 +1,6 @@ penup goto (50, 50) pendown -:two = 200 -:one = 100 forward 50 right 90 forward 50 diff --git a/ChironCore/example/example20.tl b/ChironCore/example/example20.tl new file mode 100644 index 0000000..198dd17 --- /dev/null +++ b/ChironCore/example/example20.tl @@ -0,0 +1,9 @@ +:global1 = 10 +repeat 2 [ + :global1 = 20 + if (:global1 > 15) [ + :local = :global1 + :global1 = :local + 5 + ] +] +forward :global1 \ No newline at end of file diff --git a/ChironCore/example/example21.tl b/ChironCore/example/example21.tl new file mode 100644 index 0000000..94169dc --- /dev/null +++ b/ChironCore/example/example21.tl @@ -0,0 +1,9 @@ +:a=2 +:b=3 +:c = :a+:b +if(:a < :b)[ + :c = :c+1 +] +else[ + :c = :c+2 +] \ No newline at end of file diff --git a/ChironCore/example/example22.tl b/ChironCore/example/example22.tl new file mode 100644 index 0000000..8ccb074 --- /dev/null +++ b/ChironCore/example/example22.tl @@ -0,0 +1,4 @@ +:x = 5 +:y = 10 +:z = (:x * :y + 6)/3 +forward :z \ No newline at end of file diff --git a/ChironCore/example/example23.tl b/ChironCore/example/example23.tl new file mode 100644 index 0000000..2acaab1 --- /dev/null +++ b/ChironCore/example/example23.tl @@ -0,0 +1,91 @@ +:a = 100 +:b = 50 +:c = 0 +pendown + +repeat 3 [ + + :a = :a - 20 + :b = :b + 10 + + repeat 2 [ + :shadow = :a * 2 + if (:shadow > :b) [ + + :shadow = :shadow / 2 + if(:shadow<100)[ + forward :shadow + ] + else[ + forward 100 + ] + right 45 + ] else [ + + :shadow = :shadow + :b + if(:shadow<100)[ + backward :shadow + ] + else[ + backward 100 + ] + left 90 + ] + + + :c = :c + :shadow + + repeat 1 [ + + :a = 5 + forward :a + :a = :a + (:shadow + 3) + left 90 + + if (:c > 75) [ + :b = :b - (:a * 2) + penup + if(:a<100 && :b<100)[ + goto (:a, :b) + ] + else[ + goto (0, 0) + ] + pendown + ] + ] + ] + + if (:c < 200) [ + :temp = (:a + :b) * :c + repeat 1 [ + :a = :a + (:temp / 10) + :b = :b - (:a + 5) + :c = :c + 1 + + if (:b < 40) [ + :temp = :temp / 2 + forward 10 + ] else [ + :temp = :temp * 2 + backward 10 + ] + ] + ] else [ + :d = :a * :b * :c + repeat 1 [ + :d = :d / (:a + 1) + right 20 + :c = :d + 10 + forward 10 + ] + ] + + :a = :a + :b + :b = :b - :c +] + +if (!(:d==0)) [ + goto (0, 0) + penup +] \ No newline at end of file diff --git a/ChironCore/example/example24.tl b/ChironCore/example/example24.tl new file mode 100644 index 0000000..75e6645 --- /dev/null +++ b/ChironCore/example/example24.tl @@ -0,0 +1,23 @@ +:x = 10 +:y = 20 +pendown +repeat 3 [ + if (:x < :y) [ + repeat 2 [ + forward :x + right 90 + :x = :x + 5 + ] + ] else [ + goto (:x, :y) + pendown + repeat 4 [ + left 45 + backward :y + :y = :y - 2 + ] + ] + :x = :x * 2 + :y = :y / 2 +] +penup \ No newline at end of file diff --git a/ChironCore/example/example25.tl b/ChironCore/example/example25.tl new file mode 100644 index 0000000..fbd110f --- /dev/null +++ b/ChironCore/example/example25.tl @@ -0,0 +1,12 @@ +:x = 100 +pendown +repeat 2 [ + :x = 50 + if (:x == 50) [ + forward :x + :x = :x + 10 + ] + left 30 +] +penup +goto (:x, :x) \ No newline at end of file diff --git a/ChironCore/example/example26.tl b/ChironCore/example/example26.tl new file mode 100644 index 0000000..b2d45f8 --- /dev/null +++ b/ChironCore/example/example26.tl @@ -0,0 +1,112 @@ + +:base = 100 +:offset = 20 +:depth = 3 +:step = 0 +:dir = 1 +:shape = 1 +:accel = 2 +:decay = 1 +:counter = 0 +:limit = 7 +:alternate = 0 +:geofactor = 3/2 + +pendown +repeat 4 [ + + if (:shape * :dir > :limit) [ + :shape = 1 + :dir = :dir * -1 + :alternate = :alternate + 1 + ] else [ + :shape = :shape + :accel + :accel = :accel + :decay + ] + + + if (:alternate < 3) [ + repeat :shape [ + forward :base + right (360 / :shape) + :step = :step + 1 + + + repeat 2 [ + if (:step > 5) [ + penup + goto (:base * :geofactor, :base / :geofactor) + pendown + :geofactor = :geofactor + (2/10) + + + repeat 3 [ + left 120 + forward (:base - :offset) + :offset = :offset + 5 + if (:offset > 40) [ + :offset = 20 + right 60 + forward (:base / 2) + ] + ] + ] else [ + backward (:base / 2) + left 45 + :counter = :counter + 1 + ] + ] + ] + ] else [ + + :spiralsteps = 10 + :spiralangle = 15 + :spiralgrowth = (6/5) + + repeat 3 [ + forward :spiralsteps + right :spiralangle + :spirasteps = :spirasteps * :spiralgrowth + :spiraangle = :spiraangle + 2 + + + if (:spirasteps > 50) [ + penup + goto (:spirasteps, :spiraangle) + pendown + repeat 2 [ + left 90 + forward (:spiralsteps / 2) + :spiralgrowth = :spiralgrowth - (1/10) + ] + ] + ] + ] + + + :temp = :base + :base = :base + (:counter * 2) + :counter = :temp - (:offset / 2) + :limit = :limit + (:shape * :dir) + + + if (:base > 200) [ + :base = 50 + :dir = :dir * -1 + repeat 2 [ + right 180 + forward 30 + :geofactor = :geofactor - (1/2) + ] + ] else [ + left 45 + forward (:base * (7/10)) + ] + + + penup + goto (0, 0) + pendown + :depth = :depth - 1 +] +penup \ No newline at end of file diff --git a/ChironCore/example/example27.tl b/ChironCore/example/example27.tl new file mode 100644 index 0000000..35160f2 --- /dev/null +++ b/ChironCore/example/example27.tl @@ -0,0 +1,11 @@ +pendown +:x = 25 +forward :x +:x = :x + (1/3) +forward :x +:x = :x + (1/3) +forward :x +:x = :x + (1/3) +forward :x +:x = :x + (1/3) +forward :x \ No newline at end of file diff --git a/ChironCore/example/example3.tl b/ChironCore/example/example3.tl new file mode 100644 index 0000000..18820fc --- /dev/null +++ b/ChironCore/example/example3.tl @@ -0,0 +1,10 @@ +:x = 50 +:y = 100 +penup +forward :x +right :y +pendown +backward 20 +:z = :x + :y +goto (:x+:y+:z, :z) +left :z \ No newline at end of file diff --git a/ChironCore/example/example4.tl b/ChironCore/example/example4.tl new file mode 100644 index 0000000..f1448cf --- /dev/null +++ b/ChironCore/example/example4.tl @@ -0,0 +1,10 @@ +:step = 10 +:rot = 45 +:dist = 20 +forward :step +right :rot +forward :dist +penup +forward :step + :dist +pendown +left 90 \ No newline at end of file diff --git a/ChironCore/example/example5.tl b/ChironCore/example/example5.tl new file mode 100644 index 0000000..a5fe36f --- /dev/null +++ b/ChironCore/example/example5.tl @@ -0,0 +1,15 @@ + :x = 20 + :y = 10 + :a = 10 + :b = 3 + forward :x + :a + if (:y > :x) [ + :x = 30 + :y = 0 + left :y + ] else [ + :x = 25 + right 50 + ] + forward :x+:a+:b + forward :y \ No newline at end of file diff --git a/ChironCore/example/example6.tl b/ChironCore/example/example6.tl new file mode 100644 index 0000000..e005e47 --- /dev/null +++ b/ChironCore/example/example6.tl @@ -0,0 +1,12 @@ +:x = 10 +:y = 20 +forward :x +if (:x > :y) [ + forward :y +] else [ + backward (:y - :x) + :x = :x + 1 + :x = :x + 1 + ] +right 90 +forward :x \ No newline at end of file diff --git a/ChironCore/example/example7.tl b/ChironCore/example/example7.tl new file mode 100644 index 0000000..521372a --- /dev/null +++ b/ChironCore/example/example7.tl @@ -0,0 +1,7 @@ +:x = 50 +:z = :x + 7 +if (:z < 100) [ + :z = 57 +] +backward :x + :z +forward :z \ No newline at end of file diff --git a/ChironCore/example/example8.tl b/ChironCore/example/example8.tl new file mode 100644 index 0000000..860ed36 --- /dev/null +++ b/ChironCore/example/example8.tl @@ -0,0 +1,16 @@ +:x = 5 +:a = 10 +:b = :a - 10 +:c = 10 +:d = 50 +if (:a > :b) [ + if (:c < :d) [ + :x = :c * :d + :c = :d + :d = :c + :c = :d + ] +] else [ + :x = :a - :b +] +forward :x \ No newline at end of file diff --git a/ChironCore/example/example9.tl b/ChironCore/example/example9.tl new file mode 100644 index 0000000..003154b --- /dev/null +++ b/ChironCore/example/example9.tl @@ -0,0 +1,13 @@ +:x = 0 +:a = 1 +:b = 1 +if (:a==1) [ + :x = 1 +] +if (:b==1) [ + :x = 2 +] else [ + :x = 3 + :a = 0 +] +forward :x \ No newline at end of file diff --git a/ChironCore/example/exampleSE.tl b/ChironCore/example/exampleSE.tl index f2096f5..475a66c 100644 --- a/ChironCore/example/exampleSE.tl +++ b/ChironCore/example/exampleSE.tl @@ -1,6 +1,10 @@ +:x = 1 :y = :x +:z = :x if :x <= 42 [ :y = :y + 40 + :z = :z + 39 ] else [ - :y = :y + 22 + :y = :y + 40 + :z = :z + 39 ] diff --git a/ChironCore/example/kachuapur.tl b/ChironCore/example/kachuapur.tl index be3bc51..997f872 100644 --- a/ChironCore/example/kachuapur.tl +++ b/ChironCore/example/kachuapur.tl @@ -1,3 +1,4 @@ +:delta=10 :i = 0 repeat 8 [ :i = 1 + :i diff --git a/ChironCore/example/kachuapur2.tl b/ChironCore/example/kachuapur2.tl index 448f1d3..76fbe35 100644 --- a/ChironCore/example/kachuapur2.tl +++ b/ChironCore/example/kachuapur2.tl @@ -1,5 +1,7 @@ :cnt = 0 :i = 0 +:radius=45 +:steps=10 repeat :steps [ :i = :cnt + :i :cnt = :cnt + 1 diff --git a/ChironCore/interpreter.py b/ChironCore/interpreter.py index bb30bcb..447175d 100644 --- a/ChironCore/interpreter.py +++ b/ChironCore/interpreter.py @@ -26,7 +26,7 @@ def __init__(self, irHandler, params): self.trtl.fillcolor("green") self.trtl.begin_fill() self.trtl.pensize(4) - self.trtl.speed(1) # TODO: Make it user friendly + self.trtl.speed(10) # TODO: Make it user friendly if params is not None: self.args = params diff --git a/ChironCore/liveVariables.py b/ChironCore/liveVariables.py new file mode 100644 index 0000000..29d8140 --- /dev/null +++ b/ChironCore/liveVariables.py @@ -0,0 +1,72 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" LiveOUT, VarKill and UEVar Computation for ChironLang CFG Basic Blocsk""" + +from typing import Dict, Set, List, Tuple +import networkx as nx +from ChironAST.ChironAST import ( + Var, AssignmentCommand, + MoveCommand, GotoCommand, ConditionCommand, + BinArithOp, BinCondOp, UnaryArithOp, NOT, + Num, BoolTrue, BoolFalse +) +from irhandler import IRHandler +from cfg.cfgBuilder import dumpCFG, buildCFG +from cfg.ChironCFG import BasicBlock, ChironCFG + +# Function to recursively extract used variables in an expression +def get_used_vars(expr) -> Set[str]: + if isinstance(expr, Var): + return {expr.varname} + elif isinstance(expr, (BinArithOp, BinCondOp)): + return get_used_vars(expr.lexpr) | get_used_vars(expr.rexpr) + elif isinstance(expr, (UnaryArithOp, NOT)): + return get_used_vars(expr.expr) + elif isinstance(expr, (Num, BoolTrue, BoolFalse)): + return set() + return set() + + +# Live Variable Analysis +def compute_live_vars(cfg: ChironCFG) -> Tuple[Dict, Dict, Dict, Dict]: + live_in = {bb: set() for bb in cfg.nodes()} + live_out = {bb: set() for bb in cfg.nodes()} + ue_var = {bb: set() for bb in cfg.nodes()} + var_kill = {bb: set() for bb in cfg.nodes()} + + # Compute UEVar and VarKill + for bb in cfg.nodes(): + for instr, _ in bb.instrlist: + used = set() + defined = set() + + if isinstance(instr, AssignmentCommand): + defined.add(instr.lvar.varname) + used.update(get_used_vars(instr.rexpr)) + elif isinstance(instr, MoveCommand): + used.update(get_used_vars(instr.expr)) + elif isinstance(instr, GotoCommand): + used.update(get_used_vars(instr.xcor)) + used.update(get_used_vars(instr.ycor)) + elif isinstance(instr, ConditionCommand): + used.update(get_used_vars(instr.cond)) + + for var in used: + if var not in var_kill[bb]: + ue_var[bb].add(var) + var_kill[bb].update(defined) + + # Second pass: Iterate to fixed point + changed = True + while changed: + changed = False + for bb in cfg.nodes(): + new_live_out = set().union(*(live_in[s] for s in cfg.successors(bb))) + new_live_in = ue_var[bb] | (new_live_out - var_kill[bb]) + + if new_live_in != live_in[bb]: + live_in[bb] = new_live_in + live_out[bb] = new_live_out + changed = True + + return ue_var, var_kill, live_in, live_out \ No newline at end of file diff --git a/ChironCore/ssa/SSATransformation.py b/ChironCore/ssa/SSATransformation.py new file mode 100644 index 0000000..e8104c0 --- /dev/null +++ b/ChironCore/ssa/SSATransformation.py @@ -0,0 +1,308 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +"""SSA Transformation Implementation for ChironLang""" + +from typing import Dict, Set, List, Tuple +import bisect +import networkx as nx + +from ChironAST.ChironAST import ( + Instruction, PhiCommand, Var, AssignmentCommand, + MoveCommand, GotoCommand, ConditionCommand, + BinArithOp, BinCondOp, UnaryArithOp, NOT, + Num, BoolTrue, BoolFalse +) +from cfg.cfgBuilder import dumpCFG, buildCFG +from cfg.ChironCFG import BasicBlock, ChironCFG +from dominanceFrontiers import ( + compute_dominators, compute_dominator_tree, + compute_dominance_frontiers +) +from liveVariables import compute_live_vars + + +def get_used_vars_expr(expr) -> Set[str]: + """Recursively extract used variables in an expression""" + if isinstance(expr, Var): + return {expr.varname} + elif isinstance(expr, (BinArithOp, BinCondOp)): + return get_used_vars_expr(expr.lexpr) | get_used_vars_expr(expr.rexpr) + elif isinstance(expr, (UnaryArithOp, NOT)): + return get_used_vars_expr(expr.expr) + elif isinstance(expr, (Num, BoolTrue, BoolFalse)): + return set() + return set() + +def get_used_vars_instr(instr): + """Returns the set of used variables in an instruction""" + used = set() + if isinstance(instr, AssignmentCommand): + used = get_used_vars_expr(instr.rexpr) + elif isinstance(instr, MoveCommand): + used = get_used_vars_expr(instr.expr) + elif isinstance(instr, GotoCommand): + used = get_used_vars_expr(instr.xcor) | get_used_vars_expr(instr.ycor) + elif isinstance(instr, ConditionCommand): + used = get_used_vars_expr(instr.cond) + return used + + +class SSATransformer: + """Class for SSA (Static Single Assignment) Transformation""" + + def __init__(self, ir, cfg: ChironCFG): + """Initialize the SSA transformer""" + self.cfg = cfg + self.ir = ir + self.dominators = compute_dominators(cfg) + self.dom_tree = compute_dominator_tree(self.dominators) + self.df = compute_dominance_frontiers(cfg, self.dominators) + self.ue_var, self.var_kill, self.live_in, self.live_out = compute_live_vars(cfg) + self.globals = self._compute_globals() + + def _compute_globals(self) -> Set[str]: + """Compute global variables (variables whose liveness span multiple blocks)""" + return set().union(*self.ue_var.values()) + + def insert_phi_in_ir(self, idx_phi): + """Insert phi-functions in the IR using positions known from CFG""" + idx_phi.sort(key=lambda pair: pair[0]) + offset = 0 + + for index, phi_command in idx_phi: + insertion_index = index + offset + self.ir.insert(insertion_index, (phi_command, 1)) + offset += 1 + + def synchronize_cfg_ir(self, idx_phi): + phi_indices = [idx for idx, _ in idx_phi] + + # First pass: Update non-phi instructions + for basic_block in self.cfg.nodes(): + for i, (instruction, idx) in enumerate(basic_block.instrlist): + if not isinstance(instruction, PhiCommand): + offset = bisect.bisect_right(phi_indices, idx) + basic_block.instrlist[i] = (instruction, idx + offset) + + # Second pass: Update phi instructions + for basic_block in self.cfg.nodes(): + # Count phi instructions at the beginning of the block + phi_count = 0 + for instruction, _ in basic_block.instrlist: + if isinstance(instruction, PhiCommand): + phi_count += 1 + else: + break + + # Update phi instruction indices + if phi_count > 0: + first_non_phi_idx = basic_block.instrlist[phi_count][1] if phi_count < len(basic_block.instrlist) else len(self.ir) + # Update each phi instruction + for j in range(phi_count): + instruction = basic_block.instrlist[j][0] + new_idx = first_non_phi_idx - phi_count + j + basic_block.instrlist[j] = (instruction, new_idx) + + def update_jump_offsets(self): + """Update Jump Offsets in IR after inserting instructions""" + for bb in self.cfg.nodes(): + # Skip empty blocks that aren't the END block + if len(bb.instrlist) == 0 and bb.name != 'END': + continue + + if bb.name == 'END' and len(bb.instrlist) == 0: + self._update_end_block_offsets(bb) + else: + self._update_regular_block_offsets(bb) + + def _update_end_block_offsets(self, end_block): + """Update jump offsets for the special END block case""" + for pred_block in self.cfg.predecessors(end_block): + last_instr_idx_pred = pred_block.instrlist[-1][1] + self.ir[last_instr_idx_pred] = ( + self.ir[last_instr_idx_pred][0], + len(self.ir) - last_instr_idx_pred + ) + + def _update_regular_block_offsets(self, block): + """Update jump offsets for regular blocks""" + first_instr_idx_bb = block.instrlist[0][1] + for pred_block in self.cfg.predecessors(block): + last_instr_idx_pred = pred_block.instrlist[-1][1] + + # Update jump offset for conditional false edges + if self.cfg.get_edge_label(pred_block, block) == 'Cond_False': + current_target = last_instr_idx_pred + self.ir[last_instr_idx_pred][1] + if current_target != first_instr_idx_bb: + self.ir[last_instr_idx_pred] = ( + self.ir[last_instr_idx_pred][0], + first_instr_idx_bb - last_instr_idx_pred + ) + + def insert_phi_functions(self): + """Insert phi-functions for global variables at dominance frontiers""" + idx_phi = [] # List to keep track of positions to insert in IR + for var in self.globals: + worklist = [ + bb for bb in self.cfg.nodes() + if any( + isinstance(instr, AssignmentCommand) and instr.lvar.varname == var + for instr, _ in bb.instrlist + ) + ] + + while worklist: + bb = worklist.pop(0) + for df_node in self.df[bb]: + if not self._has_phi_for_var(df_node, var): + num_preds = len(list(self.cfg.predecessors(df_node))) + phi = PhiCommand(var, [""] * num_preds) + + if len(df_node.instrlist) != 0: + idx_in_ir = df_node.instrlist[0][1] + else: + idx_in_ir = len(self.ir) + + df_node.instrlist.insert(0, (phi, idx_in_ir)) + idx_phi.append((idx_in_ir, phi)) + + if df_node not in worklist: + worklist.append(df_node) + + # Adding phi-instructions to the actual IR + self.insert_phi_in_ir(idx_phi) + + # Synchronizing instruction indices in CFG and IR + self.synchronize_cfg_ir(idx_phi) + + # Updating jump offsets in original IR + self.update_jump_offsets() + + dumpCFG(self.cfg, "cfg1_old_after_phi_insertion") + return self.cfg + + def _has_phi_for_var(self, bb: BasicBlock, var: str) -> bool: + """Check if a basic block already has phi-function for a variable""" + return any( + isinstance(instr, PhiCommand) and instr.var == var + for instr, _ in bb.instrlist + ) + + def _rename_in_expr(self, expr, stacks: Dict[str, List[str]]): + """Recursively rename variables in arithmetic or boolean expressions""" + if isinstance(expr, Var): + if expr.varname in stacks and stacks[expr.varname]: + expr.varname = stacks[expr.varname][-1] + elif isinstance(expr, (BinArithOp, BinCondOp)): + self._rename_in_expr(expr.lexpr, stacks) + self._rename_in_expr(expr.rexpr, stacks) + elif isinstance(expr, (UnaryArithOp, NOT)): + self._rename_in_expr(expr.expr, stacks) + + def all_vars_in_ir(self): + all_vars = set() + #Identify all variables + for bb in self.cfg.nodes(): + for instr, _ in bb.instrlist: + # Variables defined + if isinstance(instr, (AssignmentCommand, PhiCommand)): + var_name = instr.lvar.varname if isinstance(instr, AssignmentCommand) else instr.var + all_vars.add(var_name) + + # Variables used + if isinstance(instr, (AssignmentCommand, MoveCommand, GotoCommand, ConditionCommand)): + used = get_used_vars_instr(instr) + all_vars.update(used) + + if isinstance(instr, PhiCommand): + all_vars.update(op for op in instr.operands if op) + + return all_vars + + def rename_variables(self) -> ChironCFG: + """Perform variable renaming for all instructions""" + all_vars = self.all_vars_in_ir() + ssa_to_base = {} # Tracks SSA names to original bases + + #Initialize renaming + counters = {var: 0 for var in all_vars} + stacks = {var: [] for var in all_vars} + + # Initialize stacks with version 0 + for var in all_vars: + stacks[var].append(f"{var}_0") + ssa_to_base[var] = var + counters[var] = 1 # Next version will be _1 + + def new_name(var: str) -> str: + """Generate new SSA name and track base mapping""" + version = counters[var] + ssa_name = f"{var}_{version}" + ssa_to_base[ssa_name] = var # Track original base + counters[var] += 1 + stacks[var].append(ssa_name) + return ssa_name + + def process_block(bb: BasicBlock): + """Process a single block for variable renaming""" + defined_vars = dict() + + for idx, (instr, _) in enumerate(bb.instrlist): + if isinstance(instr, PhiCommand): + ssa_name = instr.var + base_var = ssa_to_base.get(ssa_name, ssa_name) + new_var = new_name(base_var) + instr.var = new_var + defined_vars[base_var] = defined_vars.get(base_var, 0) + 1 + elif isinstance(instr, AssignmentCommand): + original_var = ssa_to_base.get(instr.lvar.varname, instr.lvar.varname) + self._rename_in_expr(instr.rexpr, stacks) + new_var = new_name(original_var) + instr.lvar = Var(new_var) + defined_vars[original_var] = defined_vars.get(original_var, 0) + 1 + elif isinstance(instr, MoveCommand): + self._rename_in_expr(instr.expr, stacks) + elif isinstance(instr, GotoCommand): + self._rename_in_expr(instr.xcor, stacks) + self._rename_in_expr(instr.ycor, stacks) + elif isinstance(instr, ConditionCommand): + self._rename_in_expr(instr.cond, stacks) + + # Update phi operands in successors + for succ in self.cfg.successors(bb): + preds = list(self.cfg.predecessors(succ)) + pred_idx = preds.index(bb) + + for phi_instr, _ in succ.instrlist: + if isinstance(phi_instr, PhiCommand): + ssa_name = phi_instr.var + base_var = ssa_to_base.get(ssa_name, None) + if base_var is not None: + phi_instr.operands[pred_idx] = stacks[base_var][-1] + + # Process children in dominator tree + for child in self.dom_tree.get(bb, []): + process_block(child) + + # Roll back stacks + for var in defined_vars.keys(): + while defined_vars[var]: + if stacks[var]: + stacks[var].pop() + defined_vars[var] -= 1 + + # Start processing from entry block + entry_node = next(n for n in self.cfg.nodes() if n.name == 'START') + process_block(entry_node) + dumpCFG(self.cfg, "cfg2_old_after_rename") + return self.cfg + + +def build_ssa(ir, cfg: ChironCFG) -> ChironCFG: + """Interface to perform SSA transformation""" + transformer = SSATransformer(ir, cfg) + transformer.insert_phi_functions() + transformer.rename_variables() + post_ssa_CFG = buildCFG(ir, "post_ssa_control_flow_graph") + dumpCFG(post_ssa_CFG, "cfg3_new_post_ssa") + return post_ssa_CFG \ No newline at end of file diff --git a/ChironCore/ssa/SSCP.py b/ChironCore/ssa/SSCP.py new file mode 100644 index 0000000..933ce8f --- /dev/null +++ b/ChironCore/ssa/SSCP.py @@ -0,0 +1,309 @@ +""" Sparse Simple Constant Propagation for ChironLang""" +from collections import deque, defaultdict +from typing import Dict, List, Set +from ChironAST.ChironAST import ( + AssignmentCommand, PhiCommand, Var, BinArithOp, UnaryArithOp, + BinCondOp, NOT, Num, BoolTrue, BoolFalse, ConditionCommand, MoveCommand, + GotoCommand +) +from cfg.ChironCFG import ChironCFG, BasicBlock +from ssa.latticeValue import LatticeValue + +class SSCP: + """Sparse Simple Constant Propagation driver class.""" + def __init__(self, cfg: ChironCFG): + self.cfg = cfg + self.lattice: Dict[str, LatticeValue] = {} + self.def_use = defaultdict(list) # var -> list of dependent instructions + self.worklist = deque() + + self._build_def_use_chains() + self._initialize_lattice() + self.run() + + def _build_def_use_chains(self): + """Populate def-use chains by scanning all instructions.""" + for bb in self.cfg.nodes(): + for instr, _ in bb.instrlist: + if isinstance(instr, AssignmentCommand): + used_vars = self._get_used_vars(instr.rexpr) + for var in used_vars: + self.def_use[var].append(instr) + lhs_var = instr.lvar.varname + self.lattice.setdefault(lhs_var, LatticeValue.top()) + elif isinstance(instr, PhiCommand): + for operand in instr.operands: + if operand: + self.def_use[operand].append(instr) + self.lattice.setdefault(instr.var, LatticeValue.top()) + elif isinstance(instr, (MoveCommand, ConditionCommand)): + used_vars = self._get_used_vars(instr.expr) if isinstance(instr, MoveCommand) else self._get_used_vars(instr.cond) + for var in used_vars: + self.def_use[var].append(instr) + elif isinstance(instr, GotoCommand): + used_vars = self._get_used_vars(instr.xcor) + used_vars.update(self._get_used_vars(instr.ycor)) + for var in used_vars: + self.def_use[var].append(instr) + + def _get_used_vars(self, expr) -> Set[str]: + """Extract variables from an expression.""" + if isinstance(expr, Var): + return {expr.varname} + elif isinstance(expr, (BinArithOp, BinCondOp)): + return self._get_used_vars(expr.lexpr) | self._get_used_vars(expr.rexpr) + elif isinstance(expr, (UnaryArithOp, NOT)): + return self._get_used_vars(expr.expr) + elif isinstance(expr, (Num, BoolTrue, BoolFalse)): + return set() + else: + return set() + + def _initialize_lattice(self): + """Initialize lattice values and evaluate initial constants.""" + # Set all variables to TOP initially + for var in self.lattice: + self.lattice[var] = LatticeValue.top() + + # Evaluate each instruction to compute initial values + for bb in self.cfg.nodes(): + for instr, _ in bb.instrlist: + if isinstance(instr, AssignmentCommand): + self._evaluate_assignment(instr) + elif isinstance(instr, PhiCommand): + self._evaluate_phi(instr) + + def _evaluate_assignment(self, instr: AssignmentCommand): + """Evaluate AssignmentCommand and update lattice.""" + lhs_var = instr.lvar.varname + rhs_val = self._evaluate_expr(instr.rexpr) + self._update_lattice(lhs_var, rhs_val) + + def _evaluate_phi(self, instr: PhiCommand): + """Evaluate PhiCommand by meeting all operands.""" + lhs_var = instr.var + current = self.lattice[lhs_var] + new_val = LatticeValue.top() + for operand in instr.operands: + if not operand: + continue + operand_val = self.lattice.get(operand, LatticeValue.bottom()) + new_val = self._meet(new_val, operand_val) + self._update_lattice(lhs_var, new_val) + + def _evaluate_expr(self, expr) -> LatticeValue: + """Evaluate an expression to a lattice value.""" + if isinstance(expr, Num): + return LatticeValue.constant(expr.val) + elif isinstance(expr, Var): + return self.lattice.get(expr.varname, LatticeValue.bottom()) + elif isinstance(expr, BinArithOp): + left = self._evaluate_expr(expr.lexpr) + right = self._evaluate_expr(expr.rexpr) + if left.is_constant() and right.is_constant(): + try: + op = expr.symbol + a, b = left.constant, right.constant + if op == '+': + val = a + b + elif op == '-': + val = a - b + elif op == '*': + val = a * b + elif op == '/': + val = a / b if b != 0 else LatticeValue.bottom() + else: + return LatticeValue.bottom() + return LatticeValue.constant(val) + except: + return LatticeValue.bottom() + elif left.is_bottom() or right.is_bottom(): + return LatticeValue.bottom() + else: + return LatticeValue.top() + elif isinstance(expr, UnaryArithOp): + val = self._evaluate_expr(expr.expr) + if val.is_constant(): + return LatticeValue.constant(-val.constant) if expr.symbol == '-' else val + else: + return val + elif isinstance(expr, BinCondOp): + left = self._evaluate_expr(expr.lexpr) + right = self._evaluate_expr(expr.rexpr) + # Apply special meet rules for && and || + if expr.symbol == '&&': + if left.is_constant() and left.constant is False: + return LatticeValue.constant(False) + if right.is_constant() and right.constant is False: + return LatticeValue.constant(False) + elif expr.symbol == '||': + if left.is_constant() and left.constant is True: + return LatticeValue.constant(True) + if right.is_constant() and right.constant is True: + return LatticeValue.constant(True) + # General case + if left.is_constant() and right.is_constant(): + op = expr.symbol + a, b = left.constant, right.constant + try: + if op == '<': + res = a < b + elif op == '>': + res = a > b + elif op == '==': + res = a == b + elif op == '!=': + res = a != b + elif op == '<=': + res = a <= b + elif op == '>=': + res = a >= b + else: + return LatticeValue.bottom() + return LatticeValue.constant(res) + except: + return LatticeValue.bottom() + elif left.is_bottom() or right.is_bottom(): + return LatticeValue.bottom() + else: + return LatticeValue.top() + elif isinstance(expr, NOT): + val = self._evaluate_expr(expr.expr) + if val.is_constant(): + return LatticeValue.constant(not val.constant) + else: + return val + else: + return LatticeValue.top() + + def _meet(self, a: LatticeValue, b: LatticeValue) -> LatticeValue: + """Compute the meet of two lattice values.""" + if a.is_top(): + return b + elif b.is_top(): + return a + elif a.is_bottom() or b.is_bottom(): + return LatticeValue.bottom() + elif a.is_constant() and b.is_constant(): + return a if a.constant == b.constant else LatticeValue.bottom() + else: + return LatticeValue.bottom() + + def _update_lattice(self, var: str, new_val: LatticeValue): + """Update the lattice value and enqueue if changed.""" + current = self.lattice.get(var, LatticeValue.top()) + if new_val != current: + self.lattice[var] = new_val + if var not in self.worklist: + self.worklist.append(var) + + def run(self): + """Run the SSCP algorithm until worklist is empty.""" + while self.worklist: + var = self.worklist.popleft() + for instr in self.def_use.get(var, []): + if isinstance(instr, AssignmentCommand): + self._evaluate_assignment(instr) + elif isinstance(instr, PhiCommand): + self._evaluate_phi(instr) + + def get_constant(self, var: str): + """Return the constant value if known, else None.""" + val = self.lattice.get(var, LatticeValue.bottom()) + return val.constant if val.is_constant() else None + + def get_results(self) -> Dict[str, LatticeValue]: + """Return the computed lattice values for all variables.""" + print("\nLattice Values of All variables") + for var in self.lattice.keys(): + print(var, self.lattice[var]) + return self.lattice + +class SSCPOptimizer: + """Replaces constant variables with the corresponding constants in the IR.""" + + def __init__(self, cfg: ChironCFG, sscp: SSCP, ir, sscp_results: Dict[str, LatticeValue]): + self.cfg = cfg + self.sscp = sscp + self.ir = ir + self.constants: Dict[str, LatticeValue] = sscp_results + + def optimize(self): + """Replace variables with constants in the IR.""" + for i, (instr, offset) in enumerate(self.ir): + optimized_instr = self._optimize_instruction(instr) + self.ir[i] = (optimized_instr, offset) + + def _optimize_instruction(self, instr): + """Replace constant variables by the corresponding constant in a single instruction.""" + if isinstance(instr, AssignmentCommand): + return self._optimize_assignment(instr) + elif isinstance(instr, PhiCommand): + return self._optimize_phi(instr) + elif isinstance(instr, (MoveCommand, ConditionCommand, GotoCommand)): + return self._optimize_expr_instr(instr) + return instr # Other instructions remain unchanged + + def _optimize_assignment(self, instr: AssignmentCommand) -> AssignmentCommand: + """Replace variables in AssignmentCommand with constants. """ + lhs_var = instr.lvar.varname + optimized_rexpr = self._replace_vars_in_expr(instr.rexpr) + + # If LHS is a constant, replace RHS with the constant value + if self.constants.get(lhs_var, LatticeValue.bottom()).is_constant(): + const_val = self.constants[lhs_var].constant + return AssignmentCommand(instr.lvar, Num(const_val)) + else: + return AssignmentCommand(instr.lvar, optimized_rexpr) + + def _optimize_phi(self, instr: PhiCommand): + """Replace PhiCommand with AssignmentCommand if resolved to a constant.""" + lhs_var = instr.var + if self.constants.get(lhs_var, LatticeValue.bottom()).is_constant(): + const_val = self.constants[lhs_var].constant + return AssignmentCommand(Var(lhs_var), Num(const_val)) + else: + return instr + + def _optimize_expr_instr(self, instr): + """Replace variables in expressions.""" + if isinstance(instr, MoveCommand): + optimized_expr = self._replace_vars_in_expr(instr.expr) + return MoveCommand(instr.direction, optimized_expr) + elif isinstance(instr, ConditionCommand): + optimized_cond = self._replace_vars_in_expr(instr.cond) + return ConditionCommand(optimized_cond) + elif isinstance(instr, GotoCommand): + optimized_xcor = self._replace_vars_in_expr(instr.xcor) + optimized_ycor = self._replace_vars_in_expr(instr.ycor) + return GotoCommand(optimized_xcor, optimized_ycor) + return instr + + def _replace_vars_in_expr(self, expr): + """Recursively replace variables in an expression with constants.""" + if isinstance(expr, Var): + const_val = self.constants.get(expr.varname, LatticeValue.bottom()) + if const_val.is_constant(): + return Num(const_val.constant) + else: + return expr + elif isinstance(expr, BinArithOp): + new_lexpr = self._replace_vars_in_expr(expr.lexpr) + new_rexpr = self._replace_vars_in_expr(expr.rexpr) + return BinArithOp(new_lexpr, new_rexpr, expr.symbol) + elif isinstance(expr, UnaryArithOp): + new_expr = self._replace_vars_in_expr(expr.expr) + return UnaryArithOp(new_expr, expr.symbol) + elif isinstance(expr, BinCondOp): + new_lexpr = self._replace_vars_in_expr(expr.lexpr) + new_rexpr = self._replace_vars_in_expr(expr.rexpr) + return BinCondOp(new_lexpr, new_rexpr, expr.symbol) + elif isinstance(expr, NOT): + new_expr = self._replace_vars_in_expr(expr.expr) + return NOT(new_expr) + return expr + +def optimize_ir(cfg: ChironCFG, sscp: SSCP, ir, sscp_results: Dict[str, LatticeValue]): + """Interface to run optimization after sparse simple constant propagation.""" + optimizer = SSCPOptimizer(cfg, sscp, ir, sscp_results) + optimizer.optimize() \ No newline at end of file diff --git a/ChironCore/ssa/latticeValue.py b/ChironCore/ssa/latticeValue.py new file mode 100644 index 0000000..c240053 --- /dev/null +++ b/ChironCore/ssa/latticeValue.py @@ -0,0 +1,50 @@ +"""LatticeValue class for holding lattice value for a variable""" +class LatticeValue: + TOP = "TOP" + BOTTOM = "BOTTOM" + CONSTANT = "CONSTANT" + + def __init__(self, value_type: str, constant=None): + self.value_type = value_type + self.constant = constant # Only relevant for CONSTANT + + @staticmethod + def top(): + return LatticeValue(LatticeValue.TOP) + + @staticmethod + def bottom(): + return LatticeValue(LatticeValue.BOTTOM) + + @staticmethod + def constant(value): + return LatticeValue(LatticeValue.CONSTANT, value) + + def is_top(self) -> bool: + return self.value_type == LatticeValue.TOP + + def is_bottom(self) -> bool: + return self.value_type == LatticeValue.BOTTOM + + def is_constant(self) -> bool: + return self.value_type == LatticeValue.CONSTANT + + def __eq__(self, other: 'LatticeValue') -> bool: + if self.value_type != other.value_type: + return False + if self.is_constant(): + return self.constant == other.constant + return True + + def __repr__(self) -> str: + if self.is_top(): + return "⊤" + elif self.is_bottom(): + return "⊥" + else: + return f"C({self.constant})" + + def get_constant(self): + if self.is_constant(): + return self.constant + raise ValueError("LatticeValue is not a constant") \ No newline at end of file diff --git a/ChironCore/ssa/outOfSSA.py b/ChironCore/ssa/outOfSSA.py new file mode 100644 index 0000000..1d8915f --- /dev/null +++ b/ChironCore/ssa/outOfSSA.py @@ -0,0 +1,210 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +"""Out-of-SSA Transformation for ChironLang""" + +from typing import List, Tuple, Dict, Set +from ChironAST.ChironAST import ( + Instruction, PhiCommand, AssignmentCommand, ConditionCommand, BoolExpr, BoolFalse, Var, Num +) +from irhandler import IRHandler +from cfg.cfgBuilder import buildCFG, dumpCFG +from cfg.ChironCFG import ChironCFG, BasicBlock +from ssa.latticeValue import LatticeValue + + +class OutOfSSATransformer: + """Class to handle all functions for Out of SSA Transformation.""" + + def __init__(self, ir: List, cfg: ChironCFG, sscp_results: Dict[str, LatticeValue] = None): + """Initialize the Out-of-SSA transformer.""" + self.ir = ir + self.cfg = cfg + self.ir_handler = IRHandler() + self.split_blocks = {} # Maps critical edges to new blocks + self.preds_list = {} # Dict: v, list of predecessors in original order + self.sscp_results = sscp_results + self.initial_node_order = self.compute_initial_node_order() + + def compute_initial_node_order(self): + """Compute the initial order of nodes based on instruction indices.""" + initial_node_order = [] + for node in self.cfg.nodes(): + if not node.instrlist: + continue + initial_node_order.append((node.instrlist[0][1], node)) + + initial_node_order.sort(key=lambda x: x[0]) + return [node[1] for node in initial_node_order] + + def find_all_preds_list(self): + """Convert predecessors set in nx.graph to a list to retain indices.""" + for bb in list(self.cfg.nodes()): + self.preds_list[bb] = list(self.cfg.predecessors(bb)) + + def find_critical_edges(self) -> List[Tuple[BasicBlock, BasicBlock]]: + """Identify all critical edges in the CFG.""" + critical_edges = [] + for u, v in self.cfg.edges(): + if self.cfg.out_degree(u) > 1 and self.cfg.in_degree(v) > 1: + critical_edges.append((u, v)) + return critical_edges + + def split_critical_edge(self, u: BasicBlock, v: BasicBlock): + """Insert a new block between u and v to split a critical edge.""" + # Create new block + new_id = len(self.cfg.nodes()) + 1 + new_block = BasicBlock(new_id) + self.cfg.add_node(new_block) + false_cmd = ConditionCommand(BoolFalse()) + new_block.instrlist.append((false_cmd, 0)) + + # Redirect u -> v to u -> new_block -> v + old_idx_u = self.preds_list[v].index(u) + + old_label = self.cfg.get_edge_label(u, v) + self.cfg.nxgraph.remove_edge(u, v) + self.cfg.add_edge(u, new_block, label=old_label) + self.cfg.add_edge(new_block, v, label='Cond_False') + self.preds_list[v][old_idx_u] = new_block + self.split_blocks[(u, v)] = new_block + + # This case will never likely never be it. Still needs to be formally argued. + if old_label == 'Cond_True': + v_index = self.initial_node_order.index(v) + self.initial_node_order.insert(v_index, new_block) + + def split_all_critical_edges(self): + """Split all critical edges in the CFG.""" + critical_edges = self.find_critical_edges() + for u, v in critical_edges: + self.split_critical_edge(u, v) + + def replace_phi_with_copies(self): + """Replace φ-functions with copy instructions in predecessors.""" + for bb in list(self.cfg.nodes()): + phis = [] + other_instrs = [] + + # Separate phi instructions from other instructions + for instr, offset in bb.instrlist: + if isinstance(instr, PhiCommand): + phis.append((instr, offset)) + else: + other_instrs.append((instr, offset)) + + # Remove φ-functions from current block + bb.instrlist = other_instrs + + # Process each φ-function + for phi, offset in phis: + var = phi.var + operands = phi.operands + predecessors = self.preds_list[bb] + + for idx, pred in enumerate(predecessors): + operand = operands[idx] + + # Handle special case for operands ending with "_0" + if operand.endswith("_0"): + temp_assignment = AssignmentCommand(Var(var), Num(0)) + self._insert_assignment_to_predecessor(pred, temp_assignment) + else: + # Create assignment: var = operand + lhs_assgn = self._get_operand_value(operand) + assignment = AssignmentCommand(Var(var), lhs_assgn) + self._insert_assignment_to_predecessor(pred, assignment) + + def _get_operand_value(self, operand): + """Get the appropriate value for an operand based on SSCP results.""" + if (self.sscp_results is not None and + operand in self.sscp_results and + self.sscp_results[operand].is_constant()): + return Num(self.sscp_results[operand].get_constant()) + return Var(operand) + + def _insert_assignment_to_predecessor(self, pred, assignment): + """Insert assignment instruction to a predecessor block.""" + if isinstance(pred.instrlist[-1][0], ConditionCommand): + pred.instrlist.insert(len(pred.instrlist) - 1, (assignment, 0)) + else: + pred.instrlist.append((assignment, 0)) + + def handle_preds_of_end(self): + """Handle predecessors of 'END' block.""" + for node in self.cfg.nodes(): + if node.name == 'END': + for pred in self.cfg.predecessors(node): + if self.cfg.get_edge_label(pred, node) != 'Cond_False': + pred.instrlist.append((ConditionCommand(BoolFalse()), 1)) + break + + def instructions_in_original_order(self): + """Traverse the CFG and return instructions in the original IR order.""" + idx_in_ir = 0 + + # Add any missing nodes to initial_node_order + for node in self.cfg.nodes(): + if node not in self.initial_node_order and node.instrlist: + self.initial_node_order.append(node) + + # Update IR with instructions from all nodes + for node in self.initial_node_order: + for i, (instr, _) in enumerate(node.instrlist): + if idx_in_ir < len(self.ir): + self.ir[idx_in_ir] = (instr, 1) + else: + self.ir.append((instr, 1)) + node.instrlist[i] = (node.instrlist[i][0], idx_in_ir) + idx_in_ir += 1 + + def update_jump_offsets(self): + """Update jump offsets in IR after inserting instructions.""" + for bb in self.cfg.nodes(): + if not bb.instrlist and bb.name != 'END': + continue + + if bb.name == 'END': + self._update_offsets_for_end_block(bb) + else: + self._update_offsets_for_normal_block(bb) + + def _update_offsets_for_end_block(self, end_block): + """Update jump offsets for predecessors of the END block.""" + for pred_block in self.cfg.predecessors(end_block): + last_instr_idx_pred = pred_block.instrlist[-1][1] + self.ir[last_instr_idx_pred] = ( + self.ir[last_instr_idx_pred][0], + len(self.ir) - last_instr_idx_pred + ) + + def _update_offsets_for_normal_block(self, bb): + """Update jump offsets for predecessors of normal blocks.""" + first_instr_idx_bb = bb.instrlist[0][1] + for pred_block in self.cfg.predecessors(bb): + last_instr_idx_pred = pred_block.instrlist[-1][1] + if self.cfg.get_edge_label(pred_block, bb) == 'Cond_False': + if last_instr_idx_pred + self.ir[last_instr_idx_pred][1] != first_instr_idx_bb: + self.ir[last_instr_idx_pred] = ( + self.ir[last_instr_idx_pred][0], + first_instr_idx_bb - last_instr_idx_pred + ) + + def transform(self) -> Tuple[List, ChironCFG]: + """Execute out-of-SSA transformation pipeline.""" + self.find_all_preds_list() + self.split_all_critical_edges() + self.replace_phi_with_copies() + self.handle_preds_of_end() + self.instructions_in_original_order() + self.update_jump_offsets() # Renamed from Update_Jump_Offsets + return self.ir, self.cfg + + +def out_of_ssa(ir, cfg: ChironCFG, sscp_results: Dict[str, LatticeValue]) -> Tuple[List, ChironCFG]: + """Interface to perform out-of-SSA transformation.""" + transformer = OutOfSSATransformer(ir, cfg, sscp_results) + transformer.transform() + dumpCFG(cfg, "cfg4_old_out_of_ssa") + new_cfg = buildCFG(ir, 'out_of_ssa') + dumpCFG(new_cfg, 'cfg5_new_out_of_ssa') + return ir, new_cfg \ No newline at end of file diff --git a/README.md b/README.md index 74ca21d..c84e218 100644 --- a/README.md +++ b/README.md @@ -58,12 +58,40 @@ $ java -cp ../extlib/antlr-4.7.2-complete.jar org.antlr.v4.Tool \ ### Running an example -The main directory for source files is `ChironCore`. We have examples of the turtle programs in `examples` folder. -To pass parameters (input params) for running a turtle program, use the `-d` flag. Pass the parameters as a python dictionary. +Note: Use the `--ir ` flag to see the new IR printed in the terminal. Also, use the `-cfg_gen` and `-cfg_dump` flags to dump the CFG for the original IR. + +- To perform SSA transformation, use the `-ssa ` flag. (Note: If you are using the `-r ` flag (also runs the program), the program may throw an error + at a phi-instruction since it cannot be interpreted. So, you can run the program without `-r ` flag). + +- To perform out-of-SSA transformation (after SSA transformation), use `-outssa ` flag with the `-ssa ` flag. + +- To perform SSCP optimization (after SSA transformation), use `-ssa` `-sscp` and `-outssa` flags. + +The testcases are present in the `demo_testcases` directory within ChironCore. More examples are present in the `example` directory. + +### Testing SSA Transformation +- The new IR will be printed on the terminal. +- `cfg0.png` shows the CFG before transformation. +- `cfg2_old_after_rename.png` shows the CFG after SSA renaming (before IR updates). +- `cfg3_new_post_ssa.png` shows the CFG rebuilt from the SSA-transformed IR. + +### Testing Out-of-SSA Tranformation +- The new IR will be printed on the terminal. +- `cfg4_old_out_of_ssa.png` shows the CFG after Out-of-SSA transformation (before IR updates). +- `cfg5_new_out_of_ssa.png` shows the CFG rebuilt from the Out-of-SSA transformed IR. +- All other CFGs have the same description as in SSA transformation + +### Testing SSCP Optimization +- The new IR (with variables replaced by corresponding constants) is printed on the terminal. +- Lattice Values of all variables are printed on the terminal +- `cfg6_new_sscp.png` shows the CFG after SSCP optimization +- All other CFGs have the same description as in SSA and out of SSA transformation + +Running the program with (SSA + out-of-SSA + with or without SSCP) and without any transformation should produce identical outputs. ```bash $ cd ChironCore -$ ./chiron.py -r ./example/example1.tl -d '{":x": 20, "y": 30, ":z": 20, ":p": 40}' +$ ./chiron.py -r ./demo_testcases/1_straightline.tl -cfg_gen -cfg_dump --ir -ssa -outssa -sscp ``` ### See help for other command line options