From 2c2cabcc9ef04c5c7c5ca21ccf63f605fb1cef6c Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sun, 16 Feb 2025 13:26:15 +0000 Subject: [PATCH 1/6] Improved type checking - also some fixes to test cases that broke after type checking was improved --- .../compilerprogramming/ezlang/semantic/SemaAssignTypes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java b/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java index d996df5..d5504f4 100644 --- a/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java +++ b/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java @@ -269,7 +269,7 @@ public ASTVisitor visit(AST.ReturnStmt returnStmt, boolean enter) { return this; Type.TypeFunction functionType = (Type.TypeFunction) currentFuncDecl.symbol.type; if (returnStmt.expr != null) { - validType(returnStmt.expr.type, false); + validType(returnStmt.expr.type, true); checkAssignmentCompatible(functionType.returnType, returnStmt.expr.type); } else if (!(functionType.returnType instanceof Type.TypeVoid)) { From 985cf88354364012eca206c5c50647a9510f59e2 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sun, 16 Feb 2025 23:32:50 +0000 Subject: [PATCH 2/6] Initial work on implementing Null support in registervm - as well as boolean expressions in the compiler --- .../ezlang/compiler/CompiledFunction.java | 75 ++++++++++++++- .../ezlang/compiler/Compiler.java | 2 +- .../ezlang/compiler/Operand.java | 10 ++ .../ezlang/interpreter/Interpreter.java | 95 ++++++++++++++----- .../ezlang/interpreter/Value.java | 3 + .../ezlang/compiler/TestCompiler.java | 16 ++++ .../ezlang/interpreter/TestInterpreter.java | 74 +++++++++++++++ .../ezlang/semantic/SemaAssignTypes.java | 2 + 8 files changed, 247 insertions(+), 30 deletions(-) diff --git a/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java b/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java index 56d5f55..ef5a842 100644 --- a/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java +++ b/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java @@ -5,6 +5,7 @@ import com.compilerprogramming.ezlang.types.Scope; import com.compilerprogramming.ezlang.types.Symbol; import com.compilerprogramming.ezlang.types.Type; +import com.compilerprogramming.ezlang.types.TypeDictionary; import java.util.ArrayList; import java.util.List; @@ -19,6 +20,7 @@ public class CompiledFunction { private BasicBlock currentContinueTarget; public int maxLocalReg; public int maxStackSize; + private final TypeDictionary typeDictionary; /** * We essentially do a form of abstract interpretation as we generate @@ -30,7 +32,7 @@ public class CompiledFunction { */ private List virtualStack = new ArrayList<>(); - public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol) { + public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol, TypeDictionary typeDictionary) { AST.FuncDecl funcDecl = (AST.FuncDecl) functionSymbol.functionDecl; setVirtualRegisters(funcDecl.scope); this.BID = 0; @@ -38,6 +40,7 @@ public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol) { this.exit = createBlock(); this.currentBreakTarget = null; this.currentContinueTarget = null; + this.typeDictionary = typeDictionary; compileStatement(funcDecl.block); exitBlockIfNeeded(); } @@ -408,18 +411,72 @@ private boolean compileSymbolExpr(AST.NameExpr symbolExpr) { return false; } + private boolean codeBoolean(AST.BinaryExpr binaryExpr) { + BasicBlock l1; + BasicBlock l2; + BasicBlock l3; + boolean indexed; + boolean isAnd; + + l1 = createBlock(); + l2 = createBlock(); + l3 = createBlock(); + indexed = compileExpr(binaryExpr.expr1); + if (indexed) { + codeIndexedLoad(); + } + isAnd = binaryExpr.op.str.equals("&&"); + // FIXME ensure temp + var operand = pop(); + var temp = createTemp(typeDictionary.INT); + code(new Instruction.Move(operand, temp)); + if (isAnd) { + code(new Instruction.ConditionalBranch(currentBlock, pop(), l1, l2)); + } else { + code(new Instruction.ConditionalBranch(currentBlock, pop(), l2, l1)); + } + startBlock(l1); + indexed = compileExpr(binaryExpr.expr2); + if (indexed) { + codeIndexedLoad(); + } + operand = pop(); + temp = createTemp(typeDictionary.INT); + code(new Instruction.Move(operand, temp)); + jumpTo(l3); + startBlock(l2); + code(new Instruction.Move(new Operand.ConstantOperand(isAnd ? 0 : 1, typeDictionary.INT), temp)); + jumpTo(l3); + startBlock(l3); + return false; + } + + private boolean compileBinaryExpr(AST.BinaryExpr binaryExpr) { - String opCode = null; + String opCode = binaryExpr.op.str; + if (opCode.equals("&&") || + opCode.equals("||")) { + return codeBoolean(binaryExpr); + } boolean indexed = compileExpr(binaryExpr.expr1); if (indexed) codeIndexedLoad(); indexed = compileExpr(binaryExpr.expr2); if (indexed) codeIndexedLoad(); - opCode = binaryExpr.op.str; Operand right = pop(); Operand left = pop(); - if (left instanceof Operand.ConstantOperand leftconstant && + if (left instanceof Operand.NullConstantOperand && + right instanceof Operand.NullConstantOperand) { + long value = 0; + switch (opCode) { + case "==": value = 1; break; + case "!=": value = 0; break; + default: throw new CompilerException("Invalid binary op"); + } + pushConstant(value, typeDictionary.INT); + } + else if (left instanceof Operand.ConstantOperand leftconstant && right instanceof Operand.ConstantOperand rightconstant) { long value = 0; switch (opCode) { @@ -468,7 +525,11 @@ private boolean compileUnaryExpr(AST.UnaryExpr unaryExpr) { } private boolean compileConstantExpr(AST.LiteralExpr constantExpr) { - pushConstant(constantExpr.value.num.intValue(), constantExpr.type); + if (constantExpr.type instanceof Type.TypeInteger) + pushConstant(constantExpr.value.num.intValue(), constantExpr.type); + else if (constantExpr.type instanceof Type.TypeNull) + pushNullConstant(constantExpr.type); + else throw new CompilerException("Invalid constant type"); return false; } @@ -476,6 +537,10 @@ private void pushConstant(long value, Type type) { pushOperand(new Operand.ConstantOperand(value, type)); } + private void pushNullConstant(Type type) { + pushOperand(new Operand.NullConstantOperand(type)); + } + private Operand.TempRegisterOperand createTemp(Type type) { var tempRegister = new Operand.TempRegisterOperand(virtualStack.size()+maxLocalReg, type); pushOperand(tempRegister); diff --git a/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java b/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java index 5c0b365..37eaea0 100644 --- a/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java +++ b/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java @@ -16,7 +16,7 @@ private void compile(TypeDictionary typeDictionary) { for (Symbol symbol: typeDictionary.getLocalSymbols()) { if (symbol instanceof Symbol.FunctionTypeSymbol functionSymbol) { Type.TypeFunction functionType = (Type.TypeFunction) functionSymbol.type; - functionType.code = new CompiledFunction(functionSymbol); + functionType.code = new CompiledFunction(functionSymbol, typeDictionary); } } } diff --git a/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java b/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java index 9cb9a70..54188da 100644 --- a/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java +++ b/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java @@ -18,6 +18,16 @@ public String toString() { } } + public static class NullConstantOperand extends Operand { + public NullConstantOperand(Type type) { + this.type = type; + } + @Override + public String toString() { + return "null"; + } + } + public static abstract class RegisterOperand extends Operand { public final int regnum; public RegisterOperand(int regnum) { diff --git a/registervm/src/main/java/com/compilerprogramming/ezlang/interpreter/Interpreter.java b/registervm/src/main/java/com/compilerprogramming/ezlang/interpreter/Interpreter.java index 7be8ef9..5ceaeaa 100644 --- a/registervm/src/main/java/com/compilerprogramming/ezlang/interpreter/Interpreter.java +++ b/registervm/src/main/java/com/compilerprogramming/ezlang/interpreter/Interpreter.java @@ -48,6 +48,9 @@ public Value interpret(ExecutionStack execStack, Frame frame) { if (retInst.value() instanceof Operand.ConstantOperand constantOperand) { execStack.stack[base] = new Value.IntegerValue(constantOperand.value); } + else if (retInst.value() instanceof Operand.NullConstantOperand) { + execStack.stack[base] = new Value.NullValue(); + } else if (retInst.value() instanceof Operand.RegisterOperand registerOperand) { execStack.stack[base] = execStack.stack[base+registerOperand.frameSlot()]; } @@ -63,6 +66,9 @@ else if (retInst.value() instanceof Operand.RegisterOperand registerOperand) { else if (moveInst.from() instanceof Operand.ConstantOperand constantOperand) { execStack.stack[base + toReg.frameSlot()] = new Value.IntegerValue(constantOperand.value); } + else if (moveInst.from() instanceof Operand.NullConstantOperand) { + execStack.stack[base + toReg.frameSlot()] = new Value.NullValue(); + } else throw new IllegalStateException(); } else throw new IllegalStateException(); @@ -107,6 +113,9 @@ else if (cbrInst.condition() instanceof Operand.ConstantOperand constantOperand) else if (arg instanceof Operand.ConstantOperand constantOperand) { execStack.stack[base + reg] = new Value.IntegerValue(constantOperand.value); } + else if (arg instanceof Operand.NullConstantOperand) { + execStack.stack[base + reg] = new Value.NullValue(); + } reg += 1; } // Call function @@ -135,31 +144,60 @@ else if (arg instanceof Operand.ConstantOperand constantOperand) { case Instruction.Binary binaryInst -> { long x, y; long value = 0; - if (binaryInst.left() instanceof Operand.ConstantOperand constant) - x = constant.value; - else if (binaryInst.left() instanceof Operand.RegisterOperand registerOperand) - x = ((Value.IntegerValue) execStack.stack[base + registerOperand.frameSlot()]).value; - else throw new IllegalStateException(); - if (binaryInst.right() instanceof Operand.ConstantOperand constant) - y = constant.value; - else if (binaryInst.right() instanceof Operand.RegisterOperand registerOperand) - y = ((Value.IntegerValue) execStack.stack[base + registerOperand.frameSlot()]).value; - else throw new IllegalStateException(); - switch (binaryInst.binOp) { - case "+": value = x + y; break; - case "-": value = x - y; break; - case "*": value = x * y; break; - case "/": value = x / y; break; - case "%": value = x % y; break; - case "==": value = x == y ? 1 : 0; break; - case "!=": value = x != y ? 1 : 0; break; - case "<": value = x < y ? 1: 0; break; - case ">": value = x > y ? 1 : 0; break; - case "<=": value = x <= y ? 1 : 0; break; - case ">=": value = x <= y ? 1 : 0; break; - default: throw new IllegalStateException(); + boolean intOp = true; + if (binaryInst.binOp.equals("==") || binaryInst.binOp.equals("!=")) { + Operand.RegisterOperand nonNullLitOperand = null; + if (binaryInst.left() instanceof Operand.NullConstantOperand) { + nonNullLitOperand = (Operand.RegisterOperand)binaryInst.right(); + } + else if (binaryInst.right() instanceof Operand.NullConstantOperand) { + nonNullLitOperand = (Operand.RegisterOperand)binaryInst.left(); + } + if (nonNullLitOperand != null) { + intOp = false; + Value otherValue = execStack.stack[base + nonNullLitOperand.frameSlot()]; + switch (binaryInst.binOp) { + case "==": { + value = otherValue instanceof Value.NullValue ? 1 : 0; + break; + } + case "!=": { + value = otherValue instanceof Value.NullValue ? 0 : 1; + break; + } + default: + throw new IllegalStateException(); + } + execStack.stack[base + binaryInst.result().frameSlot()] = new Value.IntegerValue(value); + } + } + if (intOp) { + if (binaryInst.left() instanceof Operand.ConstantOperand constant) + x = constant.value; + else if (binaryInst.left() instanceof Operand.RegisterOperand registerOperand) + x = ((Value.IntegerValue) execStack.stack[base + registerOperand.frameSlot()]).value; + else throw new IllegalStateException(); + if (binaryInst.right() instanceof Operand.ConstantOperand constant) + y = constant.value; + else if (binaryInst.right() instanceof Operand.RegisterOperand registerOperand) + y = ((Value.IntegerValue) execStack.stack[base + registerOperand.frameSlot()]).value; + else throw new IllegalStateException(); + switch (binaryInst.binOp) { + case "+": value = x + y; break; + case "-": value = x - y; break; + case "*": value = x * y; break; + case "/": value = x / y; break; + case "%": value = x % y; break; + case "==": value = x == y ? 1 : 0; break; + case "!=": value = x != y ? 1 : 0; break; + case "<": value = x < y ? 1: 0; break; + case ">": value = x > y ? 1 : 0; break; + case "<=": value = x <= y ? 1 : 0; break; + case ">=": value = x <= y ? 1 : 0; break; + default: throw new IllegalStateException(); + } + execStack.stack[base + binaryInst.result().frameSlot()] = new Value.IntegerValue(value); } - execStack.stack[base + binaryInst.result().frameSlot()] = new Value.IntegerValue(value); } case Instruction.NewArray newArrayInst -> { execStack.stack[base + newArrayInst.destOperand().frameSlot()] = new Value.ArrayValue(newArrayInst.type); @@ -172,6 +210,9 @@ else if (binaryInst.right() instanceof Operand.RegisterOperand registerOperand) if (arrayAppendInst.value() instanceof Operand.ConstantOperand constant) { arrayValue.values.add(new Value.IntegerValue(constant.value)); } + else if (arrayAppendInst.value() instanceof Operand.NullConstantOperand) { + arrayValue.values.add(new Value.NullValue()); + } else if (arrayAppendInst.value() instanceof Operand.RegisterOperand registerOperand) { arrayValue.values.add(execStack.stack[base + registerOperand.frameSlot()]); } @@ -193,6 +234,9 @@ else if (arrayStoreInst.indexOperand() instanceof Operand.RegisterOperand regist if (arrayStoreInst.sourceOperand() instanceof Operand.ConstantOperand constantOperand) { value = new Value.IntegerValue(constantOperand.value); } + else if (arrayStoreInst.sourceOperand() instanceof Operand.NullConstantOperand) { + value = new Value.NullValue(); + } else if (arrayStoreInst.sourceOperand() instanceof Operand.RegisterOperand registerOperand) { value = execStack.stack[base + registerOperand.frameSlot()]; } @@ -221,6 +265,9 @@ else if (arrayLoadInst.indexOperand() instanceof Operand.RegisterOperand registe if (setFieldInst.sourceOperand() instanceof Operand.ConstantOperand constant) { value = new Value.IntegerValue(constant.value); } + else if (setFieldInst.sourceOperand() instanceof Operand.NullConstantOperand) { + value = new Value.NullValue(); + } else if (setFieldInst.sourceOperand() instanceof Operand.RegisterOperand registerOperand) { value = execStack.stack[base + registerOperand.frameSlot()]; } diff --git a/registervm/src/main/java/com/compilerprogramming/ezlang/interpreter/Value.java b/registervm/src/main/java/com/compilerprogramming/ezlang/interpreter/Value.java index e03fb80..7f914a6 100644 --- a/registervm/src/main/java/com/compilerprogramming/ezlang/interpreter/Value.java +++ b/registervm/src/main/java/com/compilerprogramming/ezlang/interpreter/Value.java @@ -11,6 +11,9 @@ public IntegerValue(long value) { } public final long value; } + static public class NullValue extends Value { + public NullValue() {} + } static public class ArrayValue extends Value { public final Type.TypeArray arrayType; public final ArrayList values; diff --git a/registervm/src/test/java/com/compilerprogramming/ezlang/compiler/TestCompiler.java b/registervm/src/test/java/com/compilerprogramming/ezlang/compiler/TestCompiler.java index 0b0d91f..2deb384 100644 --- a/registervm/src/test/java/com/compilerprogramming/ezlang/compiler/TestCompiler.java +++ b/registervm/src/test/java/com/compilerprogramming/ezlang/compiler/TestCompiler.java @@ -564,4 +564,20 @@ public void testFunction28() { L1: """, result); } + + @Test + public void testFunction29() { + String src = """ + struct Foo { var bar: Int } + func foo()->Foo? { return null; } + """; + String result = compileSrc(src); + Assert.assertEquals(""" +L0: + ret null + goto L1 +L1: +""", result); + } + } diff --git a/registervm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java b/registervm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java index 4913352..c99ce22 100644 --- a/registervm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java +++ b/registervm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java @@ -102,4 +102,78 @@ func bar()->Int { return foo().field } Assert.assertTrue(value instanceof Value.IntegerValue integerValue && integerValue.value == 42); } + + @Test + public void testFunction100() { + String src = """ + struct Test + { + var field: Int + } + func foo()->Test? + { + return null; + } + + """; + var value = compileAndRun(src, "foo"); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.NullValue); + } + + @Test + public void testFunction101() { + String src = """ + func foo()->Int + { + return null == null; + } + + """; + var value = compileAndRun(src, "foo"); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + + @Test + public void testFunction102() { + String src = """ + struct Foo + { + var next: Foo? + } + func foo()->Int + { + var f = new Foo{ next = null } + return null == f.next + } + + """; + var value = compileAndRun(src, "foo"); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + + @Test + public void testFunction103() { + String src = """ + struct Foo + { + var i: Int + } + func foo()->Int + { + var f = new [Foo?] { new Foo{i = 1}, null } + return null == f[1] && 1 == f[0].i + } + + """; + var value = compileAndRun(src, "foo"); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + } diff --git a/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java b/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java index d5504f4..f22c855 100644 --- a/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java +++ b/semantic/src/main/java/com/compilerprogramming/ezlang/semantic/SemaAssignTypes.java @@ -77,6 +77,8 @@ public ASTVisitor visit(AST.BinaryExpr binaryExpr, boolean enter) { else if (((binaryExpr.expr1.type instanceof Type.TypeNull && binaryExpr.expr2.type instanceof Type.TypeNullable) || (binaryExpr.expr1.type instanceof Type.TypeNullable && + binaryExpr.expr2.type instanceof Type.TypeNull) || + (binaryExpr.expr1.type instanceof Type.TypeNull && binaryExpr.expr2.type instanceof Type.TypeNull)) && (binaryExpr.op.str.equals("==") || binaryExpr.op.str.equals("!="))) { binaryExpr.type = typeDictionary.INT; From 7e3632b241d4aaeb4f179cf0bb25206be9f02fab Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Tue, 18 Feb 2025 11:51:51 +0000 Subject: [PATCH 3/6] Ensure SSA tests check output --- .../ezlang/compiler/TestSSATransform.java | 1294 ++++++++++++++++- 1 file changed, 1250 insertions(+), 44 deletions(-) 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 d4d1026..703ca2a 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java @@ -1077,7 +1077,39 @@ func foo()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func foo +Before SSA +========== +L0: + a = 5 + b = 10 + %t3 = a+b + c = %t3 + ret c + goto L1 +L1: +After SSA +========= +L0: + a_0 = 5 + b_0 = 10 + %t3_0 = a_0+b_0 + c_0 = %t3_0 + ret c_0 + goto L1 +L1: +After exiting SSA +================= +L0: + a_0 = 5 + b_0 = 10 + %t3_0 = a_0+b_0 + c_0 = %t3_0 + ret c_0 + goto L1 +L1: +""", result); } @Test @@ -1090,7 +1122,36 @@ func foo()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func foo +Before SSA +========== +L0: + a = 5 + %t1 = a+1 + a = %t1 + ret a + goto L1 +L1: +After SSA +========= +L0: + a_0 = 5 + %t1_0 = a_0+1 + a_1 = %t1_0 + ret a_1 + goto L1 +L1: +After exiting SSA +================= +L0: + a_0 = 5 + %t1_0 = a_0+1 + a_1 = %t1_0 + ret a_1 + goto L1 +L1: +""", result); } @Test @@ -1107,7 +1168,60 @@ func foo()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func foo +Before SSA +========== +L0: + a = 5 + %t1 = a>3 + if %t1 goto L2 else goto L3 +L2: + a = 10 + goto L4 +L4: + ret a + goto L1 +L1: +L3: + a = 20 + goto L4 +After SSA +========= +L0: + a_0 = 5 + %t1_0 = a_0>3 + if %t1_0 goto L2 else goto L3 +L2: + a_2 = 10 + goto L4 +L4: + a_3 = phi(a_2, a_1) + ret a_3 + goto L1 +L1: +L3: + a_1 = 20 + goto L4 +After exiting SSA +================= +L0: + a_0 = 5 + %t1_0 = a_0>3 + if %t1_0 goto L2 else goto L3 +L2: + a_2 = 10 + a_3 = a_2 + goto L4 +L4: + ret a_3 + goto L1 +L1: +L3: + a_1 = 20 + a_3 = a_1 + goto L4 +""", result); } @Test @@ -1122,7 +1236,60 @@ func foo()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func foo +Before SSA +========== +L0: + a = 0 + goto L2 +L2: + %t1 = a<5 + if %t1 goto L3 else goto L4 +L3: + %t2 = a+1 + a = %t2 + goto L2 +L4: + ret a + goto L1 +L1: +After SSA +========= +L0: + a_0 = 0 + goto L2 +L2: + a_1 = phi(a_0, a_2) + %t1_0 = a_1<5 + if %t1_0 goto L3 else goto L4 +L3: + %t2_0 = a_1+1 + a_2 = %t2_0 + goto L2 +L4: + ret a_1 + goto L1 +L1: +After exiting SSA +================= +L0: + a_0 = 0 + a_1 = a_0 + goto L2 +L2: + %t1_0 = a_1<5 + if %t1_0 goto L3 else goto L4 +L3: + %t2_0 = a_1+1 + a_2 = %t2_0 + a_1 = a_2 + goto L2 +L4: + ret a_1 + goto L1 +L1: +""", result); } @Test @@ -1144,7 +1311,90 @@ func foo()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func foo +Before SSA +========== +L0: + a = 0 + b = 10 + %t2 = b>5 + if %t2 goto L2 else goto L3 +L2: + %t3 = a<5 + if %t3 goto L5 else goto L6 +L5: + a = 5 + goto L7 +L7: + goto L4 +L4: + ret a + goto L1 +L1: +L6: + a = 15 + goto L7 +L3: + a = 20 + goto L4 +After SSA +========= +L0: + a_0 = 0 + b_0 = 10 + %t2_0 = b_0>5 + if %t2_0 goto L2 else goto L3 +L2: + %t3_0 = a_0<5 + if %t3_0 goto L5 else goto L6 +L5: + a_3 = 5 + goto L7 +L7: + a_4 = phi(a_3, a_2) + goto L4 +L4: + a_5 = phi(a_4, a_1) + ret a_5 + goto L1 +L1: +L6: + a_2 = 15 + goto L7 +L3: + a_1 = 20 + goto L4 +After exiting SSA +================= +L0: + a_0 = 0 + b_0 = 10 + %t2_0 = b_0>5 + if %t2_0 goto L2 else goto L3 +L2: + %t3_0 = a_0<5 + if %t3_0 goto L5 else goto L6 +L5: + a_3 = 5 + a_4 = a_3 + goto L7 +L7: + a_5 = a_4 + goto L4 +L4: + ret a_5 + goto L1 +L1: +L6: + a_2 = 15 + a_4 = a_2 + goto L7 +L3: + a_1 = 20 + a_5 = a_1 + goto L4 +""", result); } @Test @@ -1158,7 +1408,48 @@ func foo()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func foo +Before SSA +========== +L0: + %t2 = New([Int]) + %t2.append(1) + %t2.append(2) + arr = %t2 + arr[0] = 10 + %t3 = arr[0] + x = %t3 + ret x + goto L1 +L1: +After SSA +========= +L0: + %t2_0 = New([Int]) + %t2_0.append(1) + %t2_0.append(2) + arr_0 = %t2_0 + arr_0[0] = 10 + %t3_0 = arr_0[0] + x_0 = %t3_0 + ret x_0 + goto L1 +L1: +After exiting SSA +================= +L0: + %t2_0 = New([Int]) + %t2_0.append(1) + %t2_0.append(2) + arr_0 = %t2_0 + arr_0[0] = 10 + %t3_0 = arr_0[0] + x_0 = %t3_0 + ret x_0 + goto L1 +L1: +""", result); } @Test @@ -1175,7 +1466,73 @@ func main()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func add +Before SSA +========== +L0: + arg x + arg y + %t2 = x+y + ret %t2 + goto L1 +L1: +After SSA +========= +L0: + arg x_0 + arg y_0 + %t2_0 = x_0+y_0 + ret %t2_0 + goto L1 +L1: +After exiting SSA +================= +L0: + arg x_0 + arg y_0 + %t2_0 = x_0+y_0 + ret %t2_0 + goto L1 +L1: +func main +Before SSA +========== +L0: + a = 5 + b = 10 + %t3 = a + %t4 = b + %t5 = call add params %t3, %t4 + c = %t5 + ret c + goto L1 +L1: +After SSA +========= +L0: + a_0 = 5 + b_0 = 10 + %t3_0 = a_0 + %t4_0 = b_0 + %t5_0 = call add params %t3_0, %t4_0 + c_0 = %t5_0 + ret c_0 + goto L1 +L1: +After exiting SSA +================= +L0: + a_0 = 5 + b_0 = 10 + %t3_0 = a_0 + %t4_0 = b_0 + %t5_0 = call add params %t3_0, %t4_0 + c_0 = %t5_0 + ret c_0 + goto L1 +L1: +""", result); } @Test @@ -1194,39 +1551,250 @@ func main()->Int { } """; String result = compileSrc(src); - System.out.println(result); - } - - @Test - public void testSSA9() { - String src = """ - func main()->Int { - var a = 0 - var b = 0 - var i = 0 - var j = 0 - while (i < 3) { - j = 0 - while (j < 2) { - a = a + 1 - i = j + 1 - } - b = b + 1; - i = i + 1 - } - return a + b - } -"""; - String result = compileSrc(src); - System.out.println(result); - } - - @Test - public void testSSA10() { - String src = """ - func main()->Int { - var sum = 0 - var i = 0 + Assert.assertEquals(""" +func main +Before SSA +========== +L0: + a = 0 + b = 1 + goto L2 +L2: + %t2 = a<10 + if %t2 goto L3 else goto L4 +L3: + %t3 = a+2 + a = %t3 + goto L2 +L4: + goto L5 +L5: + %t4 = b<20 + if %t4 goto L6 else goto L7 +L6: + %t5 = b+3 + b = %t5 + goto L5 +L7: + %t6 = a+b + ret %t6 + goto L1 +L1: +After SSA +========= +L0: + a_0 = 0 + b_0 = 1 + goto L2 +L2: + a_1 = phi(a_0, a_2) + %t2_0 = a_1<10 + if %t2_0 goto L3 else goto L4 +L3: + %t3_0 = a_1+2 + a_2 = %t3_0 + goto L2 +L4: + goto L5 +L5: + b_1 = phi(b_0, b_2) + %t4_0 = b_1<20 + if %t4_0 goto L6 else goto L7 +L6: + %t5_0 = b_1+3 + b_2 = %t5_0 + goto L5 +L7: + %t6_0 = a_1+b_1 + ret %t6_0 + goto L1 +L1: +After exiting SSA +================= +L0: + a_0 = 0 + b_0 = 1 + a_1 = a_0 + goto L2 +L2: + %t2_0 = a_1<10 + if %t2_0 goto L3 else goto L4 +L3: + %t3_0 = a_1+2 + a_2 = %t3_0 + a_1 = a_2 + goto L2 +L4: + b_1 = b_0 + goto L5 +L5: + %t4_0 = b_1<20 + if %t4_0 goto L6 else goto L7 +L6: + %t5_0 = b_1+3 + b_2 = %t5_0 + b_1 = b_2 + goto L5 +L7: + %t6_0 = a_1+b_1 + ret %t6_0 + goto L1 +L1: +""", result); + } + + @Test + public void testSSA9() { + String src = """ + func main()->Int { + var a = 0 + var b = 0 + var i = 0 + var j = 0 + while (i < 3) { + j = 0 + while (j < 2) { + a = a + 1 + i = j + 1 + } + b = b + 1; + i = i + 1 + } + return a + b + } +"""; + String result = compileSrc(src); + Assert.assertEquals(""" +func main +Before SSA +========== +L0: + a = 0 + b = 0 + i = 0 + j = 0 + goto L2 +L2: + %t4 = i<3 + if %t4 goto L3 else goto L4 +L3: + j = 0 + goto L5 +L5: + %t5 = j<2 + if %t5 goto L6 else goto L7 +L6: + %t6 = a+1 + a = %t6 + %t7 = j+1 + i = %t7 + goto L5 +L7: + %t8 = b+1 + b = %t8 + %t9 = i+1 + i = %t9 + goto L2 +L4: + %t10 = a+b + ret %t10 + goto L1 +L1: +After SSA +========= +L0: + a_0 = 0 + b_0 = 0 + i_0 = 0 + j_0 = 0 + goto L2 +L2: + j_1 = phi(j_0, j_2) + i_1 = phi(i_0, i_3) + b_1 = phi(b_0, b_2) + a_1 = phi(a_0, a_2) + %t4_0 = i_1<3 + if %t4_0 goto L3 else goto L4 +L3: + j_2 = 0 + goto L5 +L5: + i_2 = phi(i_1, i_4) + a_2 = phi(a_1, a_3) + %t5_0 = j_2<2 + if %t5_0 goto L6 else goto L7 +L6: + %t6_0 = a_2+1 + a_3 = %t6_0 + %t7_0 = j_2+1 + i_4 = %t7_0 + goto L5 +L7: + %t8_0 = b_1+1 + b_2 = %t8_0 + %t9_0 = i_2+1 + i_3 = %t9_0 + goto L2 +L4: + %t10_0 = a_1+b_1 + ret %t10_0 + goto L1 +L1: +After exiting SSA +================= +L0: + a_0 = 0 + b_0 = 0 + i_0 = 0 + j_0 = 0 + j_1 = j_0 + i_1 = i_0 + b_1 = b_0 + a_1 = a_0 + goto L2 +L2: + %t4_0 = i_1<3 + if %t4_0 goto L3 else goto L4 +L3: + j_2 = 0 + i_2 = i_1 + a_2 = a_1 + goto L5 +L5: + %t5_0 = j_2<2 + if %t5_0 goto L6 else goto L7 +L6: + %t6_0 = a_2+1 + a_3 = %t6_0 + %t7_0 = j_2+1 + i_4 = %t7_0 + i_2 = i_4 + a_2 = a_3 + goto L5 +L7: + %t8_0 = b_1+1 + b_2 = %t8_0 + %t9_0 = i_2+1 + i_3 = %t9_0 + j_1 = j_2 + i_1 = i_3 + b_1 = b_2 + a_1 = a_2 + goto L2 +L4: + %t10_0 = a_1+b_1 + ret %t10_0 + goto L1 +L1: +""", result); + } + + @Test + public void testSSA10() { + String src = """ + func main()->Int { + var sum = 0 + var i = 0 var j = 0 while (i < 5) { j = 0 @@ -1241,7 +1809,135 @@ func main()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func main +Before SSA +========== +L0: + sum = 0 + i = 0 + j = 0 + goto L2 +L2: + %t3 = i<5 + if %t3 goto L3 else goto L4 +L3: + j = 0 + goto L5 +L5: + %t4 = j<5 + if %t4 goto L6 else goto L7 +L6: + %t5 = j%2 + %t6 = %t5==0 + if %t6 goto L8 else goto L9 +L8: + %t7 = sum+j + sum = %t7 + goto L9 +L9: + %t8 = j+1 + j = %t8 + goto L5 +L7: + %t9 = i+1 + i = %t9 + goto L2 +L4: + ret sum + goto L1 +L1: +After SSA +========= +L0: + sum_0 = 0 + i_0 = 0 + j_0 = 0 + goto L2 +L2: + j_1 = phi(j_0, j_3) + i_1 = phi(i_0, i_2) + sum_1 = phi(sum_0, sum_2) + %t3_0 = i_1<5 + if %t3_0 goto L3 else goto L4 +L3: + j_2 = 0 + goto L5 +L5: + j_3 = phi(j_2, j_4) + sum_2 = phi(sum_1, sum_4) + %t4_0 = j_3<5 + if %t4_0 goto L6 else goto L7 +L6: + %t5_0 = j_3%2 + %t6_0 = %t5_0==0 + if %t6_0 goto L8 else goto L9 +L8: + %t7_0 = sum_2+j_3 + sum_3 = %t7_0 + goto L9 +L9: + sum_4 = phi(sum_2, sum_3) + %t8_0 = j_3+1 + j_4 = %t8_0 + goto L5 +L7: + %t9_0 = i_1+1 + i_2 = %t9_0 + goto L2 +L4: + ret sum_1 + goto L1 +L1: +After exiting SSA +================= +L0: + sum_0 = 0 + i_0 = 0 + j_0 = 0 + j_1 = j_0 + i_1 = i_0 + sum_1 = sum_0 + goto L2 +L2: + %t3_0 = i_1<5 + if %t3_0 goto L3 else goto L4 +L3: + j_2 = 0 + j_3 = j_2 + sum_2 = sum_1 + goto L5 +L5: + %t4_0 = j_3<5 + if %t4_0 goto L6 else goto L7 +L6: + %t5_0 = j_3%2 + %t6_0 = %t5_0==0 + sum_4 = sum_2 + if %t6_0 goto L8 else goto L9 +L8: + %t7_0 = sum_2+j_3 + sum_3 = %t7_0 + sum_4 = sum_3 + goto L9 +L9: + %t8_0 = j_3+1 + j_4 = %t8_0 + j_3 = j_4 + sum_2 = sum_4 + goto L5 +L7: + %t9_0 = i_1+1 + i_2 = %t9_0 + j_1 = j_3 + i_1 = i_2 + sum_1 = sum_2 + goto L2 +L4: + ret sum_1 + goto L1 +L1: +""", result); } @Test @@ -1266,7 +1962,165 @@ else if (i > j) } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func main +Before SSA +========== +L0: + a = 0 + i = 0 + j = 0 + goto L2 +L2: + %t3 = i<3 + if %t3 goto L3 else goto L4 +L3: + j = 0 + goto L5 +L5: + %t4 = j<3 + if %t4 goto L6 else goto L7 +L6: + %t5 = i==j + if %t5 goto L8 else goto L9 +L8: + %t6 = a+i + %t7 = %t6+j + a = %t7 + goto L10 +L10: + %t10 = j+1 + j = %t10 + goto L5 +L9: + %t8 = i>j + if %t8 goto L11 else goto L12 +L11: + %t9 = a-1 + a = %t9 + goto L12 +L12: + goto L10 +L7: + %t11 = i+1 + i = %t11 + goto L2 +L4: + ret a + goto L1 +L1: +After SSA +========= +L0: + a_0 = 0 + i_0 = 0 + j_0 = 0 + goto L2 +L2: + j_1 = phi(j_0, j_3) + i_1 = phi(i_0, i_2) + a_1 = phi(a_0, a_2) + %t3_0 = i_1<3 + if %t3_0 goto L3 else goto L4 +L3: + j_2 = 0 + goto L5 +L5: + j_3 = phi(j_2, j_4) + a_2 = phi(a_1, a_6) + %t4_0 = j_3<3 + if %t4_0 goto L6 else goto L7 +L6: + %t5_0 = i_1==j_3 + if %t5_0 goto L8 else goto L9 +L8: + %t6_0 = a_4+i_1 + %t7_0 = %t6_0+j_3 + a_5 = %t7_0 + goto L10 +L10: + a_6 = phi(a_5, a_4) + %t10_0 = j_3+1 + j_4 = %t10_0 + goto L5 +L9: + %t8_0 = i_1>j_3 + if %t8_0 goto L11 else goto L12 +L11: + %t9_0 = a_2-1 + a_3 = %t9_0 + goto L12 +L12: + a_4 = phi(a_2, a_3) + goto L10 +L7: + %t11_0 = i_1+1 + i_2 = %t11_0 + goto L2 +L4: + ret a_1 + goto L1 +L1: +After exiting SSA +================= +L0: + a_0 = 0 + i_0 = 0 + j_0 = 0 + j_1 = j_0 + i_1 = i_0 + a_1 = a_0 + goto L2 +L2: + %t3_0 = i_1<3 + if %t3_0 goto L3 else goto L4 +L3: + j_2 = 0 + j_3 = j_2 + a_2 = a_1 + goto L5 +L5: + %t4_0 = j_3<3 + if %t4_0 goto L6 else goto L7 +L6: + %t5_0 = i_1==j_3 + if %t5_0 goto L8 else goto L9 +L8: + %t6_0 = a_4+i_1 + %t7_0 = %t6_0+j_3 + a_5 = %t7_0 + a_6 = a_5 + goto L10 +L10: + %t10_0 = j_3+1 + j_4 = %t10_0 + j_3 = j_4 + a_2 = a_6 + goto L5 +L9: + %t8_0 = i_1>j_3 + a_4 = a_2 + if %t8_0 goto L11 else goto L12 +L11: + %t9_0 = a_2-1 + a_3 = %t9_0 + a_4 = a_3 + goto L12 +L12: + a_6 = a_4 + goto L10 +L7: + %t11_0 = i_1+1 + i_2 = %t11_0 + j_1 = j_3 + i_1 = i_2 + a_1 = a_2 + goto L2 +L4: + ret a_1 + goto L1 +L1: +""", result); } @Test @@ -1294,7 +2148,157 @@ func main()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func main +Before SSA +========== +L0: + count = 0 + i = 0 + j = 0 + goto L2 +L2: + %t3 = i<5 + if %t3 goto L3 else goto L4 +L3: + j = 0 + goto L5 +L5: + %t4 = j<5 + if %t4 goto L6 else goto L7 +L6: + %t5 = i+j + %t6 = %t5>5 + if %t6 goto L8 else goto L9 +L8: + goto L7 +L7: + %t11 = i+1 + i = %t11 + goto L2 +L9: + %t7 = i==j + if %t7 goto L10 else goto L11 +L10: + %t8 = j+1 + j = %t8 + goto L5 +L11: + %t9 = count+1 + count = %t9 + %t10 = j+1 + j = %t10 + goto L5 +L4: + ret count + goto L1 +L1: +After SSA +========= +L0: + count_0 = 0 + i_0 = 0 + j_0 = 0 + goto L2 +L2: + j_1 = phi(j_0, j_3) + i_1 = phi(i_0, i_2) + count_1 = phi(count_0, count_2) + %t3_0 = i_1<5 + if %t3_0 goto L3 else goto L4 +L3: + j_2 = 0 + goto L5 +L5: + j_3 = phi(j_2, j_5, j_4) + count_2 = phi(count_1, count_2, count_3) + %t4_0 = j_3<5 + if %t4_0 goto L6 else goto L7 +L6: + %t5_0 = i_1+j_3 + %t6_0 = %t5_0>5 + if %t6_0 goto L8 else goto L9 +L8: + goto L7 +L7: + %t11_0 = i_1+1 + i_2 = %t11_0 + goto L2 +L9: + %t7_0 = i_1==j_3 + if %t7_0 goto L10 else goto L11 +L10: + %t8_0 = j_3+1 + j_5 = %t8_0 + goto L5 +L11: + %t9_0 = count_2+1 + count_3 = %t9_0 + %t10_0 = j_3+1 + j_4 = %t10_0 + goto L5 +L4: + ret count_1 + goto L1 +L1: +After exiting SSA +================= +L0: + count_0 = 0 + i_0 = 0 + j_0 = 0 + j_1 = j_0 + i_1 = i_0 + count_1 = count_0 + goto L2 +L2: + %t3_0 = i_1<5 + if %t3_0 goto L3 else goto L4 +L3: + j_2 = 0 + j_3 = j_2 + count_2 = count_1 + goto L5 +L5: + count_2_35 = count_2 + %t4_0 = j_3<5 + if %t4_0 goto L6 else goto L7 +L6: + %t5_0 = i_1+j_3 + %t6_0 = %t5_0>5 + if %t6_0 goto L8 else goto L9 +L8: + goto L7 +L7: + %t11_0 = i_1+1 + i_2 = %t11_0 + j_1 = j_3 + i_1 = i_2 + count_1 = count_2 + goto L2 +L9: + %t7_0 = i_1==j_3 + if %t7_0 goto L10 else goto L11 +L10: + %t8_0 = j_3+1 + j_5 = %t8_0 + j_3 = j_5 + count_2_34 = count_2 + count_2 = count_2_34 + goto L5 +L11: + %t9_0 = count_2+1 + count_3 = %t9_0 + %t10_0 = j_3+1 + j_4 = %t10_0 + j_3 = j_4 + count_2 = count_3 + goto L5 +L4: + ret count_1 + goto L1 +L1: +""", result); } @Test @@ -1319,7 +2323,209 @@ func main()->Int { } """; String result = compileSrc(src); - System.out.println(result); + Assert.assertEquals(""" +func main +Before SSA +========== +L0: + outerSum = 0 + innerSum = 0 + i = 0 + j = 0 + goto L2 +L2: + %t4 = i<4 + if %t4 goto L3 else goto L4 +L3: + j = 0 + goto L5 +L5: + %t5 = j<4 + if %t5 goto L6 else goto L7 +L6: + %t6 = i+j + %t7 = %t6%2 + %t8 = %t7==0 + if %t8 goto L8 else goto L9 +L8: + %t9 = innerSum+j + innerSum = %t9 + goto L9 +L9: + %t10 = j+1 + j = %t10 + goto L5 +L7: + %t11 = outerSum+innerSum + outerSum = %t11 + %t12 = i+1 + i = %t12 + goto L2 +L4: + ret outerSum + goto L1 +L1: +After SSA +========= +L0: + outerSum_0 = 0 + innerSum_0 = 0 + i_0 = 0 + j_0 = 0 + goto L2 +L2: + j_1 = phi(j_0, j_3) + i_1 = phi(i_0, i_2) + innerSum_1 = phi(innerSum_0, innerSum_2) + outerSum_1 = phi(outerSum_0, outerSum_2) + %t4_0 = i_1<4 + if %t4_0 goto L3 else goto L4 +L3: + j_2 = 0 + goto L5 +L5: + j_3 = phi(j_2, j_4) + innerSum_2 = phi(innerSum_1, innerSum_4) + %t5_0 = j_3<4 + if %t5_0 goto L6 else goto L7 +L6: + %t6_0 = i_1+j_3 + %t7_0 = %t6_0%2 + %t8_0 = %t7_0==0 + if %t8_0 goto L8 else goto L9 +L8: + %t9_0 = innerSum_2+j_3 + innerSum_3 = %t9_0 + goto L9 +L9: + innerSum_4 = phi(innerSum_2, innerSum_3) + %t10_0 = j_3+1 + j_4 = %t10_0 + goto L5 +L7: + %t11_0 = outerSum_1+innerSum_2 + outerSum_2 = %t11_0 + %t12_0 = i_1+1 + i_2 = %t12_0 + goto L2 +L4: + ret outerSum_1 + goto L1 +L1: +After exiting SSA +================= +L0: + outerSum_0 = 0 + innerSum_0 = 0 + i_0 = 0 + j_0 = 0 + j_1 = j_0 + i_1 = i_0 + innerSum_1 = innerSum_0 + outerSum_1 = outerSum_0 + goto L2 +L2: + %t4_0 = i_1<4 + if %t4_0 goto L3 else goto L4 +L3: + j_2 = 0 + j_3 = j_2 + innerSum_2 = innerSum_1 + goto L5 +L5: + %t5_0 = j_3<4 + if %t5_0 goto L6 else goto L7 +L6: + %t6_0 = i_1+j_3 + %t7_0 = %t6_0%2 + %t8_0 = %t7_0==0 + innerSum_4 = innerSum_2 + if %t8_0 goto L8 else goto L9 +L8: + %t9_0 = innerSum_2+j_3 + innerSum_3 = %t9_0 + innerSum_4 = innerSum_3 + goto L9 +L9: + %t10_0 = j_3+1 + j_4 = %t10_0 + j_3 = j_4 + innerSum_2 = innerSum_4 + goto L5 +L7: + %t11_0 = outerSum_1+innerSum_2 + outerSum_2 = %t11_0 + %t12_0 = i_1+1 + i_2 = %t12_0 + j_1 = j_3 + i_1 = i_2 + innerSum_1 = innerSum_2 + outerSum_1 = outerSum_2 + goto L2 +L4: + ret outerSum_1 + goto L1 +L1: +""", result); } + @Ignore + @Test + public void testSSA14() { + String src = """ + func foo()->Int + { + return 1 && 2 + } +"""; + String result = compileSrc(src); + Assert.assertEquals(""" +func foo +Before SSA +========== +L0: + if 1 goto L2 else goto L3 +L2: + %t0 = 2 + goto L4 +L4: + ret %t0 + goto L1 +L1: +L3: + %t0 = 0 + goto L4 +After SSA +========= +L0: + if 1 goto L2 else goto L3 +L2: + %t0_1 = 2 + goto L4 +L4: + %t0_2 = phi(%t0_1, %t0_0) + ret %t0_2 + goto L1 +L1: +L3: + %t0_0 = 0 + goto L4 +After exiting SSA +================= +L0: + if 1 goto L2 else goto L3 +L2: + %t0_1 = 2 + %t0_2 = %t0_1 + goto L4 +L4: + ret %t0_2 + goto L1 +L1: +L3: + %t0_0 = 0 + %t0_2 = %t0_0 + goto L4 +""", result); + } } \ No newline at end of file From cfd9b76d9fc376e77e943ba366515a5743f9f507 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Tue, 18 Feb 2025 11:19:39 +0000 Subject: [PATCH 4/6] Fix bug in EnterSSA caused by mutable operands shared across instructions, replacing the phi register cause other instructions to change inadvertantly. Fix bug in EnterSSA caused by mutable operands shared across instructions, replacing the phi register cause other instructions to change inadvertantly. Fix bug in EnterSSA caused by mutable operands shared across instructions, replacing the phi register cause other instructions to change inadvertantly. --- .../ezlang/compiler/CompiledFunction.java | 77 +++++++++- .../ezlang/compiler/Compiler.java | 2 +- .../ezlang/compiler/Instruction.java | 8 +- .../ezlang/compiler/Operand.java | 19 ++- .../compiler/TestInterferenceGraph.java | 6 +- .../ezlang/compiler/TestLiveness.java | 2 +- .../ezlang/compiler/TestSSATransform.java | 5 +- .../ezlang/interpreter/TestInterpreter.java | 143 ++++++++++++++++++ 8 files changed, 239 insertions(+), 23 deletions(-) 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 a686e8e..418db05 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java @@ -5,6 +5,7 @@ import com.compilerprogramming.ezlang.types.Scope; import com.compilerprogramming.ezlang.types.Symbol; import com.compilerprogramming.ezlang.types.Type; +import com.compilerprogramming.ezlang.types.TypeDictionary; import java.util.ArrayList; import java.util.BitSet; @@ -20,6 +21,7 @@ public class CompiledFunction { private BasicBlock currentContinueTarget; private Type.TypeFunction functionType; public final RegisterPool registerPool; + private final TypeDictionary typeDictionary; private int frameSlots; @@ -36,7 +38,7 @@ public class CompiledFunction { */ private List virtualStack = new ArrayList<>(); - public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol) { + public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol, TypeDictionary typeDictionary) { AST.FuncDecl funcDecl = (AST.FuncDecl) functionSymbol.functionDecl; this.functionType = (Type.TypeFunction) functionSymbol.type; this.registerPool = new RegisterPool(); @@ -46,13 +48,14 @@ public CompiledFunction(Symbol.FunctionTypeSymbol functionSymbol) { this.exit = createBlock(); this.currentBreakTarget = null; this.currentContinueTarget = null; + this.typeDictionary = typeDictionary; generateArgInstructions(funcDecl.scope); compileStatement(funcDecl.block); exitBlockIfNeeded(); this.frameSlots = registerPool.numRegisters(); } - public CompiledFunction(Type.TypeFunction functionType) { + public CompiledFunction(Type.TypeFunction functionType, TypeDictionary typeDictionary) { this.functionType = (Type.TypeFunction) functionType; this.registerPool = new RegisterPool(); this.BID = 0; @@ -60,6 +63,7 @@ public CompiledFunction(Type.TypeFunction functionType) { this.exit = createBlock(); this.currentBreakTarget = null; this.currentContinueTarget = null; + this.typeDictionary = typeDictionary; this.frameSlots = registerPool.numRegisters(); } @@ -437,8 +441,42 @@ private boolean compileSymbolExpr(AST.NameExpr symbolExpr) { return false; } + private boolean codeBoolean(AST.BinaryExpr binaryExpr) { + boolean isAnd = binaryExpr.op.str.equals("&&"); + BasicBlock l1 = createBlock(); + BasicBlock l2 = createBlock(); + BasicBlock l3 = createBlock(); + boolean indexed = compileExpr(binaryExpr.expr1); + if (indexed) + codeIndexedLoad(); + if (isAnd) { + code(new Instruction.ConditionalBranch(currentBlock, pop(), l1, l2)); + } else { + code(new Instruction.ConditionalBranch(currentBlock, pop(), l2, l1)); + } + startBlock(l1); + compileExpr(binaryExpr.expr2); + var temp = ensureTemp(); + jumpTo(l3); + startBlock(l2); + // Below we must write to the same temp + //code(new Instruction.Move(new Operand.ConstantOperand(isAnd ? 0 : 1, typeDictionary.INT), new Operand.TempRegisterOperand(temp.reg))); + code(new Instruction.Move(new Operand.ConstantOperand(isAnd ? 0 : 1, typeDictionary.INT), temp)); + jumpTo(l3); + startBlock(l3); + // leave temp on virtual stack +// var temp2 = (Operand.TempRegisterOperand) pop(); +// pushOperand(new Operand.TempRegisterOperand(temp2.reg)); + return false; + } + + private boolean compileBinaryExpr(AST.BinaryExpr binaryExpr) { - String opCode = null; + String opCode = binaryExpr.op.str; + if (opCode.equals("&&") || + opCode.equals("||")) { + return codeBoolean(binaryExpr); + } boolean indexed = compileExpr(binaryExpr.expr1); if (indexed) codeIndexedLoad(); @@ -511,6 +549,36 @@ private Operand.TempRegisterOperand createTemp(Type type) { return tempRegister; } + Type typeOfOperand(Operand operand) { + if (operand instanceof Operand.ConstantOperand constant) + return constant.type; +// else if (operand instanceof Operand.NullConstantOperand nullConstantOperand) +// return nullConstantOperand.type; + else if (operand instanceof Operand.RegisterOperand registerOperand) + return registerOperand.type; + else throw new CompilerException("Invalid operand"); + } + + private Operand.TempRegisterOperand createTempAndMove(Operand src) { + Type type = typeOfOperand(src); + var temp = createTemp(type); + code(new Instruction.Move(src, temp)); + return temp; + } + + private Operand.RegisterOperand ensureTemp() { + Operand top = top(); + if (top instanceof Operand.ConstantOperand + //|| top instanceof Operand.NullConstantOperand + || top instanceof Operand.LocalRegisterOperand) { + return createTempAndMove(pop()); + } else if (top instanceof Operand.IndexedOperand) { + return codeIndexedLoad(); + } else if (top instanceof Operand.TempRegisterOperand tempRegisterOperand) { + return tempRegisterOperand; + } else throw new CompilerException("Cannot convert to temporary register"); + } + private void pushLocal(Register reg) { pushOperand(new Operand.LocalRegisterOperand(reg)); } @@ -527,7 +595,7 @@ private Operand top() { return virtualStack.getLast(); } - private void codeIndexedLoad() { + private Operand.TempRegisterOperand codeIndexedLoad() { Operand indexed = pop(); var temp = createTemp(indexed.type); if (indexed instanceof Operand.LoadIndexedOperand loadIndexedOperand) { @@ -538,6 +606,7 @@ else if (indexed instanceof Operand.LoadFieldOperand loadFieldOperand) { } else code(new Instruction.Move(indexed, temp)); + return temp; } private void codeIndexedStore() { 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 df64e5c..4c24bb6 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); + var function = new CompiledFunction(functionSymbol, typeDictionary); 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 c4005a7..1651c1c 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java @@ -67,14 +67,14 @@ public List uses() { } public void replaceDef(Register newReg) { if (def == null) throw new IllegalStateException(); - def.replaceRegister(newReg); + def = def.copy(newReg); } public void replaceUses(Register[] newUses) { int j = 0; for (int i = 0; i < uses.length; i++) { Operand use = uses[i]; if (use != null && use instanceof Operand.RegisterOperand registerOperand) { - registerOperand.replaceRegister(newUses[j++]); + uses[i] = registerOperand.copy(newUses[j++]); } } } @@ -83,7 +83,7 @@ public boolean replaceUse(Register source, Register target) { for (int i = 0; i < uses.length; i++) { Operand operand = uses[i]; if (operand != null && operand instanceof Operand.RegisterOperand registerOperand && registerOperand.reg.id == source.id) { - registerOperand.replaceRegister(target); + uses[i] = registerOperand.copy(target); replaced = true; } } @@ -362,7 +362,7 @@ public Phi(Register value, List inputs) { } } public void replaceInput(int i, Register newReg) { - uses[i].replaceRegister(newReg); + uses[i] = new Operand.RegisterOperand(newReg); } /** * This will fail in input was replaced by a constant 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 e91531e..b4bde07 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java @@ -6,8 +6,6 @@ public class Operand { Type type; - public void replaceRegister(Register register) {} - public static class ConstantOperand extends Operand { public final long value; public ConstantOperand(long value, Type type) { @@ -21,17 +19,16 @@ public String toString() { } public static class RegisterOperand extends Operand { - Register reg; - public RegisterOperand(Register reg) { + final Register reg; + protected RegisterOperand(Register reg) { this.reg = reg; if (reg == null) throw new NullPointerException(); } public int frameSlot() { return reg.nonSSAId(); } - @Override - public void replaceRegister(Register register) { - this.reg = register; + public RegisterOperand copy(Register register) { + return new RegisterOperand(register); } @Override @@ -44,6 +41,10 @@ public static class LocalRegisterOperand extends RegisterOperand { public LocalRegisterOperand(Register reg) { super(reg); } + @Override + public RegisterOperand copy(Register register) { + return new LocalRegisterOperand(register); + } } public static class LocalFunctionOperand extends Operand { @@ -66,6 +67,10 @@ public static class TempRegisterOperand extends RegisterOperand { public TempRegisterOperand(Register reg) { super(reg); } + @Override + public RegisterOperand copy(Register register) { + return new TempRegisterOperand(register); + } } public static class IndexedOperand extends Operand {} 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 2cfe554..e5a4299 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestInterferenceGraph.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestInterferenceGraph.java @@ -13,7 +13,7 @@ private CompiledFunction buildTest1() { Type.TypeFunction functionType = new Type.TypeFunction("foo"); functionType.addArg(new Symbol.ParameterSymbol("a", typeDictionary.INT)); functionType.setReturnType(typeDictionary.INT); - CompiledFunction function = new CompiledFunction(functionType); + CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; Register a = regPool.newReg("a", typeDictionary.INT); Register b = regPool.newReg("b", typeDictionary.INT); @@ -82,7 +82,7 @@ private CompiledFunction buildTest2() { TypeDictionary typeDictionary = new TypeDictionary(); Type.TypeFunction functionType = new Type.TypeFunction("foo"); functionType.setReturnType(typeDictionary.VOID); - CompiledFunction function = new CompiledFunction(functionType); + CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; Register a = regPool.newReg("a", typeDictionary.INT); Register b = regPool.newReg("b", typeDictionary.INT); @@ -158,7 +158,7 @@ public static CompiledFunction buildTest4() { TypeDictionary typeDictionary = new TypeDictionary(); Type.TypeFunction functionType = new Type.TypeFunction("foo"); functionType.setReturnType(typeDictionary.VOID); - CompiledFunction function = new CompiledFunction(functionType); + CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; Register a = regPool.newReg("a", typeDictionary.INT); Register b = regPool.newReg("b", 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 b1b6222..c9c2d27 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java @@ -247,7 +247,7 @@ static CompiledFunction buildTest3() { TypeDictionary typeDictionary = new TypeDictionary(); Type.TypeFunction functionType = new Type.TypeFunction("foo"); functionType.setReturnType(typeDictionary.INT); - CompiledFunction function = new CompiledFunction(functionType); + CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; Register i = regPool.newReg("i", typeDictionary.INT); Register s = regPool.newReg("s", typeDictionary.INT); 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 703ca2a..3f8a052 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java @@ -640,7 +640,7 @@ static CompiledFunction buildLostCopyTest() { Type.TypeFunction functionType = new Type.TypeFunction("foo"); functionType.addArg(new Symbol.ParameterSymbol("p", typeDictionary.INT)); functionType.setReturnType(typeDictionary.INT); - CompiledFunction function = new CompiledFunction(functionType); + CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; Register p = regPool.newReg("p", typeDictionary.INT); Register x1 = regPool.newReg("x1", typeDictionary.INT); @@ -708,7 +708,7 @@ static CompiledFunction buildSwapTest() { Type.TypeFunction functionType = new Type.TypeFunction("foo"); functionType.addArg(new Symbol.ParameterSymbol("p", typeDictionary.INT)); functionType.setReturnType(typeDictionary.VOID); - CompiledFunction function = new CompiledFunction(functionType); + CompiledFunction function = new CompiledFunction(functionType, typeDictionary); RegisterPool regPool = function.registerPool; Register p = regPool.newReg("p", typeDictionary.INT); Register a1 = regPool.newReg("a1", typeDictionary.INT); @@ -2469,7 +2469,6 @@ func main()->Int { """, result); } - @Ignore @Test public void testSSA14() { String src = """ 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 01eb89c..e463a29 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java @@ -445,4 +445,147 @@ func foo()->Int { && integerValue.value == 5+(5*21+25/5)); } + +// @Test +// public void testFunction100() { +// String src = """ +// struct Test +// { +// var field: Int +// } +// func foo()->Test? +// { +// return null; +// } +// +// """; +// var value = compileAndRun(src, "foo", Options.OPT); +// Assert.assertNotNull(value); +// Assert.assertTrue(value instanceof Value.NullValue); +// } +// +// @Test +// public void testFunction101() { +// String src = """ +// func foo()->Int +// { +// return null == null; +// } +// +// """; +// var value = compileAndRun(src, "foo", Options.OPT); +// Assert.assertNotNull(value); +// Assert.assertTrue(value instanceof Value.IntegerValue integerValue && +// integerValue.value == 1); +// } +// +// @Test +// public void testFunction102() { +// String src = """ +// struct Foo +// { +// var next: Foo? +// } +// func foo()->Int +// { +// var f = new Foo{ next = null } +// return null == f.next +// } +// +// """; +// var value = compileAndRun(src, "foo", Options.OPT); +// Assert.assertNotNull(value); +// Assert.assertTrue(value instanceof Value.IntegerValue integerValue && +// integerValue.value == 1); +// } +// +// @Test +// public void testFunction103() { +// String src = """ +// struct Foo +// { +// var i: Int +// } +// func foo()->Int +// { +// var f = new [Foo?] { new Foo{i = 1}, null } +// return null == f[1] && 1 == f[0].i +// } +// +// """; +// var value = compileAndRun(src, "foo", Options.OPT); +// Assert.assertNotNull(value); +// Assert.assertTrue(value instanceof Value.IntegerValue integerValue && +// integerValue.value == 1); +// } + + @Test + public void testFunction104() { + String src = """ + func foo()->Int + { + return 1 == 1 && 2 == 2 + } + + """; + var value = compileAndRun(src, "foo", Options.OPT); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + + @Test + public void testFunction105() { + String src = """ + func bar(a: Int, b: Int)->Int + { + return a+1 == b-1 && b / a == 2 + } + func foo()->Int + { + return bar(3,5) + } + """; + var value = compileAndRun(src, "foo", Options.OPT); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 0); + } + + @Test + public void testFunction106() { + String src = """ + func bar(a: Int, b: Int)->Int + { + return a+1 == b-1 || b / a == 2 + } + func foo()->Int + { + return bar(3,5) + } + """; + var value = compileAndRun(src, "foo", Options.OPT); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + + @Test + public void testFunction107() { + String src = """ + func bar(a: [Int])->Int + { + return a[0]+a[2] == a[1]-a[2] || a[1] / a[0] == 2 + } + func foo()->Int + { + return bar(new [Int] {3,5,1}) + } + """; + var value = compileAndRun(src, "foo", Options.OPT); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + } From 0c82836788495d07a1401e1c24065e05cd5aae11 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Mon, 17 Feb 2025 22:45:40 +0000 Subject: [PATCH 5/6] more work on boolean binary expressions Fix bug in EnterSSA caused by mutable operands shared across instructions, replacing the phi register cause other instructions to change inadvertantly. Ensure SSA tests check output --- .../ezlang/compiler/CompiledFunction.java | 67 +++++++++++------- .../ezlang/interpreter/TestInterpreter.java | 68 +++++++++++++++++++ 2 files changed, 110 insertions(+), 25 deletions(-) diff --git a/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java b/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java index ef5a842..9cd5112 100644 --- a/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java +++ b/registervm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java @@ -412,42 +412,28 @@ private boolean compileSymbolExpr(AST.NameExpr symbolExpr) { } private boolean codeBoolean(AST.BinaryExpr binaryExpr) { - BasicBlock l1; - BasicBlock l2; - BasicBlock l3; - boolean indexed; - boolean isAnd; - - l1 = createBlock(); - l2 = createBlock(); - l3 = createBlock(); - indexed = compileExpr(binaryExpr.expr1); - if (indexed) { + boolean isAnd = binaryExpr.op.str.equals("&&"); + BasicBlock l1 = createBlock(); + BasicBlock l2 = createBlock(); + BasicBlock l3 = createBlock(); + boolean indexed = compileExpr(binaryExpr.expr1); + if (indexed) codeIndexedLoad(); - } - isAnd = binaryExpr.op.str.equals("&&"); - // FIXME ensure temp - var operand = pop(); - var temp = createTemp(typeDictionary.INT); - code(new Instruction.Move(operand, temp)); if (isAnd) { code(new Instruction.ConditionalBranch(currentBlock, pop(), l1, l2)); } else { code(new Instruction.ConditionalBranch(currentBlock, pop(), l2, l1)); } startBlock(l1); - indexed = compileExpr(binaryExpr.expr2); - if (indexed) { - codeIndexedLoad(); - } - operand = pop(); - temp = createTemp(typeDictionary.INT); - code(new Instruction.Move(operand, temp)); + compileExpr(binaryExpr.expr2); + var temp = ensureTemp(); jumpTo(l3); startBlock(l2); + // Below we must write to the same temp code(new Instruction.Move(new Operand.ConstantOperand(isAnd ? 0 : 1, typeDictionary.INT), temp)); jumpTo(l3); startBlock(l3); + // leave temp on virtual stack return false; } @@ -549,6 +535,36 @@ private Operand.TempRegisterOperand createTemp(Type type) { return tempRegister; } + Type typeOfOperand(Operand operand) { + if (operand instanceof Operand.ConstantOperand constant) + return constant.type; + else if (operand instanceof Operand.NullConstantOperand nullConstantOperand) + return nullConstantOperand.type; + else if (operand instanceof Operand.RegisterOperand registerOperand) + return registerOperand.type; + else throw new CompilerException("Invalid operand"); + } + + private Operand.TempRegisterOperand createTempAndMove(Operand src) { + Type type = typeOfOperand(src); + var temp = createTemp(type); + code(new Instruction.Move(src, temp)); + return temp; + } + + private Operand.RegisterOperand ensureTemp() { + Operand top = top(); + if (top instanceof Operand.ConstantOperand + || top instanceof Operand.NullConstantOperand + || top instanceof Operand.LocalRegisterOperand) { + return createTempAndMove(pop()); + } else if (top instanceof Operand.IndexedOperand) { + return codeIndexedLoad(); + } else if (top instanceof Operand.TempRegisterOperand tempRegisterOperand) { + return tempRegisterOperand; + } else throw new CompilerException("Cannot convert to temporary register"); + } + private void pushLocal(int regnum, String varName) { pushOperand(new Operand.LocalRegisterOperand(regnum, varName)); } @@ -565,7 +581,7 @@ private Operand top() { return virtualStack.getLast(); } - private void codeIndexedLoad() { + private Operand.TempRegisterOperand codeIndexedLoad() { Operand indexed = pop(); var temp = createTemp(indexed.type); if (indexed instanceof Operand.LoadIndexedOperand loadIndexedOperand) { @@ -576,6 +592,7 @@ else if (indexed instanceof Operand.LoadFieldOperand loadFieldOperand) { } else code(new Instruction.Move(indexed, temp)); + return temp; } private void codeIndexedStore() { diff --git a/registervm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java b/registervm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java index c99ce22..fcb93e2 100644 --- a/registervm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java +++ b/registervm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java @@ -176,4 +176,72 @@ func foo()->Int integerValue.value == 1); } + @Test + public void testFunction104() { + String src = """ + func foo()->Int + { + return 1 == 1 && 2 == 2 + } + + """; + var value = compileAndRun(src, "foo"); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + + @Test + public void testFunction105() { + String src = """ + func bar(a: Int, b: Int)->Int + { + return a+1 == b-1 && b / a == 2 + } + func foo()->Int + { + return bar(3,5) + } + """; + var value = compileAndRun(src, "foo"); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 0); + } + + @Test + public void testFunction106() { + String src = """ + func bar(a: Int, b: Int)->Int + { + return a+1 == b-1 || b / a == 2 + } + func foo()->Int + { + return bar(3,5) + } + """; + var value = compileAndRun(src, "foo"); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + + @Test + public void testFunction107() { + String src = """ + func bar(a: [Int])->Int + { + return a[0]+a[2] == a[1]-a[2] || a[1] / a[0] == 2 + } + func foo()->Int + { + return bar(new [Int] {3,5,1}) + } + """; + var value = compileAndRun(src, "foo"); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } } From f666febd54b59f6a8c8541620005fcf8ae09c9ec Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Tue, 18 Feb 2025 14:09:03 +0000 Subject: [PATCH 6/6] Initial support for nulls in optvm - but we are missing null check at runtime for scenarios where T is assigned T? - this is allowed by compiler but must be null checked at runtime --- .../ezlang/compiler/CompiledFunction.java | 32 ++-- .../ezlang/compiler/InterferenceGraph.java | 2 +- .../ezlang/compiler/Operand.java | 10 ++ .../SparseConditionalConstantPropagation.java | 47 +++--- .../ezlang/interpreter/Interpreter.java | 95 +++++++++--- .../ezlang/interpreter/Value.java | 3 + .../ezlang/compiler/TestSSATransform.java | 94 ++++++++++++ .../ezlang/interpreter/TestInterpreter.java | 144 +++++++++--------- 8 files changed, 300 insertions(+), 127 deletions(-) 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 418db05..06c42b5 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java @@ -460,13 +460,10 @@ private boolean codeBoolean(AST.BinaryExpr binaryExpr) { jumpTo(l3); startBlock(l2); // Below we must write to the same temp - //code(new Instruction.Move(new Operand.ConstantOperand(isAnd ? 0 : 1, typeDictionary.INT), new Operand.TempRegisterOperand(temp.reg))); code(new Instruction.Move(new Operand.ConstantOperand(isAnd ? 0 : 1, typeDictionary.INT), temp)); jumpTo(l3); startBlock(l3); // leave temp on virtual stack -// var temp2 = (Operand.TempRegisterOperand) pop(); -// pushOperand(new Operand.TempRegisterOperand(temp2.reg)); return false; } @@ -483,10 +480,19 @@ private boolean compileBinaryExpr(AST.BinaryExpr binaryExpr) { indexed = compileExpr(binaryExpr.expr2); if (indexed) codeIndexedLoad(); - opCode = binaryExpr.op.str; Operand right = pop(); Operand left = pop(); - if (left instanceof Operand.ConstantOperand leftconstant && + if (left instanceof Operand.NullConstantOperand && + right instanceof Operand.NullConstantOperand) { + long value = 0; + switch (opCode) { + case "==": value = 1; break; + case "!=": value = 0; break; + default: throw new CompilerException("Invalid binary op"); + } + pushConstant(value, typeDictionary.INT); + } + else if (left instanceof Operand.ConstantOperand leftconstant && right instanceof Operand.ConstantOperand rightconstant) { long value = 0; switch (opCode) { @@ -535,7 +541,11 @@ private boolean compileUnaryExpr(AST.UnaryExpr unaryExpr) { } private boolean compileConstantExpr(AST.LiteralExpr constantExpr) { - pushConstant(constantExpr.value.num.intValue(), constantExpr.type); + if (constantExpr.type instanceof Type.TypeInteger) + pushConstant(constantExpr.value.num.intValue(), constantExpr.type); + else if (constantExpr.type instanceof Type.TypeNull) + pushNullConstant(constantExpr.type); + else throw new CompilerException("Invalid constant type"); return false; } @@ -543,6 +553,10 @@ private void pushConstant(long value, Type type) { pushOperand(new Operand.ConstantOperand(value, type)); } + private void pushNullConstant(Type type) { + pushOperand(new Operand.NullConstantOperand(type)); + } + private Operand.TempRegisterOperand createTemp(Type type) { var tempRegister = new Operand.TempRegisterOperand(registerPool.newTempReg(type)); pushOperand(tempRegister); @@ -552,8 +566,8 @@ private Operand.TempRegisterOperand createTemp(Type type) { Type typeOfOperand(Operand operand) { if (operand instanceof Operand.ConstantOperand constant) return constant.type; -// else if (operand instanceof Operand.NullConstantOperand nullConstantOperand) -// return nullConstantOperand.type; + else if (operand instanceof Operand.NullConstantOperand nullConstantOperand) + return nullConstantOperand.type; else if (operand instanceof Operand.RegisterOperand registerOperand) return registerOperand.type; else throw new CompilerException("Invalid operand"); @@ -569,7 +583,7 @@ private Operand.TempRegisterOperand createTempAndMove(Operand src) { private Operand.RegisterOperand ensureTemp() { Operand top = top(); if (top instanceof Operand.ConstantOperand - //|| top instanceof Operand.NullConstantOperand + || top instanceof Operand.NullConstantOperand || top instanceof Operand.LocalRegisterOperand) { return createTempAndMove(pop()); } else if (top instanceof Operand.IndexedOperand) { diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraph.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraph.java index 1714f6b..1749d49 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraph.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraph.java @@ -65,7 +65,7 @@ public void rename(Integer source, Integer target) { var toSet = edges.get(target); if (toSet == null) { //throw new RuntimeException("Cannot find edge " + target + " from " + source); - return; // FIXME this is workaround to handle sceanrio where target is arg register but we need a better way + return; // FIXME this is workaround to handle scenario where target is arg register but we need a better way } toSet.addAll(fromSet); // If any node interfered with from 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 b4bde07..57a691e 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java @@ -18,6 +18,16 @@ public String toString() { } } + public static class NullConstantOperand extends Operand { + public NullConstantOperand(Type type) { + this.type = type; + } + @Override + public String toString() { + return "null"; + } + } + public static class RegisterOperand extends Operand { final Register reg; protected RegisterOperand(Register reg) { diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java index 73a04fc..c83ee31 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java @@ -377,35 +377,40 @@ private boolean evalInstruction(Instruction instruction) { } case Instruction.Binary binaryInst -> { var cell = valueLattice.get(binaryInst.result().reg); - LatticeElement left, right; + LatticeElement left = null; + LatticeElement right = null; + // TODO we cannot yet evaluate null in comparisons if (binaryInst.left() instanceof Operand.ConstantOperand constant) left = new LatticeElement(V_CONSTANT, constant.value); else if (binaryInst.left() instanceof Operand.RegisterOperand registerOperand) left = valueLattice.get(registerOperand.reg); - else throw new IllegalStateException(); if (binaryInst.right() instanceof Operand.ConstantOperand constant) right = new LatticeElement(V_CONSTANT, constant.value); else if (binaryInst.right() instanceof Operand.RegisterOperand registerOperand) right = valueLattice.get(registerOperand.reg); - else throw new IllegalStateException(); - switch (binaryInst.binOp) { - case "+": - case "-": - case "*": - case "/": - case "%": - changed = evalArith(cell, left, right, binaryInst.binOp); - break; - case "==": - case "!=": - case "<": - case ">": - case "<=": - case ">=": - changed = evalLogical(cell, left, right, binaryInst.binOp); - break; - default: - throw new IllegalStateException(); + if (left != null && right != null) { + switch (binaryInst.binOp) { + case "+": + case "-": + case "*": + case "/": + case "%": + changed = evalArith(cell, left, right, binaryInst.binOp); + break; + case "==": + case "!=": + case "<": + case ">": + case "<=": + case ">=": + changed = evalLogical(cell, left, right, binaryInst.binOp); + break; + default: + throw new IllegalStateException(); + } + } + else { + cell.setKind(V_VARYING); } } case Instruction.NewArray newArrayInst -> { diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/interpreter/Interpreter.java b/optvm/src/main/java/com/compilerprogramming/ezlang/interpreter/Interpreter.java index 6aec1b8..d59e1f0 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/interpreter/Interpreter.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/interpreter/Interpreter.java @@ -48,6 +48,9 @@ public Value interpret(ExecutionStack execStack, Frame frame) { if (retInst.value() instanceof Operand.ConstantOperand constantOperand) { execStack.stack[base] = new Value.IntegerValue(constantOperand.value); } + else if (retInst.value() instanceof Operand.NullConstantOperand) { + execStack.stack[base] = new Value.NullValue(); + } else if (retInst.value() instanceof Operand.RegisterOperand registerOperand) { execStack.stack[base] = execStack.stack[base+registerOperand.frameSlot()]; } @@ -63,6 +66,9 @@ else if (retInst.value() instanceof Operand.RegisterOperand registerOperand) { else if (moveInst.from() instanceof Operand.ConstantOperand constantOperand) { execStack.stack[base + toReg.frameSlot()] = new Value.IntegerValue(constantOperand.value); } + else if (moveInst.from() instanceof Operand.NullConstantOperand) { + execStack.stack[base + toReg.frameSlot()] = new Value.NullValue(); + } else throw new IllegalStateException(); } else throw new IllegalStateException(); @@ -107,6 +113,9 @@ else if (cbrInst.condition() instanceof Operand.ConstantOperand constantOperand) else if (arg instanceof Operand.ConstantOperand constantOperand) { execStack.stack[base + reg] = new Value.IntegerValue(constantOperand.value); } + else if (arg instanceof Operand.NullConstantOperand) { + execStack.stack[base + reg] = new Value.NullValue(); + } reg += 1; } // Call function @@ -135,31 +144,60 @@ else if (arg instanceof Operand.ConstantOperand constantOperand) { case Instruction.Binary binaryInst -> { long x, y; long value = 0; - if (binaryInst.left() instanceof Operand.ConstantOperand constant) - x = constant.value; - else if (binaryInst.left() instanceof Operand.RegisterOperand registerOperand) - x = ((Value.IntegerValue) execStack.stack[base + registerOperand.frameSlot()]).value; - else throw new IllegalStateException(); - if (binaryInst.right() instanceof Operand.ConstantOperand constant) - y = constant.value; - else if (binaryInst.right() instanceof Operand.RegisterOperand registerOperand) - y = ((Value.IntegerValue) execStack.stack[base + registerOperand.frameSlot()]).value; - else throw new IllegalStateException(); - switch (binaryInst.binOp) { - case "+": value = x + y; break; - case "-": value = x - y; break; - case "*": value = x * y; break; - case "/": value = x / y; break; - case "%": value = x % y; break; - case "==": value = x == y ? 1 : 0; break; - case "!=": value = x != y ? 1 : 0; break; - case "<": value = x < y ? 1: 0; break; - case ">": value = x > y ? 1 : 0; break; - case "<=": value = x <= y ? 1 : 0; break; - case ">=": value = x <= y ? 1 : 0; break; - default: throw new IllegalStateException(); + boolean intOp = true; + if (binaryInst.binOp.equals("==") || binaryInst.binOp.equals("!=")) { + Operand.RegisterOperand nonNullLitOperand = null; + if (binaryInst.left() instanceof Operand.NullConstantOperand) { + nonNullLitOperand = (Operand.RegisterOperand)binaryInst.right(); + } + else if (binaryInst.right() instanceof Operand.NullConstantOperand) { + nonNullLitOperand = (Operand.RegisterOperand)binaryInst.left(); + } + if (nonNullLitOperand != null) { + intOp = false; + Value otherValue = execStack.stack[base + nonNullLitOperand.frameSlot()]; + switch (binaryInst.binOp) { + case "==": { + value = otherValue instanceof Value.NullValue ? 1 : 0; + break; + } + case "!=": { + value = otherValue instanceof Value.NullValue ? 0 : 1; + break; + } + default: + throw new IllegalStateException(); + } + execStack.stack[base + binaryInst.result().frameSlot()] = new Value.IntegerValue(value); + } + } + if (intOp) { + if (binaryInst.left() instanceof Operand.ConstantOperand constant) + x = constant.value; + else if (binaryInst.left() instanceof Operand.RegisterOperand registerOperand) + x = ((Value.IntegerValue) execStack.stack[base + registerOperand.frameSlot()]).value; + else throw new IllegalStateException(); + if (binaryInst.right() instanceof Operand.ConstantOperand constant) + y = constant.value; + else if (binaryInst.right() instanceof Operand.RegisterOperand registerOperand) + y = ((Value.IntegerValue) execStack.stack[base + registerOperand.frameSlot()]).value; + else throw new IllegalStateException(); + switch (binaryInst.binOp) { + case "+": value = x + y; break; + case "-": value = x - y; break; + case "*": value = x * y; break; + case "/": value = x / y; break; + case "%": value = x % y; break; + case "==": value = x == y ? 1 : 0; break; + case "!=": value = x != y ? 1 : 0; break; + case "<": value = x < y ? 1: 0; break; + case ">": value = x > y ? 1 : 0; break; + case "<=": value = x <= y ? 1 : 0; break; + case ">=": value = x <= y ? 1 : 0; break; + default: throw new IllegalStateException(); + } + execStack.stack[base + binaryInst.result().frameSlot()] = new Value.IntegerValue(value); } - execStack.stack[base + binaryInst.result().frameSlot()] = new Value.IntegerValue(value); } case Instruction.NewArray newArrayInst -> { execStack.stack[base + newArrayInst.destOperand().frameSlot()] = new Value.ArrayValue(newArrayInst.type); @@ -172,6 +210,9 @@ else if (binaryInst.right() instanceof Operand.RegisterOperand registerOperand) if (arrayAppendInst.value() instanceof Operand.ConstantOperand constant) { arrayValue.values.add(new Value.IntegerValue(constant.value)); } + else if (arrayAppendInst.value() instanceof Operand.NullConstantOperand) { + arrayValue.values.add(new Value.NullValue()); + } else if (arrayAppendInst.value() instanceof Operand.RegisterOperand registerOperand) { arrayValue.values.add(execStack.stack[base + registerOperand.frameSlot()]); } @@ -193,6 +234,9 @@ else if (arrayStoreInst.indexOperand() instanceof Operand.RegisterOperand regist if (arrayStoreInst.sourceOperand() instanceof Operand.ConstantOperand constantOperand) { value = new Value.IntegerValue(constantOperand.value); } + else if (arrayStoreInst.sourceOperand() instanceof Operand.NullConstantOperand) { + value = new Value.NullValue(); + } else if (arrayStoreInst.sourceOperand() instanceof Operand.RegisterOperand registerOperand) { value = execStack.stack[base + registerOperand.frameSlot()]; } @@ -221,6 +265,9 @@ else if (arrayLoadInst.indexOperand() instanceof Operand.RegisterOperand registe if (setFieldInst.sourceOperand() instanceof Operand.ConstantOperand constant) { value = new Value.IntegerValue(constant.value); } + else if (setFieldInst.sourceOperand() instanceof Operand.NullConstantOperand) { + value = new Value.NullValue(); + } else if (setFieldInst.sourceOperand() instanceof Operand.RegisterOperand registerOperand) { value = execStack.stack[base + registerOperand.frameSlot()]; } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/interpreter/Value.java b/optvm/src/main/java/com/compilerprogramming/ezlang/interpreter/Value.java index e03fb80..7f914a6 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/interpreter/Value.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/interpreter/Value.java @@ -11,6 +11,9 @@ public IntegerValue(long value) { } public final long value; } + static public class NullValue extends Value { + public NullValue() {} + } static public class ArrayValue extends Value { public final Type.TypeArray arrayType; public final ArrayList values; 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 3f8a052..1c4fc5b 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java @@ -2525,6 +2525,100 @@ func foo()->Int %t0_0 = 0 %t0_2 = %t0_0 goto L4 +""", result); + } + + @Test + public void testSSA15() { + String src = """ + struct Foo + { + var i: Int + } + func foo()->Int + { + var f = new [Foo?] { new Foo{i = 1}, null } + return null == f[1] && 1 == f[0].i + } +"""; + String result = compileSrc(src); + Assert.assertEquals(""" +func foo +Before SSA +========== +L0: + %t1 = New([Foo?]) + %t2 = New(Foo) + %t2.i = 1 + %t1.append(%t2) + %t1.append(null) + f = %t1 + %t3 = f[1] + %t4 = null==%t3 + if %t4 goto L2 else goto L3 +L2: + %t5 = f[0] + %t6 = %t5.i + %t7 = 1==%t6 + goto L4 +L4: + ret %t7 + goto L1 +L1: +L3: + %t7 = 0 + goto L4 +After SSA +========= +L0: + %t1_0 = New([Foo?]) + %t2_0 = New(Foo) + %t2_0.i = 1 + %t1_0.append(%t2_0) + %t1_0.append(null) + f_0 = %t1_0 + %t3_0 = f_0[1] + %t4_0 = null==%t3_0 + if %t4_0 goto L2 else goto L3 +L2: + %t5_0 = f_0[0] + %t6_0 = %t5_0.i + %t7_1 = 1==%t6_0 + goto L4 +L4: + %t7_2 = phi(%t7_1, %t7_0) + ret %t7_2 + goto L1 +L1: +L3: + %t7_0 = 0 + goto L4 +After exiting SSA +================= +L0: + %t1_0 = New([Foo?]) + %t2_0 = New(Foo) + %t2_0.i = 1 + %t1_0.append(%t2_0) + %t1_0.append(null) + f_0 = %t1_0 + %t3_0 = f_0[1] + %t4_0 = null==%t3_0 + if %t4_0 goto L2 else goto L3 +L2: + %t5_0 = f_0[0] + %t6_0 = %t5_0.i + %t7_1 = 1==%t6_0 + %t7_2 = %t7_1 + goto L4 +L4: + ret %t7_2 + goto L1 +L1: +L3: + %t7_0 = 0 + %t7_2 = %t7_0 + goto L4 """, result); } } \ No newline at end of file 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 e463a29..4f8c0f0 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/interpreter/TestInterpreter.java @@ -446,78 +446,78 @@ func foo()->Int { } -// @Test -// public void testFunction100() { -// String src = """ -// struct Test -// { -// var field: Int -// } -// func foo()->Test? -// { -// return null; -// } -// -// """; -// var value = compileAndRun(src, "foo", Options.OPT); -// Assert.assertNotNull(value); -// Assert.assertTrue(value instanceof Value.NullValue); -// } -// -// @Test -// public void testFunction101() { -// String src = """ -// func foo()->Int -// { -// return null == null; -// } -// -// """; -// var value = compileAndRun(src, "foo", Options.OPT); -// Assert.assertNotNull(value); -// Assert.assertTrue(value instanceof Value.IntegerValue integerValue && -// integerValue.value == 1); -// } -// -// @Test -// public void testFunction102() { -// String src = """ -// struct Foo -// { -// var next: Foo? -// } -// func foo()->Int -// { -// var f = new Foo{ next = null } -// return null == f.next -// } -// -// """; -// var value = compileAndRun(src, "foo", Options.OPT); -// Assert.assertNotNull(value); -// Assert.assertTrue(value instanceof Value.IntegerValue integerValue && -// integerValue.value == 1); -// } -// -// @Test -// public void testFunction103() { -// String src = """ -// struct Foo -// { -// var i: Int -// } -// func foo()->Int -// { -// var f = new [Foo?] { new Foo{i = 1}, null } -// return null == f[1] && 1 == f[0].i -// } -// -// """; -// var value = compileAndRun(src, "foo", Options.OPT); -// Assert.assertNotNull(value); -// Assert.assertTrue(value instanceof Value.IntegerValue integerValue && -// integerValue.value == 1); -// } + @Test + public void testFunction100() { + String src = """ + struct Test + { + var field: Int + } + func foo()->Test? + { + return null; + } + + """; + var value = compileAndRun(src, "foo", Options.OPT); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.NullValue); + } + + @Test + public void testFunction101() { + String src = """ + func foo()->Int + { + return null == null; + } + + """; + var value = compileAndRun(src, "foo", Options.OPT); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + + @Test + public void testFunction102() { + String src = """ + struct Foo + { + var next: Foo? + } + func foo()->Int + { + var f = new Foo{ next = null } + return null == f.next + } + + """; + var value = compileAndRun(src, "foo", Options.OPT); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } + + @Test + public void testFunction103() { + String src = """ + struct Foo + { + var i: Int + } + func foo()->Int + { + var f = new [Foo?] { new Foo{i = 1}, null } + return null == f[1] && 1 == f[0].i + } + + """; + var value = compileAndRun(src, "foo", Options.OPT); + Assert.assertNotNull(value); + Assert.assertTrue(value instanceof Value.IntegerValue integerValue && + integerValue.value == 1); + } @Test public void testFunction104() {