From b9793154ceb9d80044d40bfaa00802e0b5d9c493 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sun, 10 Aug 2025 14:56:52 +0100 Subject: [PATCH 1/9] #57 Implement phase 1 of SSA Exit - make conventional SSA --- .../ezlang/compiler/BasicBlock.java | 5 + .../ezlang/compiler/ExitSSA2.java | 127 ++++++++++++++++++ .../ezlang/compiler/Instruction.java | 63 +++++++++ 3 files changed, 195 insertions(+) create mode 100644 optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java index 51f7d51..e45d63b 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java @@ -158,6 +158,11 @@ public int whichPred(BasicBlock pred) { } throw new IllegalStateException(); } + public BasicBlock predecessor(int i) { + if (i >= predecessors.size()) + return null; + return predecessors.get(i); + } public int whichSucc(BasicBlock succ) { int i = 0; for (BasicBlock s: successors) { diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java new file mode 100644 index 0000000..40e4025 --- /dev/null +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java @@ -0,0 +1,127 @@ +package com.compilerprogramming.ezlang.compiler; + +import java.util.*; + +/** + * Implement method to exit SSA by converting to conventional SSA, + * without coalescing. This is the basic form. + */ +public class ExitSSA2 { + + CompiledFunction function; + Map parallelCopies = new HashMap<>(); + List allBlocks; + + public ExitSSA2(CompiledFunction function, EnumSet options) { + this.function = function; + allBlocks = function.getBlocks(); + insertPCopiesForEachBlock(); + makeConventionalSSA(); + removePhis(); + sequentialzeParallelCopies(); + } + + private void insertPCopiesForEachBlock() { + // We do not actually insert pcopy instruction until needed + // but we create an auxiliary data structure to help us track these + for (BasicBlock block: allBlocks) { + parallelCopies.put(block,new PCopy(block)); + } + } + private void insertAtEnd(BasicBlock bb, Instruction i) { + assert bb.instructions.size() > 0; + // Last instruction is a branch - so new instruction will + // go before that + int pos = bb.instructions.size()-1; + bb.add(pos, i); + } + + private Instruction.ParallelCopyInstruction getParallelCopyAtEnd(BasicBlock block) { + PCopy pcopy = parallelCopies.get(block); + if (pcopy.pCopyEnd == null) { + pcopy.pCopyEnd = new Instruction.ParallelCopyInstruction(); + insertAtEnd(block,pcopy.pCopyEnd); + } + return pcopy.pCopyEnd; + } + + private void insertAfterPhis(BasicBlock bb, Instruction newInst) { + assert bb.instructions.size() > 0; + int insertionPos = -1; + for (int pos = 0; pos < bb.instructions.size(); pos++) { + Instruction i = bb.instructions.get(pos); + if (i instanceof Instruction.Phi) { + insertionPos = pos+1; // After phi + } + else + break; + } + if (insertionPos < 0) { + throw new IllegalStateException(); + } + bb.add(insertionPos, newInst); + } + + private Instruction.ParallelCopyInstruction getParallelCopyAtBegin(BasicBlock block) { + PCopy pcopy = parallelCopies.get(block); + if (pcopy.pCopyBegin == null) { + pcopy.pCopyBegin = new Instruction.ParallelCopyInstruction(); + insertAfterPhis(block,pcopy.pCopyBegin); + } + return pcopy.pCopyBegin; + } + + /** + * Isolate phi nodes to make SSA conventionalS + * This is Phase 1 as described in Engineering a Compiler 3rd Edition, p490. + */ + private void makeConventionalSSA() { + var blocks = function.getBlocks(); + for (BasicBlock block: blocks) { + var phis = block.phis(); + if (phis.isEmpty()) + continue; + for (var phi: phis) { + for (int j = 0; j < phi.numInputs(); j++) { + BasicBlock pred = block.predecessor(j); + var pCopyBEnd = getParallelCopyAtEnd(pred); + var oldInput = phi.input(j); + var newInput = function.registerPool.newTempReg(oldInput.type); + pCopyBEnd.addCopy(oldInput,new Operand.RegisterOperand(newInput)); + phi.replaceInput(j,newInput); + } + var oldPhiVar = phi.value(); + var newPhiVar = function.registerPool.newTempReg(oldPhiVar.type); + phi.replaceValue(newPhiVar); + var pCopyBBegin = getParallelCopyAtBegin(block); + pCopyBBegin.addCopy(new Operand.RegisterOperand(newPhiVar),new Operand.RegisterOperand(oldPhiVar)); + } + } + } + + private void removePhis() { + + } + + private void sequentialzeParallelCopies() { + + } + + static final class PCopy { + BasicBlock block; + /** + * Parallel copy instruction after any Phi instructions + * in the block, null if not present + */ + Instruction.ParallelCopyInstruction pCopyBegin = null; + /** + * Parallel copy instruction at the end of a block, + * null if not present + */ + Instruction.ParallelCopyInstruction pCopyEnd = null; + + public PCopy(BasicBlock block) { + this.block = block; + } + } +} diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java index 3ea0f44..b4b506a 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java @@ -25,6 +25,7 @@ public abstract class Instruction { static final int I_ARRAY_LOAD = 13; static final int I_FIELD_GET = 14; static final int I_FIELD_SET = 15; + static final int I_PARALLEL_COPY = 16; public final int opcode; protected Operand.RegisterOperand def; @@ -477,5 +478,67 @@ public StringBuilder toStr(StringBuilder sb) { } } + /** + * The parallel copy instruction is only used temporarily to exit + * SSA form. It represents the parallel copy semantics of phi instructions. + */ + public static class ParallelCopyInstruction extends Instruction { + + List sourceOperands = new ArrayList<>(); + List destOperands = new ArrayList<>(); + + protected ParallelCopyInstruction() { + super(I_PARALLEL_COPY); + } + + @Override + public StringBuilder toStr(StringBuilder sb) { + sb.append("("); + for (int i = 0; i < destOperands.size(); i++) { + if (i > 0) + sb.append(","); + sb.append(destOperands.get(i)); + } + sb.append(") = ("); + for (int i = 0; i < sourceOperands.size(); i++) { + if (i > 0) + sb.append(","); + sb.append(sourceOperands.get(i)); + } + sb.append(")"); + return sb; + } + + public void addCopy(Operand sourceOperand, Operand.RegisterOperand destOperand) { + sourceOperands.add(sourceOperand); + destOperands.add(destOperand); + } + + @Override + public Register def() { + throw new UnsupportedOperationException(); + } + @Override + public void replaceDef(Register newDef) { + throw new UnsupportedOperationException(); + } + @Override + public boolean definesVar() { + return false; + } + @Override + public List uses() { + return Collections.emptyList(); + } + @Override + public void replaceUses(Register[] newUses) { + throw new UnsupportedOperationException(); + } + @Override + public boolean replaceUse(Register source, Register target) { + throw new UnsupportedOperationException(); + } + } + public abstract StringBuilder toStr(StringBuilder sb); } From fe514cf896410059b19939b7373fa11b5b0121c7 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sun, 10 Aug 2025 16:46:17 +0100 Subject: [PATCH 2/9] #57 remove phis --- .../ezlang/compiler/ExitSSA2.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java index 40e4025..ef8f9a2 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java @@ -18,7 +18,7 @@ public ExitSSA2(CompiledFunction function, EnumSet options) { insertPCopiesForEachBlock(); makeConventionalSSA(); removePhis(); - sequentialzeParallelCopies(); + sequenceParallelCopies(); } private void insertPCopiesForEachBlock() { @@ -100,10 +100,26 @@ private void makeConventionalSSA() { } private void removePhis() { - + var blocks = function.getBlocks(); + for (BasicBlock block: blocks) { + var phis = block.phis(); + if (phis.isEmpty()) + continue; + // Insert copy in predecessor + for (var phi: phis) { + for (int j = 0; j < phi.numInputs(); j++) { + BasicBlock pred = block.predecessor(j); + var pCopyBEnd = getParallelCopyAtEnd(pred); + var phiInput = phi.input(j); + var phiVar = phi.value(); + pCopyBEnd.addCopy(phiInput,new Operand.RegisterOperand(phiVar)); + } + } + block.instructions.removeIf(instruction -> instruction instanceof Instruction.Phi); + } } - private void sequentialzeParallelCopies() { + private void sequenceParallelCopies() { } From c77a931de1fea400bdc6e1e8ee4e5566df16c855 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sun, 10 Aug 2025 23:22:53 +0100 Subject: [PATCH 3/9] #57 Operands (constants and register operands) can be compared for equality --- .../ezlang/compiler/Operand.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java index f6af988..7759cbc 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java @@ -3,6 +3,8 @@ import com.compilerprogramming.ezlang.types.Symbol; import com.compilerprogramming.ezlang.types.EZType; +import java.util.Objects; + public class Operand { EZType type; @@ -17,6 +19,19 @@ public ConstantOperand(long value, EZType type) { public String toString() { return String.valueOf(value); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConstantOperand that = (ConstantOperand) o; + return value == that.value; + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } } public static class NullConstantOperand extends Operand { @@ -46,6 +61,19 @@ public RegisterOperand copy(Register register) { public String toString() { return reg.name(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegisterOperand operand = (RegisterOperand) o; + return Objects.equals(reg, operand.reg); + } + + @Override + public int hashCode() { + return Objects.hashCode(reg); + } } public static class LocalRegisterOperand extends RegisterOperand { From ce644ee08265b77de023bf38831110e4f47afc35 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sun, 10 Aug 2025 23:23:23 +0100 Subject: [PATCH 4/9] #57 Utility to replace 1 instruction with many --- .../ezlang/compiler/BasicBlock.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java index e45d63b..d44eaf4 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java @@ -172,7 +172,17 @@ public int whichSucc(BasicBlock succ) { } throw new IllegalStateException(); } - + public void replaceInstruction(Instruction instruction, List replacements) { + int i; + for (i = 0; i < instructions.size(); i++) + if (instructions.get(i) == instruction) + break; + assert i < instructions.size(); + for (int j = replacements.size()-1; j >= 0; j--) { + instructions.add(i+1, replacements.get(j)); + } + instructions.remove(i); + } public static StringBuilder toStr(StringBuilder sb, BasicBlock bb, BitSet visited, boolean dumpLiveness) { if (visited.get(bb.bid)) From b54ee29d9ff2cab9e4913b21d43adecdc3bae655 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sun, 10 Aug 2025 23:25:04 +0100 Subject: [PATCH 5/9] #57 WIP sequence parallel copies --- .../ezlang/compiler/ExitSSA2.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java index ef8f9a2..0249e37 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java @@ -120,7 +120,122 @@ private void removePhis() { } private void sequenceParallelCopies() { + for (var block: function.getBlocks()) { + var pcopy = parallelCopies.get(block); + if (pcopy.pCopyBegin != null) + sequenceParallelCopy(block,pcopy.pCopyBegin); + if (pcopy.pCopyEnd != null) + sequenceParallelCopy(block,pcopy.pCopyEnd); + } + } + + private void replaceInstruction(BasicBlock block, Instruction.ParallelCopyInstruction pcopy, ArrayList instructions) { + block.replaceInstruction(pcopy, instructions); + } + + static final class Copy { + Operand src; + Operand dest; + boolean removed = false; + + public Copy(Operand src, Operand dest) { + this.src = src; + this.dest = dest; + } + } + + List getCopies(Instruction.ParallelCopyInstruction pcopy) { + List copies = new ArrayList<>(); + for (int i = 0; i < pcopy.sourceOperands.size(); i++) { + var src = pcopy.sourceOperands.get(i); + var dest = pcopy.destOperands.get(i); + if (src instanceof Operand.RegisterOperand srcR && dest instanceof Operand.RegisterOperand destR) { + if (srcR.reg.id == destR.reg.id) + continue; + } + copies.add(new Copy(src,dest)); + } + return copies; + } + + private void sequenceParallelCopy(BasicBlock block, Instruction.ParallelCopyInstruction pcopy) { + var copyInstructions = new ArrayList(); + var copies = getCopies(pcopy); + + while (copies.size() > 0) { + boolean progress = false; + + for (var copy: copies) { + boolean cycle = false; + for (int i = 0; i < copies.size(); i++) { + if (copy.removed == true) + continue; + if (copy.src.equals(copies.get(i).dest)) { + cycle = true; + break; + } + } + if (!cycle) { + copyInstructions.add(new Instruction.Move(copy.src,copy.dest)); + copy.removed = true; + progress = true; + } + } + + copies.removeIf(c->c.removed); + if (progress) + continue; + + var copy = copies.removeFirst(); + var temp = new Operand.RegisterOperand(function.registerPool.newTempReg(copy.src.type)); + copyInstructions.add(new Instruction.Move(copy.src,temp)); + copies.add(new Copy(copy.dest,temp)); + } + replaceInstruction(block,pcopy,copyInstructions); + } + private void sequenceParallelCopyX(BasicBlock block, Instruction.ParallelCopyInstruction pcopy) { + var copyInstructions = new ArrayList(); + var ready = new ArrayList(); + var toDo = new ArrayList(); + var directPred = new HashMap(); + var loc = new HashMap(); + for (int i = 0; i Date: Mon, 11 Aug 2025 10:17:33 +0100 Subject: [PATCH 6/9] #57 WIP sequence parallel copies --- .../ezlang/compiler/ExitSSA2.java | 144 +++++++----------- 1 file changed, 53 insertions(+), 91 deletions(-) diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java index 0249e37..0010abb 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java @@ -133,111 +133,73 @@ private void replaceInstruction(BasicBlock block, Instruction.ParallelCopyInstru block.replaceInstruction(pcopy, instructions); } - static final class Copy { - Operand src; - Operand dest; - boolean removed = false; + private boolean isEqual(Operand op1, Operand op2) { + if (op1 instanceof Operand.RegisterOperand reg1 && op2 instanceof Operand.RegisterOperand reg2) + return reg1.reg.id == reg2.reg.id; + return false; + } - public Copy(Operand src, Operand dest) { - this.src = src; - this.dest = dest; - } + // The parallel move algo below is from + // https://xavierleroy.org/publi/parallel-move.pdf + // Tilting at windmills with Coq: + // formal verification of a compilation algorithm + // for parallel moves + // Laurence Rideau, Bernard Paul Serpette, Xavier Leroy + enum MoveStatus { + TO_MOVE, BEING_MOVED, MOVED } - List getCopies(Instruction.ParallelCopyInstruction pcopy) { - List copies = new ArrayList<>(); - for (int i = 0; i < pcopy.sourceOperands.size(); i++) { - var src = pcopy.sourceOperands.get(i); - var dest = pcopy.destOperands.get(i); - if (src instanceof Operand.RegisterOperand srcR && dest instanceof Operand.RegisterOperand destR) { - if (srcR.reg.id == destR.reg.id) - continue; - } - copies.add(new Copy(src,dest)); + static final class MoveCtx { + Operand[] src; + Operand[] dst; + MoveStatus[] status; + ArrayList copyInstructions; + + MoveCtx(Instruction.ParallelCopyInstruction pcopy) { + src = pcopy.sourceOperands.toArray(new Operand[0]); + dst = pcopy.destOperands.toArray(new Operand[0]); + copyInstructions = new ArrayList(); + status = new MoveStatus[src.length]; + Arrays.fill(status, MoveStatus.TO_MOVE); } - return copies; } - private void sequenceParallelCopy(BasicBlock block, Instruction.ParallelCopyInstruction pcopy) { - var copyInstructions = new ArrayList(); - var copies = getCopies(pcopy); - - while (copies.size() > 0) { - boolean progress = false; - - for (var copy: copies) { - boolean cycle = false; - for (int i = 0; i < copies.size(); i++) { - if (copy.removed == true) - continue; - if (copy.src.equals(copies.get(i).dest)) { - cycle = true; - break; + private void moveOne(MoveCtx ctx, int i) { + Operand[] src = ctx.src; + Operand[] dst = ctx.dst; + if (!isEqual(src[i], dst[i])) { + ctx.status[i] = MoveStatus.BEING_MOVED; + for (int j = 0; j < src.length; j++) { + if (isEqual(src[j],dst[i])) { + // cycle found + switch (ctx.status[j]) { + case TO_MOVE: + moveOne(ctx, j); + break; + case BEING_MOVED: + var temp = new Operand.RegisterOperand(function.registerPool.newTempReg(src[j].type)); + ctx.copyInstructions.add(new Instruction.Move(src[j], temp)); + src[j] = temp; + break; + case MOVED: + break; } } - if (!cycle) { - copyInstructions.add(new Instruction.Move(copy.src,copy.dest)); - copy.removed = true; - progress = true; - } } - - copies.removeIf(c->c.removed); - if (progress) - continue; - - var copy = copies.removeFirst(); - var temp = new Operand.RegisterOperand(function.registerPool.newTempReg(copy.src.type)); - copyInstructions.add(new Instruction.Move(copy.src,temp)); - copies.add(new Copy(copy.dest,temp)); + ctx.copyInstructions.add(new Instruction.Move(src[i], dst[i])); + ctx.status[i] = MoveStatus.MOVED; } - replaceInstruction(block,pcopy,copyInstructions); } - private void sequenceParallelCopyX(BasicBlock block, Instruction.ParallelCopyInstruction pcopy) { - var copyInstructions = new ArrayList(); - var ready = new ArrayList(); - var toDo = new ArrayList(); - var directPred = new HashMap(); - var loc = new HashMap(); - for (int i = 0; i Date: Mon, 11 Aug 2025 10:18:11 +0100 Subject: [PATCH 7/9] #57 bug fix - the phi elimination must use simple copy --- .../com/compilerprogramming/ezlang/compiler/ExitSSA2.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java index 0010abb..f9bcda8 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java @@ -105,14 +105,14 @@ private void removePhis() { var phis = block.phis(); if (phis.isEmpty()) continue; - // Insert copy in predecessor + // Insert copy in predecessor, since we are in CSSA, this is + // a simple assignment from phi input to phi var for (var phi: phis) { for (int j = 0; j < phi.numInputs(); j++) { BasicBlock pred = block.predecessor(j); - var pCopyBEnd = getParallelCopyAtEnd(pred); var phiInput = phi.input(j); var phiVar = phi.value(); - pCopyBEnd.addCopy(phiInput,new Operand.RegisterOperand(phiVar)); + insertAtEnd(pred,new Instruction.Move(phiInput,new Operand.RegisterOperand(phiVar))); } } block.instructions.removeIf(instruction -> instruction instanceof Instruction.Phi); From cc1d9bdc7aae70ccd9ff46aa91885e7278506b96 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Mon, 11 Aug 2025 10:24:27 +0100 Subject: [PATCH 8/9] #57 bug fix - set out of ssa flag --- .../java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java | 1 + 1 file changed, 1 insertion(+) diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java index f9bcda8..0e19d7c 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java @@ -19,6 +19,7 @@ public ExitSSA2(CompiledFunction function, EnumSet options) { makeConventionalSSA(); removePhis(); sequenceParallelCopies(); + function.isSSA = false; } private void insertPCopiesForEachBlock() { From e3ae2f7e318cb144988e52775888d334e499f917 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Mon, 11 Aug 2025 10:54:55 +0100 Subject: [PATCH 9/9] #57 refactor --- .../ezlang/compiler/ExitSSA.java | 275 +---------------- ...2.java => ExitSSABoissinotNoCoalesce.java} | 45 ++- .../ezlang/compiler/ExitSSABriggs.java | 282 ++++++++++++++++++ .../ezlang/compiler/Options.java | 2 + 4 files changed, 327 insertions(+), 277 deletions(-) rename optvm/src/main/java/com/compilerprogramming/ezlang/compiler/{ExitSSA2.java => ExitSSABoissinotNoCoalesce.java} (82%) create mode 100644 optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSABriggs.java diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java index af66aff..bbf86e8 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java @@ -4,279 +4,12 @@ /** * Converts from SSA form to non-SSA form. - * Implementation is based on description in - * 'Practical Improvements to the Construction and Destruction - * of Static Single Assignment Form' by Preston Briggs. - * - * The JikesRVM LeaveSSA implements a version of the - * same algorithm. */ public class ExitSSA { - - CompiledFunction function; - NameStack[] stacks; - DominatorTree tree; - public ExitSSA(CompiledFunction function, EnumSet options) { - this.function = function; - if (!function.isSSA) throw new IllegalStateException(); - function.livenessAnalysis(); - if (options.contains(Options.DUMP_SSA_LIVENESS)) function.dumpIR(true, "SSA Liveness Analysis"); - tree = new DominatorTree(function.entry); - if (options.contains(Options.DUMP_SSA_DOMTREE)) { - System.out.println("Pre SSA Dominator Tree"); - System.out.println(tree.generateDotOutput()); - } - initStack(); - insertCopies(function.entry); - removePhis(); - function.isSSA = false; - if (options.contains(Options.DUMP_POST_SSA_IR)) function.dumpIR(false, "After exiting SSA"); - } - - private void removePhis() { - for (BasicBlock block : tree.blocks) { - block.instructions.removeIf(instruction -> instruction instanceof Instruction.Phi); - } - } - - /* Algorithm for iterating through blocks to perform phi replacement */ - private void insertCopies(BasicBlock block) { - List pushed = new ArrayList<>(); - for (Instruction i: block.instructions) { - // replace all uses u with stacks[i] - replaceUses(i); - } - scheduleCopies(block, pushed); - for (BasicBlock c: block.dominatedChildren) { - insertCopies(c); - } - for (Integer name: pushed) { - stacks[name].pop(); - } - } - - /** - * replace all uses u with stacks[i] - */ - private void replaceUses(Instruction i) { - if (i instanceof Instruction.Phi) - // FIXME check this can never be valid - // tests 8/9 in TestInterpreter invoke on Phi but - // replacements are same as existing inputs - return; - var oldUses = i.uses(); - Register[] newUses = new Register[oldUses.size()]; - for (int u = 0; u < oldUses.size(); u++) { - Register use = oldUses.get(u); - if (!stacks[use.id].isEmpty()) - newUses[u] = stacks[use.id].top(); - else - newUses[u] = use; - } - i.replaceUses(newUses); - } - - static class CopyItem { - /** Phi input can be a register or a constant so we record the operand */ - final Operand src; - /** The phi destination */ - final Register dest; - /** The basic block where the phi was present */ - final BasicBlock destBlock; - boolean removed; - - public CopyItem(Operand src, Register dest, BasicBlock destBlock) { - this.src = src; - this.dest = dest; - this.destBlock = destBlock; - this.removed = false; - } - } - - private void scheduleCopies(BasicBlock block, List pushed) { - /* Pass 1 - Initialize data structures */ - /* In this pass we count the number of times a name is used by other phi-nodes */ - List copySet = new ArrayList<>(); - Map map = new HashMap<>(); - BitSet usedByAnother = new BitSet(function.registerPool.numRegisters()*2); - for (BasicBlock s: block.successors) { - int j = s.whichPred(block); - for (Instruction.Phi phi: s.phis()) { - Register dst = phi.value(); - Operand srcOperand = phi.input(j); // jth operand of phi node - if (srcOperand instanceof Operand.RegisterOperand srcRegisterOperand) { - Register src = srcRegisterOperand.reg; - map.put(src.id, src); - usedByAnother.set(src.id); - } - copySet.add(new CopyItem(srcOperand, dst, s)); - map.put(dst.id, dst); - } - } - - /* Pass 2: setup up the worklist of initial copies */ - /* In this pass we build a worklist of names that are not used in other phi nodes */ - List workList = new ArrayList<>(); - for (CopyItem copyItem: copySet) { - if (usedByAnother.get(copyItem.dest.id) != true) { - copyItem.removed = true; - workList.add(copyItem); - } - } - copySet.removeIf(copyItem -> copyItem.removed); - - /* Pass 3: iterate over the worklist, inserting copies */ - /* Copy operations whose destinations are not used by other copy operations can be scheduled immediately */ - /* Each time we insert a copy operation we add the source of that op to the worklist */ - while (!workList.isEmpty() || !copySet.isEmpty()) { - while (!workList.isEmpty()) { - final CopyItem copyItem = workList.remove(0); - final Operand src = copyItem.src; - final Register dest = copyItem.dest; - final BasicBlock destBlock = copyItem.destBlock; - /* Engineering a Compiler: We can avoid the lost copy - problem by checking the liveness of the target name - for each copy that we try to insert. When we discover - a copy target that is live, we must preserve the live - value in a temporary name and rewrite subsequent uses to - refer to the temporary name. - - This captures the cases when the result of a phi - in a control successor is live on exit of the current block. - This means that it is incorrect to simply insert a copy - of the destination in the current block. So we rename - the destination to a new temporary, and record the renaming - so that the dominator blocks get the new name. Comment adapted - from JikesRVM LeaveSSA - */ - if (block.liveOut.get(dest.id)) { - /* Insert a copy from dest to a new temp t at phi node defining dest */ - final Register t = addMoveToTempAfterPhi(destBlock, dest); - stacks[dest.id].push(t); // record the temp name - pushed.add(dest.id); - } - /* Insert a copy operation from map[src] to dest at end of BB */ - if (src instanceof Operand.RegisterOperand srcRegisterOperand) { - addMoveAtBBEnd(block, map.get(srcRegisterOperand.reg.id), dest); - map.put(srcRegisterOperand.reg.id, dest); - /* If src is the name of a dest in copySet add item to worklist */ - /* see comment on phi cycles below. */ - CopyItem item = isCycle(copySet, srcRegisterOperand.reg); - if (item != null) { - workList.add(item); - } - } - else if (src instanceof Operand.ConstantOperand srcConstantOperand) { - addMoveAtBBEnd(block, srcConstantOperand, dest); - } - } - /* Engineering a Compiler: To solve the swap problem - we can detect cases where phi functions reference the - targets of other phi functions in the same block. For each - cycle of references, it must insert a copy to a temporary - that breaks the cycle. Then we can schedule the copies to - respect the dependencies implied by the phi functions. - - An empty work list with work remaining in the copy set - implies a cycle in the dependencies amongst copies. To break - the cycle copy the destination of an arbitrary member of the - copy set to a temporary. This destination has therefore been - saved and can be safely overwritten. So then add the copy to the - work list. Comment adapted from JikesRVM LeaveSSA. - */ - if (!copySet.isEmpty()) { - CopyItem copyItem = copySet.remove(0); - /* Insert a copy from dst to new temp at the end of Block */ - Register t = addMoveToTempAtBBEnd(block, copyItem.dest); - map.put(copyItem.dest.id, t); - workList.add(copyItem); - } - } - } - - private void insertAtEnd(BasicBlock bb, Instruction i) { - assert bb.instructions.size() > 0; - // Last instruction is a branch - so new instruction will - // go before that - int pos = bb.instructions.size()-1; - bb.add(pos, i); - } - - private void insertAfterPhi(BasicBlock bb, Register phiDef, Instruction newInst) { - assert bb.instructions.size() > 0; - int insertionPos = -1; - for (int pos = 0; pos < bb.instructions.size(); pos++) { - Instruction i = bb.instructions.get(pos); - if (i instanceof Instruction.Phi phi) { - if (phi.value().id == phiDef.id) { - insertionPos = pos+1; // After phi - break; - } - } - } - if (insertionPos < 0) { - throw new IllegalStateException(); - } - bb.add(insertionPos, newInst); - } - - /* Insert a copy from dest to new temp at end of BB, and return temp */ - private Register addMoveToTempAtBBEnd(BasicBlock block, Register dest) { - var temp = function.registerPool.newTempReg(dest.name(), dest.type); - var inst = new Instruction.Move(new Operand.RegisterOperand(dest), new Operand.RegisterOperand(temp)); - insertAtEnd(block, inst); - return temp; - } - - /* If src is the name of a dest in copySet remove the item */ - private CopyItem isCycle(List copySet, Register src) { - for (int i = 0; i < copySet.size(); i++) { - CopyItem copyItem = copySet.get(i); - if (copyItem.dest.id == src.id) { - copySet.remove(i); - return copyItem; - } - } - return null; - } - - /* Insert a copy from src to dst at end of BB */ - private void addMoveAtBBEnd(BasicBlock block, Register src, Register dest) { - var inst = new Instruction.Move(new Operand.RegisterOperand(src), new Operand.RegisterOperand(dest)); - insertAtEnd(block, inst); - // If the copy instruction is followed by a cbr which uses the old var - // then we need to update the cbr instruction - // This is not specified in the Briggs paper but t - var brInst = block.instructions.getLast(); - if (brInst instanceof Instruction.ConditionalBranch cbr) { - cbr.replaceUse(src,dest); - } - } - /* Insert a copy from constant src to dst at end of BB */ - private void addMoveAtBBEnd(BasicBlock block, Operand.ConstantOperand src, Register dest) { - var inst = new Instruction.Move(src, new Operand.RegisterOperand(dest)); - insertAtEnd(block, inst); - } - /* Insert a copy dest to a new temp at phi node defining dest, return temp */ - private Register addMoveToTempAfterPhi(BasicBlock block, Register dest) { - var temp = function.registerPool.newTempReg(dest.name(), dest.type); - var inst = new Instruction.Move(new Operand.RegisterOperand(dest), new Operand.RegisterOperand(temp)); - insertAfterPhi(block, dest, inst); - return temp; - } - - private void initStack() { - stacks = new NameStack[function.registerPool.numRegisters()]; - for (int i = 0; i < stacks.length; i++) - stacks[i] = new NameStack(); - } - - static class NameStack { - List stack = new ArrayList<>(); - void push(Register r) { stack.add(r); } - Register top() { return stack.getLast(); } - void pop() { stack.removeLast(); } - boolean isEmpty() { return stack.isEmpty(); } + if (options.contains(Options.SSA_DESTRUCTION_BOISSINOT_NOCOALESCE)) + new ExitSSABoissinotNoCoalesce(function,options); + else + new ExitSSABriggs(function,options); } } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSABoissinotNoCoalesce.java similarity index 82% rename from optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java rename to optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSABoissinotNoCoalesce.java index 0e19d7c..633cf7e 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSABoissinotNoCoalesce.java @@ -5,25 +5,50 @@ /** * Implement method to exit SSA by converting to conventional SSA, * without coalescing. This is the basic form. + * + * This is essentially Method 1 described by + * Translating Out of Static Single Assignment Form + * Vugranam C. Sreedhar, Roy Dz-Ching Ju, David M. Gillies, and Vatsa Santhanam + * + * However, Sreedhar left out details such as using parallel copies + * and sequencing of parallel copies. + * + * Revisiting Out-of-SSA Translation for Correctness, Code Quality, and Efficiency + * Benoit Boissinot, Alain Darte, Fabrice Rastello, BenoƮt Dupont de Dinechin, Christophe Guillon + * + * The Boissinot paper gives a more correct description, discussing the need for parallel copy + * and sequencing the parallel copy, but the paper describes a + * more complex approach that performs coalescing. + * + * Engineering a Compiler, 3rd edition, describes the simpler form - omitting the coalescing part. + * + * Our implementation is similar to EaC. + * + * We do not use the sequencing algo described in Boissinot paper. Instead, we use: + * + * https://xavierleroy.org/publi/parallel-move.pdf + * Tilting at windmills with Coq: formal verification of a compilation algorithm for parallel moves + * Laurence Rideau, Bernard Paul Serpette, Xavier Leroy */ -public class ExitSSA2 { +public class ExitSSABoissinotNoCoalesce { CompiledFunction function; Map parallelCopies = new HashMap<>(); List allBlocks; - public ExitSSA2(CompiledFunction function, EnumSet options) { + public ExitSSABoissinotNoCoalesce(CompiledFunction function, EnumSet options) { this.function = function; allBlocks = function.getBlocks(); - insertPCopiesForEachBlock(); + init(); makeConventionalSSA(); removePhis(); sequenceParallelCopies(); function.isSSA = false; + if (options.contains(Options.DUMP_POST_SSA_IR)) function.dumpIR(false, "After exiting SSA"); } - private void insertPCopiesForEachBlock() { - // We do not actually insert pcopy instruction until needed + private void init() { + // We do not actually insert parallel copy instruction until needed // but we create an auxiliary data structure to help us track these for (BasicBlock block: allBlocks) { parallelCopies.put(block,new PCopy(block)); @@ -73,8 +98,9 @@ private Instruction.ParallelCopyInstruction getParallelCopyAtBegin(BasicBlock bl } /** - * Isolate phi nodes to make SSA conventionalS + * Isolate phi nodes to make SSA conventional. * This is Phase 1 as described in Engineering a Compiler 3rd Edition, p490. + * It is also described as method 1 by Sreedhar, and explained in detail by Boissinot. */ private void makeConventionalSSA() { var blocks = function.getBlocks(); @@ -100,6 +126,9 @@ private void makeConventionalSSA() { } } + /** + * Phase 2 in Engineering a Compiler + */ private void removePhis() { var blocks = function.getBlocks(); for (BasicBlock block: blocks) { @@ -120,6 +149,9 @@ private void removePhis() { } } + /** + * Phase 3 in Engineering a Compiler. + */ private void sequenceParallelCopies() { for (var block: function.getBlocks()) { var pcopy = parallelCopies.get(block); @@ -146,6 +178,7 @@ private boolean isEqual(Operand op1, Operand op2) { // formal verification of a compilation algorithm // for parallel moves // Laurence Rideau, Bernard Paul Serpette, Xavier Leroy + enum MoveStatus { TO_MOVE, BEING_MOVED, MOVED } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSABriggs.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSABriggs.java new file mode 100644 index 0000000..c7a4c9b --- /dev/null +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSABriggs.java @@ -0,0 +1,282 @@ +package com.compilerprogramming.ezlang.compiler; + +import java.util.*; + +/** + * Converts from SSA form to non-SSA form. + * Implementation is based on description in + * 'Practical Improvements to the Construction and Destruction + * of Static Single Assignment Form' by Preston Briggs. + * + * The JikesRVM LeaveSSA implements a version of the + * same algorithm. + */ +public class ExitSSABriggs { + + CompiledFunction function; + NameStack[] stacks; + DominatorTree tree; + + public ExitSSABriggs(CompiledFunction function, EnumSet options) { + this.function = function; + if (!function.isSSA) throw new IllegalStateException(); + function.livenessAnalysis(); + if (options.contains(Options.DUMP_SSA_LIVENESS)) function.dumpIR(true, "SSA Liveness Analysis"); + tree = new DominatorTree(function.entry); + if (options.contains(Options.DUMP_SSA_DOMTREE)) { + System.out.println("Pre SSA Dominator Tree"); + System.out.println(tree.generateDotOutput()); + } + initStack(); + insertCopies(function.entry); + removePhis(); + function.isSSA = false; + if (options.contains(Options.DUMP_POST_SSA_IR)) function.dumpIR(false, "After exiting SSA"); + } + + private void removePhis() { + for (BasicBlock block : tree.blocks) { + block.instructions.removeIf(instruction -> instruction instanceof Instruction.Phi); + } + } + + /* Algorithm for iterating through blocks to perform phi replacement */ + private void insertCopies(BasicBlock block) { + List pushed = new ArrayList<>(); + for (Instruction i: block.instructions) { + // replace all uses u with stacks[i] + replaceUses(i); + } + scheduleCopies(block, pushed); + for (BasicBlock c: block.dominatedChildren) { + insertCopies(c); + } + for (Integer name: pushed) { + stacks[name].pop(); + } + } + + /** + * replace all uses u with stacks[i] + */ + private void replaceUses(Instruction i) { + if (i instanceof Instruction.Phi) + // FIXME check this can never be valid + // tests 8/9 in TestInterpreter invoke on Phi but + // replacements are same as existing inputs + return; + var oldUses = i.uses(); + Register[] newUses = new Register[oldUses.size()]; + for (int u = 0; u < oldUses.size(); u++) { + Register use = oldUses.get(u); + if (!stacks[use.id].isEmpty()) + newUses[u] = stacks[use.id].top(); + else + newUses[u] = use; + } + i.replaceUses(newUses); + } + + static class CopyItem { + /** Phi input can be a register or a constant so we record the operand */ + final Operand src; + /** The phi destination */ + final Register dest; + /** The basic block where the phi was present */ + final BasicBlock destBlock; + boolean removed; + + public CopyItem(Operand src, Register dest, BasicBlock destBlock) { + this.src = src; + this.dest = dest; + this.destBlock = destBlock; + this.removed = false; + } + } + + private void scheduleCopies(BasicBlock block, List pushed) { + /* Pass 1 - Initialize data structures */ + /* In this pass we count the number of times a name is used by other phi-nodes */ + List copySet = new ArrayList<>(); + Map map = new HashMap<>(); + BitSet usedByAnother = new BitSet(function.registerPool.numRegisters()*2); + for (BasicBlock s: block.successors) { + int j = s.whichPred(block); + for (Instruction.Phi phi: s.phis()) { + Register dst = phi.value(); + Operand srcOperand = phi.input(j); // jth operand of phi node + if (srcOperand instanceof Operand.RegisterOperand srcRegisterOperand) { + Register src = srcRegisterOperand.reg; + map.put(src.id, src); + usedByAnother.set(src.id); + } + copySet.add(new CopyItem(srcOperand, dst, s)); + map.put(dst.id, dst); + } + } + + /* Pass 2: setup up the worklist of initial copies */ + /* In this pass we build a worklist of names that are not used in other phi nodes */ + List workList = new ArrayList<>(); + for (CopyItem copyItem: copySet) { + if (usedByAnother.get(copyItem.dest.id) != true) { + copyItem.removed = true; + workList.add(copyItem); + } + } + copySet.removeIf(copyItem -> copyItem.removed); + + /* Pass 3: iterate over the worklist, inserting copies */ + /* Copy operations whose destinations are not used by other copy operations can be scheduled immediately */ + /* Each time we insert a copy operation we add the source of that op to the worklist */ + while (!workList.isEmpty() || !copySet.isEmpty()) { + while (!workList.isEmpty()) { + final CopyItem copyItem = workList.remove(0); + final Operand src = copyItem.src; + final Register dest = copyItem.dest; + final BasicBlock destBlock = copyItem.destBlock; + /* Engineering a Compiler: We can avoid the lost copy + problem by checking the liveness of the target name + for each copy that we try to insert. When we discover + a copy target that is live, we must preserve the live + value in a temporary name and rewrite subsequent uses to + refer to the temporary name. + + This captures the cases when the result of a phi + in a control successor is live on exit of the current block. + This means that it is incorrect to simply insert a copy + of the destination in the current block. So we rename + the destination to a new temporary, and record the renaming + so that the dominator blocks get the new name. Comment adapted + from JikesRVM LeaveSSA + */ + if (block.liveOut.get(dest.id)) { + /* Insert a copy from dest to a new temp t at phi node defining dest */ + final Register t = addMoveToTempAfterPhi(destBlock, dest); + stacks[dest.id].push(t); // record the temp name + pushed.add(dest.id); + } + /* Insert a copy operation from map[src] to dest at end of BB */ + if (src instanceof Operand.RegisterOperand srcRegisterOperand) { + addMoveAtBBEnd(block, map.get(srcRegisterOperand.reg.id), dest); + map.put(srcRegisterOperand.reg.id, dest); + /* If src is the name of a dest in copySet add item to worklist */ + /* see comment on phi cycles below. */ + CopyItem item = isCycle(copySet, srcRegisterOperand.reg); + if (item != null) { + workList.add(item); + } + } + else if (src instanceof Operand.ConstantOperand srcConstantOperand) { + addMoveAtBBEnd(block, srcConstantOperand, dest); + } + } + /* Engineering a Compiler: To solve the swap problem + we can detect cases where phi functions reference the + targets of other phi functions in the same block. For each + cycle of references, it must insert a copy to a temporary + that breaks the cycle. Then we can schedule the copies to + respect the dependencies implied by the phi functions. + + An empty work list with work remaining in the copy set + implies a cycle in the dependencies amongst copies. To break + the cycle copy the destination of an arbitrary member of the + copy set to a temporary. This destination has therefore been + saved and can be safely overwritten. So then add the copy to the + work list. Comment adapted from JikesRVM LeaveSSA. + */ + if (!copySet.isEmpty()) { + CopyItem copyItem = copySet.remove(0); + /* Insert a copy from dst to new temp at the end of Block */ + Register t = addMoveToTempAtBBEnd(block, copyItem.dest); + map.put(copyItem.dest.id, t); + workList.add(copyItem); + } + } + } + + private void insertAtEnd(BasicBlock bb, Instruction i) { + assert bb.instructions.size() > 0; + // Last instruction is a branch - so new instruction will + // go before that + int pos = bb.instructions.size()-1; + bb.add(pos, i); + } + + private void insertAfterPhi(BasicBlock bb, Register phiDef, Instruction newInst) { + assert bb.instructions.size() > 0; + int insertionPos = -1; + for (int pos = 0; pos < bb.instructions.size(); pos++) { + Instruction i = bb.instructions.get(pos); + if (i instanceof Instruction.Phi phi) { + if (phi.value().id == phiDef.id) { + insertionPos = pos+1; // After phi + break; + } + } + } + if (insertionPos < 0) { + throw new IllegalStateException(); + } + bb.add(insertionPos, newInst); + } + + /* Insert a copy from dest to new temp at end of BB, and return temp */ + private Register addMoveToTempAtBBEnd(BasicBlock block, Register dest) { + var temp = function.registerPool.newTempReg(dest.name(), dest.type); + var inst = new Instruction.Move(new Operand.RegisterOperand(dest), new Operand.RegisterOperand(temp)); + insertAtEnd(block, inst); + return temp; + } + + /* If src is the name of a dest in copySet remove the item */ + private CopyItem isCycle(List copySet, Register src) { + for (int i = 0; i < copySet.size(); i++) { + CopyItem copyItem = copySet.get(i); + if (copyItem.dest.id == src.id) { + copySet.remove(i); + return copyItem; + } + } + return null; + } + + /* Insert a copy from src to dst at end of BB */ + private void addMoveAtBBEnd(BasicBlock block, Register src, Register dest) { + var inst = new Instruction.Move(new Operand.RegisterOperand(src), new Operand.RegisterOperand(dest)); + insertAtEnd(block, inst); + // If the copy instruction is followed by a cbr which uses the old var + // then we need to update the cbr instruction + // This is not specified in the Briggs paper but t + var brInst = block.instructions.getLast(); + if (brInst instanceof Instruction.ConditionalBranch cbr) { + cbr.replaceUse(src,dest); + } + } + /* Insert a copy from constant src to dst at end of BB */ + private void addMoveAtBBEnd(BasicBlock block, Operand.ConstantOperand src, Register dest) { + var inst = new Instruction.Move(src, new Operand.RegisterOperand(dest)); + insertAtEnd(block, inst); + } + /* Insert a copy dest to a new temp at phi node defining dest, return temp */ + private Register addMoveToTempAfterPhi(BasicBlock block, Register dest) { + var temp = function.registerPool.newTempReg(dest.name(), dest.type); + var inst = new Instruction.Move(new Operand.RegisterOperand(dest), new Operand.RegisterOperand(temp)); + insertAfterPhi(block, dest, inst); + return temp; + } + + private void initStack() { + stacks = new NameStack[function.registerPool.numRegisters()]; + for (int i = 0; i < stacks.length; i++) + stacks[i] = new NameStack(); + } + + static class NameStack { + List stack = new ArrayList<>(); + void push(Register r) { stack.add(r); } + Register top() { return stack.getLast(); } + void pop() { stack.removeLast(); } + boolean isEmpty() { return stack.isEmpty(); } + } +} diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Options.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Options.java index b53c8cd..2af20d9 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Options.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Options.java @@ -5,6 +5,8 @@ public enum Options { ISSA, // Incremental SSA OPTIMIZE, + SSA_DESTRUCTION_BRIGGS, + SSA_DESTRUCTION_BOISSINOT_NOCOALESCE, SCCP, CCP, // constant comparison propagation REGALLOC,