From 02edd460c48424f4a3983b202dd1c488ae8d41a6 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sat, 22 Feb 2025 01:18:02 +0000 Subject: [PATCH] WIP implement incremental SSA construction using Braun's method WIP implement incremental SSA construction using Braun's method WIP implement incremental SSA construction using Braun's method WIP implement incremental SSA construction using Braun's method WIP implement incremental SSA construction using Braun's method Small refactor to help with the incremental SSA diff Add option to control ISSA Add missing use replacement in Phis Some other cleanup and comments Bug fix Some prep work on enabling more tests Consider temp registers too as part of SSA because in boolean expressions, a temp is assigned value more than once so although generally temps are SSA already, in this case they are not. phi vars must also be SSA values When temps are used for creating arrays and structs ensure they get processed in the same way as all temps. Delete some unused code bug fixes Have to be seal BBs as soon as possible - will need to review this carefully WIP pending issue with while stmt body - if we seal it at the start we have an issue Dump out the non SSA ID with the register pool data so that we can see what slots were assigned Seal the body block immediately after the condition in while stmt Interpreter was using wrong regnum for SSA vars - this was okay until now because luckily the SSA vars did not survive, but with incremental SSA implementation, the bug became evident --- .../ezlang/compiler/BasicBlock.java | 8 +- .../ezlang/compiler/CompiledFunction.java | 553 ++++++++++++++++-- .../ezlang/compiler/Compiler.java | 2 +- .../ezlang/compiler/Instruction.java | 18 +- .../ezlang/compiler/Operand.java | 9 +- .../ezlang/compiler/Optimizer.java | 3 +- .../ezlang/compiler/Options.java | 4 +- .../ezlang/compiler/Register.java | 4 + .../ezlang/compiler/RegisterPool.java | 2 +- .../ezlang/compiler/SSAEdges.java | 10 +- .../ezlang/compiler/TestIncrementalSSA.java | 132 +++++ .../compiler/TestInterferenceGraph.java | 7 +- .../ezlang/compiler/TestLiveness.java | 66 ++- .../ezlang/compiler/TestSSATransform.java | 14 +- .../ezlang/interpreter/TestInterpreter.java | 1 + 15 files changed, 724 insertions(+), 109 deletions(-) create mode 100644 optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestIncrementalSSA.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 a57fc2b..ccb8b09 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java @@ -111,10 +111,10 @@ public void deleteInstruction(Instruction instruction) { instructions.remove(instruction); } public void addSuccessor(BasicBlock successor) { - assert successors.contains(successor) == false; - successors.add(successor); - assert successor.predecessors.contains(this) == false; - successor.predecessors.add(this); + if (!successors.contains(successor)) { + successors.add(successor); + successor.predecessors.add(this); + } } public void removeSuccessor(BasicBlock successor) { successors.remove(successor); diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java index 2eaf92b..0d577d2 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java @@ -7,9 +7,7 @@ import com.compilerprogramming.ezlang.types.Type; import com.compilerprogramming.ezlang.types.TypeDictionary; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.List; +import java.util.*; public class CompiledFunction { @@ -27,6 +25,7 @@ public class CompiledFunction { public boolean isSSA; public boolean hasLiveness; + private final IncrementalSSA issa; /** * We essentially do a form of abstract interpretation as we generate @@ -38,10 +37,11 @@ public class CompiledFunction { */ private List virtualStack = new ArrayList<>(); - public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol, TypeDictionary typeDictionary) { + public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol, TypeDictionary typeDictionary, EnumSet options) { AST.FuncDecl funcDecl = (AST.FuncDecl) functionSymbol.functionDecl; this.functionType = (Type.TypeFunction) functionSymbol.type; this.registerPool = new RegisterPool(); + this.issa = (options != null && options.contains(Options.ISSA)) ? new IncrementalSSABraun(this) : new NoopIncrementalSSA(); setVirtualRegisters(funcDecl.scope); this.BID = 0; this.entry = this.currentBlock = createBlock(); @@ -52,18 +52,22 @@ public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol, TypeDictionary generateArgInstructions(funcDecl.scope); compileStatement(funcDecl.block); exitBlockIfNeeded(); + issa.finish(options); this.frameSlots = registerPool.numRegisters(); } - + public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol, TypeDictionary typeDictionary) { + this(functionSymbol,typeDictionary,null); + } public CompiledFunction(Type.TypeFunction functionType, TypeDictionary typeDictionary) { this.functionType = (Type.TypeFunction) functionType; this.registerPool = new RegisterPool(); - this.BID = 0; + this.issa = new NoopIncrementalSSA(); this.BID = 0; this.entry = this.currentBlock = createBlock(); this.exit = createBlock(); this.currentBreakTarget = null; this.currentContinueTarget = null; this.typeDictionary = typeDictionary; + issa.finish(null); this.frameSlots = registerPool.numRegisters(); } @@ -71,7 +75,7 @@ private void generateArgInstructions(Scope scope) { if (scope.isFunctionParameterScope) { for (Symbol symbol: scope.getLocalSymbols()) { if (symbol instanceof Symbol.ParameterSymbol parameterSymbol) { - code(new Instruction.ArgInstruction(new Operand.LocalRegisterOperand(registerPool.getReg(parameterSymbol.regNumber)))); + codeArg(new Operand.LocalRegisterOperand(registerPool.getReg(parameterSymbol.regNumber), parameterSymbol)); } } } @@ -122,7 +126,7 @@ private void compileReturn(AST.ReturnStmt returnStmt) { if (isIndexed) codeIndexedLoad(); if (virtualStack.size() == 1) - code(new Instruction.Ret(pop())); + codeReturn(pop()); else if (virtualStack.size() > 1) throw new CompilerException("Virtual stack has more than one item at return"); } @@ -179,7 +183,7 @@ private void compileAssign(AST.AssignStmt assignStmt) { codeIndexedStore(); else if (assignStmt.lhs instanceof AST.NameExpr symbolExpr) { Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) symbolExpr.symbol; - code(new Instruction.Move(pop(), new Operand.LocalRegisterOperand(registerPool.getReg(varSymbol.regNumber)))); + codeMove(pop(), new Operand.LocalRegisterOperand(registerPool.getReg(varSymbol.regNumber), varSymbol)); } else throw new CompilerException("Invalid assignment expression: " + assignStmt.lhs); @@ -196,34 +200,37 @@ private void compileExprStmt(AST.ExprStmt exprStmt) { private void compileContinue(AST.ContinueStmt continueStmt) { if (currentContinueTarget == null) throw new CompilerException("No continue target found"); + assert !issa.isSealed(currentContinueTarget); jumpTo(currentContinueTarget); } private void compileBreak(AST.BreakStmt breakStmt) { if (currentBreakTarget == null) throw new CompilerException("No break target found"); + assert !issa.isSealed(currentBreakTarget); jumpTo(currentBreakTarget); } private void compileWhile(AST.WhileStmt whileStmt) { - BasicBlock loopBlock = createLoopHead(); + BasicBlock loopHead = createLoopHead(); BasicBlock bodyBlock = createBlock(); BasicBlock exitBlock = createBlock(); BasicBlock savedBreakTarget = currentBreakTarget; BasicBlock savedContinueTarget = currentContinueTarget; currentBreakTarget = exitBlock; - currentContinueTarget = loopBlock; - startBlock(loopBlock); + currentContinueTarget = loopHead; + startBlock(loopHead); // ISSA cannot seal until all back edges done boolean indexed = compileExpr(whileStmt.condition); if (indexed) codeIndexedLoad(); - code(new Instruction.ConditionalBranch(currentBlock, pop(), bodyBlock, exitBlock)); + codeCBR(currentBlock, pop(), bodyBlock, exitBlock); assert vstackEmpty(); - startBlock(bodyBlock); + startSealedBlock(bodyBlock); // ISSA If we seal this here fib test fails, wrong code generated, why? compileStatement(whileStmt.stmt); if (!isBlockTerminated(currentBlock)) - jumpTo(loopBlock); - startBlock(exitBlock); + jumpTo(loopHead); + issa.sealBlock(loopHead); + startSealedBlock(exitBlock); currentContinueTarget = savedContinueTarget; currentBreakTarget = savedBreakTarget; } @@ -235,6 +242,7 @@ private boolean isBlockTerminated(BasicBlock block) { public void jumpTo(BasicBlock block) { assert !isBlockTerminated(currentBlock); + assert !issa.isSealed(block); currentBlock.add(new Instruction.Jump(block)); currentBlock.addSuccessor(block); } @@ -245,28 +253,32 @@ public void startBlock(BasicBlock block) { } currentBlock = block; } - + public void startSealedBlock(BasicBlock block) { + startBlock(block); + assert !issa.isSealed(currentBlock); + issa.sealBlock(currentBlock); + } private void compileIf(AST.IfElseStmt ifElseStmt) { - BasicBlock ifBlock = createBlock(); + BasicBlock thenBlock = createBlock(); boolean needElse = ifElseStmt.elseStmt != null; BasicBlock elseBlock = needElse ? createBlock() : null; BasicBlock exitBlock = createBlock(); boolean indexed = compileExpr(ifElseStmt.condition); if (indexed) codeIndexedLoad(); - code(new Instruction.ConditionalBranch(currentBlock, pop(), ifBlock, needElse ? elseBlock : exitBlock)); + codeCBR(currentBlock, pop(), thenBlock, needElse ? elseBlock : exitBlock); assert vstackEmpty(); - startBlock(ifBlock); + startSealedBlock(thenBlock); // ISSA seal immediately compileStatement(ifElseStmt.ifStmt); if (!isBlockTerminated(currentBlock)) jumpTo(exitBlock); if (elseBlock != null) { - startBlock(elseBlock); + startSealedBlock(elseBlock); // ISSA seal immediately compileStatement(ifElseStmt.elseStmt); if (!isBlockTerminated(currentBlock)) jumpTo(exitBlock); } - startBlock(exitBlock); + startSealedBlock(exitBlock); // ISSA seal immediately } private void compileLet(AST.VarStmt letStmt) { @@ -274,7 +286,7 @@ private void compileLet(AST.VarStmt letStmt) { boolean indexed = compileExpr(letStmt.expr); if (indexed) codeIndexedLoad(); - code(new Instruction.Move(pop(), new Operand.LocalRegisterOperand(registerPool.getReg(letStmt.symbol.regNumber)))); + codeMove(pop(), new Operand.LocalRegisterOperand(registerPool.getReg(letStmt.symbol.regNumber), letStmt.symbol)); } } @@ -328,7 +340,7 @@ private boolean compileCallExpr(AST.CallExpr callExpr) { if (!(arg instanceof Operand.TempRegisterOperand) ) { var origArg = pop(); arg = createTemp(origArg.type); - code(new Instruction.Move(origArg, arg)); + codeMove(origArg, arg); } args.add((Operand.RegisterOperand) arg); } @@ -339,9 +351,8 @@ private boolean compileCallExpr(AST.CallExpr callExpr) { if (callExpr.callee.type instanceof Type.TypeFunction tf && !(tf.returnType instanceof Type.TypeVoid)) { ret = createTemp(tf.returnType); - //assert ret.regnum-maxLocalReg == returnStackPos; } - code(new Instruction.Call(returnStackPos, ret, calleeType, args.toArray(new Operand.RegisterOperand[args.size()]))); + codeCall(returnStackPos, ret, calleeType, args.toArray(new Operand.RegisterOperand[args.size()])); return false; } @@ -419,7 +430,7 @@ private boolean compileSymbolExpr(AST.NameExpr symbolExpr) { pushOperand(new Operand.LocalFunctionOperand(functionType)); else { Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) symbolExpr.symbol; - pushLocal(registerPool.getReg(varSymbol.regNumber)); + pushLocal(registerPool.getReg(varSymbol.regNumber), varSymbol); } return false; } @@ -433,28 +444,27 @@ private boolean codeBoolean(AST.BinaryExpr binaryExpr) { if (indexed) codeIndexedLoad(); if (isAnd) { - code(new Instruction.ConditionalBranch(currentBlock, pop(), l1, l2)); + codeCBR(currentBlock, pop(), l1, l2); } else { - code(new Instruction.ConditionalBranch(currentBlock, pop(), l2, l1)); + codeCBR(currentBlock, pop(), l2, l1); } - startBlock(l1); + startSealedBlock(l1); // ISSA seal immediately compileExpr(binaryExpr.expr2); var temp = ensureTemp(); jumpTo(l3); - startBlock(l2); + startSealedBlock(l2); // ISSA seal immediately // Below we must write to the same temp - code(new Instruction.Move(new Operand.ConstantOperand(isAnd ? 0 : 1, typeDictionary.INT), temp)); + codeMove(new Operand.ConstantOperand(isAnd ? 0 : 1, typeDictionary.INT), temp); jumpTo(l3); - startBlock(l3); + startSealedBlock(l3); // ISSA seal immediately // leave temp on virtual stack return false; } - private boolean compileBinaryExpr(AST.BinaryExpr binaryExpr) { String opCode = binaryExpr.op.str; if (opCode.equals("&&") || - opCode.equals("||")) { + opCode.equals("||")) { return codeBoolean(binaryExpr); } boolean indexed = compileExpr(binaryExpr.expr1); @@ -466,7 +476,7 @@ private boolean compileBinaryExpr(AST.BinaryExpr binaryExpr) { Operand right = pop(); Operand left = pop(); if (left instanceof Operand.NullConstantOperand && - right instanceof Operand.NullConstantOperand) { + right instanceof Operand.NullConstantOperand) { long value = 0; switch (opCode) { case "==": value = 1; break; @@ -496,7 +506,7 @@ else if (left instanceof Operand.ConstantOperand leftconstant && } else { var temp = createTemp(binaryExpr.type); - code(new Instruction.Binary(opCode, temp, left, right)); + codeBinary(opCode, temp, left, right); } return false; } @@ -518,7 +528,7 @@ private boolean compileUnaryExpr(AST.UnaryExpr unaryExpr) { } else { var temp = createTemp(unaryExpr.type); - code(new Instruction.Unary(opCode, temp, top)); + codeUnary(opCode, temp, top); } return false; } @@ -559,7 +569,7 @@ else if (operand instanceof Operand.RegisterOperand registerOperand) private Operand.TempRegisterOperand createTempAndMove(Operand src) { Type type = typeOfOperand(src); var temp = createTemp(type); - code(new Instruction.Move(src, temp)); + codeMove(src, temp); return temp; } @@ -576,8 +586,8 @@ private Operand.RegisterOperand ensureTemp() { } else throw new CompilerException("Cannot convert to temporary register"); } - private void pushLocal(Register reg) { - pushOperand(new Operand.LocalRegisterOperand(reg)); + private void pushLocal(Register reg, Symbol.VarSymbol varSymbol) { + pushOperand(new Operand.LocalRegisterOperand(reg, varSymbol)); } private void pushOperand(Operand operand) { @@ -596,13 +606,13 @@ private Operand.TempRegisterOperand codeIndexedLoad() { Operand indexed = pop(); var temp = createTemp(indexed.type); if (indexed instanceof Operand.LoadIndexedOperand loadIndexedOperand) { - code(new Instruction.ArrayLoad(loadIndexedOperand, temp)); + codeArrayLoad(loadIndexedOperand, temp); } else if (indexed instanceof Operand.LoadFieldOperand loadFieldOperand) { - code(new Instruction.GetField(loadFieldOperand, temp)); + codeGetField(loadFieldOperand, temp); } else - code(new Instruction.Move(indexed, temp)); + codeMove(indexed, temp); return temp; } @@ -610,30 +620,158 @@ private void codeIndexedStore() { Operand value = pop(); Operand indexed = pop(); if (indexed instanceof Operand.LoadIndexedOperand loadIndexedOperand) { - code(new Instruction.ArrayStore(value, loadIndexedOperand)); + codeArrayStore(value, loadIndexedOperand); } else if (indexed instanceof Operand.LoadFieldOperand loadFieldOperand) { - code(new Instruction.SetField(value, loadFieldOperand)); + codeSetField(value, loadFieldOperand); } else - code(new Instruction.Move(value, indexed)); + codeMove(value, indexed); } private void codeNew(Type type) { - var temp = createTemp(type); - if (type instanceof Type.TypeArray typeArray) { - code(new Instruction.NewArray(typeArray, temp)); - } - else if (type instanceof Type.TypeStruct typeStruct) { - code(new Instruction.NewStruct(typeStruct, temp)); - } + if (type instanceof Type.TypeArray typeArray) + codeNewArray(typeArray); + else if (type instanceof Type.TypeStruct typeStruct) + codeNewStruct(typeStruct); else throw new CompilerException("Unexpected type: " + type); } + private void codeNewArray(Type.TypeArray typeArray) { + var temp = createTemp(typeArray); + var target = (Operand.RegisterOperand) issa.write(temp); + var insn = new Instruction.NewArray(typeArray, target); + issa.recordDef(target, insn); + code(insn); + } + + private void codeNewStruct(Type.TypeStruct typeStruct) { + var temp = createTemp(typeStruct); + var target = (Operand.RegisterOperand) issa.write(temp); + var insn = new Instruction.NewStruct(typeStruct, target); + issa.recordDef(target, insn); + code(insn); + } + private void codeStoreAppend() { - var operand = pop(); - code(new Instruction.AStoreAppend((Operand.RegisterOperand) top(), operand)); + var operand = issa.read(pop()); + Operand.RegisterOperand arrayOperand = (Operand.RegisterOperand) issa.read(top()); + var insn = new Instruction.AStoreAppend(arrayOperand, operand); + issa.recordUse(arrayOperand, insn); + issa.recordUse(operand, insn); + code(insn); + } + + private void codeArg(Operand.LocalRegisterOperand target) { + var newtarget = (Operand.RegisterOperand) issa.write(target); + var insn = new Instruction.ArgInstruction(newtarget); + issa.recordDef(newtarget, insn); + code(insn); + } + + private void codeMove(Operand srcOperand, Operand destOperand) { + srcOperand = issa.read(srcOperand); + destOperand = issa.write(destOperand); + var insn = new Instruction.Move(srcOperand, destOperand); + issa.recordDef(destOperand, insn); + issa.recordUse(srcOperand, insn); + code(insn); + } + + private void codeReturn(Operand resultOperand) { + resultOperand = issa.read(resultOperand); + var insn = new Instruction.Ret(resultOperand); + issa.recordUse(resultOperand, insn); + code(insn); + } + + private void codeCBR(BasicBlock block, Operand condition, BasicBlock trueBlock, BasicBlock falseBlock) { + condition = issa.read(condition); + var insn = new Instruction.ConditionalBranch(block, condition, trueBlock, falseBlock); + assert !issa.isSealed(trueBlock); + assert !issa.isSealed(falseBlock); + block.addSuccessor(trueBlock); + block.addSuccessor(falseBlock); + issa.recordUse(condition, insn); + code(insn); + } + + private void codeCall(int newBase, + Operand.RegisterOperand targetOperand, + Type.TypeFunction calleeType, + Operand.RegisterOperand ...arguments) { + if (targetOperand != null) + targetOperand = (Operand.RegisterOperand) issa.write(targetOperand); + Operand.RegisterOperand args[] = new Operand.RegisterOperand[arguments.length]; + for (int i = 0; i < args.length; i++) { + args[i] = (Operand.RegisterOperand) issa.read(arguments[i]); + } + var insn = new Instruction.Call(newBase, targetOperand, calleeType, args); + for (int i = 0; i < args.length; i++) { + issa.recordUse(args[i], insn); + } + if (targetOperand != null) + issa.recordDef(targetOperand, insn); + code(insn); + } + + private void codeUnary(String opCode, Operand.RegisterOperand result, Operand operand) { + operand = issa.read(operand); + result = (Operand.RegisterOperand) issa.write(result); + var insn = new Instruction.Unary(opCode, result, operand); + issa.recordDef(result, insn); + issa.recordUse(operand, insn); + code(insn); + } + + private void codeBinary(String opCode, Operand.RegisterOperand result, Operand left, Operand right) { + left = issa.read(left); + right = issa.read(right); + result = (Operand.RegisterOperand) issa.write(result); + var insn = new Instruction.Binary(opCode, result, left, right); + issa.recordDef(result, insn); + issa.recordUse(left, insn); + issa.recordUse(right, insn); + code(insn); + } + + private void codeArrayLoad(Operand.LoadIndexedOperand loadIndexedOperand, Operand.RegisterOperand target) { + loadIndexedOperand = new Operand.LoadIndexedOperand(issa.read(loadIndexedOperand.arrayOperand), issa.read(loadIndexedOperand.indexOperand)); + target = (Operand.RegisterOperand) issa.write(target); + var insn = new Instruction.ArrayLoad(loadIndexedOperand, target); + issa.recordDef(target, insn); + issa.recordUse(loadIndexedOperand.arrayOperand, insn); + issa.recordUse(loadIndexedOperand.indexOperand, insn); + code(insn); + } + + private void codeGetField(Operand.LoadFieldOperand loadFieldOperand, Operand.RegisterOperand target) { + loadFieldOperand = new Operand.LoadFieldOperand(issa.read(loadFieldOperand.structOperand), loadFieldOperand.fieldName, loadFieldOperand.fieldIndex); + target = (Operand.RegisterOperand) issa.write(target); + var insn = new Instruction.GetField(loadFieldOperand, target); + issa.recordDef(target, insn); + issa.recordUse(loadFieldOperand.structOperand, insn); + code(insn); + } + + private void codeArrayStore(Operand value, Operand.LoadIndexedOperand loadIndexedOperand) { + loadIndexedOperand = new Operand.LoadIndexedOperand(issa.read(loadIndexedOperand.arrayOperand), issa.read(loadIndexedOperand.indexOperand)); + value = issa.read(value); + var insn = new Instruction.ArrayStore(value, loadIndexedOperand); + issa.recordUse(loadIndexedOperand.arrayOperand, insn); + issa.recordUse(loadIndexedOperand.indexOperand, insn); + issa.recordUse(value, insn); + code(insn); + } + + private void codeSetField(Operand value, Operand.LoadFieldOperand loadFieldOperand) { + loadFieldOperand = new Operand.LoadFieldOperand(issa.read(loadFieldOperand.structOperand), loadFieldOperand.fieldName, loadFieldOperand.fieldIndex); + value = issa.read(value); + var insn = new Instruction.SetField(value, loadFieldOperand); + issa.recordUse(loadFieldOperand.structOperand, insn); + issa.recordUse(value, insn); + code(insn); } private boolean vstackEmpty() { @@ -677,4 +815,307 @@ public StringBuilder toDot(StringBuilder sb, boolean verbose) { sb.append("}\n"); return sb; } + + interface IncrementalSSA { + Operand read(Operand operand); + Operand write(Operand operand); + void recordUse(Operand operand, Instruction instruction); + void recordDef(Register reg, Instruction instruction); + void recordDef(Operand operand, Instruction instruction); + void sealBlock(BasicBlock block); + boolean isSealed(BasicBlock block); + void finish(EnumSet options); + } + + /** + * Support for AST to SSA IR using Braun's method. + * See Simple and Efficient Construction of Static Single Assignment Form, 2013 + * Matthias Braun, Sebastian Buchwald, Sebastian Hack, Roland Leißa + */ + static final class IncrementalSSABraun implements IncrementalSSA { + + CompiledFunction function; + + // For each unique variable (variable with same name in different scopes must be distinct) + // a mapping is maintained for the name to SSA value (virtual register) in each block. + // we could add this mapping to the BB itself but it seems nicer to keep it separate + // at least for now. + // Note that we use the nonSSAId as proxy for variable name + // because this id is unique for non-SSA variables, but for SSA versions this refers + // back to the original ID + Map> currentDef = new HashMap<>(); + // Flags blocks that are completed in terms of instruction generation + BitSet sealedBlocks = new BitSet(); + // Tracks Phis that are not finalized because the basic block is not yet sealed + Map> incompletePhis = new HashMap<>(); + + // Not explicitly stated in the paper but implicit in the algo is + // the availability of Def-use chains. We have to main this incrementally as we + // generate code - used when eliminating trivial phis + Map ssaDefUses = new HashMap<>(); + + // This is not part of the spec, it is just an implementation detail + // We pre-assign registers to local declared vars, but then the SSA part + // creates new ones. However, the first time a variable is versioned we could just use the + // original regnum. + // This set tracks whether a version is being created first time + // It also helps us maintain version numbers similar to traditional SSA + Map versioned = new HashMap<>(); + + private IncrementalSSABraun(CompiledFunction function) { + this.function = function; + } + + /** + * Associates a new definition (value) to a variable name within a basic block + */ + private void writeVariable(Register variable, BasicBlock block, Register value) { + currentDef.computeIfAbsent(variable.nonSSAId(), k -> new HashMap<>()).put(block, value); + } + + /** + * Looks up the current SSA value (virtual register) associated with a name, inside a block. + * If no mapping is found, processing depends on status of the block. + * @see #readVariableRecursive(Register, BasicBlock) + */ + private Register readVariable(Register variable, BasicBlock block) { + Map defs = currentDef.get(variable.nonSSAId()); + if (defs != null && defs.containsKey(block)) { + // local value numbering + return defs.get(block); + } + // global value numbering + return readVariableRecursive(variable, block); + } + + /** + * Called when a block does not have a mapping for a variable. + * If the block is still under construction, then a Phi is inserted into the + * block - and the phi is marked as incomplete. + * If block is constructed, then we look at predecessors for definitions; + * in case of more than 1 predecessor a phi is created with input + * obtained recursively via each predecessor block. + * In case of 1 predecessor the value is read recursively from that predecessor. + */ + private Register readVariableRecursive(Register variable, BasicBlock block) { + Register val; + if (!isSealed(block)) { + // incomplete CFG + val = makeVersion(variable); + Instruction.Phi phi = makePhi(val, block); + incompletePhis.computeIfAbsent(block, k -> new HashMap<>()).put(variable, phi); + } + else if (block.predecessors.size() == 1) { + // Optimize the common case of one predecessor: No phi needed + val = readVariable(variable, block.predecessors.get(0)); + } + else { + // Break potential cycles with operandless phis + val = makeVersion(variable); + Instruction.Phi phi = makePhi(val, block); + writeVariable(variable, block, val); + val = addPhiOperands(variable,phi); + } + writeVariable(variable, block, val); + return val; + } + + private Instruction.Phi makePhi(Register val, BasicBlock block) { + Instruction.Phi phi = new Instruction.Phi(val, new ArrayList<>()); + recordDef(val, phi); + block.add(0, phi); + return phi; + } + + /** + * Populate the members of a phi instruction + */ + private Register addPhiOperands(Register variable, Instruction.Phi phi) { + assert phi.numInputs() == 0; + // Determine operands from predecessors + for (BasicBlock pred: phi.block.predecessors) { + phi.addInput(readVariable(variable,pred)); + } + return tryRemovingPhi(phi); + } + + private Register tryRemovingPhi(Instruction.Phi phi) { + Register same = null; + // Check if phi has distinct inputs + for (int i = 0; i < phi.numInputs(); i++) { + if (!phi.isRegisterInput(i)) + // Cannot happen? + throw new IllegalStateException(); + var use = phi.inputAsRegister(i); + if (use.equals(same) || use.equals(phi.value())) + continue; // Unique value or self reference + if (same != null) + // More than 1 distinct value, so keep phi + return phi.value(); + same = use; + } + if (same == null) { + // phi is unreachable or in the start block + // Paper suggests we create an Undef, but we throw an exception + // same = function.registerPool.newReg("Undef", null); + throw new CompilerException("Undefined value for phi " + phi.value()); + } + // remember uses except phi + var users = getUsesExcept(phi); + // remove all uses of phi to same and remove phi + replacePhiValueAndUsers(phi, same); + phi.block.deleteInstruction(phi); + // try to recursively remove all phi users, which might have become trivial + for (var use: users) { + if (use instanceof Instruction.Phi phiuser) + tryRemovingPhi(phiuser); + } + return same; + } + + /** + * Reroute all uses of phi to new value + */ + private void replacePhiValueAndUsers(Instruction.Phi phi, Register newValue) { + var oldDefUseChain = ssaDefUses.get(phi.value()); + var newDefUseChain = ssaDefUses.get(newValue); + if (newDefUseChain == null) { + // Can be null because this may be existing def + newDefUseChain = SSAEdges.addDef(ssaDefUses, newValue, phi); + } + if (oldDefUseChain != null) { + for (Instruction instruction: oldDefUseChain.useList) { + if (instruction instanceof Instruction.Phi somePhi) { + somePhi.replaceInput(phi.value(), newValue); + } + else { + instruction.replaceUse(phi.value(), newValue); + } + } + // Users of phi old value become users of the new value + newDefUseChain.useList.addAll(oldDefUseChain.useList); + oldDefUseChain.useList.clear(); + // FIXME remove old def from def-use chains + } + } + + private List getUsesExcept(Instruction.Phi phi) { + var oldDefUseChain = ssaDefUses.get(phi.value()); + if (oldDefUseChain == null) { + return new ArrayList<>(); + } + var useList = new ArrayList<>(oldDefUseChain.useList); + useList.remove(phi); + return useList; + } + + @Override + public Operand read(Operand operand) { + // We have to consider temps too because of boolean expressions + // where temps are not SSA + if (operand instanceof Operand.RegisterOperand localRegisterOperand) { + var reg = readVariable(localRegisterOperand.reg, function.currentBlock); + operand = new Operand.RegisterOperand(reg); + } + return operand; + } + @Override + public Operand write(Operand operand) { + // We have to consider temps too because of boolean expressions + // where temps are not SSA + if (operand instanceof Operand.RegisterOperand localRegisterOperand) { + var variable = localRegisterOperand.reg; + Register newValue = makeVersion(variable); + writeVariable(variable, function.currentBlock, newValue); + operand = new Operand.RegisterOperand(newValue); + } + return operand; + } + + private Register makeVersion(Register variable) { + Register newValue; + Integer version = versioned.get(variable.nonSSAId()); + // Avoid creating a new value first time because we already + // have a pre-created register we can use + if (version == null) { + newValue = variable; + versioned.put(variable.nonSSAId(), 1); + } + else { + versioned.put(variable.nonSSAId(), version + 1); + newValue = function.registerPool.ssaReg(variable, version); + } + return newValue; + } + + @Override + public void recordUse(Operand operand, Instruction instruction) { + if (operand instanceof Operand.RegisterOperand registerOperand) { + SSAEdges.recordUse(ssaDefUses, instruction, registerOperand.reg); + } + } + @Override + public void recordDef(Register reg, Instruction instruction) { + SSAEdges.recordDef(ssaDefUses, reg, instruction); + } + @Override + public void recordDef(Operand operand, Instruction instruction) { + if (operand instanceof Operand.RegisterOperand registerOperand) { + SSAEdges.recordDef(ssaDefUses, registerOperand.reg, instruction); + } + } + @Override + public void sealBlock(BasicBlock block) { + if (isSealed(block)) + return; + var pendingPhis = incompletePhis.remove(block); + if (pendingPhis != null) { + for (var variable : pendingPhis.keySet()) { + addPhiOperands(variable, pendingPhis.get(variable)); + } + } + sealedBlocks.set(block.bid); + } + @Override + public boolean isSealed(BasicBlock block) { + return sealedBlocks.get(block.bid); + } + @Override + public void finish(EnumSet options) { + function.isSSA = true; + if (options != null && options.contains(Options.DUMP_SSA_IR)) { + function.dumpIR(false, "Post SSA IR"); + } + } + } + + static final class NoopIncrementalSSA implements IncrementalSSA { + @Override + public Operand read(Operand operand) { + return operand; + } + @Override + public Operand write(Operand operand) { + return operand; + } + @Override + public void recordUse(Operand operand, Instruction instruction) { + } + @Override + public void recordDef(Register reg, Instruction instruction) { + } + @Override + public void recordDef(Operand operand, Instruction instruction) { + } + @Override + public void sealBlock(BasicBlock block) { + } + @Override + public boolean isSealed(BasicBlock block) { + return false; + } + @Override + public void finish(EnumSet options) { + } + } } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java index 4c24bb6..b15a157 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java @@ -16,7 +16,7 @@ private void compile(TypeDictionary typeDictionary, EnumSet options) { for (Symbol symbol: typeDictionary.getLocalSymbols()) { if (symbol instanceof Symbol.FunctionTypeSymbol functionSymbol) { Type.TypeFunction functionType = (Type.TypeFunction) functionSymbol.type; - var function = new CompiledFunction(functionSymbol, typeDictionary); + var function = new CompiledFunction(functionSymbol, typeDictionary, options); if (options.contains(Options.DUMP_INITIAL_IR)) function.dumpIR(false, "Initial IR"); functionType.code = function; 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 1651c1c..7bb016b 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java @@ -290,8 +290,6 @@ public ConditionalBranch(BasicBlock currentBlock, Operand condition, BasicBlock super(I_CBR, (Operand.RegisterOperand) null, condition); this.trueBlock = trueBlock; this.falseBlock = falseBlock; - currentBlock.addSuccessor(trueBlock); - currentBlock.addSuccessor(falseBlock); } public Operand condition() { return uses[0]; } @Override @@ -426,6 +424,22 @@ public StringBuilder toStr(StringBuilder sb) { sb.append(")"); return sb; } + public void addInput(Register register) { + var newUses = new Operand[uses.length + 1]; + System.arraycopy(uses, 0, newUses, 0, uses.length); + newUses[newUses.length-1] = new Operand.RegisterOperand(register); + this.uses = newUses; + } + public void replaceInput(Register oldReg, Register newReg) { + for (int i = 0; i < numInputs(); i++) { + if (isRegisterInput(i)) { + Register in = inputAsRegister(i); + if (in.equals(oldReg)) { + replaceInput(i, newReg); + } + } + } + } } public static class ArgInstruction extends Instruction { 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 57a691e..8ff84e4 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java @@ -1,5 +1,6 @@ package com.compilerprogramming.ezlang.compiler; +import com.compilerprogramming.ezlang.types.Symbol; import com.compilerprogramming.ezlang.types.Type; public class Operand { @@ -35,7 +36,7 @@ protected RegisterOperand(Register reg) { if (reg == null) throw new NullPointerException(); } - public int frameSlot() { return reg.nonSSAId(); } + public int frameSlot() { return reg.frameSlot(); } public RegisterOperand copy(Register register) { return new RegisterOperand(register); @@ -48,12 +49,14 @@ public String toString() { } public static class LocalRegisterOperand extends RegisterOperand { - public LocalRegisterOperand(Register reg) { + Symbol.VarSymbol variable; + public LocalRegisterOperand(Register reg, Symbol.VarSymbol variable) { super(reg); + this.variable = variable; } @Override public RegisterOperand copy(Register register) { - return new LocalRegisterOperand(register); + return new LocalRegisterOperand(register, variable); } } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Optimizer.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Optimizer.java index c9e21d8..1856e74 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Optimizer.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Optimizer.java @@ -6,7 +6,8 @@ public class Optimizer { public void optimize(CompiledFunction function, EnumSet options) { if (options.contains(Options.OPTIMIZE)) { - new EnterSSA(function, options); + if (!function.isSSA) + new EnterSSA(function, options); if (options.contains(Options.SCCP)) { new SparseConditionalConstantPropagation().constantPropagation(function).apply(options); if (new ConstantComparisonPropagation(function).apply(options)) { 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 56280e2..b49d9b5 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Options.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Options.java @@ -3,6 +3,7 @@ import java.util.EnumSet; public enum Options { + ISSA, // Incremental SSA OPTIMIZE, SCCP, CCP, // constant comparison propagation @@ -22,5 +23,6 @@ public enum Options { public static final EnumSet NONE = EnumSet.noneOf(Options.class); public static final EnumSet OPT = EnumSet.of(Options.OPTIMIZE,Options.SCCP,Options.CCP,Options.REGALLOC); - public static final EnumSet OPT_VERBOSE = EnumSet.allOf(Options.class); + public static final EnumSet VERBOSE = EnumSet.range(DUMP_INITIAL_IR, DUMP_POST_CHAITIN_IR); + public static final EnumSet OPT_VERBOSE = EnumSet.range(OPTIMIZE, DUMP_POST_CHAITIN_IR); } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Register.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Register.java index 6ad3258..3376e61 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Register.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Register.java @@ -83,11 +83,13 @@ public String name() { * During SSA form this is not valid for registers that are instances of SSARegister. */ public int nonSSAId() { + //assert frameSlot >= 0; // assert inteferes with verbose display return frameSlot; } public void updateSlot(int slot) { this.frameSlot = slot; } + public int frameSlot() { return frameSlot; } /** * An SSA Register retains a reference to the original @@ -106,4 +108,6 @@ public int nonSSAId() { return originalRegNumber; } } + + } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/RegisterPool.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/RegisterPool.java index 5cb9e93..88469af 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/RegisterPool.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/RegisterPool.java @@ -49,7 +49,7 @@ public int numRegisters() { } public void toStr(StringBuilder sb) { for (Register reg : registers) { - sb.append("Reg #").append(reg.id).append(" ").append(reg.name()).append("\n"); + sb.append("Reg #").append(reg.id).append(" ").append(reg.name()).append(" ").append(reg.nonSSAId()).append("\n"); } } } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java index e6fcdcb..4e81e91 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java @@ -83,12 +83,16 @@ private static void recordUses(CompiledFunction function, Map private static void recordUses(Map defUseChains, Register[] inputs, BasicBlock block, Instruction instruction) { for (Register register : inputs) { - SSADef def = defUseChains.get(register); - def.useList.add(instruction); + recordUse(defUseChains, instruction, register); } } - private static void recordDef(Map defUseChains, Register value, Instruction instruction) { + public static void recordUse(Map defUseChains, Instruction instruction, Register register) { + SSADef def = defUseChains.get(register); + def.useList.add(instruction); + } + + public static void recordDef(Map defUseChains, Register value, Instruction instruction) { if (defUseChains.containsKey(value)) throw new CompilerException("Register already defined, invalid multiple definition in SSA"); defUseChains.put(value, new SSADef(instruction)); diff --git a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestIncrementalSSA.java b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestIncrementalSSA.java new file mode 100644 index 0000000..864cb42 --- /dev/null +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestIncrementalSSA.java @@ -0,0 +1,132 @@ +package com.compilerprogramming.ezlang.compiler; + +import org.junit.Test; + +import java.util.EnumSet; + +public class TestIncrementalSSA { + String compileSrc(String src) { + var compiler = new Compiler(); + var typeDict = compiler.compileSrc(src, EnumSet.of(Options.ISSA,Options.DUMP_SSA_IR)); + return compiler.dumpIR(typeDict,true); + } + + @Test + public void test1() { + String src = """ + func foo(d: Int) { + var a = 42; + var b = a; + var c = a + b; + a = c + 23; + c = a + d; + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void test2() { + String src = """ + func foo(d: Int)->Int { + var a = 42 + if (d) + { + a = a + 1 + } + else + { + a = a - 1 + } + return a + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void test3() { + String src = """ + func factorial(num: Int)->Int { + var result = 1 + while (num > 1) + { + result = result * num + num = num - 1 + } + return result + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + + @Test + public void test4() { + String src = """ + func print(a: Int, b: Int, c:Int, d:Int) {} + func example14_66(p: Int, q: Int, r: Int, s: Int, t: Int) { + var i = 1 + var j = 1 + var k = 1 + var l = 1 + while (1) { + if (p) { + j = i + if (q) { + l = 2 + } + else { + l = 3 + } + k = k + 1 + } + else { + k = k + 2 + } + print(i,j,k,l) + while (1) { + if (r) { + l = l + 4 + } + if (!s) + break + } + i = i + 6 + if (!t) + break + } + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void test5() { + String src = """ + func fib(n: Int)->Int { + var i: Int; + var temp: Int; + var f1=1; + var f2=1; + i=n; + while( i>1 ){ + temp = f1+f2; + f1=f2; + f2=temp; + i=i-1; + } + return f2; + } + func foo()->Int { + return fib(10); + } + """; + String result = compileSrc(src); + System.out.println(result); + } +} \ No newline at end of file diff --git a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestInterferenceGraph.java b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestInterferenceGraph.java index e5a4299..8018cba 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestInterferenceGraph.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestInterferenceGraph.java @@ -11,7 +11,8 @@ public class TestInterferenceGraph { private CompiledFunction buildTest1() { TypeDictionary typeDictionary = new TypeDictionary(); Type.TypeFunction functionType = new Type.TypeFunction("foo"); - functionType.addArg(new Symbol.ParameterSymbol("a", typeDictionary.INT)); + var argSymbol = new Symbol.ParameterSymbol("a", typeDictionary.INT); + functionType.addArg(argSymbol); functionType.setReturnType(typeDictionary.INT); CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; @@ -19,7 +20,7 @@ private CompiledFunction buildTest1() { Register b = regPool.newReg("b", typeDictionary.INT); Register c = regPool.newReg("c", typeDictionary.INT); Register d = regPool.newReg("d", typeDictionary.INT); - function.code(new Instruction.ArgInstruction(new Operand.LocalRegisterOperand(a))); + function.code(new Instruction.ArgInstruction(new Operand.LocalRegisterOperand(a, argSymbol))); function.code(new Instruction.Binary( "+", new Operand.RegisterOperand(a), @@ -100,6 +101,8 @@ private CompiledFunction buildTest2() { function.currentBlock, new Operand.RegisterOperand(a), b1, b2)); + function.currentBlock.addSuccessor(b1); + function.currentBlock.addSuccessor(b2); function.startBlock(b1); function.code(new Instruction.Move( new Operand.ConstantOperand(2, typeDictionary.INT), diff --git a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java index 8e632d7..7a324b2 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java @@ -39,19 +39,19 @@ func foo() { String output = Compiler.dumpIR(typeDict, true); Assert.assertEquals(""" func print(n: Int) -Reg #0 n +Reg #0 n 0 L0: arg n goto L1 L1: func foo() -Reg #0 i -Reg #1 s -Reg #2 %t2 -Reg #3 %t3 -Reg #4 %t4 -Reg #5 %t5 -Reg #6 %t6 +Reg #0 i 0 +Reg #1 s 1 +Reg #2 %t2 2 +Reg #3 %t3 3 +Reg #4 %t4 4 +Reg #5 %t5 5 +Reg #6 %t6 6 L0: i = 1 s = 1 @@ -159,13 +159,13 @@ func foo(a: Int, b: Int) { String output = Compiler.dumpIR(typeDict, true); Assert.assertEquals(""" func foo(a: Int,b: Int) -Reg #0 a -Reg #1 b -Reg #2 %t2 -Reg #3 %t3 -Reg #4 %t4 -Reg #5 %t5 -Reg #6 %t6 +Reg #0 a 0 +Reg #1 b 1 +Reg #2 %t2 2 +Reg #3 %t3 3 +Reg #4 %t4 4 +Reg #5 %t5 5 +Reg #6 %t6 6 L0: arg a arg b @@ -264,6 +264,8 @@ static CompiledFunction buildTest3() { function.currentBlock, new Operand.RegisterOperand(i), b2, b3)); + function.currentBlock.addSuccessor(b2); + function.currentBlock.addSuccessor(b3); function.startBlock(b2); function.code(new Instruction.Move( new Operand.ConstantOperand(0, typeDictionary.INT), @@ -284,6 +286,8 @@ static CompiledFunction buildTest3() { function.currentBlock, new Operand.RegisterOperand(i), b1, b4)); + function.currentBlock.addSuccessor(b1); + function.currentBlock.addSuccessor(b4); function.startBlock(b4); function.code(new Instruction.Ret(new Operand.RegisterOperand(s))); function.startBlock(function.exit); @@ -301,8 +305,8 @@ public void test3() { String actual = function.toStr(new StringBuilder(), true).toString(); Assert.assertEquals(""" func foo()->Int -Reg #0 i -Reg #1 s +Reg #0 i 0 +Reg #1 s 1 L0: i = 1 goto L2 @@ -366,11 +370,11 @@ public void testSwapProblem() { String actual = function.toStr(new StringBuilder(), true).toString(); Assert.assertEquals(""" func foo(p: Int) -Reg #0 p -Reg #1 a1 -Reg #2 a2 -Reg #3 b1 -Reg #4 b2 +Reg #0 p 0 +Reg #1 a1 1 +Reg #2 a2 2 +Reg #3 b1 3 +Reg #4 b2 4 L0: arg p a1 = 42 @@ -409,10 +413,10 @@ public void testLostCopyProblem() { String actual = function.toStr(new StringBuilder(), true).toString(); Assert.assertEquals(""" func foo(p: Int)->Int -Reg #0 p -Reg #1 x1 -Reg #2 x3 -Reg #3 x2 +Reg #0 p 0 +Reg #1 x1 1 +Reg #2 x3 2 +Reg #3 x2 3 L0: arg p x1 = 1 @@ -466,7 +470,7 @@ func foo()->Int Assert.assertEquals(""" Pre-SSA func foo()->Int -Reg #0 %t0 +Reg #0 %t0 0 L0: if 1 goto L2 else goto L3 #PHIDEFS = {} @@ -511,10 +515,10 @@ func foo()->Int #LIVEOUT = {0} Post-SSA func foo()->Int -Reg #0 %t0 -Reg #1 %t0_0 -Reg #2 %t0_1 -Reg #3 %t0_2 +Reg #0 %t0 0 +Reg #1 %t0_0 0 +Reg #2 %t0_1 0 +Reg #3 %t0_2 0 L0: if 1 goto L2 else goto L3 #PHIDEFS = {} diff --git a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java index 1c4fc5b..2a91a12 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java @@ -638,13 +638,14 @@ func bar(arg: Int)->Int { static CompiledFunction buildLostCopyTest() { TypeDictionary typeDictionary = new TypeDictionary(); Type.TypeFunction functionType = new Type.TypeFunction("foo"); - functionType.addArg(new Symbol.ParameterSymbol("p", typeDictionary.INT)); + var argSymbol = new Symbol.ParameterSymbol("p", typeDictionary.INT); + functionType.addArg(argSymbol); functionType.setReturnType(typeDictionary.INT); CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; Register p = regPool.newReg("p", typeDictionary.INT); Register x1 = regPool.newReg("x1", typeDictionary.INT); - function.code(new Instruction.ArgInstruction(new Operand.LocalRegisterOperand(p))); + function.code(new Instruction.ArgInstruction(new Operand.LocalRegisterOperand(p, argSymbol))); function.code(new Instruction.Move( new Operand.ConstantOperand(1, typeDictionary.INT), new Operand.RegisterOperand(x1))); @@ -659,6 +660,8 @@ static CompiledFunction buildLostCopyTest() { new Operand.ConstantOperand(1, typeDictionary.INT))); function.code(new Instruction.ConditionalBranch(B2, new Operand.RegisterOperand(p), B2, function.exit)); + function.currentBlock.addSuccessor(B2); + function.currentBlock.addSuccessor(function.exit); function.startBlock(function.exit); function.code(new Instruction.Ret(new Operand.RegisterOperand(x2))); function.isSSA = true; @@ -706,7 +709,8 @@ public void testLostCopyProblem() { static CompiledFunction buildSwapTest() { TypeDictionary typeDictionary = new TypeDictionary(); Type.TypeFunction functionType = new Type.TypeFunction("foo"); - functionType.addArg(new Symbol.ParameterSymbol("p", typeDictionary.INT)); + var argSymbol = new Symbol.ParameterSymbol("p", typeDictionary.INT); + functionType.addArg(argSymbol); functionType.setReturnType(typeDictionary.VOID); CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; @@ -715,7 +719,7 @@ static CompiledFunction buildSwapTest() { Register a2 = regPool.newReg("a2", typeDictionary.INT); Register b1 = regPool.newReg("b1", typeDictionary.INT); Register b2 = regPool.newReg("b2", typeDictionary.INT); - function.code(new Instruction.ArgInstruction(new Operand.LocalRegisterOperand(p))); + function.code(new Instruction.ArgInstruction(new Operand.LocalRegisterOperand(p, argSymbol))); function.code(new Instruction.Move( new Operand.ConstantOperand(42, typeDictionary.INT), new Operand.RegisterOperand(a1))); @@ -728,6 +732,8 @@ static CompiledFunction buildSwapTest() { function.code(new Instruction.Phi(b2, Arrays.asList(b1, a2))); function.code(new Instruction.ConditionalBranch(B2, new Operand.RegisterOperand(p), B2, function.exit)); + function.currentBlock.addSuccessor(B2); + function.currentBlock.addSuccessor(function.exit); function.startBlock(function.exit); function.isSSA = true; return function; diff --git a/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java b/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java index 4f8c0f0..fcfe0f5 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java @@ -13,6 +13,7 @@ Value compileAndRun(String src, String mainFunction) { return compileAndRun(src, mainFunction, Options.NONE); } Value compileAndRun(String src, String mainFunction, EnumSet options) { + //options.add(Options.ISSA); var compiler = new Compiler(); var typeDict = compiler.compileSrc(src, options); var compiled = compiler.dumpIR(typeDict);