From 28209703c72fb7e9955961dbdb84e24a0f871acd Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 10 Aug 2026 11:11:18 -0700 Subject: Chapter 25: Modules --- .../java/com/seaofnodes/simple/BrainFuckTest.java | 24 +- .../java/com/seaofnodes/simple/Chapter01Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter03Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 7 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 16 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter09Test.java | 6 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 87 +-- .../java/com/seaofnodes/simple/Chapter11Test.java | 89 +-- .../java/com/seaofnodes/simple/Chapter12Test.java | 19 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 124 ++--- .../java/com/seaofnodes/simple/Chapter14Test.java | 6 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 74 +-- .../java/com/seaofnodes/simple/Chapter16Test.java | 93 ++-- .../java/com/seaofnodes/simple/Chapter17Test.java | 102 ++-- .../java/com/seaofnodes/simple/Chapter18Test.java | 165 +++--- .../java/com/seaofnodes/simple/Chapter19Test.java | 124 +++-- .../java/com/seaofnodes/simple/Chapter20Test.java | 62 +-- .../java/com/seaofnodes/simple/Chapter21Test.java | 94 ++-- .../java/com/seaofnodes/simple/Chapter22Test.java | 141 ++--- .../java/com/seaofnodes/simple/Chapter23Test.java | 288 ++++------ .../java/com/seaofnodes/simple/Chapter24Test.java | 548 ++----------------- .../java/com/seaofnodes/simple/Chapter25Test.java | 286 ++++++++++ src/test/java/com/seaofnodes/simple/Compile.java | 65 --- src/test/java/com/seaofnodes/simple/Eval2.java | 123 +++-- src/test/java/com/seaofnodes/simple/EvalArm64.java | 40 +- .../java/com/seaofnodes/simple/MergeSortTest.java | 80 ++- .../java/com/seaofnodes/simple/SchedulerTest.java | 74 --- src/test/java/com/seaofnodes/simple/Simple.java | 310 +++++------ src/test/java/com/seaofnodes/simple/TestArm64.java | 44 +- src/test/java/com/seaofnodes/simple/TestC.java | 182 +++++-- src/test/java/com/seaofnodes/simple/TestRisc5.java | 44 +- src/test/java/com/seaofnodes/simple/TypeTest.java | 62 ++- .../com/seaofnodes/simple/evaluator/Evaluator.java | 382 ------------- .../com/seaofnodes/simple/evaluator/Scheduler.java | 605 --------------------- .../java/com/seaofnodes/simple/fuzzer/Fuzzer.java | 6 +- .../com/seaofnodes/simple/fuzzer/FuzzerUtils.java | 4 +- src/test/java/com/seaofnodes/simple/test0/A.smp | 6 + src/test/java/com/seaofnodes/simple/test0/A/B.smp | 1 + src/test/java/com/seaofnodes/simple/test5/par.smp | 20 + .../com/seaofnodes/simple/test5/par/child1.smp | 12 + .../com/seaofnodes/simple/test5/par/child2.smp | 13 + .../seaofnodes/simple/test_smp/seed_repro/sys.smp | 3 + .../simple/test_smp/seed_repro/sys/Ary.smp | 6 + .../simple/test_smp/seed_repro/sys/aryi64.smp | 29 + .../simple/test_smp/seed_repro/sys/aryu8.smp | 50 ++ 47 files changed, 1766 insertions(+), 2772 deletions(-) create mode 100644 src/test/java/com/seaofnodes/simple/Chapter25Test.java delete mode 100644 src/test/java/com/seaofnodes/simple/Compile.java delete mode 100644 src/test/java/com/seaofnodes/simple/SchedulerTest.java delete mode 100644 src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java delete mode 100644 src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java create mode 100644 src/test/java/com/seaofnodes/simple/test0/A.smp create mode 100644 src/test/java/com/seaofnodes/simple/test0/A/B.smp create mode 100644 src/test/java/com/seaofnodes/simple/test5/par.smp create mode 100644 src/test/java/com/seaofnodes/simple/test5/par/child1.smp create mode 100644 src/test/java/com/seaofnodes/simple/test5/par/child2.smp create mode 100644 src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys.smp create mode 100644 src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/Ary.smp create mode 100644 src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryi64.smp create mode 100644 src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryu8.smp (limited to 'src/test/java/com/seaofnodes') diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index de6c5a6b..0ebee2c4 100644 --- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -1,9 +1,7 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.codegen.CodeGen; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; + import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -23,24 +21,24 @@ val brain_fuck = { -> for( int pc = 0; pc < program#; pc++ ) { var command = program[pc]; - if (command == 62) { + if (command == '>') { d++; - } else if (command == 60) { + } else if (command == '<') { d--; - } else if (command == 43) { + } else if (command == '+') { data[d]++; - } else if (command == 45) { + } else if (command == '-') { data[d]--; - } else if (command == 46) { + } else if (command == '.') { // Output a byte; increase the output array size var old = output; output = new u8[output# + 1]; for( int i = 0; i < old#; i++ ) output[i] = old[i]; output[old#] = data[d]; // Add the extra byte on the end - } else if (command == 44) { + } else if (command == ',') { data[d] = 42; - } else if (command == 91) { + } else if (command == '[') { if (data[d] == 0) { for( int d = 1; d > 0; ) { command = program[++pc]; @@ -48,7 +46,7 @@ val brain_fuck = { -> if (command == 93) d--; } } - } else if (command == 93) { + } else if (command == ']') { if (data[d]) { for( int d = 1; d > 0; ) { command = program[--pc]; @@ -61,9 +59,9 @@ val brain_fuck = { -> return output; }; """; - TestC.run(src,"brain_fuck", null, brain_fuck, 40); + TestC.runC(src,"brain_fuck", brain_fuck, 40); - EvalRisc5 R5 = TestRisc5.build("brain_fuck", src, 0, 28, false); + EvalRisc5 R5 = TestRisc5.build( src, "brain_fuck", 0, 28, false); int trap = R5.step(100000); assertEquals(0,trap); int ptr = (int)R5.regs[com.seaofnodes.simple.node.cpus.riscv.riscv.A0]; diff --git a/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/src/test/java/com/seaofnodes/simple/Chapter01Test.java index ac9adacc..fa7fb68a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -11,7 +11,7 @@ public class Chapter01Test { @Test public void testSimpleProgram() { - CodeGen code = new CodeGen("return 1;").parse(); + CodeGen code = new CodeGen("1;").parse(); Node expr = code.expr(); if( expr instanceof ConstantNode con ) { assertEquals(code._start,con.in(0)); @@ -53,7 +53,7 @@ public class Chapter01Test { @Test public void testNotBad3() { // this test used to fail in chapter 1 - assertEquals("return 12;", new CodeGen("return - -12;").parse()._stop.print()); + assertEquals("return 12;", new CodeGen("return - -12;").parse().print()); } @Test @@ -62,14 +62,14 @@ public class Chapter01Test { new CodeGen("return 100").parse(); fail(); } catch( RuntimeException e ) { - assertEquals("Syntax error, expected ;: ",e.getMessage()); + assertEquals("Syntax error, expected `;` but found `EOF`",e.getMessage()); } } @Test public void testNotBad5() { // this test used to fail in chapter 1 - assertEquals("return -100;", new CodeGen("return -100;").parse()._stop.print()); + assertEquals("return -100;", new CodeGen("return -100;").parse().print()); } @Test @@ -88,7 +88,7 @@ public class Chapter01Test { new CodeGen("return 1;}").parse(); fail(); } catch( RuntimeException e ) { - assertEquals("Syntax error, unexpected: }",e.getMessage()); + assertEquals("Syntax error, unexpected but found `}`",e.getMessage()); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/src/test/java/com/seaofnodes/simple/Chapter03Test.java index c4e4c91e..c96acddc 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -66,7 +66,7 @@ public class Chapter03Test { new CodeGen("int a=1; int b=2; int !c=0; { int b=3; c=a+b;").parse(); fail(); } catch( RuntimeException e ) { - assertEquals("Syntax error, expected }: ",e.getMessage()); + assertEquals("Syntax error, expected `}` but found `EOF`",e.getMessage()); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/src/test/java/com/seaofnodes/simple/Chapter04Test.java index 1361d8b6..eaa959cb 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -63,7 +63,7 @@ public class Chapter04Test { // Adding functions means `main` does not constant fold the incoming arg value @Ignore @Test public void testConstantArg() { - CodeGen code = new CodeGen("return arg; ", TypeInteger.constant(2), 123L, true).parse(); + CodeGen code = new CodeGen("return arg; ", TypeInteger.constant(2)).parse(); assertEquals("return 2;", code.expr().in(1).toString()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/src/test/java/com/seaofnodes/simple/Chapter05Test.java index d1dfd216..6ac08fda 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -116,7 +116,10 @@ if( arg==0 ) return arg+a+b; """); code.parse().opto(); - assertEquals("return ((arg+Phi(Region,1,0))+Phi(Region,2,0));", code.print()); + String ir = code.print(); + assertTrue(ir, + ir.equals("return ((arg+Phi(Region,1,0))+Phi(Region,2,0));") || + ir.equals("return (Phi(Region,((!=0)arg+1),0)+Phi(Region,2,0));")); } @Test @@ -162,7 +165,7 @@ return a; @Test public void testBadNum() { try { new CodeGen("return 1-;").parse(); fail(); } - catch( RuntimeException e ) { assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); } + catch( RuntimeException e ) { assertEquals("Syntax error, expected `an identifier or expression` but found `;`",e.getMessage()); } } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 1ff875b4..fdf6963f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -104,7 +104,7 @@ else if( arg==3 ) else a=5; return a; -""", TypeInteger.constant(2), 123L, true).parse().opto(); +""", TypeInteger.constant(2)).parse().opto(); assertEquals("return 5;", code.print()); } @@ -124,7 +124,7 @@ else if( arg==3 ) else a=5; return a; -""", TypeInteger.constant(1), 123L, true).parse().opto(); +""", TypeInteger.constant(1)).parse().opto(); assertEquals("return 3;", code.print()); } @@ -164,7 +164,7 @@ else if( arg==3 ) else a=5; return a; -""", TypeInteger.constant(1), 123L, true); +""", TypeInteger.constant(1)); code.parse().opto(); assertEquals("return 3;", code.print()); } @@ -185,7 +185,7 @@ else if( arg==3 ) else a=5; return a; -""", TypeInteger.constant(3), 123L, true); +""", TypeInteger.constant(3)); code.parse().opto(); assertEquals("return 4;", code.print()); } @@ -220,7 +220,7 @@ if( arg ) { else b = 3; } return a+b; -""", TypeInteger.constant(1), 123L, true); +""", TypeInteger.constant(1)); code.parse().opto(); assertEquals("return 4;", code.print()); } @@ -238,7 +238,7 @@ if( arg ) { else b = 3; } return a+b; -""", TypeInteger.constant(0), 123L, true); +""", TypeInteger.constant(0)); code.parse().opto(); assertEquals("return 1;", code.print()); } @@ -277,7 +277,7 @@ if( arg ) { else b = 3; } return a+b+c; -""", TypeInteger.constant(1), 123L, true); +""", TypeInteger.constant(1)); code.parse().opto(); assertEquals("return 6;", code.print()); } @@ -297,7 +297,7 @@ if( arg ) { else b = 3; } return a+b+c; -""", TypeInteger.constant(2), 123L, true); +""", TypeInteger.constant(2)); code.parse().opto(); assertEquals("return 5;", code.print()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 88554323..2ca6f8dd 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -17,7 +17,7 @@ while(arg < 10) { } return arg; """); - code.parse(); + code.parse().opto(); assertEquals("return Phi(Loop,arg,(Phi_arg+1));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -106,7 +106,7 @@ while(a < 10) { } return a; """); - code.parse(); + code.parse().opto(); assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -164,7 +164,7 @@ while(a < 10) { } return a; """); - code.parse(); + code.parse().opto(); assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -197,7 +197,7 @@ while(a < 10) { } return a; """); - code.parse(); + code.parse().opto(); assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 54a14a25..d36cdf7f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -209,9 +209,9 @@ return -0+0+0; @Test public void testFuzz3() { - CodeGen code = new CodeGen("int v0=0; while(0==69) while(v0) return 0; return 0;"); + CodeGen code = new CodeGen("int v0=0; while(0==69) while(v0) return 0; return 1;"); code.parse().opto(); - assertEquals("return 0;", code.print()); + assertEquals("return 1;", code.print()); } @Test @@ -255,7 +255,7 @@ return 0!=0; int v0=0; while(0==1) while(v0) v0=1+v0; -return 0; +return 0; """); code.parse().opto(); assertEquals("return 0;", code.print()); diff --git a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/src/test/java/com/seaofnodes/simple/Chapter10Test.java index ec363239..6b237b18 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -35,15 +35,15 @@ return p-r; @Test public void testStruct() { CodeGen code = new CodeGen(""" -struct Bar { +struct _Bar { int a; int b; }; -struct Foo { +struct _Foo { int x; }; -Foo? foo = null; -Bar !bar = new Bar; +_Foo? foo = null; +_Bar !bar = new _Bar; bar.a = 1; bar.a = 2; return bar.a; @@ -65,7 +65,7 @@ else return v; """); code.parse().opto(); - assertEquals("return Vector2D;", code.print()); + assertEquals("Stop[ return Test.Vector2D; return MEM[ 2:.x=0; 3:.y=0;]; ]", code.print()); } @Test @@ -77,8 +77,8 @@ struct s0 { s0? v1=null; int v3=v1.zAicm; """); - try { code.parse(); fail(); } - catch( Exception e ) { assertEquals("Accessing unknown field 'zAicm' from 'null'",e.getMessage()); } + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'zAicm'",e.getMessage()); } } @Test @@ -103,7 +103,7 @@ while (arg) { return bar.a; """); code.parse().opto(); - assertEquals("return Phi(Loop,0,(Phi_a+2));", code.print()); + assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MEM[ 2:.a=0;]; ]", code.print()); } @Test @@ -116,7 +116,7 @@ bar.a = 1; return bar.a; """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type null is not of declared type *Bar",e.getMessage()); } + catch( Exception e ) { assertEquals("Type null is not of declared type *Test.Bar",e.getMessage()); } } @Test @@ -142,28 +142,28 @@ bar.a = 1; return bar.a; """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type null is not of declared type *Bar", e.getMessage()); } + catch( Exception e ) { assertEquals("Type null is not of declared type *Test.Bar", e.getMessage()); } } @Test public void testIfOrNull() { CodeGen code = new CodeGen(""" -struct Bar { int a; }; -Bar? !bar = new Bar; +struct _Bar { int a; }; +_Bar? !bar = new _Bar; if (arg) bar = null; if( bar ) bar.a = 1; -return bar; +return bar.a; """); code.parse().opto(); - assertEquals("return Phi(Region,null,Bar);", code.print()); + assertEquals("return Phi(Region,1,.a);", code.print()); } @Test public void testIfOrNull2() { CodeGen code = new CodeGen( """ -struct Bar { int a; }; -Bar? !bar = new Bar; +struct _Bar { int a; }; +_Bar? !bar = new _Bar; if (arg) bar = null; int rez = 3; if( !bar ) rez=4; @@ -225,7 +225,7 @@ while( i.x < i.len ) { return sum; """); code.parse().opto(); - assertEquals("return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", code.print()); + assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MEM[ 2:.x=0; 3:.len=0;]; ]", code.print()); } @@ -244,44 +244,63 @@ while(arg) { return ret; """); code.parse().opto(); - assertEquals("return Phi(Loop,s0,Phi(Region,s0,Phi_ret));", code.print()); + assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MEM[ 2:.v0=0;]; ]", code.print()); } @Test public void test2() { CodeGen code = new CodeGen(""" -struct s0 {int v0;}; -s0 !ret = new s0; -s0 !v0 = new s0; +struct _s0 {int v0;}; +_s0 !ret = new _s0; +_s0 !v0 = new _s0; while(arg) { v0.v0 = arg; arg = arg-1; if (arg==5) ret=v0; - } -return ret; +return ret.v0; """); code.parse().opto(); - assertEquals("return Phi(Loop,s0,Phi(Region,s0,Phi_ret));", code.print()); + assertEquals("return .v0;", code.print()); } @Test public void test3() { CodeGen code = new CodeGen(""" -struct s0 {int v0;}; -s0 !ret = new s0; +struct _s0 {int v0;}; +_s0 !ret = new _s0; while(arg < 10) { - s0 !v0 = new s0; - if (arg == 5) ret=v0; + _s0 !v1 = new _s0; + if (arg == 5) ret=v1; arg = arg + 1; } -return ret; +return ret.v0; """); code.parse().opto(); - assertEquals("return Phi(Loop,s0,Phi(Region,s0,Phi_ret));", code.print()); + assertEquals("return .v0;", code.print()); } + + + @Test + public void testModGlobalInConstructor() { + CodeGen code = new CodeGen( +""" +int cnt = 1; +// cnt is incremented exactly once, it is not part of s0 constructor +// hence s0.x is always 1, and cnt remains at 2 +struct s0 { int x = cnt++; }; +s0 a = new s0(); +s0 b = new s0(); +// a.x==1, b.x==1, cnt==2 +return a.x * 100 + b.x * 10 + cnt; +"""); + code.parse().iter().opto(); + assertEquals("Stop[ return 112; return MEM[ 2:___ 3:.x=1;]; ]", code.print()); + } + + @Test public void testBug3() { CodeGen code = new CodeGen(""" @@ -290,7 +309,7 @@ return new s0; int v0=null.f0; """); try { code.parse(); fail(); } - catch( Exception e ) { assertEquals("Syntax error, expected ;: .",e.getMessage()); } + catch( Exception e ) { assertEquals("Syntax error, expected `;` but found `.`",e.getMessage()); } } @Test @@ -320,8 +339,8 @@ if(0) return 0; else return new s0; if(new s0.f0) return 0; """); - code.parse().opto(); - assertEquals("return s0;", code.print()); + code.parse().iter().opto().typeCheck(); + assertEquals("Stop[ return Test.s0; return MEM[ 2:.f0=0;]; ]", code.print()); } @Test @@ -349,7 +368,7 @@ s0 v1 = v0; return v1; """); code.parse().opto(); - assertEquals("return s0;", code.print()); + assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:.f0=0;]; ]", code.print()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 99aa9579..ab730b45 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -43,14 +43,31 @@ return primeCount; assertEquals("4", Eval2.eval(code, 10)); // 2, 3, 5, 7 } + @Test + public void testClassField() { + CodeGen code = new CodeGen( +""" +if( arg < 2 ) return 0; +int primeCount = 1; +while( arg-- ) + primeCount++; +return primeCount; +"""); + code.driver(CodeGen.Phase.LocalSched); + assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below + assertEquals("3", Eval2.eval(code, 2)); // +} + + @Test public void testAntiDeps1() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v=new S; +struct _S { int f; }; +_S !v=new _S; v.f = 2; -int i=new S.f; +_S q=new _S; +int i=q.f; i=v.f; if (arg) v.f=1; return i; @@ -65,10 +82,10 @@ return i; public void testAntiDeps2() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v = new S; +struct _S { int f; }; +_S !v = new _S; v.f = arg; -S !t = new S; +_S !t = new _S; int i = 0; if (arg) { if (arg+1) v = t; @@ -86,19 +103,19 @@ return i; public void testAntiDeps3() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v0 = new S; -S? v1; -if (arg) v1 = new S; +struct _S { int f; }; +_S !v0 = new _S; +_S? v1; +if (arg) v1 = new _S; if (v1) { v0.f = v1.f; } else { v0.f = 2; } -return v0; +return v0.f; """); code.parse().opto(); - assertEquals("return S;", code.print()); + assertEquals("return Phi(Region,.f,2);", code.print()); } @@ -106,10 +123,10 @@ return v0; public void testAntiDeps4() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v = new S; +struct _S { int f; }; +_S !v = new _S; v.f = arg; -S t = new S; +_S t = new _S; int i = v.f; if (arg+1) arg= 0; while (arg) v.f = 2; @@ -123,27 +140,27 @@ return i; public void testAntiDeps5() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v = new S; +struct _S { int f; }; +_S !v = new _S; while(1) { while(arg+1) { arg=arg-1; } if (arg) break; v.f = 2; } -return v; +return v.f; """); code.parse().opto(); - assertEquals("return S;", code.print()); + assertEquals("return Phi(Loop,0,2);", code.print()); } @Test public void testAntiDeps6() { CodeGen code = new CodeGen( """ -struct s { int v; }; -s !ptr=new s; +struct _S { int v; }; +_S !ptr=new _S; while( -arg ) - ptr = new s; + ptr = new _S; while(1) arg = arg+ptr.v; """); @@ -155,9 +172,9 @@ while(1) public void testAntiDeps7() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v = new S; -S t = new S; +struct _S { int f; }; +_S !v = new _S; +_S t = new _S; int i = v.f; while (arg) { v.f = arg; @@ -173,9 +190,9 @@ return i; public void testAntiDeps8() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v = new S; -S t = new S; +struct _S { int f; }; +_S !v = new _S; +_S t = new _S; while(arg) { arg=arg-1; int f = v.f; @@ -193,26 +210,26 @@ return arg; public void testAntiDeps9() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v = new S; -S t = new S; +struct _S { int f; }; +_S !v = new _S; +_S t = new _S; if (arg) { v.f=2; int i=t.f; v.f=i; } -return v; +return v.f; """); code.parse().opto(); - assertEquals("return S;", code.print()); + assertEquals("return 0;", code.print()); } @Test public void testExample2() { CodeGen code = new CodeGen( """ -struct S { int f; }; -S !v = new S; +struct _S { int f; }; +_S !v = new _S; int i = arg; while (arg > 0) { int j = i/3; @@ -223,7 +240,7 @@ while (arg > 0) { return v; """); code.parse().opto(); - //assertEquals("return new S;", code.print()); + //assertEquals("return new _S;", code.print()); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 8069ddaa..85317644 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -1,32 +1,31 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.StopNode; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class Chapter12Test { @Test public void testJig() { CodeGen code = new CodeGen( """ -return 3.14; +return 0; """); code.parse().opto(); - assertEquals("return 3.14;", code.print()); - assertEquals("3.14", Eval2.eval(code, 0)); + assertEquals("return 0;", code.print()); + assertEquals("0", Eval2.eval(code, 0)); } @Test public void testFloat() { CodeGen code = new CodeGen( """ -return 3.14; +flt x = 3.14; +return 0; """); code.parse().opto(); - assertEquals("return 3.14;", code.print()); - assertEquals("3.14", Eval2.eval(code, 0)); + assertEquals("return 0;", code.print()); + assertEquals("0", Eval2.eval(code, 0)); } @Test @@ -41,8 +40,10 @@ while( 1 ) { } return guess; """); + // TODO: One of the problems with returning an OS error code, + // is it chops all results to int. code.parse().opto(); - assertEquals("return Phi(Loop,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)*0.5f));", code.print()); + assertEquals("return Phi(Loop,(flt)arg,((Phi_guess+(ToFloat/Phi_guess))*0.5f));", code.print()); assertEquals("3.0", Eval2.eval(code, 9)); assertEquals("1.414213562373095", Eval2.eval(code, 2)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 4e1db3b4..8bbcf21c 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -10,11 +10,11 @@ public class Chapter13Test { public void testJig() { CodeGen code = new CodeGen( """ -return 3.14; +return 3; """); code.parse().opto(); - assertEquals("return 3.14;", code.print()); - assertEquals("3.14", Eval2.eval(code, 0)); + assertEquals("return 3;", code.print()); + assertEquals("3", Eval2.eval(code, 0)); } @Test @@ -40,22 +40,22 @@ return head.next.i; public void testLinkedList1() { CodeGen code = new CodeGen( """ -struct LLI { LLI? next; int q; }; -LLI? !head = null; +struct _LLI { _LLI? next; int q; }; +_LLI? !head = null; while( arg ) { - LLI !x = new LLI; + _LLI !x = new _LLI; x.next = head; x.q = arg; head = x; arg = arg-1; } if( !head ) return 0; -LLI? next = head.next; +_LLI? next = head.next; if( next==null ) return 1; return next.q; """); code.parse().opto().typeCheck(); - assertEquals("return Phi(Region,0,1,.q);", code.print()); + assertEquals("return Phi(Region,1,.q,0);", code.print()); assertEquals("2", Eval2.eval(code, 3)); } @@ -63,17 +63,17 @@ return next.q; public void testCoRecur() { CodeGen code = new CodeGen( """ -struct int0 { int i; flt0? f; }; -struct flt0 { flt f; int0? i; }; -int0 !i0 = new int0; +struct _int0 { int i; Test._flt0? f; }; +struct _flt0 { flt f; _int0? i; }; +_int0 !i0 = new _int0; i0.i = 17; -flt0 !f0 = new flt0; +_flt0 !f0 = new _flt0; f0.f = 3.14; i0.f = f0; f0.i = i0; return f0.i.f.i.i; """); - code.parse().opto(); + code.parse().opto().typeCheck(); assertEquals("return 17;", code.print()); } @@ -81,8 +81,8 @@ return f0.i.f.i.i; public void testNullRef0() { CodeGen code = new CodeGen( """ -struct N { N? next; int i; }; -N n = new N; +struct _N { _N? next; int i; }; +_N n = new _N; return n.next; """); code.parse().opto(); @@ -94,12 +94,12 @@ return n.next; CodeGen code = new CodeGen( """ struct M { int m; }; -struct N { M next; int i; }; -N n = new N { next = new M; }; +struct N { M next; int i; new N = { M m -> next = m; }; }; +N n = new N(new M); return n.next; """); code.parse().opto(); - assertEquals("return (const)M;", code.print()); + assertEquals("Stop[ return (const)Test.M; return MEM[ 2:.m=0;]; return MEM[ 2:___ 3:___ 4:___ 5:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 6:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:.i=0;]; ]", code.print()); } @Test @@ -107,12 +107,12 @@ return n.next; CodeGen code = new CodeGen( """ struct M { int m; }; -struct N { M next; int i; }; -N n = new N { next = null; }; +struct N { M next; int i; new N = { M m -> next = m; }; }; +N n = new N(null); return n.next; """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type null is not of declared type *M",e.getMessage()); } + catch( Exception e ) { assertEquals("Argument #0 isa null, but must be a *Test.M {i64 !m; }",e.getMessage()); } } @Test @@ -132,20 +132,20 @@ return n.i; public void testNullRef4() { CodeGen code = new CodeGen("return -null-5/null-5;"); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Cannot '//' null",e.getMessage()); } + catch( Exception e ) { assertEquals("Cannot 5 // null",e.getMessage()); } } @Test public void testNullRef5() { CodeGen code = new CodeGen("return null+42;"); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Cannot '+' null",e.getMessage()); } + catch( Exception e ) { assertEquals("Cannot null + 42",e.getMessage()); } } @Test public void testEmpty() { CodeGen code = new CodeGen( """ -struct S{}; +struct _S{}; return 0; """); code.parse().opto(); @@ -160,7 +160,7 @@ return 0; struct S1 { S2? s; }; return new S2; """); - try { code.parse().opto(); fail(); } + try { code.parse().opto().typeCheck(); fail(); } catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); } } @@ -168,12 +168,12 @@ return new S2; public void testForwardRef1() { CodeGen code = new CodeGen( """ -struct S1 { S2? s; }; -struct S2 { int x; }; -return new S1.s=new S2; +struct _S1 { _S2? s; }; +struct _S2 { int x; }; +return (new _S1).s=new _S2; """); code.parse().opto(); - assertEquals("return S2;", code.print()); + assertEquals("return _S2;", code.print()); } @Test @@ -197,9 +197,9 @@ return p1.pi.i + 1; public void testCoRecur2() { CodeGen code = new CodeGen( """ -struct A { B? f0; C? f1; }; A !a = new A; -struct B { C? f0; A? f1; }; B !b = new B; -struct C { A? f0; B? f1; }; C !c = new C; +struct _A { Test._B? f0; Test._C? f1; }; _A !a = new _A; +struct _B { Test._C? f0; Test._A? f1; }; _B !b = new _B; +struct _C { Test._A? f0; Test._B? f1; }; _C !c = new _C; a.f0=b; a.f1=c; b.f0=c; b.f1=a; @@ -209,40 +209,40 @@ return a.f0.f1.f0.f1.f0; """); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("return B;", code._stop.toString()); - assertEquals("B{f0=C},f1=A{f0=$cyclic,f1=$cyclic}}", Eval2.eval(code, 0)); + assertEquals("return Test._B;", code.print()); + assertEquals("Test._B{f0=Test._C{f0=Test._A{f0=$cyclic,f1=$cyclic},f1=$cyclic},f1=$cyclic}", Eval2.eval(code, 0)); } @Test public void testCoRecur3() { CodeGen code = new CodeGen( """ -struct A { L? a; T? b; F? c; }; A !a = new A; -struct B { M? a; U? b; G? c; }; B !b = new B; -struct C { N? a; V? b; H? c; }; C !c = new C; -struct D { O? a; W? b; I? c; }; D !d = new D; -struct E { P? a; X? b; J? c; }; E !e = new E; -struct F { Q? a; Y? b; K? c; }; F !f = new F; -struct G { R? a; Z? b; L? c; }; G !g = new G; -struct H { S? a; A? b; M? c; }; H !h = new H; -struct I { T? a; B? b; N? c; }; I !i = new I; -struct J { U? a; C? b; O? c; }; J !j = new J; -struct K { V? a; D? b; P? c; }; K !k = new K; -struct L { W? a; E? b; Q? c; }; L !l = new L; -struct M { X? a; F? b; R? c; }; M !m = new M; -struct N { Y? a; G? b; S? c; }; N !n = new N; -struct O { Z? a; H? b; T? c; }; O !o = new O; -struct P { A? a; I? b; U? c; }; P !p = new P; -struct Q { B? a; J? b; V? c; }; Q !q = new Q; -struct R { C? a; K? b; W? c; }; R !r = new R; -struct S { D? a; L? b; X? c; }; S !s = new S; -struct T { E? a; M? b; Y? c; }; T !t = new T; -struct U { F? a; N? b; Z? c; }; U !u = new U; -struct V { G? a; O? b; A? c; }; V !v = new V; -struct W { H? a; P? b; B? c; }; W !w = new W; -struct X { I? a; Q? b; C? c; }; X !x = new X; -struct Y { J? a; R? b; D? c; }; Y !y = new Y; -struct Z { K? a; S? b; E? c; }; Z !z = new Z; +struct _A { Test._L? a; Test._T? b; Test._F? c; }; _A !a = new _A; +struct _B { Test._M? a; Test._U? b; Test._G? c; }; _B !b = new _B; +struct _C { Test._N? a; Test._V? b; Test._H? c; }; _C !c = new _C; +struct _D { Test._O? a; Test._W? b; Test._I? c; }; _D !d = new _D; +struct _E { Test._P? a; Test._X? b; Test._J? c; }; _E !e = new _E; +struct _F { Test._Q? a; Test._Y? b; Test._K? c; }; _F !f = new _F; +struct _G { Test._R? a; Test._Z? b; Test._L? c; }; _G !g = new _G; +struct _H { Test._S? a; Test._A? b; Test._M? c; }; _H !h = new _H; +struct _I { Test._T? a; Test._B? b; Test._N? c; }; _I !i = new _I; +struct _J { Test._U? a; Test._C? b; Test._O? c; }; _J !j = new _J; +struct _K { Test._V? a; Test._D? b; Test._P? c; }; _K !k = new _K; +struct _L { Test._W? a; Test._E? b; Test._Q? c; }; _L !l = new _L; +struct _M { Test._X? a; Test._F? b; Test._R? c; }; _M !m = new _M; +struct _N { Test._Y? a; Test._G? b; Test._S? c; }; _N !n = new _N; +struct _O { Test._Z? a; Test._H? b; Test._T? c; }; _O !o = new _O; +struct _P { Test._A? a; Test._I? b; Test._U? c; }; _P !p = new _P; +struct _Q { Test._B? a; Test._J? b; Test._V? c; }; _Q !q = new _Q; +struct _R { Test._C? a; Test._K? b; Test._W? c; }; _R !r = new _R; +struct _S { Test._D? a; Test._L? b; Test._X? c; }; _S !s = new _S; +struct _T { Test._E? a; Test._M? b; Test._Y? c; }; _T !t = new _T; +struct _U { Test._F? a; Test._N? b; Test._Z? c; }; _U !u = new _U; +struct _V { Test._G? a; Test._O? b; Test._A? c; }; _V !v = new _V; +struct _W { Test._H? a; Test._P? b; Test._B? c; }; _W !w = new _W; +struct _X { Test._I? a; Test._Q? b; Test._C? c; }; _X !x = new _X; +struct _Y { Test._J? a; Test._R? b; Test._D? c; }; _Y !y = new _Y; +struct _Z { Test._K? a; Test._S? b; Test._E? c; }; _Z !z = new _Z; a.a=l; a.b=t; a.c=f; b.a=m; b.b=u; b.c=g; @@ -275,7 +275,7 @@ return a.b.c.a.b.c.a.b.c.a.b.c.a.b.c.a.b.c; """); code.parse().opto().typeCheck().GCM().localSched(); - assertEquals("return R;", code._stop.toString()); - assertEquals("R{a=C{a=N},b=V},c=H}},b=K{a=$cyclic,b=D{a=O},b=W},c=I}},c=P}},c=$cyclic}", Eval2.eval(code, 0)); + assertEquals("return Test._R;", code.print()); + assertEquals("Test._R{a=Test._C{a=Test._N{a=Test._Y{a=Test._J{a=Test._U{a=Test._F{a=Test._Q{a=Test._B{a=Test._M{a=Test._X{a=Test._I{a=Test._T{a=Test._E{a=Test._P{a=Test._A{a=Test._L{a=Test._W{a=Test._H{a=Test._S{a=Test._D{a=Test._O{a=Test._Z{a=Test._K{a=Test._V{a=Test._G{a=$cyclic,b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic}", Eval2.eval(code, 0)); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 7af35b56..807905a8 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -92,12 +92,12 @@ return b; public void testRefLoad() { CodeGen code = new CodeGen( """ -struct Foo { u1 b; }; -Foo !f = new Foo; +struct _Foo { u1 b; }; +_Foo !f = new _Foo; f.b = 123; return f.b; """); - code.parse().opto(); + code.parse().opto().typeCheck(); assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 6ba4e2b1..d4790b3b 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -25,14 +25,14 @@ return 3.14; public void testCyclic() { CodeGen code = new CodeGen( """ -struct C { C? l; }; -C !c = new C; +struct _C { _C? l; }; +_C !c = new _C; c.l = c; return c; """); code.parse().opto(); - assertEquals("return C;", code.print()); - assertEquals("C{l=$cyclic}", Eval2.eval(code, 0)); + assertEquals("return Test._C;", code.print()); + assertEquals("Test._C{l=$cyclic}", Eval2.eval(code, 0)); } @Test @@ -97,34 +97,34 @@ return a[0]; public void testBasic4() { CodeGen code = new CodeGen( """ -struct A { int i; }; -A?[] !a = new A?[2]; +struct _A { int i; }; +_A?[] !a = new _A?[2]; return a; """); code.parse().opto(); - assertEquals("return []*A?;", code.print()); - assertEquals("*A {i64 !i; }?[ null,null]", Eval2.eval(code, 0)); + assertEquals("return []*Test._A?;", code.print()); + assertEquals("*Test._A {i64 !i; }?[ null,null]", Eval2.eval(code, 0)); } @Test public void testBasic5() { CodeGen code = new CodeGen( """ -struct S { int x; flt y; }; -// A new S -S !s = new S; s.x=99; s.y = 3.14; +struct _S { int x; flt y; }; +// A new _S +_S !s = new _S; s.x=99; s.y = 3.14; -// Double-d array of Ss. Fill in one row. -S?[]?[] !iss = new S?[]?[2]; -iss[0] = new S?[7]; +// Double-d array of _Ss. Fill in one row. +_S?[]?[] !iss = new _S?[]?[2]; +iss[0] = new _S?[7]; iss[0][2] = s; // Now pull out the filled-in value, with null checks flt rez; -S?[]? is = iss[arg]; +_S?[]? is = iss[arg]; if( !is ) rez = 1.2; else { - S? i = is[2]; + _S? i = is[2]; if( !i ) rez = 2.3; else rez = i.y; } @@ -140,13 +140,13 @@ return rez; public void testBasic6() { CodeGen code = new CodeGen( """ -struct S { int x; flt y; }; -// A new S -S !s = new S; s.x=99; s.y = 3.14; +struct _S { int x; flt y; }; +// A new _S +_S !s = new _S; s.x=99; s.y = 3.14; -// Double-d array of Ss. Fill in one row. -S?[]?[] !iss = new S?[]?[2]; -iss[0] = new S?[7]; +// Double-d array of _Ss. Fill in one row. +_S?[]?[] !iss = new _S?[]?[2]; +iss[0] = new _S?[7]; iss[0][2] = s; // Now pull out the filled-in value, with null checks @@ -167,22 +167,22 @@ return rez;"""); CodeGen code = new CodeGen( """ // Can we define a forward-reference array? -struct Tree { Tree?[]? _kids; }; -Tree !root = new Tree; -root._kids = new Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES???? -root._kids[0] = new Tree; +struct _Tree { _Tree?[]? _kids; }; +_Tree !root = new _Tree; +root._kids = new _Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES???? +root._kids[0] = new _Tree; return root; """); - code.parse().opto(); - assertEquals("return Tree;", code.print()); - assertEquals("Tree{_kids=*Tree {*[]*Tree?? !_kids; }?[ Tree{_kids=null},null]}", Eval2.eval(code, 0)); + code.parse().opto().typeCheck(); + assertEquals("return Test._Tree;", code.print()); + assertEquals("Test._Tree{_kids=*Test._Tree {*[]*Test._Tree?? !_kids; }?[ Test._Tree{_kids=null},null]}", Eval2.eval(code, 0)); } @Test public void testNestedStructAddMemProj() { CodeGen code = new CodeGen( """ -struct S { int a; int[] b; }; +struct _S { int a; int[] b; }; return 0; """); code.parse().opto(); @@ -208,7 +208,7 @@ while( i < ary# - 1 ) { } return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """); - code.parse().opto(); + code.parse().opto().typeCheck(); assertEquals("return (.[]+(.[]*1000));", code.print()); assertEquals("1006", Eval2.eval(code, 4)); } @@ -226,12 +226,12 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 public void testNewNodeInit() { CodeGen code = new CodeGen( """ -struct S {int i; flt f;}; -S !s1 = new S; -S !s2 = new S; +struct _S {int i; flt f;}; +_S !s1 = new _S; +_S !s2 = new _S; s2.i = 3; s2.f = 2.0; -if (arg) s1 = new S; +if (arg) s1 = new _S; return s1.i + s1.f; """); code.parse().opto(); @@ -268,8 +268,8 @@ return is; int[] is = new int[3.14]; return is[1]; """); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Cannot allocate an array with length 3.14",e.getMessage()); } + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Cannot '<<' 3.14",e.getMessage()); } } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 2623fee5..80b0a2bf 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -71,8 +71,8 @@ return y; @Test public void testConstruct0() { CodeGen code = new CodeGen(""" -struct X { int x=3; }; -X z = new X; +struct _X { int x=3; }; +_X z = new _X; return z.x; """); code.parse().opto(); @@ -83,8 +83,8 @@ return z.x; @Test public void testConstruct1() { CodeGen code = new CodeGen(""" -struct X { int !x; }; -X z = new X { x=3; }; +struct _X { int !x; new _X = { int xx -> x=xx; }; }; +_X z = new _X(3); return z.x; """); code.parse().opto(); @@ -95,8 +95,8 @@ return z.x; @Test public void testConstruct2() { CodeGen code = new CodeGen(""" -struct X { int x=3; }; -X z = new X { x = 4; }; +struct _X { int x=3; new _X = { int xx -> x=xx; }; }; +_X z = new _X(4); return z.x; """); code.parse().opto(); @@ -108,20 +108,20 @@ return z.x; @Test public void testStructFinal0() { CodeGen code = new CodeGen(""" -struct Point { int !x, !y; }; -Point p = new Point { x=3; y=4; }; +struct _Point { int !x, !y; new _Point = { int xx, int yy -> x=xx; y=yy; }; }; +_Point p = new _Point(3,4); return p; """); code.parse().opto(); - assertEquals("return Point;", code.print()); - assertEquals("Point{x=3,y=4}", Eval2.eval(code, 0)); + assertEquals("return (const)Test._Point;", code.print()); + assertEquals("Test._Point{x=3,y=4}", Eval2.eval(code, 0)); } @Test public void testStructFinal1() { CodeGen code = new CodeGen(""" -struct Point { int x=3, y=4; }; -val p = new Point { x=5; y=6; }; +struct _Point { int x=3, y=4; new _Point = { int xx, int yy -> x=xx; y=yy; }; }; +val p = new _Point(5,6); p.x++; return p; """); @@ -132,8 +132,8 @@ return p; @Test public void testStructFinal2() { CodeGen code = new CodeGen(""" -struct Point { int x=3, y=4; }; -val p = new Point; +struct _Point { int x=3, y=4; }; +val p = new _Point; p.x++; return p; """); @@ -144,20 +144,20 @@ return p; @Test public void testStructFinal3() { CodeGen code = new CodeGen(""" -struct Point { var x; var y; }; -Point p = new Point; +struct _Point { var x; var y; }; +_Point p = new _Point; p.x++; return p; """); try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); } + catch( Exception e ) { assertEquals("'Test._Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); } } @Test public void testStructFinal4() { CodeGen code = new CodeGen(""" -struct Point { val x=3; val y=4; }; -Point p = new Point; +struct _Point { val x=2; val y=4; }; +_Point p = new _Point; p.x++; return p; """); @@ -168,14 +168,14 @@ return p; @Test public void testStructFinal5() { CodeGen code = new CodeGen(""" -struct Point { var x=3; var y=4; }; -Point !p = new Point; +struct _Point { var x=3; var y=4; }; +_Point !p = new _Point; p.x++; return p; """); code.parse().opto(); - assertEquals("return Point;", code.print()); - assertEquals("Point{x=4,y=4}", Eval2.eval(code, 0)); + assertEquals("return Test._Point;", code.print()); + assertEquals("Test._Point{x=4,y=4}", Eval2.eval(code, 0)); } // Same as the Chapter13 test with the same name, but using the new @@ -184,19 +184,19 @@ return p; public void testLinkedList1() { CodeGen code = new CodeGen( """ -struct LLI { LLI? next; int i; }; -LLI? !head = null; +struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int ii -> next=n; i=ii; }; }; +_LLI? !head = null; while( arg ) { - head = new LLI { next=head; i=arg; }; + head = new _LLI(head,arg); arg = arg-1; } if( !head ) return 0; -LLI? next = head.next; +_LLI? next = head.next; if( !next ) return 1; return next.i; """); code.parse().opto(); - assertEquals("return Phi(Region,0,1,.i);", code.print()); + assertEquals("return Phi(Region,1,.i,0);", code.print()); assertEquals("0", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); assertEquals("2", Eval2.eval(code, 3)); @@ -206,28 +206,27 @@ return next.i; public void testLinkedList2() { CodeGen code = new CodeGen( """ -struct LLI { LLI? next; int i; }; -LLI? !head = null; +struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int a -> + next=n; + int !tmp=a; + while( a > 10 ) { + tmp = tmp + a; + a = a - 1; + } + i=tmp; +}; }; +_LLI? !head = null; while( arg ) { - head = new LLI { - next=head; - // Any old code in the constructor - int !tmp=arg; - while( arg > 10 ) { - tmp = tmp + arg; - arg = arg - 1; - } - i=tmp; - }; + head = new _LLI(head,arg); arg = arg-1; } if( !head ) return 0; -LLI? next = head.next; +_LLI? next = head.next; if( !next ) return 1; return next.i; """); code.parse().opto(); - assertEquals("return Phi(Region,0,1,.i);", code.print()); + assertEquals("return Phi(Region,1,.i,0);", code.print()); assertEquals("0", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); assertEquals("2", Eval2.eval(code, 11)); @@ -237,7 +236,7 @@ return next.i; public void testSquare() { CodeGen code = new CodeGen( """ -struct Square { +struct _Square { flt !side = arg; // Newtons approximation to the square root, computed in a constructor. // The actual allocation will copy in this result as the initial @@ -249,11 +248,11 @@ struct Square { diag = next; } }; -return new Square; +return new _Square; """); code.parse().opto(); - assertEquals("return Square;", code.print()); - assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); - assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); + assertEquals("return Test._Square;", code.print()); + assertEquals("Test._Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); + assertEquals("Test._Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/src/test/java/com/seaofnodes/simple/Chapter17Test.java index 53c23ecd..24daa164 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter17Test.java @@ -79,8 +79,8 @@ return xs[0]; @Test public void testInc5() { CodeGen code = new CodeGen(""" -struct S { u16 x; }; -S !s = new S; +struct _S { u16 x; }; +_S !s = new _S; s.x--; return s.x; """); @@ -132,13 +132,13 @@ return s.x; @Test public void testVar0() { CodeGen code = new CodeGen("var d; return d;"); try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Syntax error, expected =expression: ;",e.getMessage()); } + catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); } } @Test public void testVar1() { CodeGen code = new CodeGen("val d; return d;"); try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Syntax error, expected =expression: ;",e.getMessage()); } + catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); } } @Test public void testVar2() { @@ -156,14 +156,14 @@ return s.x; } @Test public void testVar4() { - CodeGen code = new CodeGen("struct S{int x;}; S? s; s=new S; s.x++; return s.x; // Ok, no initializer so x is mutable "); + CodeGen code = new CodeGen("struct _S{int x;}; _S? s; s=new _S; s.x++; return s.x; // Ok, no initializer so x is mutable "); code.parse().opto(); assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); } @Test public void testVar5() { - CodeGen code = new CodeGen("struct S{int x;}; S? s; s=new S{x=3;}; s.x++; return s.x; // Ok, no initializer so x is mutable "); + CodeGen code = new CodeGen("struct _S{int x; new _S={ int xx -> x=xx; };}; _S? s; s=new _S(3); s.x++; return s.x; // Ok, no initializer so x is mutable "); code.parse().opto(); assertEquals("return 4;", code.print()); assertEquals("4", Eval2.eval(code, 0)); @@ -176,20 +176,20 @@ return s.x; } @Test public void testVar7() { - CodeGen code = new CodeGen("struct S{int x;}; val s = new S{x=3;}; s.x++; return s.x; // Error initializer so x is immutable "); + CodeGen code = new CodeGen("struct S{int x; new S={ int xx -> x=xx; };}; val s = new S(3); s.x++; return s.x; // Error initializer so x is immutable "); try { code.parse().opto().typeCheck(); fail(); } catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } } @Test public void testVar8() { - CodeGen code = new CodeGen("struct S{int x;}; S !s=new S; s.x++; return s.x; // Ok, has '!' so s.x is mutable "); + CodeGen code = new CodeGen("struct _S{int x;}; _S !s=new _S; s.x++; return s.x; // Ok, has '!' so s.x is mutable "); code.parse().opto(); assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); } @Test public void testVar9() { - CodeGen code = new CodeGen("struct S{int x;}; var s=new S; s.x++; return s.x; // Ok, has var so s.x is mutable "); + CodeGen code = new CodeGen("struct _S{int x;}; var s=new _S; s.x++; return s.x; // Ok, has var so s.x is mutable "); code.parse().opto(); assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); @@ -207,8 +207,8 @@ struct Bar { int x; }; Bar !bar = new Bar; bar.x = 3; // Ok, bar is mutable -struct Foo { Bar? !bar; int y; }; -Foo !foo = new Foo { bar = bar; }; +struct Foo { Bar? !bar; int y; new Foo = { Bar? b -> bar = b; }; }; +Foo !foo = new Foo(bar); foo.bar = bar; // Ok foo is mutable foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable @@ -222,12 +222,12 @@ return xfoo.bar.x; @Test public void testVar12() { CodeGen code = new CodeGen(""" -struct Bar { int x; }; -Bar !bar = new Bar; +struct _Bar { int x; }; +_Bar !bar = new _Bar; bar.x = 3; // Ok, bar is mutable -struct Foo { Bar? !bar; int y; }; -Foo !foo = new Foo; +struct _Foo { _Bar? !bar; int y; }; +_Foo !foo = new _Foo; foo.bar = bar; // Ok bar is mutable foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable @@ -256,41 +256,41 @@ int i,i++; public void testVar14() { CodeGen code = new CodeGen(""" struct B {}; -struct A { B b; }; -A x = new A { +struct A { B b; new A = { -> return b; // read before init b = new B; -}; +}; }; +A x = new A; """); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Cannot read uninitialized field 'b'",e.getMessage()); } + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("Field 'b' might not be initialized",e.getMessage()); } } @Test public void testVar15() { CodeGen code = new CodeGen(""" struct B {}; -struct A { B b; }; -return new A { +struct A { B b; new A = { -> if (arg) b = new B; // Constructor ends with partial init of b -}.b; +}; }; +return (new A).b; """); - try { code.parse(); fail(); } - catch( Exception e ) { assertEquals("'A' is not fully initialized, field 'b' needs to be set in a constructor",e.getMessage()); } + try { code.parse().opto().typeCheck(); fail(); } + catch( Exception e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); } } @Test public void testVar16() { CodeGen code = new CodeGen( """ -struct S{}; +struct S{ new S = { -> x = 2; }; }; val x = 1; -val s = new S{x = 2;}; +val s = new S; return x; """ ); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } + catch( Exception e ) { assertEquals("Undefined name 'x'",e.getMessage()); } } @Test @@ -309,7 +309,7 @@ return me; """ ); try { code.parse(); fail(); } - catch( Exception e ) { assertEquals("Syntax error, expected =expression: ;",e.getMessage()); } + catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); } } // --------------------------------------------------------------- @@ -337,21 +337,22 @@ return arg ? 0 : arg; @Test public void testTrinary2() { CodeGen code = new CodeGen(""" -struct Bar { int x; }; -var b = arg ? new Bar : null; +struct _Bar { int x; }; +var b = arg ? new _Bar : null; return b ? b.x++ + b.x++ : -1; """); code.parse().opto(); assertEquals("return Phi(Region,((.x<<1)+1),-1);", code.print()); assertEquals("-1", Eval2.eval(code, 0)); - assertEquals("1", Eval2.eval(code, 1)); + + assertEquals("1", Eval2.eval(code, 1)); } @Test public void testTrinary3() { CodeGen code = new CodeGen(""" -struct Bar { int x; }; -var b = arg ? new Bar; +struct _Bar { int x; }; +var b = arg ? new _Bar; return b ? b.x++ + b.x++ : -1; """); code.parse().opto(); @@ -364,12 +365,12 @@ return b ? b.x++ + b.x++ : -1; public void testTrinary4() { // This test case will benefit from an unzipping transformation CodeGen code = new CodeGen(""" -struct Bar { Bar? next; int x; }; -var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; }; +struct _Bar { _Bar? next; int x; new _Bar = { _Bar? n, int xx -> next=n; x=xx; }; }; +var b = arg ? new _Bar((arg==2) ? new _Bar(null,2), 1); return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" """); code.parse().opto(); - assertEquals("return Phi(Region,.x,0);", code.print()); + assertEquals("return Phi(Region,.x,0,.x);", code.print()); assertEquals("0", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); assertEquals("2", Eval2.eval(code, 2)); @@ -394,7 +395,7 @@ struct S{}; return arg ? 7 : new S; """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("No common type amongst int and reference",e.getMessage()); } + catch( Exception e ) { assertEquals("No common type amongst 7 and *Test.S {}",e.getMessage()); } } // --------------------------------------------------------------- @@ -469,7 +470,7 @@ return i; for(;;arg++;) {} """); try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Syntax error, expected Unexpected code after expression: ;",e.getMessage()); } + catch( Exception e ) { assertEquals("Syntax error, expected `Unexpected code after expression` but found `;`",e.getMessage()); } } @@ -478,39 +479,40 @@ for(;;arg++;) {} @Test public void testForward0() { CodeGen code = new CodeGen(""" -struct A{ +struct _A { B? f1; B? f2; }; -return new A; +return new _A; """); code.parse().opto(); - assertEquals("return A;", code.print()); - assertEquals("A{f1=null,f2=null}", Eval2.eval(code,0)); + assertEquals("return Test._A;", code.print()); + assertEquals("Test._A{f1=null,f2=null}", Eval2.eval(code,0)); } @Test public void testForward1() { CodeGen code = new CodeGen(""" -struct A { +struct _A { B?[]? nil_array_of_b; B?[] array_of_b; + new _A = { B?[] bs -> array_of_b = bs; }; }; -return new A{array_of_b = new B?[0]; }.array_of_b; +return new _A(new B?[0]).array_of_b; """); code.parse().opto(); - assertEquals("return (const)[]*B?;", code.print()); - assertEquals("*B {... }?[]", Eval2.eval(code,0)); + assertEquals("return []*B?;", code.print()); + assertEquals("*B {}?[]", Eval2.eval(code,0)); } // --------------------------------------------------------------- @Test public void testLinkedList2() { CodeGen code = new CodeGen(""" -struct LLI { LLI? next; int i; }; -LLI? !head = null; +struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int ii -> next=n; i=ii; }; }; +_LLI? !head = null; while( arg-- ) - head = new LLI { next=head; i=arg; }; + head = new _LLI(head,arg); int sum=0; var ptr = head; // A read-only ptr, to be assigned from read-only next fields for( ; ptr; ptr = ptr.next ) diff --git a/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 00b6292e..b65e101b 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -16,7 +16,7 @@ public class Chapter18Test { return 0; """); code.driver(Phase.LocalSched); - assertEquals("return 0;", code._stop.toString()); + assertEquals("return 0;", code.print()); assertEquals("0", Eval2.eval(code, 2)); } @@ -34,7 +34,7 @@ while(arg--) { return a; """); code.parse().opto().typeCheck(); - assertEquals("return Phi(Loop,1,Phi(Loop,2,Phi_a));", code._stop.toString()); + assertEquals("return Phi(Loop,1,Phi(Loop,2,Phi_a));", code.print()); assertEquals("1", Eval2.eval(code, 0)); assertEquals("2", Eval2.eval(code, 1)); assertEquals("1", Eval2.eval(code, 2)); @@ -51,7 +51,7 @@ return a; return x2; """); code.parse().opto(); - assertEquals("return null;", code._stop.toString()); + assertEquals("return null;", code.print()); assertEquals("null", Eval2.eval(code, 0 ) ); } @@ -64,31 +64,50 @@ return x2; }; """); code.parse().opto(); - assertEquals("return (Parm_x(sq,i64)*x);", code._stop.toString()); - //assertEquals("{ int -> int #1}", Eval2.eval(code, 3)); + assertEquals("Stop[ return { sq}; return (Parm_x(sq,i64)*x); ]", code.print()); + assertEquals("{ ptr i64 -> i64 #[ 3]}", Eval2.eval(code, 3)); } @Test public void testFcn1() { CodeGen code = new CodeGen( """ -var sq = { int x -> +var _sq = { int x -> x*x; }; -return sq(arg)+sq(3); +return _sq(arg)+_sq(3); """); code.driver(Phase.LocalSched); - assertEquals("Stop[ return (#2+#2); return (Parm_x(sq,i64)*x); ]", code._stop.toString()); + assertEquals("return ((arg*arg)+9);", code.print()); assertEquals("13", Eval2.eval(code, 2)); } // Function scope test @Test - public void testFcn2() { + public void testFcn2A() { CodeGen code = new CodeGen( """ -int cnt=1; -return { -> cnt; }; +int cnt=1; // Global scope; exactly one of these +return { -> cnt++; }; // Escape global-scope variable is OK +"""); + code.parse().opto(); + assertEquals("Stop[ return { ptr -> i64 #[ 3]}; return .cnt; ]", code.print()); + assertEquals("{ ptr -> i64 #[ 3]}", Eval2.eval(code, 0)); + } + + // Function scope test + @Test + public void testFcn2B() { + CodeGen code = new CodeGen( +""" +val ctr = { -> + int cnt; // Function-local var + return { -> cnt=cnt+1; cnt; }; // Escape cnt out of scope +}; +val A = ctr(); // Make a counter +val B = ctr(); // Make a counter +A(); // Increment counter A +return A()*10 + B(); // Incr A and B; result: 2*10+1 == 21 """); try { code.parse().opto(); fail(); } catch( Exception e ) { assertEquals("Variable 'cnt' is out of function scope and must be a final constant",e.getMessage()); } @@ -103,7 +122,7 @@ val cnt=2; return { -> cnt; }(); """); code.parse().opto(); - assertEquals("return 2;", code._stop.toString()); + assertEquals("return 2;", code.print()); assertEquals("2", Eval2.eval(code, 0)); } @@ -116,7 +135,7 @@ var fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; return fcn(3); """); code.parse().opto(); - assertEquals("Stop[ return #2; return 9; return 6; ]", code._stop.toString()); + assertEquals("Stop[ return #2; return (Parm_x($fun3,i64,3)*x); return (Parm_x($fun4,i64,3)<<1); ]", code.print()); assertEquals("6", Eval2.eval(code, 0)); assertEquals("9", Eval2.eval(code, 1)); } @@ -124,9 +143,11 @@ return fcn(3); // Recursive factorial test @Test public void testFcn5() { - CodeGen code = new CodeGen("val fact = { int x -> x <= 1 ? 1 : x*fact(x-1); }; return fact(arg);"); + CodeGen code = new CodeGen("val _fact = { int x -> x <= 1 ? 1 : x*_fact(x-1); }; return _fact(arg);"); code.parse().opto().typeCheck(); - assertEquals("Stop[ return #2; return Phi(Region,1,(Parm_x(fact,i64)*#2)); ]", code._stop.toString()); + String ir = code.print(); + assertTrue(ir.equals("Stop[ return Phi(Region,1,(arg*#2)); return Phi(Region,1,(Parm_x(_fact,(arg-1),(x-1))*#2)); ]") || + ir.equals("Stop[ return Phi(Region,1,(arg*Phi(Region,1,((arg-1)*#2)))); return Phi(Region,1,(Parm_x(_fact,(Sub-1),(x-1))*#2)); ]")); assertEquals( "1", Eval2.eval(code, 0)); assertEquals( "1", Eval2.eval(code, 1)); assertEquals( "2", Eval2.eval(code, 2)); @@ -134,31 +155,55 @@ return fcn(3); assertEquals("24", Eval2.eval(code, 4)); } + // The multiply initially sees one integer input and one unresolved + // The multiply initially sees one integer input and one unresolvedfcn8 + // recursive result, but the floating base case requires a floating mode. + @Test + public void testFcn5Float() { + CodeGen code = new CodeGen("val _fact = { int x -> x <= 1 ? 1.0 : x*_fact(x-1); }; return _fact(arg);"); + code.parse().opto().typeCheck(); + assertEquals("1.0", Eval2.eval(code, 0)); + assertEquals("1.0", Eval2.eval(code, 1)); + assertEquals("2.0", Eval2.eval(code, 2)); + assertEquals("6.0", Eval2.eval(code, 3)); + assertEquals("24.0", Eval2.eval(code, 4)); + } + + @Test + public void testFcn5Minus() { + CodeGen code = new CodeGen("val _neg = { int x -> x ? -_neg(x-1) : 1; }; return _neg(arg);"); + code.parse().opto().typeCheck(); + assertEquals( "1", Eval2.eval(code, 0)); + assertEquals("-1", Eval2.eval(code, 1)); + assertEquals( "1", Eval2.eval(code, 2)); + assertEquals("-1", Eval2.eval(code, 3)); + } + @Test public void testFcn6() { CodeGen code = new CodeGen( """ -struct S { int i; }; -val newS = { int x -> return new S { i=x; }; }; -return newS(1).i; +struct _S { int i; new _S = { int x -> i=x; }; }; +val _newS = { int x -> return new _S(x); }; +return _newS(1).i; """); code.parse().opto().typeCheck(); - assertEquals("return 1;", code._stop.toString()); + assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); } - // Double forward reference + // Only forward refs to direct function calls allowed @Test public void testFcn7() { CodeGen code = new CodeGen( """ -if( arg ? f : g ) return 1; -val f = {->1;}; -val g = {->2;}; +val _f = {->1;}; +val _g = {->2;}; +if( arg ? _f : _g ) return 1; return 2; """); code.parse().opto().typeCheck(); - assertEquals("return 1;", code._stop.toString()); + assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 0)); } @@ -166,17 +211,17 @@ return 2; public void testFcn8() { CodeGen code = new CodeGen( """ -{int -> int}? i2i = null; -var id = {{int->int} f-> return f;}; +{int -> int}? _i2i = null; +var _id = {{int->int} f-> return f;}; for(;;) { - if (i2i) return i2i(arg); - var x = {int i-> return i;}; - arg = x(3); - i2i = id(x); + if (_i2i) return _i2i(arg); + var _x = {int i-> return i;}; + arg = _x(3); + _i2i = _id(_x); } """); code.driver(Phase.LocalSched); - assertEquals("Stop[ return #2; return Parm_i(x,i64); ]", code._stop.toString()); + assertEquals("return Phi(Loop,arg,3);", code.print()); assertEquals("3", Eval2.eval(code, 0)); } @@ -192,8 +237,8 @@ for(;;) { } """); code.driver(Phase.LocalSched); - assertEquals("return Top;", code._stop.toString()); - assertEquals("null", Eval2.eval(code, 0)); + assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print()); + assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, never exits } @@ -201,23 +246,23 @@ for(;;) { public void testFcn10() { CodeGen code = new CodeGen( """ -struct Person { +struct _Person { int age; }; -val fcn = { Person?[] ps, int x -> +val _fcn = { _Person?[] ps, int x -> val tmp = ps[x]; if( ps[x] ) ps[x].age++; }; -var ps = new Person?[2]; -ps[0] = new Person; -ps[1] = new Person; -return fcn(ps,1); +var ps = new _Person?[2]; +ps[0] = new _Person; +ps[1] = new _Person; +return _fcn(ps,1); """); code.driver(Phase.LocalSched); - assertEquals("return 0;", code._stop.toString()); + assertEquals("return .age;", code.print()); assertEquals("0", Eval2.eval(code, 0)); } @@ -252,7 +297,7 @@ for(;;) { return 0; """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Might be null calling { i64 -> i64 #21}?",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null calling { ptr i64 -> i64 #[ 3]}?",e.getMessage()); } } @@ -264,7 +309,7 @@ val f = { int i, int j -> return i+j; }; return f(); """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Expecting 2 arguments, but found 0",e.getMessage()); } + catch( Exception e ) { assertEquals("Expecting 3 arguments, but found 1",e.getMessage()); } } @@ -276,16 +321,14 @@ struct S { {int} f = { -> x(); return 0; }; // Do not let fref x be a field }; val x = { -> return 1; }; -S? s = null; -for(;;) { - if (s) return s.x; -} +S? s = new S; +return s.x; """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Accessing unknown field 'x' from '*S'",e.getMessage()); } + catch( Exception e ) { assertEquals("Accessing unknown field 'x' from '*Test.S'",e.getMessage()); } } - @Test + @Test @Ignore public void testErr5() { CodeGen code = new CodeGen( """ @@ -308,8 +351,8 @@ val is_even = { int x -> x ? is_odd (x-1) : true ; }; val is_odd = { int x -> x ? is_even(x-1) : false; }; return is_even(arg); """); - code.parse().opto(); - assertEquals("Stop[ return is_even( arg); return Phi(Region,Phi(Region,is_even( ((Parm_x(is_even,int,arg,Sub)-1)-1)),0),1); ]", code._stop.toString()); + code.parse().opto().typeCheck(); + assertEquals("Stop[ return #2; return Phi(Region,0,1,#2); ]", code.print()); assertEquals("1", Eval2.eval(code, 0)); assertEquals("0", Eval2.eval(code, 1)); assertEquals("1", Eval2.eval(code, 2)); @@ -331,7 +374,7 @@ for(;;) { } """); code.parse().opto().typeCheck().GCM(); - assertEquals("return 0;", code._stop.toString()); + assertEquals("return 0;", code.print()); assertEquals("0", Eval2.eval(code, 0)); } @@ -340,15 +383,15 @@ for(;;) { public void testInline() { CodeGen code = new CodeGen( """ -{int->int}?! i2i = {int i->return i;}; -{{int->int}->{int->int}}! f2f = {{int->int} f->return f;}; -val o = i2i; -if (arg) i2i = null; -if (i2i) return i2i(arg); -return f2f(o)(1); +{int->int}?! _i2i_noInline = {int i->return i;}; +{{int->int}->{int->int}}! _f2f = {{int->int} f->return f;}; +val _o = _i2i_noInline; +if (arg) _i2i_noInline = null; +if (_i2i_noInline) return _i2i_noInline(arg); +return _f2f(_o)(1); """); code.driver(Phase.LocalSched); - assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(i2i.o,i64); ]", code._stop.toString()); + assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(_i2i_noInline,i64); ]", code.print()); assertEquals("1", Eval2.eval(code, 2)); } @@ -356,15 +399,15 @@ return f2f(o)(1); public void testOperField() { CodeGen code = new CodeGen( """ -struct Person { +struct _Person { int coffee_count; }; -Person !p = new Person; +_Person !p = new _Person; p.coffee_count += 1; return p.coffee_count; """); code.driver(Phase.LocalSched); - assertEquals("return 1;", code._stop.toString()); + assertEquals("return 1;", code.print()); assertEquals("1", Eval2.eval(code, 2)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 6d751946..eaf76041 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -3,8 +3,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen.Phase; import com.seaofnodes.simple.codegen.CodeGen; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import org.junit.Test; import static org.junit.Assert.*; @@ -16,18 +14,19 @@ public class Chapter19Test { return 0; """); code.parse().opto().typeCheck(); - assertEquals("return 0;", code._stop.toString()); + assertEquals("return 0;", code.print()); assertEquals("0", Eval2.eval(code, 2)); } @Test - public void testString() throws IOException { + public void testString() { String src = """ struct String { - u8[] cs; + u8[~] cs; int _hashCode; + new String = { u8[~] s -> cs=s; }; }; // Compare two Strings @@ -40,12 +39,6 @@ val equals = { String self, String s -> return true; }; -// Return the String hashCode (cached, and never 0) -val hashCode = { String self -> - self._hashCode - ? self._hashCode - : (self._hashCode = _hashCodeString(self)); -}; val _hashCodeString = { String self -> int hash=0; @@ -54,107 +47,141 @@ val _hashCodeString = { String self -> if( !hash ) hash = 123456789; return hash; }; + +// Return the String hashCode (cached, and never 0) +val hashCode = { String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; + +hashCode(new String("Hello, World!")); """; - CodeGen code = new CodeGen(src).parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return Phi(Region,1,0,0,1); return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code._stop.toString()); - //assertEquals("-4898613127354160978", Eval2.eval(code, 2)); + CodeGen code = new CodeGen(src).driver(Phase.LocalSched); + assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MEM[ 2:___ 3:___ 4:.cs=Parm_s(Test.String.String,*[]u8); 5:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:._hashCode=0;]; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(((Phi_hash<<5)-Phi_hash)+.[])))); ]", code.print()); + assertEquals("4029215624828139541", Eval2.eval(code, 2)); + } + + @Test + public void testTernaryStoreCall() { + String src = +""" +struct String { + u8[~] cs; + int _hashCode; + new String = { u8[~] s -> cs=s; }; +}; + +val f = { String self -> 7; }; + +val hashCode = { String self -> + self._hashCode + ? 3 + : (self._hashCode = f(self)); +}; + +hashCode(new String("Hello")); +"""; + CodeGen code = new CodeGen(src).driver(Phase.LocalSched); + assertFalse(code.print().contains("return Top")); + assertEquals("7", Eval2.eval(code, 2)); } @Test public void testBasic0() { CodeGen code = new CodeGen("return 0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return 0;", code._stop.toString()); + assertEquals("return 0;", code.print()); } @Test public void testBasic1() { CodeGen code = new CodeGen("return arg+1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (inc,arg);", code._stop.toString()); + assertEquals("return (inc,arg);", code.print()); } @Test public void testBasic2() { CodeGen code = new CodeGen("return -17;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return -17;", code._stop.toString()); + assertEquals("return -17;", code.print()); } @Test public void testBasic3() { CodeGen code = new CodeGen("return arg==1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (set==,(cmp,arg));", code._stop.toString()); + assertEquals("return (set==,(cmp,arg));", code.print()); } @Test public void testBasic4() { CodeGen code = new CodeGen("return arg<<1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (shli,arg);", code._stop.toString()); + assertEquals("return (shli,arg);", code.print()); } @Test public void testBasic5() { CodeGen code = new CodeGen("return arg >> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (sari,arg);", code._stop.toString()); + assertEquals("return (sari,arg);", code.print()); } @Test public void testBasic6() { CodeGen code = new CodeGen("return arg >>> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (shri,arg);", code._stop.toString()); + assertEquals("return (shri,arg);", code.print()); } @Test public void testBasic7() { CodeGen code = new CodeGen("return arg / 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (div,arg,2);", code._stop.toString()); + assertEquals("return (div,arg,2);", code.print()); } @Test public void testBasic8() { CodeGen code = new CodeGen("return arg * 6;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (muli,arg);", code._stop.toString()); + assertEquals("return (muli,arg);", code.print()); } @Test public void testBasic9() { CodeGen code = new CodeGen("return arg & 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (andi,arg);", code._stop.toString()); + assertEquals("return (andi,arg);", code.print()); } @Test public void testBasic10() { CodeGen code = new CodeGen("return arg | 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (ori,arg);", code._stop.toString()); + assertEquals("return (ori,arg);", code.print()); } @Test public void testBasic11() { CodeGen code = new CodeGen("return arg ^ 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (xori,arg);", code._stop.toString()); + assertEquals("return (xori,arg);", code.print()); } @Test public void testBasic12() { CodeGen code = new CodeGen("return arg + 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (addf,(cvtf,arg),2.0f);", code._stop.toString()); + assertEquals("return (addf,(cvtf,arg),2.0f);", code.print()); } @Test public void testBasic13() { CodeGen code = new CodeGen("return arg - 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (subf,(cvtf,arg),2.0f);", code._stop.toString()); + assertEquals("return (subf,(cvtf,arg),2.0f);", code.print()); } @Test public void testBasic14() { CodeGen code = new CodeGen("return arg * 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (mulf,(cvtf,arg),2.0f);", code._stop.toString()); + assertEquals("return (mulf,(cvtf,arg),2.0f);", code.print()); } @Test public void testBasic15() { CodeGen code = new CodeGen("return arg / 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (mulf,(cvtf,arg),0.5f);", code._stop.toString()); + assertEquals("return (mulf,(cvtf,arg),0.5f);", code.print()); } @Test @@ -164,7 +191,7 @@ val _hashCodeString = { String self -> int arg1 = arg + 1; return arg1 / arg;"""); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (div,(inc,arg),arg);", code._stop.toString()); + assertEquals("return (div,(inc,arg),arg);", code.print()); } @Test @@ -175,7 +202,7 @@ int arg1 = arg + 1; return arg1 * arg; """); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (mul,(inc,arg),arg);", code._stop.toString()); + assertEquals("return (mul,(inc,arg),arg);", code.print()); } @Test @@ -185,7 +212,7 @@ int a = arg; return a + 2.0; """ ).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (addf,(cvtf,arg),2.0f);", code._stop.toString()); + assertEquals("return (addf,(cvtf,arg),2.0f);", code.print()); } @Test @@ -234,10 +261,10 @@ return sum;"""); public void testAlloc1() { CodeGen code = new CodeGen( """ -struct S { int a; S? c; }; -return new S;"""); +struct _S { int a; _S? c; }; +return new _S;"""); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return S;", code.print()); + assertEquals("return Test._S;", code.print()); } @Test @@ -325,7 +352,7 @@ return A[1]; public void testNewton() throws IOException { String src = """ -val test_sqrt = { flt x -> +val _test_sqrt = { flt x -> flt epsilon = 1e-15; flt guess = x; while( 1 ) { @@ -335,15 +362,16 @@ val test_sqrt = { flt x -> guess = next; } }; -flt farg = arg; return test_sqrt(farg); +flt farg = arg; +return _test_sqrt(farg); """; CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return Phi(Loop,(cvtf,arg),(mulf,(addf,(divf,cvtf,Phi_guess),Phi_guess),0.5f));", code.print()); + assertEquals("return Phi(Loop,(cvtf,arg),(mulf,(addf,Phi_guess,(divf,cvtf,Phi_guess)),0.5f));", code.print()); }; @Test - public void sieveOfEratosthenes() throws IOException { + public void sieveOfEratosthenes() { String src = """ val sieve = { int N -> @@ -378,8 +406,8 @@ val sieve = { int N -> }; """; CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return []u32;", code.print()); - //assertEquals("u32[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20)); + assertEquals("Stop[ return { sieve}; return []u32; ]", code.print()); + //assertEquals("{ ptr i64 -> *[]u32 #[ 3]}",Eval2.eval(code, 20)); } @@ -387,21 +415,21 @@ val sieve = { int N -> public void testFcn1() { CodeGen code = new CodeGen( """ -val fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; -return fcn(2)*10 + fcn(3); +val _fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; +return _fcn(2)*10 + _fcn(3); """); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,Parm_x($fun21,[2-3]),x); return (shli,Parm_x($fun22,[2-3])); ]", code.print()); + assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,Parm_x($fun3,[2-3]),x); return (shli,Parm_x($fun4,[2-3])); ]", code.print()); } @Test public void testFcn2() { CodeGen code = new CodeGen( """ -val sq = { int x -> x*x; }; -return sq(arg) + sq(3); +val _sq = { int x -> x*x; }; +return _sq(arg) + _sq(3); """); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,i64),x); ]", code.print()); + assertEquals("return (addi,(mul,arg,arg));", code.print()); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 02de57a2..17b0fd0a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; -import java.io.IOException; import org.junit.Test; import static org.junit.Assert.*; @@ -11,7 +10,7 @@ public class Chapter20Test { public void testJig() { CodeGen code = new CodeGen("return 0;"); code.parse().opto().typeCheck(); - assertEquals("return 0;", code._stop.toString()); + assertEquals("return 0;", code.print()); assertEquals("0", Eval2.eval(code, 2)); } @@ -20,10 +19,10 @@ public class Chapter20Test { code.driver(CodeGen.Phase.RegAlloc,cpu,os); int delta = spills>>3; if( delta==0 ) delta = 1; - if( spills != -1 ) + if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); if( stop != null ) - assertEquals(stop, code._stop.toString()); + assertEquals(stop, code.print()); } private static void testAllCPUs( String src, int spills, String stop ) { @@ -50,41 +49,40 @@ public class Chapter20Test { String src = """ // Newtons approximation to the square root -val sqrt = { int x -> +val _sqrt = { int x -> int guess = x; while( 1 ) { - int next = (x/guess + guess)/2; + int next = (x/guess + guess)>>1; if( next == guess ) return guess; guess = next; } }; -int cast_int = arg+2; -return sqrt(arg) + sqrt(cast_int); +return _sqrt(arg) + _sqrt(arg+2); """; - testCPU(src,"x86_64_v2", "Win64" ,55,null); + testCPU(src,"x86_64_v2", "win64" ,38,null); testCPU(src,"riscv" , "SystemV",19,null); testCPU(src,"arm" , "SystemV",19,null); } @Test - public void testNewtonFloat() throws IOException { + public void testNewtonFloat() { String src = """ -val test_sqrt = { flt x -> +val _test_sqrt_noInline = { flt x -> flt epsilon = 1e-15; flt guess = x; while( 1 ) { flt next = (x/guess + guess)/2; - if( guess-epsilon <= next & next <= guess+epsilon ) return guess; - //if( guess==next ) return guess; + if( guess-epsilon <= next && next <= guess+epsilon ) + return guess; guess = next; } }; -flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0); +flt farg = arg; return _test_sqrt_noInline(farg) + _test_sqrt_noInline(farg+2.0); """; - testCPU(src,"x86_64_v2", "SystemV",39,null); - testCPU(src,"riscv" , "SystemV",17,null); - testCPU(src,"arm" , "SystemV",18,null); + testCPU(src,"x86_64_v2", "SystemV",65,null); + testCPU(src,"riscv" , "SystemV",35,null); + testCPU(src,"arm" , "SystemV",35,null); } @Test @@ -98,7 +96,7 @@ flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0); @Test - public void testArray1() throws IOException { + public void testArray1() { String src = """ int[] !ary = new int[arg]; @@ -111,13 +109,13 @@ for( int i=0; i return hash; }; """; - testCPU(src,"x86_64_v2", "SystemV", 9,null); - testCPU(src,"riscv" , "SystemV", 3,null); - testCPU(src,"arm" , "SystemV", 3,null); + testCPU(src,"x86_64_v2", "SystemV",18,null); + testCPU(src,"riscv" , "SystemV", 5,null); + testCPU(src,"arm" , "SystemV", 4,null); } @Test public void testCast() { String src = "struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;"; - testCPU(src,"x86_64_v2", "SystemV",2,null); - testCPU(src,"riscv" , "SystemV",2,null); - testCPU(src,"arm" , "SystemV",2,null); + testCPU(src,"x86_64_v2", "SystemV",3,null); + testCPU(src,"riscv" , "SystemV",3,null); + testCPU(src,"arm" , "SystemV",3,null); } @Test @@ -172,15 +170,15 @@ val _hashCodeString = { String self -> @Test public void testFlags1() { String src = """ -bool b1 = arg == 1; +bool _b1 = arg == 1; bool b2 = arg == 2; -if (b2) if (b1) return 1; -if (b1) return 2; +if (b2) if (_b1) return 1; +if (_b1) return 2; return 0; """; - testCPU(src,"x86_64_v2", "SystemV",0,"return Phi(Region,1,2,0);"); - testCPU(src,"riscv" , "SystemV",0,"return Phi(Region,1,2,0);"); - testCPU(src,"arm" , "SystemV",0,"return Phi(Region,1,2,0);"); + testCPU(src,"x86_64_v2", "SystemV",0,"return Phi(Region,2,0,1);"); + testCPU(src,"riscv" , "SystemV",0,"return Phi(Region,2,0,1);"); + testCPU(src,"arm" , "SystemV",0,"return Phi(Region,2,0,1);"); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/src/test/java/com/seaofnodes/simple/Chapter21Test.java index ec55735e..e4d49e7f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -5,9 +5,6 @@ import com.seaofnodes.simple.node.cpus.arm.arm; import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.util.SB; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -17,24 +14,24 @@ public class Chapter21Test { public void testJig() throws IOException { String src = """ -struct s0 { +struct _s0 { bool v1; i16 v2; int v3; i8 v4; byte v5; }; -while(new s0.v3) - while(new s0.v5<>3; if( delta==0 ) delta = 1; - if( spills != -1 ) + if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); if( stop != null ) - assertEquals(stop, code._stop.toString()); + assertEquals(stop, code.print()); } @@ -58,14 +55,14 @@ return new s0.v1; } @Test public void testInfinite() { - String src = "struct S { int i; }; S !s = new S; while(1) s.i++; return s.i;"; + String src = "struct _S { int i; }; _S !s = new _S; while(1) s.i++; return s.i;"; testCPU(src,"x86_64_v2", "SystemV",0,"return Top;"); testCPU(src,"riscv" , "SystemV",2,"return Top;"); testCPU(src,"arm" , "SystemV",2,"return Top;"); } @Test - public void testArray1() throws IOException { + public void testArray1() { String src = """ int[] !ary = new int[arg]; @@ -83,13 +80,13 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 } @Test - public void testAntiDeps1() throws IOException { + public void testAntiDeps1() { String src = """ -struct S { int f; }; -var v0 = new S; -S? v1; -if (arg) v1 = new S; +struct _S { int f; }; +var v0 = new _S; +_S? v1; +if (arg) v1 = new _S; if (v1) { v0.f = v1.f; } else { @@ -97,13 +94,13 @@ if (v1) { } return v0; """; - testCPU(src,"x86_64_v2", "SystemV", 7,"return mov(mov(S));"); - testCPU(src,"riscv" , "SystemV",10,"return mov(mov(S));"); - testCPU(src,"arm" , "SystemV",10,"return mov(mov(S));"); + testCPU(src,"x86_64_v2", "SystemV",10,"return mov(mov(Test._S));"); + testCPU(src,"riscv" , "SystemV",10,"return mov(mov(Test._S));"); + testCPU(src,"arm" , "SystemV",10,"return mov(mov(Test._S));"); } @Test - public void testString() throws IOException { + public void testString() { String src = """ struct String { @@ -136,9 +133,9 @@ val _hashCodeString = { String self -> return hash; }; """; - testCPU(src,"x86_64_v2", "SystemV", 9,null); - testCPU(src,"riscv" , "SystemV", 3,null); - testCPU(src,"arm" , "SystemV", 3,null); + testCPU(src,"x86_64_v2", "SystemV",18,null); + testCPU(src,"riscv" , "SystemV", 5,null); + testCPU(src,"arm" , "SystemV", 4,null); } @Test public void testStringExport() throws IOException { @@ -174,10 +171,10 @@ val _hashCodeString = { String self -> return hash; }; """; - TestC.run(src, "stringHash",null, "", 9); + TestC.runC(src, "stringHash", "",18); } - @Test public void testLoop2() throws IOException { + @Test public void testLoop2() { String src = """ int i = 0; @@ -190,9 +187,9 @@ while(true) { } return i; """; - testCPU(src,"x86_64_v2", "Win64" ,0,"return (inc,Phi(Loop,0,inc));"); - testCPU(src,"riscv" , "SystemV",0,"return ( Phi(Loop,0,addi) + #1 );"); - testCPU(src,"arm" , "SystemV",0,"return (inc,Phi(Loop,0,inc));"); + testCPU(src,"x86_64_v2", "win64" ,0,"return (!=0)(inc,Phi(Loop,0,Phi(Region,(!=0),(!=0),0)));"); + testCPU(src,"riscv" , "SystemV",0,"return (!=0)( Phi(Loop,0,Phi(Region,(!=0),(!=0),0)) + #1 );"); + testCPU(src,"arm" , "SystemV",0,"return (!=0)(inc,Phi(Loop,0,Phi(Region,(!=0),(!=0),0)));"); } @Test public void testNewtonExport() throws IOException { @@ -221,9 +218,9 @@ val test_sqrt = { flt x -> } }; """; - TestC.run(src, "newtonFloat", null, result, 34); + TestC.runC(src, "newtonFloat", result, 42); - EvalRisc5 R5 = TestRisc5.build("newtonFloat",src, 0, 10, false); + EvalRisc5 R5 = TestRisc5.build( src, "test_sqrt", 0, 10, false); R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 3.0; int trap_r5 = R5.step(1000); assertEquals(0,trap_r5); @@ -231,7 +228,7 @@ val test_sqrt = { flt x -> assertEquals(1.732051,R5.fregs[riscv.FA0 - riscv.F_OFFSET], 0.00001); // arm - EvalArm64 A5 = TestArm64.build("newtonFloat", src,0, 10, false); + EvalArm64 A5 = TestArm64.build("test_sqrt", src,0, 10, false); A5.fregs[arm.D0 - arm.D_OFFSET] = 3.0; int trap_arm = A5.step(1000); assertEquals(0,trap_arm); @@ -281,11 +278,11 @@ val sieve = { int N -> String sprimes = sb.p("]").toString(); // Compile, link against native C; expect the above string of primes to be printed out by C - TestC.run(src, "sieve", null, sprimes, 257); + TestC.runC(src, "sieve", sprimes, 186); // Evaluate on RISC5 emulator; expect return of an array of primes in // the simulated heap. - EvalRisc5 R5 = TestRisc5.build("sieve", src, 100, 160, false); + EvalRisc5 R5 = TestRisc5.build( src, "sieve", 100, 90, false); int trap = R5.step(10000); assertEquals(0,trap); // Return register A0 holds sieve(100) @@ -297,7 +294,7 @@ val sieve = { int N -> // Evaluate on ARM5 emulator; expect return of an array of primes in // the simulated heap. - EvalArm64 A5 = TestArm64.build("sieve", src, 100, 160, false); + EvalArm64 A5 = TestArm64.build("sieve", src, 100, 105, false); int trap_arm = A5.step(10000); assertEquals(0, trap_arm); int ary_arm = (int)A5.regs[arm.X0]; @@ -310,7 +307,7 @@ val sieve = { int N -> @Test public void testFibExport() throws IOException { String src = """ -val fib = {int n -> +val fib = { int n -> int f1=1; int f2=1; while( n-- > 1 ){ @@ -322,9 +319,9 @@ val fib = {int n -> }; """; String fib = "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]"; - TestC.run(src, "fib", null, fib, 24); + TestC.runC(src, "fib", fib, 24); - EvalRisc5 R5 = TestRisc5.build("fib", src, 9, 17, false); + EvalRisc5 R5 = TestRisc5.build( src, "fib", 9, 17, false); int trap = R5.step(100); assertEquals(0,trap); // Return register A0 holds fib(8)==55 @@ -351,7 +348,7 @@ val fib = {int n -> }; """; String person = "6\n"; - TestC.run(src, "person", null, person, 0); + TestC.run(src, "person1", null, TestC.CALL_CONVENTION, "", TestC.C_DRIVERS_DIR+"person.c", person, 2); // Memory layout starting at PS: int ps = 1<<16; // Person array pointer starts at heap start @@ -362,7 +359,7 @@ val fib = {int n -> int p1 = ps+4*8+1*8; // P2 = { age } // sizeof=8 int p2 = ps+4*8+2*8; - EvalRisc5 R5 = TestRisc5.build("person", src, ps, 0, false); + EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 0, false); R5.regs[riscv.A1] = 1; // Index 1 R5.st8(ps,3); // Length R5.st8(ps+1*8,p0); @@ -378,7 +375,7 @@ val fib = {int n -> assertEquals(17+1,R5.ld8(p1)); assertEquals(60+0,R5.ld8(p2)); - EvalArm64 A5 = TestArm64.build("person", src, ps, 0, false); + EvalArm64 A5 = TestArm64.build("fcn", src, ps, 0, false); A5.regs[arm.X1] = 1; // Index 1 A5.st8(ps, 3); A5.st8(ps+1*8,p0); @@ -396,8 +393,8 @@ val fib = {int n -> } @Test public void testArgCount() throws IOException { - // Test passes more args than registers in Sys5, which is far far more - // than what Win64 allows - so Win64 gets a lot more spills here. + // Test passes more args than registers in Sys5, which is far, far more + // than what win64 allows - so win64 gets a lot more spills here. String src = """ val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, int x8, flt f9, int i10, flt f11, int i12, flt f13, int i14, flt f15, int x16, flt f17 int x18, flt f19 -> @@ -408,9 +405,9 @@ val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, i """; String arg_count = "191.000000\n"; - TestC.run(src, "arg_count", null, arg_count, TestC.CALL_CONVENTION.equals("Win64") ? 42 : 15); + TestC.runC(src, "arg_count", arg_count, TestC.CALL_CONVENTION.equals("win64") ? 31 : 15); - EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", src, 0, 4, false); + EvalRisc5 R5 = TestRisc5.build( src, "addAll", 0, 4, false); // Todo: handle stack(imaginary stack in emulator) // pass in float arguments @@ -423,14 +420,13 @@ val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, i R5.fregs[riscv.FA6 - riscv.F_OFFSET] = 1.1; R5.fregs[riscv.FA7 - riscv.F_OFFSET] = 1.1; - // a0 is passed in arg + R5.regs[riscv.A0] = 2; R5.regs[riscv.A1] = 2; R5.regs[riscv.A2] = 2; R5.regs[riscv.A3] = 2; R5.regs[riscv.A4] = 2; R5.regs[riscv.A5] = 2; R5.regs[riscv.A6] = 2; - R5.regs[riscv.A7] = 2; int trap = R5.step(100); assertEquals(0,trap); @@ -440,7 +436,7 @@ val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, i assertEquals(22.8, result, 0.00001); // arm - EvalArm64 A5 = TestArm64.build("no_stack_arg_count", src, 0, 4, false); + EvalArm64 A5 = TestArm64.build("addAll", src, 0, 4, false); A5.fregs[arm.D0 - arm.D_OFFSET] = 1.1; A5.fregs[arm.D1 - arm.D_OFFSET] = 1.1; diff --git a/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/src/test/java/com/seaofnodes/simple/Chapter22Test.java index 78b9e7a1..91b8310d 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -1,12 +1,10 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.codegen.CodeGen; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - import com.seaofnodes.simple.node.cpus.arm.arm; import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -15,25 +13,25 @@ public class Chapter22Test { @Test public void testJig() throws IOException { - String src = -""" -val fib = {int n -> - int temp=0; - int f1=1; - int f2=1; - int i=n; - while( i>1 ){ - temp = f1+f2; - f1=f2; - f2=temp; - i=i-1; - } - return f2; + String src = """ +struct s0 { + bool v1; + i16 v2; + int v3; + i8 v4; + byte v5; }; - -fib(10); +while(new s0.v3) + while(new s0.v5<>3; if( delta==0 ) delta = 1; - if( spills != -1 ) + if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); if( stop != null ) - assertEquals(stop, code._stop.toString()); + assertEquals(stop, code.print()); return code; } - static int testCPUSize( String src, String cpu, String os, int spills, String stop ) { - return testCPU(src,cpu,os,spills,stop)._encoding._bits.size(); + static int testCPUSize( String src, String cpu, String os, int spills ) { + return testCPU(src,cpu,os,spills, "return 0;" )._encoding._bits.size(); } // Should not fold away @Test public void testSextFail() throws IOException { String src = """ -struct Person { i32 age;}; -Person !p = new Person; +struct _Person { i32 age;}; +_Person !p = new _Person; p.age = (arg<<17)>>17; return 0; """; - assertEquals(46, testCPUSize(src, "x86_64_v2","Win64",2,"return 0;")); - assertEquals(60, testCPUSize(src, "riscv","SystemV",4,"return 0;")); - assertEquals(56, testCPUSize(src, "arm","SystemV",4,"return 0;")); + assertEquals(83, testCPUSize(src, "x86_64_v2","win64", 3 )); + assertEquals(96, testCPUSize(src, "riscv", "SystemV", 4 )); + assertEquals(96, testCPUSize(src, "arm" , "SystemV", 4 )); // do assertEquals here - EvalRisc5 R5 = TestRisc5.build("sext_str_not_fold_away", src,0, 4, false); + EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away", 0, 4, false); int trap = R5.step(100); assertEquals(0,trap); @@ -80,14 +78,13 @@ return 0; // Should not fold away @Test public void testSextFail2() throws IOException { String src = """ - - struct Person { i32 age;}; - Person !p = new Person; + struct _Person { i32 age;}; + _Person !p = new _Person; p.age = (arg<<48)>>48; return 0; """; - EvalRisc5 R5 = TestRisc5.build("sext_str_not_fold_away_2", src, 0, 4, false); + EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away_2", 0, 4, false); int trap = R5.step(100); assertEquals(0,trap); @@ -95,9 +92,9 @@ return 0; int trap_arm = A5.step(100); assertEquals(0,trap_arm); - assertEquals(46, testCPUSize(src, "x86_64_v2","Win64",2,"return 0;")); - assertEquals(60, testCPUSize(src, "riscv","SystemV",4,"return 0;")); - assertEquals(56, testCPUSize(src, "arm","SystemV",4,"return 0;")); + assertEquals(83, testCPUSize(src, "x86_64_v2","win64",3 )); + assertEquals(96, testCPUSize(src, "riscv", "SystemV",4 )); + assertEquals(96, testCPUSize(src, "arm", "SystemV",4 )); } @@ -105,13 +102,13 @@ return 0; @Test public void testSextSuccess() throws IOException { String src = """ // Should fold away sign extend -struct Person { i8 age;}; -Person !p = new Person; +struct _Person { i8 age;}; +_Person !p = new _Person; p.age = (arg<<48)>>48; return 0; - """; +"""; - EvalRisc5 R5 = TestRisc5.build("sext_str_fold_away", src, 0, 5, false); + EvalRisc5 R5 = TestRisc5.build( src, "sext_str_fold_away", 0, 5, false); int trap = R5.step(100); assertEquals(0,trap); @@ -119,9 +116,9 @@ return 0; int trap_arm = A5.step(100); assertEquals(0,trap_arm); - assertEquals(41, testCPUSize(src, "x86_64_v2","Win64",2,"return 0;")); - assertEquals(56, testCPUSize(src, "riscv","SystemV",5,"return 0;")); - assertEquals(52, testCPUSize(src, "arm","SystemV",5,"return 0;")); + assertEquals(71, testCPUSize(src, "x86_64_v2","win64",3 )); + assertEquals(92, testCPUSize(src, "riscv", "SystemV",5 )); + assertEquals(92, testCPUSize(src, "arm", "SystemV",5 )); // do assertEquals here } @@ -130,17 +127,17 @@ return 0; @Test public void testPerson() throws IOException { String src = """ -struct Person { +struct Person { // Exports a field typed singleton+final *class:Person{age:i32} i32 age; }; -val fcn = { Person?[] ps, int x -> +val fcn = { Person?[] ps, int x -> // exports a field with a constant fcn ptr; exports the fcn also if( ps[x] ) ps[x].age++; }; """; String person = "6\n"; - TestC.run(src, "person", null, person, 0); + TestC.run(src, "person2", null, TestC.CALL_CONVENTION, "", TestC.C_DRIVERS_DIR+"person.c", person, 2); // Memory layout starting at PS: int ps = 1<<16; // Person array pointer starts at heap start @@ -151,7 +148,7 @@ val fcn = { Person?[] ps, int x -> int p1 = ps+4*8+1*8; // P2 = { age } // sizeof=8 int p2 = ps+4*8+2*8; - EvalRisc5 R5 = TestRisc5.build("person", src, ps, 0, false); + EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 0, false); R5.regs[riscv.A1] = 1; // Index 1 R5.st8(ps,3); // Length R5.st8(ps+1*8,p0); @@ -167,7 +164,7 @@ val fcn = { Person?[] ps, int x -> assertEquals(17+1,R5.ld8(p1)); assertEquals(60+0,R5.ld8(p2)); - EvalArm64 A5 = TestArm64.build("person", src, ps, 0, false); + EvalArm64 A5 = TestArm64.build("fcn", src, ps, 0, false); A5.regs[arm.X1] = 1; // Index 1 A5.st8(ps, 3); A5.st8(ps+1*8,p0); @@ -187,41 +184,47 @@ val fcn = { Person?[] ps, int x -> @Test public void testCoRecur() { String src = """ -struct A { B? b; C? c; i64 ax; val az = x*2; }; -struct B { A? a; C? c; f32 bx; val bz = x*3; }; -struct C { A? a; B? b; f64 cx; val cz = x*x; }; -A !aa = new A{ ax=17; }; -B !bb = new B{ bx=3.14; a = aa; }; -C !cc = new C{ cx=2.73; a = aa; b = bb; }; +val x = 5; // aa.az; // Error to self-define forward ref +struct _A { Test._B? b; Test._C? c; i64 ax; val az = x*2; new _A = { i64 x -> ax=x; }; }; +struct _B { Test._A? a; Test._C? c; f32 bx; val bz = x*3; new _B = { f32 x, Test._A? aa -> bx=x; a=aa; }; }; +struct _C { Test._A? a; Test._B? b; f64 cx; val cz = x*x; new _C = { f64 x, Test._A? aa, Test._B? bb -> cx=x; a=aa; b=bb; }; }; +_A !aa = new _A(17); +_B !bb = new _B(3.14f, aa); +_C !cc = new _C(2.73, aa, bb); aa.b = bb; aa.c = cc; bb.c = cc; -val x = 5; // aa.az; // Error to self-define forward ref return cc.cz; """; CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("return 25;", code._stop.toString()); + assertEquals("return 25;", code.print()); assertEquals("25", Eval2.eval(code, 0)); } @Test public void testHelloWorld() throws IOException { - String src = -""" -sys.io.p("Hello, World!"); + String src = """ +// fd buf len -> len +{ i32 i64 u32 -> u32 } write = "C"; +// Write byte array to stdout +val p = { u8[~] str -> + i64 ptr = str; // cast array base to i64 + return write(1,ptr,str#); +}; +p("Hello, World!"); return 0; """; - TestC.run(src,TestC.CALL_CONVENTION,null, null,null,"build/objs/helloWorld","","Hello, World!",0); + TestC.runSF(src,"helloWorld", "Hello, World!",2); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("helloWorld", src, 0, 2, false); + EvalRisc5 R5 = TestRisc5.build( src, "helloWorld", 0, 5, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); assertEquals("Hello, World!",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("helloWorld", src,0, 2, false); + EvalArm64 arm = TestArm64.build("helloWorld", src,0, 5, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(0,arm.regs[0]); @@ -235,16 +238,16 @@ int N=4; i32[] !is = new i32[N]; for( int i=0; i // final array +val _sum = { i32[~] is -> // final array int sum=0; for( int i=0; i>3; if( delta==0 ) delta = 1; - if( spills != -1 ) + if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); if( stop != null ) - assertEquals(stop, code._stop.toString()); + assertEquals(stop, code.print()); return code; } @Test public void testOr() throws IOException { - String src = - """ - int a = 1; - int b = 0; - - if(a++ || b++ ) { - if(b == 0 && a == 2) { - sys.io.p("Or"); - } - } else{ - sys.io.p("And"); - } - return 0; - """; - - TestC.runSF("or1", src, null, "Or", 0); + String src = """ +int a = 1; +int b = 0; +int rez = 2; + +if( a++ || b++ ) { + if( b == 0 && a == 2 ) + rez = 0; // Expected answer +} else { + rez = 1; +} +return rez; +"""; + + TestC.runSF( src, "or1", "", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("or1", src, 0, 2, false); + EvalRisc5 R5 = TestRisc5.build( src, "or1", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Or",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("or1", src,0, 2, false); + EvalArm64 arm = TestArm64.build("or1", src,0, 0, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(0,arm.regs[0]); - assertEquals("Or",arm._stdout.toString()); } @Test public void testAnd() throws IOException { - String src = - """ - int a = 1; - int b = 1; - - if(a && b) { - sys.io.p("And"); - } else { - sys.io.p("Or"); - } - return 0; - """; - TestC.runSF("and1", src, null, "And", 0); + String src = """ +int a = 1; +int b = 1; +return a && b ? 0 : 1; // Expected answer 0 +"""; + TestC.runSF( src, "and1", "", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("and1", src, 0, 2, false); + EvalRisc5 R5 = TestRisc5.build( src, "and1", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); - assertEquals("And",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("and1", src, 0, 2, false); + EvalArm64 arm = TestArm64.build("and1", src, 0, 0, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(0,arm.regs[0]); - assertEquals("And",arm._stdout.toString()); } @Test public void testAndPtr() throws IOException { // Todo: have one src here - String src = - """ - struct S { S? fld; }; - val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};}; - return ptr && ptr.fld ? "true" : "false"; - """; - String src2 = - """ - struct S { S? fld; }; - - val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};}; - if( ptr && ptr.fld ) { - sys.io.p("true"); - } else { - sys.io.p("false"); - } - - return 0; - """; + String src = """ +struct S { S? fld; new S = { S? f -> fld=f; }; }; +val ptr = arg == 1 ? null : new S(arg==1 ? null : new S(null)); +return ptr && ptr.fld ? "true" : "false"; +"""; CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("[97-117][ 116,114,117,101]", Eval2.eval(code, 0)); + assertEquals("true", Eval2.eval(code, 0)); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("ptrand",src2, 1, 8, false); + EvalRisc5 R5 = TestRisc5.build( src, "class:Test.", 1, 8, false); int trap = R5.step(100); assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("false",R5._stdout.toString()); + int adr = (int)R5.regs[riscv.A0]; // Returns a Simple *u8[~] string + assertEquals(4,R5.ld4s(adr)); // String length + assertEquals(0x65757274,R5.ld4s(adr+4)); // "true" // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("ptrand", src2, 1, 8, false); + EvalArm64 arm = TestArm64.build("class:Test.", src, 1, 8, false); trap = arm.step(100); assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("false",arm._stdout.toString()); + int adr2 = (int)arm.regs[0]; // Returns a Simple *u8[~] string + assertEquals(4,arm.ld4s(adr2)); // String length + assertEquals(0x65757274,arm.ld4s(adr2+4)); // "true" } // conditional side effect @Test public void testCondSideEffAnd() throws IOException { - String src = - """ - int a = 1; - int b = 1; - - int x=1; - int y=1; - int z=0; - - int g = x++ && y++ && z++; - - if(x == 2 && y == 2 && z == 1 && g == 0) { - sys.io.p("Effected"); - } else { - sys.io.p("Not effected"); - } - return 0; - """; - - TestC.runSF("and2", src, null, "Effected", 0); + String src = """ +int x=1; +int y=1; +int z=0; + +int g = x++ && y++ && z++; +return x == 2 && y == 2 && z == 1 && g == 0 + ? 0 // Effected, Expected + : 1; // Error, not effected +"""; + + TestC.runSF( src, "and2", "", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("and2", src, 0, 2, false); + EvalRisc5 R5 = TestRisc5.build( src, "and2", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Effected",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("and2", src, 0, 2, false); + EvalArm64 arm = TestArm64.build("and2", src, 0, 0, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(0,arm.regs[0]); - assertEquals("Effected",arm._stdout.toString()); } @Test public void testCondSideEffOr() throws IOException { - String src = - """ - int a = 1; - int b = 1; - - int x = -1; - int y=1; - int z= -1; - - int g = x++ || y++ || z++; - - int switch = 0; - if(x == 4 || y == 4 || z == 4) { - switch = -1; - } else { - switch = 1; - } - int cd = 1; - if((x == 0 && y == 1 && z==-1) && switch && g) { - sys.io.p("Effected"); - } else { - sys.io.p("Not effected"); - } - return 0; - """; - TestC.runSF("or2", src, null, "Effected", 0); + String src = """ +int x = -1; +int y = 1; +int z = -1; +int g = x++ || y++ || z++; +int switch = (x == 4 || y == 4 || z == 4) ? -1 : 1; +return (x == 0 && y == 1 && z==-1) && switch && g + ? 0 // Effected, expected + : 1; // Error +"""; + TestC.runSF( src, "or2", "", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("or2", src, 0, 2, false); + EvalRisc5 R5 = TestRisc5.build( src, "or2", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Effected",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("or2", src, 0, 2, false); + EvalArm64 arm = TestArm64.build("or2", src, 0, 0, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(0,arm.regs[0]); - assertEquals("Effected",arm._stdout.toString()); } // test it with function calls @Test public void testFuncCall() throws IOException { - String src = - """ - // -*- mode: java; -*- - int a = 1; - int b = 1; - - var sq = { int x -> - x*x; - }; - - if(a && sq(0)) { - sys.io.p("And"); - } else { - sys.io.p("Or"); - } - return 0; - """; - TestC.runSF("and3", src, null, "Or", 0); + String src = """ +int a = 1; +int b = 1; +var sq_noInline = { int x -> + x*x; +}; +return (a && sq_noInline(0)) + ? 1 // Error + : 0; // Expected +"""; + TestC.runSF( src, "and3", "", 2); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("and3", src, 0, 2, false); + EvalRisc5 R5 = TestRisc5.build( src, "and3", 0, 5, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Or",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("and3", src, 0, 2, false); + EvalArm64 arm = TestArm64.build("and3", src, 0, 5, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(0,arm.regs[0]); - assertEquals("Or",arm._stdout.toString()); } @Test public void testFRefFields() { - String src = -""" + String src = """ // A hypothetical scanner class -struct Scan { +struct _Scan { int !x; u8[~] buf; + new _Scan = { u8[~] b -> buf=b; }; // Skip whitespace - val skip = { Scan s -> - while( s.buf[s.x] <= ' ' ) - s.x++; + val skip = { -> + while( buf[x] <= ' ' ) + x++; }; // Peek a character; if matched consume it, else false. - val peek = { Scan s, u8 c -> - skip(s); - if( s.buf[s.x] != c ) return false; - s.x++; + val peek = { u8 c -> + skip(); + if( buf[x] != c ) return false; + x++; return true; }; }; -Scan !s = new Scan{ buf = " q"; }; -return Scan.peek(s,'q'); - """; +_Scan !_s = new _Scan(" q"); +return _s.peek('q'); +"""; CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("return Phi(Region,0,1);", code._stop.toString()); + assertEquals("return Phi(Region,0,1);", code.print()); assertEquals("1", Eval2.eval(code, 0)); - testCPU(src,"x86_64_v2", "Win64" ,17,null); + testCPU(src,"x86_64_v2", "win64",1,null); }; @Test public void testFRefFields2() { - String src = -""" + String src =""" // A hypothetical scanner class -struct Scan { +struct _Scan { int !x; u8[~] buf; + new _Scan = { u8[~] b -> buf=b; }; // Skip whitespace val skip = { -> while( buf[x] <= ' ' ) @@ -305,23 +244,26 @@ struct Scan { return self; }; }; -val s = new Scan{ buf = " q"; }; +val s = new _Scan(" q"); return s.skip().x; - """; +"""; try { new CodeGen(src).parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Argument #0 isa *Scan {i64 x; *[]u8 buf; { *Scan -> *Scan {i64 !x; *[]u8 buf; {21} skip; } #21} skip; }, but must be a *Scan {i64 !x; *[]u8 buf; ... }",e.getMessage()); } + catch( Exception e ) { + // Bad error message, but basically requires a mutable 'x' field. + assertEquals("Argument #0 isa *Test._Scan {i64 x; *[]u8 buf; { *Test._Scan -> *Test._Scan {i64 !x; *[]u8 buf; {[ 5]} skip; } #[ 5]} skip; }, but must be a *Test._Scan {i64 !x; *[]u8 buf; { *Test._Scan -> *Test._Scan #[ 5]} skip; }",e.getMessage()); + } }; @Test public void testMethod() { - String src = -""" + String src = """ // A hypothetical scanner class -struct Scan { +struct _Scan { int !x; u8[~] buf; + new _Scan = { u8[~] b -> buf=b; }; // Skip whitespace val skip = { -> while( buf[x] <= ' ' ) @@ -332,14 +274,14 @@ struct Scan { buf[x++]==ch; }; }; -Scan !s = new Scan{ buf = " [1,2]"; }; -return s.require('['); - """; +_Scan !_s = new _Scan(" [1,2]"); +return _s.require('['); +"""; CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("return (.[]==91);", code._stop.toString()); + assertEquals("return (.[]==91);", code.print()); assertEquals("1", Eval2.eval(code, 0)); - testCPU(src,"x86_64_v2", "Win64" ,16,null); + testCPU(src,"x86_64_v2", "win64", 0, null); }; diff --git a/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/src/test/java/com/seaofnodes/simple/Chapter24Test.java index 1d67949a..fe8aaf2e 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter24Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter24Test.java @@ -11,7 +11,7 @@ public class Chapter24Test { @Test public void testJig() throws IOException { String src = "int v0=0; v0 = arg && 0== 0 !=0;"; - testCPU(src,"x86_64_v2", "Win64" ,-1,null); + testCPU(src,"x86_64_v2", "win64" ,-1,null); testCPU(src,"riscv" , "SystemV",-1,null); testCPU(src,"arm" , "SystemV",-1,null); } @@ -20,428 +20,101 @@ public class Chapter24Test { CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); int delta = spills>>3; if( delta==0 ) delta = 1; - if( spills != -1 ) + if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); if( stop != null ) - assertEquals(stop, code._stop.toString()); + assertEquals(stop, code.print()); return code; } - @Test - public void testOr() throws IOException { - String src = -""" -int a = 1; -int b = 0; - -if(a++ || b++ ) { - if(b == 0 && a == 2) { - sys.io.p("Or"); - } -} else{ - sys.io.p("And"); -} -return 0; -"""; - - TestC.runSF("or1", src, null, "Or", 0); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("or1", src, 0, 2, false); - int trap = R5.step(100); - assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Or",R5._stdout.toString()); - - // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("or1", src,0, 2, false); - trap = arm.step(100); - assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("Or",arm._stdout.toString()); - } - - @Test - public void testAnd() throws IOException { - String src = -""" -int a = 1; -int b = 1; - -if(a && b) { - sys.io.p("And"); -} else { - sys.io.p("Or"); -} -return 0; -"""; - TestC.runSF("and1", src, null, "And", 0); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("and1", src, 0, 2, false); - int trap = R5.step(100); - assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("And",R5._stdout.toString()); - - // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("and1", src, 0, 2, false); - trap = arm.step(100); - assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("And",arm._stdout.toString()); - } - - @Test - public void testAndPtr() throws IOException { - // Todo: have one src here - String src = -""" -struct S { S? fld; }; -val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};}; -return ptr && ptr.fld ? "true" : "false"; -"""; - String src2 = -""" -struct S { S? fld; }; - -val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};}; -if( ptr && ptr.fld ) { - sys.io.p("true"); -} else { - sys.io.p("false"); -} - -return 0; -"""; - CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("[97-117][ 116,114,117,101]", Eval2.eval(code, 0)); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("ptrand",src2, 1, 8, false); - int trap = R5.step(100); - assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("false",R5._stdout.toString()); - - // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("ptrand", src2, 1, 8, false); - trap = arm.step(100); - assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("false",arm._stdout.toString()); - } - - // conditional side effect - @Test - public void testCondSideEffAnd() throws IOException { - String src = -""" -int a = 1; -int b = 1; - -int x=1; -int y=1; -int z=0; - -int g = x++ && y++ && z++; - -if(x == 2 && y == 2 && z == 1 && g == 0) { - sys.io.p("Effected"); -} else { - sys.io.p("Not effected"); -} -return 0; -"""; - - TestC.runSF("and2", src, null, "Effected", 0); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("and2", src, 0, 2, false); - int trap = R5.step(100); - assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Effected",R5._stdout.toString()); - - // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("and2", src, 0, 2, false); - trap = arm.step(100); - assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("Effected",arm._stdout.toString()); - } - - - @Test - public void testCondSideEffOr() throws IOException { - String src = -""" -int a = 1; -int b = 1; - -int x = -1; -int y=1; -int z= -1; - -int g = x++ || y++ || z++; - -int switch = 0; -if(x == 4 || y == 4 || z == 4) { - switch = -1; -} else { - switch = 1; -} -int cd = 1; -if((x == 0 && y == 1 && z==-1) && switch && g) { - sys.io.p("Effected"); -} else { - sys.io.p("Not effected"); -} -return 0; -"""; - TestC.runSF("or2", src, null, "Effected", 0); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("or2", src, 0, 2, false); - int trap = R5.step(100); - assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Effected",R5._stdout.toString()); - - // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("or2", src, 0, 2, false); - trap = arm.step(100); - assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("Effected",arm._stdout.toString()); - } - - // test it with function calls - @Test - public void testFuncCall() throws IOException { - String src = -""" -// -*- mode: java; -*- -int a = 1; -int b = 1; - -var sq = { int x -> - x*x; -}; - -if(a && sq(0)) { - sys.io.p("And"); -} else { - sys.io.p("Or"); -} -return 0; -"""; - TestC.runSF("and3", src, null, "Or", 0); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("and3", src, 0, 2, false); - int trap = R5.step(100); - assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Or",R5._stdout.toString()); - - // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("and3", src, 0, 2, false); - trap = arm.step(100); - assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("Or",arm._stdout.toString()); - } - // test it with function calls @Test public void testStack1() throws IOException { - String src = -""" + String src =""" int something_specific = 11; - -if (10 < something_specific < 20) { - sys.io.p("In Range"); -} else { - sys.io.p("Out of Range"); -} -return 0; -"""; - - TestC.runSF("stacked_r_1", src, null, "In Range", 0); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_1", src, 0, 2, false); - int trap = R5.step(100); - assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("In Range",R5._stdout.toString()); - - // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("stacked_r_1", src, 0, 2, false); - trap = arm.step(100); - assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("In Range",arm._stdout.toString()); - } - - @Test - public void testStack2() throws IOException { - String src = -""" -int score = 75; - -if (60 <= score < 90) { - sys.io.p("In Range"); -} else { -sys.io.p("Out of range"); -} -return 0; +return (10 < something_specific < 20) + ? 0 // Expected + : 1; // Error """; - - TestC.runSF("stacked_r_2", src, null, "In Range", 0); + TestC.runSF( src, "stacked_r_1", "", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_2", src, 0, 2, false); + EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_1", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); - assertEquals("In Range",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("stacked_r_2", src, 0, 2, false); + EvalArm64 arm = TestArm64.build("stacked_r_1", src, 0, 0, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(0,arm.regs[0]); - assertEquals("In Range",arm._stdout.toString()); } @Test public void testStack3() throws IOException { - String src = -""" -var sq = { int x -> + String src =""" +var sq_noInline = { int x -> x*x; }; - -int score = 5; - -val str = (sq(2) <= score < sq(3)) ? "In Range" : "Out of range"; -sys.io.p(str); -return 0; +int score = 4; +return (sq_noInline(2) <= score < sq_noInline(3)) + ? 0 // Expected + : 1; // Error """; - - TestC.runSF("stacked_r_3", src, null, "In Range", 5); + TestC.runSF( src, "stacked_r_3", "", 4); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_3", src, 0, 5, false); + EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_3", 0, 5, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); - assertEquals("In Range",R5._stdout.toString()); // Evaluate on ARM emulator EvalArm64 arm = TestArm64.build("stacked_r_3", src, 0, 5, false); int trap_1 = arm.step(100); assertEquals(0,trap_1); assertEquals(0,arm.regs[0]); - assertEquals("In Range",arm._stdout.toString()); } @Test public void testStack4() throws IOException { - String src = -""" + String src =""" int something_specific = 9; - -if (10 < something_specific < 20) { - sys.io.p("In Range"); -} else { - sys.io.p("Out of Range"); -} -return 0; +return (10 < something_specific < 20) + ? 1 // Error + : 0; // Expected """; - TestC.runSF("stacked_r_4", src, null, "Out of Range", 0); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_4", src,0, 2, false); - int trap = R5.step(100); - assertEquals(0,trap); - assertEquals(0,R5.regs[riscv.A0]); - assertEquals("Out of Range",R5._stdout.toString()); - - // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("stacked_r_4", src, 0, 2, false); - trap = arm.step(100); - assertEquals(0,trap); - assertEquals(0,arm.regs[0]); - assertEquals("Out of Range",arm._stdout.toString()); - } - - @Test - public void testStack5() throws IOException { - String src = -""" -var sq = { int x -> - x*x; -}; - -int score = 5; - -if (sq(2) <= score < sq(3)) { -sys.io.p("In Range"); -} else { -sys.io.p("Out of range"); -} - -return 0; -"""; - TestC.runSF("stacked_r_5", src, null, "In Range", 5); + TestC.runSF( src, "stacked_r_4", "", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_5", src, 0, 7, false); + EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_4", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); - assertEquals("In Range",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("stacked_r_5", src, 0, 7, false); + EvalArm64 arm = TestArm64.build("stacked_r_4", src, 0, 0, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(0,arm.regs[0]); - assertEquals("In Range",arm._stdout.toString()); } - @Test public void testStack6() { - try { new CodeGen( - """ - return 0 < arg > 1; - """ - ).parse(); fail(); } + try { new CodeGen("return 0 < arg > 1;" ).parse(); fail(); } catch( RuntimeException e ) { assertEquals("Mixing relational directions in a chained relational test",e.getMessage()); } } @Test public void testStack9() throws IOException { - String stack9 = "1"; - String src = -""" -var stack9 = { int x -> - (x < 1) == 1; -}; -"""; - String src2 = -""" -return (0 < 1) == 1; -"""; - TestC.run(src, "stacked_r_9x", null, stack9,0); + String src = "var stack9 = { int x -> (x < 1) == 1; };"; + String src2 = "return (0 < 1) == 1;"; + TestC.runC(src, "stacked_r_9x", "1", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_9", src2, 0, 0, false); + EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_9", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(1,R5.regs[riscv.A0]); @@ -455,21 +128,12 @@ return (0 < 1) == 1; @Test public void testStack10() throws IOException { - String stack10 = "1"; - String src = -""" -var stack10 = { int x -> - (x < 1) == (1>x); -}; -"""; - String src2 = -""" -return (0 < 1) == (1>0); -"""; - TestC.run(src, "stacked_r_10x", null, stack10,0); + String src ="var stack10 = { int x -> (x < 1) == (1>x);};"; + String src2 ="return (0 < 1) == (1>0);"; + TestC.runC(src, "stacked_r_10x", "1",0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_10", src2, 0, 0, false); + EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_10", 0, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(1,R5.regs[riscv.A0]); @@ -483,27 +147,20 @@ return (0 < 1) == (1>0); @Test public void testStack11() throws IOException { - String stack11 = "1"; - String src = -""" -var stack11 = { int x -> - 0 < x < x+1 < 4; -}; -"""; - String src2 = -""" -return 0 < arg < arg+1 < 4; -"""; - TestC.run(src, "stacked_r_11x", null, stack11,0); + String src = "var stack11 = { int x -> 0 < x < x+1 < 4;}; "; + String src2 = "return 0 < arg < arg+1 < 4;"; + TestC.runC(src, "stacked_r_11x", "1", 2); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_11", src2, 1, 2, false); + EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_11", 1, 0, false); + R5.regs[riscv.A1] = 1; // Arg in A1, Test.class in A0 int trap = R5.step(100); assertEquals(0,trap); assertEquals(1,R5.regs[riscv.A0]); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("stacked_r_11", src2, 1, 2, false); + EvalArm64 arm = TestArm64.build("stacked_r_11", src2, 1, 0, false); + arm.regs[1] = 1; // Arg in r1, Test.class in r0 trap = arm.step(100); assertEquals(0,trap); assertEquals(1,arm.regs[0]); @@ -513,21 +170,12 @@ return 0 < arg < arg+1 < 4; // ((0 != x) != 2) @Test public void testStack12() throws IOException { - String stack11 = "1"; - String src = - """ - var stack12 = { int x -> - 0 != x != 2; - }; - """; - String src2 = - """ - return 0 != arg != 2; - """; - TestC.run(src, "stacked_r_12x", null, stack11,0); + String src = "var stack12 = { int x -> 0 != x != 2; };"; + String src2 = "return 0 != arg != 2;"; + TestC.runC(src, "stacked_r_12x", "1",0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_12", src2, 1, 0, false); + EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_12", 1, 0, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(1,R5.regs[riscv.A0]); @@ -544,115 +192,25 @@ return 0 < arg < arg+1 < 4; // ((0 == x) == 1) @Test public void testStack13() throws IOException { - String stack12 = "1"; - String src = - """ - var stack13 = { int x -> - 0 == x == 1; - }; - """; - String src2 = - """ - return 0 == arg == 1; - """; - TestC.run(src, "stacked_r_13x", null, stack12,0); + String src = "var stack13 = { int x -> 0 == x == 1; };"; + String src2 = "return 0 == arg == 1;"; + TestC.runC(src, "stacked_r_13x", "1",0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_13", src2, 1, 0, false); + EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_13", 1, 0, false); + R5.regs[riscv.A1] = 1; // Arg in A1, Test.class in A0 int trap = R5.step(100); assertEquals(0,trap); assertEquals(1,R5.regs[riscv.A0]); // Evaluate on ARM emulator EvalArm64 arm = TestArm64.build("stacked_r_13", src2, 1, 0, false); + arm.regs[1] = 1; // Arg in r1, Test.class in r0 trap = arm.step(100); assertEquals(0,trap); assertEquals(1,arm.regs[0]); } - @Test - public void testFRefFields() { - String src = -""" -// A hypothetical scanner class -struct Scan { - int !x; - u8[~] buf; - // Skip whitespace - val skip = { Scan s -> - while( s.buf[s.x] <= ' ' ) - s.x++; - }; - // Peek a character; if matched consume it, else false. - val peek = { Scan s, u8 c -> - skip(s); - if( s.buf[s.x] != c ) return false; - s.x++; - return true; - }; -}; -Scan !s = new Scan{ buf = " q"; }; -return Scan.peek(s,'q'); -"""; - - CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("return Phi(Region,0,1);", code._stop.toString()); - assertEquals("1", Eval2.eval(code, 0)); - testCPU(src,"x86_64_v2", "Win64" ,17,null); - }; - - @Test - public void testFRefFields2() { - String src = -""" -// A hypothetical scanner class -struct Scan { - int !x; - u8[~] buf; - // Skip whitespace - val skip = { -> - while( buf[x] <= ' ' ) - x++; - return self; - }; -}; -val s = new Scan{ buf = " q"; }; -return s.skip().x; -"""; - - try { new CodeGen(src).parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Argument #0 isa *Scan {i64 x; *[]u8 buf; { *Scan -> *Scan {i64 !x; *[]u8 buf; {21} skip; } #21} skip; }, but must be a *Scan {i64 !x; *[]u8 buf; ... }",e.getMessage()); } - }; - - - @Test - public void testMethod() { - String src = -""" -// A hypothetical scanner class -struct Scan { - int !x; - u8[~] buf; - // Skip whitespace - val skip = { -> - while( buf[x] <= ' ' ) - x++; - }; - val require = { u8 ch -> - skip(); - buf[x++]==ch; - }; -}; -Scan !s = new Scan{ buf = " [1,2]"; }; -return s.require('['); -"""; - - CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("return (.[]==91);", code._stop.toString()); - assertEquals("1", Eval2.eval(code, 0)); - testCPU(src,"x86_64_v2", "Win64" ,16,null); - }; - @Test public void testSCCP0() { String src = @@ -668,7 +226,7 @@ while (cnt) { return x; """; CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("return 1;", code._stop.toString()); + assertEquals("return 1;", code.print()); } @Test @@ -683,7 +241,7 @@ while( cnt-- ) return x; """; CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("return 1;", code._stop.toString()); + assertEquals("return 1;", code.print()); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/src/test/java/com/seaofnodes/simple/Chapter25Test.java new file mode 100644 index 00000000..313ae1cf --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter25Test.java @@ -0,0 +1,286 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.ElfReader; +import com.seaofnodes.simple.codegen.ParseAll; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.Ary; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter25Test { + + private static final String SYS_MODDIR = "src/main/smp"; + private static final String SYS_BLDDIR = "build/objs/lib_"+TestC.CPU_ABI; + private static final String RELEASE_SYS_BLDDIR = TestC.RELEASE_SYS_DIR; + + @Test + public void testPostfixFieldUpdate() { + String src = """ + struct V { + u32 !len = 0; + i64[] !buf; + new V = { i64[] b -> buf=b; }; + val grow = { i64 n -> self; }; + val add = { i64 x -> grow(1).buf[len++] = x; self; }; + }; + return new V(new i64[1]).add(7).len; + """; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals("1",Eval2.eval(code,0)); + } + + @Test + public void testStringEscapes() { + String src = "u8[~] s=\"A\\n\\t\\r\\\\\\\"B\"; " + + "return s#==7 && s[0]=='A' && s[1]==10 && s[2]==9 && s[3]==13 && " + + "s[4]==92 && s[5]==34 && s[6]=='B';"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals("1",Eval2.eval(code,0)); + } + + + @Test @Ignore + public void testModule0() throws IOException { + String MODDIR = "src/test/java/com/seaofnodes/simple/test0"; + String BLDDIR = "build/objs/test0"; + String a_obj = BLDDIR+"/A.o"; + String ab_obj = BLDDIR+"/A/B.o"; + // Remove any prior results so the test runs from scratch. + delELFiles(new File(BLDDIR)); + writeB(MODDIR,5); + + // Compile MODDIR/A.smp into MODDIR/A.o + // Since A refers to B also: + // Compile MODDIR/A/B.smp into MODDIR/A/B.o + CodeGen code1 = new CodeGen(MODDIR, BLDDIR,null, + "A",null,123L,TypeInteger.BOT); + code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION); + + // Verify produces A.o, A/B.o + File a_file = new File( a_obj); + File ab_file = new File(ab_obj); + assertTrue( a_file.exists() ); + assertTrue( ab_file.exists() ); + long a_msec1 = a_file.lastModified(); + long ab_msec1 = ab_file.lastModified(); + + // Link and execute: arg is true, so compute "5+1" as the exit code + String rez1 = TestC.gcc("A", 1.2, a_obj, ab_obj); + assertEquals("exec exit code: 6",rez1); + + // Compile again A, expecting both A.o and A/B.o to be up-to-date and not compiled + CodeGen code2 = new CodeGen(MODDIR, BLDDIR, null, + "A",null,123L,TypeInteger.BOT); + code2.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION); + + assertTrue( a_file.exists() ); + assertTrue( ab_file.exists() ); + long a_msec2 = a_file.lastModified(); + long ab_msec2 = ab_file.lastModified(); + + assertEquals( a_msec1, a_msec2 ); + assertEquals( ab_msec1, ab_msec2 ); + + // Touch A.smp and recompile. A/B.o should not recompile. + new File(MODDIR+"/A.smp").setLastModified(System.currentTimeMillis()); + CodeGen code3 = new CodeGen(MODDIR, BLDDIR,null, + "A",null,123L,TypeInteger.BOT); + code3.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION); + + long a_msec3 = a_file.lastModified(); + long ab_msec3 = ab_file.lastModified(); + assertTrue ( a_msec1 < a_msec3 ); + assertEquals( ab_msec1, ab_msec3 ); + + // Link and execute: arg is true, so compute "5+1" as the exit code + String rez3 = TestC.gcc("A", 1.2, a_obj, ab_obj); + assertEquals("exec exit code: 6",rez3); + + + // Modify B.smp and recompile A/B.o; it should recompile and A.o should not. + writeB(MODDIR,7); + CodeGen code4 = new CodeGen(MODDIR, BLDDIR,null, + "A/B",null,123L,TypeInteger.BOT); + code4.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION); + + long a_msec4 = a_file.lastModified(); + long ab_msec4 = ab_file.lastModified(); + assertEquals( a_msec3, a_msec4 ); + assertTrue ( ab_msec3 < ab_msec4 ); + + // Link and execute: uses stale A.o, so remains '6' not '7+1' == 8 + String rez4 = TestC.gcc("A", 1.2, a_obj, ab_obj); + assertEquals("exec exit code: 6",rez4); + + // Recompile A.o, it should recompile despite not being touched because + // it depends on A/B.o which recompiled in the prior step. + CodeGen code5 = new CodeGen(MODDIR, BLDDIR,null, + "A",null,123L,TypeInteger.BOT); + code5.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION); + + long a_msec5 = a_file.lastModified(); + long ab_msec5 = ab_file.lastModified(); + assertTrue ( a_msec4 < a_msec5 ); + assertEquals( ab_msec4, ab_msec5 ); + + // Link and execute: updates A.o from B.o inlining, without compiling B.o + String rez5 = TestC.gcc("A", 1.2, a_obj, ab_obj); + assertEquals("exec exit code: 8",rez5); + // Reset for next time + writeB(MODDIR,5); + } + + private void writeB(String MODDIR, int x) throws IOException { + var bsmp = new FileWriter(MODDIR+"/A/B.smp"); + bsmp.write("val x="+x+";\n"); + bsmp.close(); + } + + + // Recursive search (TODO: gzip, archives) and delete all .o files + private void delELFiles( File dir) { + if( dir.isDirectory() ) + for( File f : dir.listFiles() ) + delELFiles(f); + else if( dir.getName().endsWith(".o") ) + dir.delete(); + } + + + private File buildTestSys(boolean clean) { + if( clean ) + delELFiles(new File(SYS_BLDDIR)); + File sys_file = new File(SYS_BLDDIR+"/sys.o" ); + if( sys_file.exists() ) + return sys_file; + + // Compile SYS_MODDIR/sys.smp into SYS_BLDDIR/sys.o. Chapter25 + // compiler tests intentionally depend on this freshly built sys.o. + CodeGen code1 = new CodeGen(SYS_MODDIR, SYS_BLDDIR,null, + "sys",null,123L,TypeInteger.BOT); + code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false,0); + assertTrue( sys_file.exists() ); + return sys_file; + } + + @Test + public void testSys() { + delELFiles(new File(SYS_BLDDIR)); + + CodeGen code1 = new CodeGen(SYS_MODDIR, SYS_BLDDIR,null, + "sys",null,123L,TypeInteger.BOT); + code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false,0); + + // Verify produces sys.o, sys/io.o + File sys_file = new File(SYS_BLDDIR+"/sys.o" ); + assertTrue( sys_file.exists() ); + + // Can read the ELF files + ElfReader sys_elf = ElfReader.load( sys_file, null); + sys_elf.loadSimple(code1); + + // Elf files are sane + + // Sys depends on io, libc, aryi64, aryu8 + assertEquals(6,sys_elf._deps.length); + assertSame("sys/aryu8" ,sys_elf._deps[0]); + assertSame("sys/io" ,sys_elf._deps[1]); + assertSame("sys/aryi64",sys_elf._deps[2]); + assertSame("sys/Ary" ,sys_elf._deps[3]); + assertSame("sys/Scan" ,sys_elf._deps[4]); + assertSame("sys/libc" ,sys_elf._deps[5]); + assertSame("class:sys" ,sys_elf._clz._name); + } + + @Test + public void testHelloWorld() throws IOException { + File sys_file = buildTestSys(false); + assertTrue("Missing "+sys_file+"; testHelloWorld depends on testSys building it", sys_file.exists()); + + String expected = "Hello, World!\n"; + String prog = "return sys.io.p(\""+expected+"\") - "+expected.length()+";"; + TestC.run(prog,"helloWorld",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION, null, null, expected,0); + } + + @Test + public void testHelloWorldDriver() throws Exception { + File sys_file = new File(RELEASE_SYS_BLDDIR+"/sys.o"); + assertTrue("Missing "+sys_file+"; run make release or make tests_sys first", sys_file.exists()); + Simple.main(new String[]{"-L",RELEASE_SYS_BLDDIR,"--norun","docs/examples/A_helloWorld.smp"}); + } + + @Test + public void testHelloWorldDriverLibFile() throws Exception { + File sys_file = new File(RELEASE_SYS_BLDDIR+"/sys.o"); + assertTrue("Missing "+sys_file+"; run make release or make tests_sys first", sys_file.exists()); + Simple.main(new String[]{"-L",sys_file.toString(),"docs/examples/A_helloWorld.smp"}); + } + + @Test + public void testHelloWorldNoInline() throws Exception { + File sys_file = buildTestSys(false); + assertTrue("Missing "+sys_file+"; testHelloWorldNoInline depends on testSys building it", sys_file.exists()); + + String base = "helloWorldNoInline"; + String expected = "Hello, World!\n"; + String prog = "return sys.io.p_noInline(\""+expected+"\") - "+expected.length()+";"; + CodeGen code = new CodeGen(null,"build/objs",new Ary<>(new String[]{SYS_BLDDIR}), + base,prog,123L,TypeInteger.BOT); + code.driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,true); + + String obj = "build/objs/"+base+".o"; + String exe = "build/objs/"+base+(TestC.OS.startsWith("Windows") ? ".exe" : ""); + String syms = run(new String[]{"nm",obj}); + assertTrue(syms, syms.contains(" U sys.io.p_noInline")); + + String out = run(new String[]{"gcc",obj,sys_file.toString(),"-lm","-g","-o",exe}); + assertEquals("",out); + String rez = run(new String[]{exe}); + assertEquals(expected,rez); + } + + private static String run(String[] cmd) throws Exception { + Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start(); + boolean normal = p.waitFor(5, TimeUnit.SECONDS); + String out = new String(p.getInputStream().readAllBytes()); + assertTrue(out, normal); + assertEquals(out,0,p.exitValue()); + return out; + } + + @Test + public void testRedirectedRead() throws IOException { + File sys_file = buildTestSys(false); + assertTrue("Missing "+sys_file+"; testRedirectedRead depends on testSys building it", sys_file.exists()); + String src = """ +u8[] buf = new u8[10]; +i64 ptr = buf; +int rez = sys.libc.read(0,ptr,buf#); +return rez < buf# ? 0 : sys.libc._exit(-2); +"""; + TestC.run(src,"redirectedRead",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION, null, null, + "abc", "", -1); + } + + @Test + public void testBubbles() throws IOException { + File sys_file = buildTestSys(false); + assertTrue("Missing "+sys_file+"; testBubbles depends on testSys building it", sys_file.exists()); + String src = Files.readString( Path.of("docs/examples/BubbleSort.smp")); + TestC.run(src,"BubbleSort",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION, null, null, + "[3, 2,-17, 999 ] ", "[-17, 2, 3, 999]", -1); + } + +} diff --git a/src/test/java/com/seaofnodes/simple/Compile.java b/src/test/java/com/seaofnodes/simple/Compile.java deleted file mode 100644 index 088055f9..00000000 --- a/src/test/java/com/seaofnodes/simple/Compile.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.FunNode; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import org.junit.Ignore; -import org.junit.Test; -import static org.junit.Assert.*; - -public class Compile { - - private static Process run(String simple, String c) throws IOException, InterruptedException { - boolean USE_WSL = System.getProperty("os.name").startsWith("Windows"); - CodeGen code = new CodeGen(simple); - code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched().regAlloc().encode(); - if (c != null) for (var n:code._start._outputs) if (n instanceof FunNode f && f._name.equals("main")) f._name = "simple_main"; - code.exportELF("test.o"); - var params = new ArrayList(); - if (USE_WSL) params.add("wsl.exe"); - params.add("gcc"); - params.add("-o"); - params.add("test"); - if (c != null) { - Files.writeString(Path.of("test.c"), c); - params.add("test.c"); - } - params.add("test.o"); - Process p = Runtime.getRuntime().exec(params.toArray(String[]::new)); - assertEquals(0, p.waitFor()); - return Runtime.getRuntime().exec(USE_WSL ? new String[]{"wsl.exe", "./test"}:new String[]{"./test"}); - } - - @Test - @Ignore - public void testCompile() throws IOException, InterruptedException { - var p = run(""" - val testMy = { flt x -> - flt guess = x; - while( 1 ) { - flt next = (x/guess + guess)/2; - if( next == guess ) return guess; - guess = next; - } - }; - """,""" - - #include - - extern double testMy(double); - - int main(int argc, char** argv) { - printf("%f\\n", testMy(2.0)); - return 0; - } - - """); - assertEquals(0, p.waitFor()); - assertEquals("1.414214\n", new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); - } - -} diff --git a/src/test/java/com/seaofnodes/simple/Eval2.java b/src/test/java/com/seaofnodes/simple/Eval2.java index 5994e7e1..e88e9caa 100644 --- a/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/src/test/java/com/seaofnodes/simple/Eval2.java @@ -117,7 +117,7 @@ public abstract class Eval2 { public static String eval( CodeGen code, long arg ) { return eval(code,arg,1000); } public static String eval( CodeGen code, long arg, int timeout ) { if( code._start.uctrl()==null ) return ""; // The empty program - SB trace = null; // = new SB(); // TRACE, set to null for off, new SB() for on + SB trace = null; // new SB(); // TRACE, set to null for off, new SB() for on // Force local scheduling phase code.driver(CodeGen.Phase.LocalSched); // Set global, so don't have to pass everywhere @@ -134,6 +134,14 @@ public abstract class Eval2 { CFGNode BB = code._start, prior = null; // Return from main hits Stop F.put(BB,new Closure(code._stop,F)); + // For all / Clazzes, install their uninitialized memory + for( FunNode fun : code._linker ) + if( fun!=null && fun.isClz() ) { + TypeStruct clz = ((TypeMemPtr)fun.sig().arg(0))._obj; + // Map from the self parameter to the + // singleton class structure. + F.put(fun.parm(2),alloc(clz)); + } // ------------ @@ -182,28 +190,37 @@ public abstract class Eval2 { int path = r._inputs.find( prior ); // Parameters read from prior frame, Phis from local frame Frame frame = (r instanceof FunNode ? F._prior : F); - boolean isMain = r instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main); + boolean isClz = r instanceof FunNode fun && fun.isClz(); // Parallel assign Phis. First parallel read and cache int i; for( i = 0; i < r.nOuts(); i++ ) { + // Due to scheduling, all Phis are first in the outputs; first + // non-Phi is the end of all Phis. if( !(r.out(i) instanceof PhiNode phi) ) break; - if( isMain && phi instanceof ParmNode parm && parm._idx==2 ) - PHICACHE[i] = arg; // Reading the initial arguments to main() + + // Parms read from Call args - + // - except RPC is the actual prior frame + // - except takes the singleton class object and external args + Object val; + if( isClz && phi instanceof ParmNode parm && parm._idx==3 ) + PHICACHE[i] = arg; // Reading the initial arguments to else { Node n = phi instanceof ParmNode parm - // RPC reads the Call directly; + // RPC uses the prior Call directly; // Parms may not be linked, so read call args directly ? (parm._idx==0 ? prior : prior.in(parm._idx)) // Phis read from path input : phi.in(path); + // Read the frame before overwriting PHICACHE[i] = frame.get(n); } } // Parallel assign; might assign before read, so read from cache for( int j=0; j < i; j++ ) traceData(F.put0(r.out(j),PHICACHE[j]),trace); - // Return point in basic block past last Phi + // Return point in basic block past last Phi; the Phis have been + // evaluated here, and now we need to evaluate the rest of the block return i; } @@ -252,36 +269,43 @@ public abstract class Eval2 { // From here down shamelessly copied from Evaluator, written by @Xmilia private static Object compute( Node n ) { return switch( n ) { - case AddFNode adf -> d(adf.in(1)) + d(adf.in(2)); - case AddNode add -> x(add.in(1)) + x(add.in(2)); + case AddNode add -> add.mode()==1 + ? (Object)(x(add.in(1)) + x(add.in(2))) + : (Object)(d(add.in(1)) + d(add.in(2))); case AndNode and -> x(and.in(1)) & x(and.in(2)); - case BoolNode.EQF eqf -> d(eqf.in(1)) == d(eqf.in(2)) ? 1L : 0L; - case BoolNode.LEF lef -> d(lef.in(1)) <= d(lef.in(2)) ? 1L : 0L; - case BoolNode.LTF ltf -> d(ltf.in(1)) < d(ltf.in(2)) ? 1L : 0L; case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; // Bool EQ supports pointers, nil, and integers - case BoolNode.LE le -> x(le .in(1)) <= x(le .in(2)) ? 1L : 0L; - case BoolNode.LT lt -> x(lt .in(1)) < x(lt .in(2)) ? 1L : 0L; - case CastNode cast -> val(cast.in(1)); + case BoolNode.LE le -> le.mode()==1 + ? (x(le.in(1)) <= x(le.in(2)) ? 1L : 0L) + : (d(le.in(1)) <= d(le.in(2)) ? 1L : 0L); + case BoolNode.LT lt -> lt.mode()==1 + ? (x(lt.in(1)) < x(lt.in(2)) ? 1L : 0L) + : (d(lt.in(1)) < d(lt.in(2)) ? 1L : 0L); + case CheckCastNode cast -> val(cast.in(1)); + case GuardNode guard -> val(guard.in(1)); case ConstantNode con -> con(con._con); - case DivFNode dvf -> d(dvf.in(2))==0 ? 0D : d(dvf.in(1)) / d(dvf.in(2)); - case DivNode div -> x(div.in(2))==0 ? 0L : x(div.in(1)) / x(div.in(2)); + case DivNode div -> div.mode()==1 + ? (Object)(x(div.in(1)) / x(div.in(2))) + : (Object)((d(div.in(2))==0 ? 0D : d(div.in(1))) / d(div.in(2))); + case EscapeNode esc -> esc(esc); + case FunPtrNode fptr -> con(fptr._con); case LoadNode ld -> load(ld); - case MinusFNode mnf -> - d(mnf.in(1)); - case MinusNode sub -> - x(sub.in(1)); - case MulFNode mlf -> d(mlf.in(1)) * d(mlf.in(2)); - case MulNode mul -> x(mul.in(1)) * x(mul.in(2)); + case MemMergeNode merge-> "$mem"; + case MinusNode sub -> sub.mode()==1 ? (Object)(- x(sub.in(1))) : (Object)(- d(sub.in(1))); + case MulNode mul -> mul.mode()==1 + ? (Object)(x(mul.in(1)) * x(mul.in(2))) + : (Object)(d(mul.in(1)) * d(mul.in(2))); case NewNode alloc-> alloc(alloc); - case NotNode not -> x(not.in(1)) == 0 ? 1L : 0L; + case NotNode not -> not(not); case OrNode or -> x(or .in(1)) | x(or .in(2)); case ProjNode proj -> proj._type instanceof TypeMem ? "$mem" : val(proj.in(0)); case ReadOnlyNode read -> val(read.in(1)); case SarNode sar -> x(sar.in(1)) >> x(sar.in(2)); - case MemMergeNode merge-> "$mem"; case ShlNode shl -> x(shl.in(1)) << x(shl.in(2)); case ShrNode shr -> x(shr.in(1)) >>>x(shr.in(2)); case StoreNode st -> store(st); - case SubNode sub -> x(sub.in(1)) - x(sub.in(2)); - case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2)); + case SubNode sub -> sub.mode()==1 + ? (Object)(x(sub.in(1)) - x(sub.in(2))) + : (Object)(d(sub.in(1)) - d(sub.in(2))); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); @@ -295,7 +319,9 @@ public abstract class Eval2 { // Fetch and unbox as primitive double static double d( Node n ) { Object d = F.get(n); return d==null ? 0 : (Double)d; } // Fetch and unbox a function constant - static TypeFunPtr tfp(Node n) { return (TypeFunPtr)F.get(n); } + static TypeFunPtr tfp(Node n) { + return (TypeFunPtr)F.get(n); + } // Fetch and unbox a closure static Closure clj(Node n) { return (Closure)F.get(n); } @@ -321,16 +347,33 @@ public abstract class Eval2 { } } else { - // Generic TMP (since Simple is not currently making actual - // memory constants), used as a default input to a function; - // should never execute. - yield tmp.toString(); + // Constant object with all zeros + yield alloc(tmp._obj); } } default -> null; }; } + // Escapes of constant memory actually needs to set the memory + private static Object esc(EscapeNode esc) { + if( esc.priv() instanceof ConstantNode con ) { + TypeMem mem = (TypeMem)con._type; + Object val = con(mem._t); + TypeMemPtr tmp = (TypeMemPtr)esc.self()._type; + int idx = tmp._obj.findAlias(esc.fld()._alias); + Object[] fs = (Object[])val(esc.self()); + if( tmp._obj.isAry() ) { + assert idx==0; // length field + assert (Long)val==fs.length; + } else { + fs[idx] = val; + } + } + return "$mem"; + } + + // Convert array size to array element count private static int offToIdx( long off, TypeStruct t) { off -= t.aryBase(); @@ -340,8 +383,8 @@ public abstract class Eval2 { } // Builds and returns a pointer Object - private static Object alloc(NewNode alloc) { - TypeStruct type = alloc._ptr._obj; + private static Object[] alloc(NewNode alloc) { + TypeStruct type = alloc._ts; if( type.isAry() ) { long sz = (Long)val(alloc.in(1)); long x = offToIdx(sz, type); @@ -360,6 +403,10 @@ public abstract class Eval2 { return ary; } + return alloc(type); + } + private static Object[] alloc(TypeStruct type) { + assert !type.isAry(); int num = type._fields.length; Object[] ptr = new Object[num]; for( int i=0; i { if( visit.containsKey(x) ) yield sb.p("$cyclic"); visit.put(x,x); - assert !tmp.isFRef(); Object[] xs = (Object[])x; // Array of fields if( tmp._obj.isAry() ) { Type elem = tmp._obj._fields[1]._t; - if( elem == TypeInteger.U8 ) { + if( elem.isa(TypeInteger.U8) ) { // Shortcut u8[] as a String for( Object o : xs ) sb.p((char)(long)(Long)o); diff --git a/src/test/java/com/seaofnodes/simple/EvalArm64.java b/src/test/java/com/seaofnodes/simple/EvalArm64.java index 9575d9fe..32a5b152 100644 --- a/src/test/java/com/seaofnodes/simple/EvalArm64.java +++ b/src/test/java/com/seaofnodes/simple/EvalArm64.java @@ -63,9 +63,9 @@ public class EvalArm64 { double frval = 0; int pc = _pc; int cycle = _cycle; - boolean is_f = false; outer: for(int icount = 0; icount < maxops; icount++) { + boolean is_f = false; int ir = 0; rval = 0; frval = 0; @@ -96,29 +96,28 @@ public class EvalArm64 { } case 0x1E: { // floats is_f = true; - int rs1 = (ir >> 5) & 0x1F; + int rs1 = (ir >> 5) & 0x1F; int rs2 = (ir >> 16) & 0x1F; int op = (ir >> 10) & 0x3F; switch(op) { case 16: frval = fregs[rs1]; break; // fmov - case 10:frval = fregs[rs1] + fregs[rs2]; break; // fadd + case 10: frval = fregs[rs1] + fregs[rs2]; break; // fadd case 6: if (fregs[rs2] == 0) frval = 0; else frval = fregs[rs1] / fregs[rs2]; break; // fdiv case 8: { // fcmp - double lhs = fregs[rs1]; + double lhs = fregs[rs1]; double rhs = fregs[rs2]; Z = (lhs == rhs); - N = (lhs < rhs); - C = lhs >= rhs; + N = (lhs < rhs); + C = (lhs > rhs); V = Double.isNaN(lhs) || Double.isNaN(rhs); rdid = -1; - break; } - case 2: frval = fregs[rs1] * fregs[rs2]; break; // fmul + case 2: frval = fregs[rs1] * fregs[rs2]; break; // fmul case 14: frval = fregs[rs1] - fregs[rs2]; break; // fsub default: trap = (2+1); } @@ -298,10 +297,12 @@ public class EvalArm64 { break; } - case 0x90: { // adrp - int immhi = ( (ir& ~0x1F) << 8 ) >> 8; + case 0x90, 0xB0, 0xD0, 0xF0: { // adrp + int immhi = ( ir << 8 ) >>> (8+5); int immlo = (ir>>29)& 3; - int imm = ((immhi<<2) | immlo) << 12; + int imm = ((immhi<<2) | immlo); + imm = (imm << 11) >> 11; + imm <<= 12; rval = (pc & ~0xFFF) + imm; break; } @@ -361,13 +362,11 @@ public class EvalArm64 { int encodedCond = (ir >> 12) & 0xF; int decodedCond = encodedCond ^ 1; // conditional select(csel) - int rm = (ir >> 16) & 0x1F; - int rn = (ir >> 5) & 0x1F; boolean cond = switch (decodedCond) { case 0x0 -> Z ; // eq case 0x1 -> !Z ; // ne case 0x2 -> C ; // cs - case 0x3 -> !C ; + case 0x3 -> !C ; // cc case 0x4 -> N ; // mi case 0x5 -> !N ; // pl case 0x6 -> V ; // vs @@ -382,11 +381,7 @@ public class EvalArm64 { case 0xF -> false ; // never default -> throw Utils.TODO(); }; -// int reg = cond ? rn : rm; -// rval = reg==31 ? 0 : regs[reg]; -// if( cond ) rval++; - if(cond) rval = 1; - else rval = 0; + if( cond ) rval = 1; break; } case 0x9B: { // mul @@ -753,16 +748,15 @@ public class EvalArm64 { break; } if(rdid != -1) { - if(is_f) {fregs[rdid] = frval; is_f = false;} - else regs[rdid] = rval; + if(is_f) fregs[rdid] = frval; + else regs[rdid] = rval; } pc += 4; } // Handle traps and interrupts. - if(trap != 0) { + if(trap != 0) return trap; - } _cycle = cycle; _pc = pc; return 0; diff --git a/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/src/test/java/com/seaofnodes/simple/MergeSortTest.java index 9c7c1360..ca33438a 100644 --- a/src/test/java/com/seaofnodes/simple/MergeSortTest.java +++ b/src/test/java/com/seaofnodes/simple/MergeSortTest.java @@ -1,11 +1,9 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.util.SB; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; + import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -15,52 +13,52 @@ public class MergeSortTest { @Test public void testMergeSort0() throws IOException { // Test merge sort on a small array of primes String src = -""" - val merge_sort = { int[] a -> - int[] !b = new int[a#]; - copy_array (a, 0, a#, b); - split_merge(a, 0, a#, b); - }; - - val split_merge = { int[] b, int begin, int end, int[] a -> - if (end - begin <= 1) - return 0; - int middle = (end + begin) / 2; - split_merge(a, begin, middle, b); - split_merge(a, middle, end, b); - merge(b, begin, middle, end, a); - return 0; - }; - - val merge = { int[] b, int begin, int middle, int end, int[] a -> - int i = begin, j = middle; - - for (int k = begin; k < end; k++) { - // && and || - bool cond = false; - if (i < middle) { - if (j >= end) cond = true; - else if (a[i] <= a[j]) cond = true; - } - if (cond) b[k] = a[i++]; - else b[k] = a[j++]; - } - }; - - val copy_array = { int[] a, int begin, int end, int[] b -> - for (int k = begin; k < end; k++) - b[k] = a[k]; - }; +""" +val merge_sort = { int[] a -> + int[] !b = new int[a#]; + _copy_array (a, 0, a#, b); + _split_merge(a, 0, a#, b); +}; + +val _split_merge = { int[] b, int begin, int end, int[] a -> + if (end - begin <= 1) + return 0; + int middle = (end + begin) / 2; + _split_merge(a, begin, middle, b); + _split_merge(a, middle, end, b); + _merge(b, begin, middle, end, a); + return 0; +}; + +val _merge = { int[] b, int begin, int middle, int end, int[] a -> + int i = begin, j = middle; + + for (int k = begin; k < end; k++) { + // && and || + bool cond = false; + if (i < middle) { + if (j >= end) cond = true; + else if (a[i] <= a[j]) cond = true; + } + if (cond) b[k] = a[i++]; + else b[k] = a[j++]; + } +}; + +val _copy_array = { int[] a, int begin, int end, int[] b -> + for (int k = begin; k < end; k++) + b[k] = a[k]; +}; """; int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, }; SB sb = new SB().p(primes.length).p("["); for( int prime : primes ) sb.p(prime).p(", "); String sprimes = sb.p("]").toString(); - TestC.run(src, "sort", null, sprimes, 40); + TestC.runC(src, "sort", sprimes, 34); - EvalRisc5 R5 = TestRisc5.build("src/test/java/com/seaofnodes/simple/progs",src, "sort", "merge_sort", 0, 36, false); + EvalRisc5 R5 = TestRisc5.build( src, "merge_sort", 0, 36, false); // Allocate an array of primes, filled in reverse order int ps = R5._heap; R5._heap += (primes.length+1)*8; // 25 primes, and length diff --git a/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/src/test/java/com/seaofnodes/simple/SchedulerTest.java deleted file mode 100644 index ca29bf84..00000000 --- a/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class SchedulerTest { - - private static void assertObj(Object obj, String name, Object... fields) { - assertEquals(Evaluator.Obj.class, obj.getClass()); - var o = (Evaluator.Obj)obj; - assertEquals(name, o.struct()._name); - Assert.assertArrayEquals(fields, o.fields()); - } - - @Test - @Ignore - public void testJig() { - CodeGen code = new CodeGen(""" -struct S { - int f; -} -S !v=new S; -v.f = 2; -int i=v.f; -if (arg) v.f=1; -return i; -"""); - code.parse();//.opto(); - var eval = new Evaluator(code._stop); - assertEquals(2L, eval.evaluate(1, 10)); - } - - @Test - public void testStoreInIf() { - CodeGen code = new CodeGen(""" -struct S { - int f; -}; -S !v0=new S; -if(arg) v0.f=1; -return v0; -"""); - code.parse().opto(); - var eval = new Evaluator(code._stop); - assertObj(eval.evaluate(0, 10), "S", 0L); - assertObj(eval.evaluate(1, 10), "S", 1L); - } - - @Test - public void testStoreInIf2() { - CodeGen code = new CodeGen(""" -struct S { - int f; -}; -S !v=new S; -v.f = 2; -int i=new S.f; -i=v.f; -if (arg) v.f=1; -return i; -"""); - code.parse().opto(); - var eval = new Evaluator(code._stop); - assertEquals(2L, eval.evaluate(0, 10)); - assertEquals(2L, eval.evaluate(1, 10)); - } - -} diff --git a/src/test/java/com/seaofnodes/simple/Simple.java b/src/test/java/com/seaofnodes/simple/Simple.java index be582a64..35b9b922 100644 --- a/src/test/java/com/seaofnodes/simple/Simple.java +++ b/src/test/java/com/seaofnodes/simple/Simple.java @@ -1,69 +1,66 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.CompUnit; +import com.seaofnodes.simple.codegen.CodeGen.Phase; import com.seaofnodes.simple.print.*; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.util.Utils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.stream.Collectors; public class Simple { - public static final String PORTS = "com.seaofnodes.simple.node.cpus"; - - static final int DUMP_AFTER_PARSE = 1<<0; - static final int DUMP_AFTER_OPTO = 1<<1; - static final int DUMP_AFTER_TYPE_CHECK = 1<<2; - static final int DUMP_AFTER_LOOP_TREE = 1<<3; - static final int DUMP_AFTER_SELECT = 1<<4; - static final int DUMP_AFTER_GCM = 1<<5; - static final int DUMP_AFTER_LOCAL_SCHED = 1<<6; - static final int DUMP_AFTER_REG_ALLOC = 1<<7; - static final int DUMP_AFTER_ENCODE = 1<<8; - - static final int DUMP_FINAL = 1<<16; - - static final int DUMP_DOT = 1<<29; - static final int DUMP_PASS_NAME = 1<<30; - static final int DUMP_AFTER_ALL = 0xff | DUMP_PASS_NAME; - static final int DUMP_DOT_AFTER_ALL = 0xff | DUMP_DOT; - - static final int DUMP_DISASSEMBLE = 1<<31; - static void usage() { System.out.println( """ -simple [options] ...args... +simple [options] input-file.smp ...program args... +Options are applied in-order. Options: - --dump - dump final intermediate representation + --help - This text + + --root root/ - Project root directory; this must end in a '/' and input + files are relative paths to this. If missing, the one input + file path is treated as its own root directory. + + -o output - Project output directory; similar to --root, if 'output' ends in a + '/', then all result files use relative paths to 'output', using a input file + root-relative path. If --root is missing, the one result file is named 'output' + + -L path - add an external Simple object search path + --eval - (slowly) evaluate the compiled code in emulator + --run - run the compiled code natively; this is the default + --main - export the top-level initializer as C `main` + --cpu - use specific CPU (x86_64_v2, riscv, arm) + --abi - use specific ABI variant (SystemV) + --target - print native CPU and ABI + --version + -S - dump generated assembler code + --dump-size - print the size of generated code + --dump-time - print compilation and execution times --dump-after-parse - dump intermediate representation after parse + --dump-after-iter - dump intermediate representation after pessimistic iter pass --dump-after-opto - dump intermediate representation after opto pass --dump-after-type-check - dump intermediate representation after type check pass - --dump-after-select - dump intermediate representation after instrution selection pass + --dump-after-select - dump intermediate representation after instruction selection pass --dump-after-gcm - dump intermediate representation after GCM pass --dump-after-local-sched - dump intermediate representation after local scheduling pass --dump-after-reg-alloc - dump intermediate representation after register allocation pass --dump-after-encode - dump intermediate representation after encoding pass --dump-after-all - dump intermediate representation after all passes + --dump - dump final intermediate representation only --dot - dump grapical representation of intermediate code into *.dot file(s) - -S - dump generated assembler code - --eval - evaluate the compiled code in emulator - --run - run the compiled code natively; this is the default - --dump-size - print the size of generated code - --dump-time - print compilation and execution times - --cpu - use specific CPU (x86_64_v2, riscv, arm) - --abi - use speific ABI variant (SystemV) - --target - print native CPU and ABI - --version - --help """ ); System.exit(0); } - static void bad_usage() { bad("Invalid usage (use --help)"); } + static void bad_usage() { throw bad("Invalid usage (use --help)"); } static RuntimeException bad(String err) { System.err.println("ERROR: "+err); System.exit(1); @@ -76,91 +73,35 @@ Options: System.out.println(TestC.CALL_CONVENTION); } - static void dump(CodeGen code, int dump, int pass) { - if ((dump & pass) != 0) { - if ((dump & DUMP_DOT) != 0) { - String fn = switch (pass) { - case DUMP_AFTER_PARSE -> "01-parse.dot"; - case DUMP_AFTER_OPTO -> "02-opto.dot"; - case DUMP_AFTER_TYPE_CHECK -> "03-type_check.dot"; - case DUMP_AFTER_LOOP_TREE -> "04-loop_tree.dot"; - case DUMP_AFTER_SELECT -> "05-instr_select.dot"; - case DUMP_AFTER_GCM -> "06-gcm.dot"; - case DUMP_AFTER_LOCAL_SCHED -> "07-local_sched.dot"; - case DUMP_AFTER_REG_ALLOC -> "08-reg_allos.dot"; - case DUMP_AFTER_ENCODE -> "09-local_sched.dot"; - case DUMP_FINAL -> "10-final.dot"; - default -> throw Utils.TODO(); - }; - - try { - Files.writeString(Path.of(fn), - new GraphVisualizer().generateDotOutput(code._stop, null, null)); - } catch(IOException e) { throw bad("Cannot write DOT file"); } - } else { - if ((dump & DUMP_PASS_NAME) != 0) { - System.err.println(switch (pass) { - case DUMP_AFTER_PARSE -> "After Parse:"; - case DUMP_AFTER_OPTO -> "After OPTO:"; - case DUMP_AFTER_TYPE_CHECK -> "After Type Check:"; - case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:"; - case DUMP_AFTER_SELECT -> "After Code Selection:"; - case DUMP_AFTER_GCM -> "After GCM:"; - case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:"; - case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:"; - case DUMP_AFTER_ENCODE -> "After Encoding:"; - case DUMP_FINAL -> "Final:"; - default -> throw Utils.TODO(); - }); - } - - System.err.println(IRPrinter.prettyPrint(code._stop, 9999)); - } - } - } - static void print_compilation_times(CodeGen code) { - double t, total = 0; - - total += t = code._tParse / 1e3; - System.out.printf( "Parsing Time: %.3f sec%n", t); - total += t = code._tOpto / 1e3; - System.out.printf( "Optimization Time: %.3f sec%n", t); - total += t = code._tTypeCheck / 1e3; - System.out.printf( "Type Checking Time: %.3f sec%n", t); - total += t = code._tLoopTree / 1e3; - System.out.printf( "Loop Tree Time: %.3f sec%n", t); - total += t = code._tInsSel / 1e3; - System.out.printf( "Code Selection Time: %.3f sec%n", t); - total += t = code._tGCM / 1e3; - System.out.printf( "GCM Time: %.3f sec%n", t); - total += t = code._tLocal / 1e3; - System.out.printf( "Local Scheduling Time: %.3f sec%n", t); - total += t = code._tRegAlloc / 1e3; - System.out.printf( "Register Allocation Time: %.3f sec%n", t); - total += t = code._tEncode / 1e3; - System.out.printf( "Encoding Time: %.3f sec%n", t); - System.out.printf( "TOTAL COMPILATION TIME: %.3f sec%n", total); + long[] times = code._times; + for( int i=0; i libPaths = new ArrayList<>(); // Parse command line int i; for( i = 0; i < args.length; i++ ) { @@ -172,24 +113,33 @@ Options: switch (arg) { case "-": input_filename=""; break; - case "--dump": dump |= DUMP_FINAL; break; - case "--dump-after-parse": dump |= DUMP_AFTER_PARSE; break; - case "--dump-after-opto": dump |= DUMP_AFTER_OPTO; break; - case "--dump-after-type-check": dump |= DUMP_AFTER_TYPE_CHECK; break; - case "--dump-after-loop-tree": dump |= DUMP_AFTER_LOOP_TREE; break; - case "--dump-after-select": dump |= DUMP_AFTER_SELECT; break; - case "--dump-after-gcm": dump |= DUMP_AFTER_GCM; break; - case "--dump-after-local-sched": dump |= DUMP_AFTER_LOCAL_SCHED; break; - case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break; - case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break; - case "--dump-after-all": dump |= DUMP_AFTER_ALL; break; - case "--dot": dump |= DUMP_DOT; break; - case "-S": dump |= DUMP_DISASSEMBLE; break; + case "--dump-after-parse": dump |= 1<= args.length || args[i + 1].charAt(0) == '-') bad_usage(); + out = args[++i]; do_codegen=true; + break; + case "-L": if (i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage(); + libPaths.add(args[++i]); + break; case "--cpu": if (cpu != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage(); cpu = args[++i]; break; @@ -203,16 +153,68 @@ Options: } } + // Break the paths into these parts: + // - A module path, given from "--root" or inferred from the one input file + // - Some sources [modpath/relpath/file.smp]*, all relative to the module path. + // - A build path, given from "-o" or inferred from the one input file + // + // All the sources are compiled, with outputs in [buildpath/relpath/file.o]* + if (input_filename == null) throw bad("no input file' (use --help)"); if( !input_filename.isEmpty() && !input_filename.endsWith(".smp") ) throw bad("File extension must be .smp"); - String base = input_filename.substring(0,input_filename.length()-4); + Path inPath = Path.of(input_filename); + + // Figure out the module path + Path modPath; + if( root == null ) { + modPath = inPath.getParent(); + if( modPath == null ) + modPath = Path.of("."); + } else { + throw Utils.TODO(); + } + + // Compute the module-relative paths for all sources + Path srcRelPath = modPath.relativize(inPath); + int n = srcRelPath.getNameCount(); + Path relPath = n==1 ? null : srcRelPath.subpath(0,n-1); + // Compute source/class name + String srcNameExt = srcRelPath.getName(n-1).toString(); + String srcName = srcNameExt.substring(0,srcNameExt.length()-4); + + // Compute the build path. + Path outPath; + if( out == null ) { + // If no output given, the build dir is the modPath + outPath = modPath; + } else { + // If the output is a directory, that is our out path + outPath = Path.of(out); + if( !out.endsWith("/") ) { + // If the output is a file, the directory is our outPath + Path fullOut = outPath; + outPath = outPath.getParent(); + if( outPath == null ) + outPath = Path.of("."); + Path foo = outPath.relativize(fullOut); + if( !foo.toString().equals(srcName+".o") ) + throw bad("Output path needs to be of the form "+srcName+".o"); + } + } + String outBase = outPath.resolve(srcName).toString(); - if (do_run || (dump & DUMP_DISASSEMBLE) != 0 || do_print_size) { + if( do_run || print_asm || print_size ) { if (cpu == null) cpu = TestC.CPU_PORT; if (abi == null) abi = TestC.CALL_CONVENTION; do_codegen = true; } + if( cpu == null ) cpu = TestC.CPU_PORT; + if( abi == null ) abi = TestC.CALL_CONVENTION; + + String simpleHome = System.getenv("SIMPLE_HOME"); + if( simpleHome != null && !simpleHome.isEmpty() ) + libPaths.add(Path.of(simpleHome,cpu+"_"+abi).toString()); // Read input file try { @@ -220,70 +222,32 @@ Options: } catch( IOException e ) { throw bad("Cannot read input file: "+input_filename); } // Compilation pipeline - CodeGen code = new CodeGen(src); - - code.parse(); - dump(code, dump, DUMP_AFTER_PARSE); - - code.opto(); - dump(code, dump, DUMP_AFTER_OPTO); - - code.typeCheck(); - dump(code, dump, DUMP_AFTER_TYPE_CHECK); - - code.loopTree(); - dump(code, dump, DUMP_AFTER_LOOP_TREE); + Ary externPaths = libPaths.isEmpty() ? null : new Ary<>(libPaths.toArray(new String[0])); + CodeGen code = new CodeGen(modPath.toString(), outPath.toString(), externPaths, srcName, src, 456, TypeInteger.BOT ); + code.driver(Phase.LastPhase,cpu,abi, false, emit_main || do_run || do_eval, dump); - if (do_codegen) { - code.instSelect(cpu, abi, PORTS); - dump(code, dump, DUMP_AFTER_SELECT); - } - - code.GCM(); - dump(code, dump, DUMP_AFTER_GCM); - - code.localSched(); - dump(code, dump, DUMP_AFTER_LOCAL_SCHED); - - if (do_codegen) { - code.regAlloc(); - dump(code, dump, DUMP_AFTER_REG_ALLOC); - - code.encode(); - dump(code, dump, DUMP_AFTER_ENCODE); - - code.exportELF(base+".o"); - } - - dump(code, dump, DUMP_FINAL); - - if (do_codegen && (dump & DUMP_DISASSEMBLE) != 0) { + if( do_codegen && print_asm ) System.out.println(code.asm()); - } - if (do_print_size) { - System.out.printf( "Code Size: %d%n", code._encoding._bits.size()); - } + if( print_size ) + System.out.printf( "Code Size: %d%n", code._encoding._bits.size()); - if (do_print_time) { + if( print_time ) print_compilation_times(code); - } - if (do_eval) { + if( do_eval ) { // TODO: Support for evaluation of functions with different argument numbers and types long t = System.currentTimeMillis(); long arg = (i+1 < args.length) ? Integer.parseInt(args[i+1]) : 0; System.out.println(Eval2.eval(code, arg, 100000)); - if (do_print_time) { - System.out.printf( "EXECUTION TIME: %.3f sec%n", - (System.currentTimeMillis() - t) / 1e3); - } + if( print_time ) + System.out.printf( "EXECUTION TIME: %.3f sec%n", (System.currentTimeMillis() - t) / 1e3); } - if (do_run) { + if( do_run ) { if ( !TestC.CPU_PORT.equals( cpu ) || !TestC.CALL_CONVENTION.equals( abi ) ) throw bad("cannot run code on not native target"); - String exe = TestC.OS.startsWith("Windows") ? base+".exe" : base; - String result = TestC.gcc(base+".o", null, null, true, exe); + String exe = TestC.OS.startsWith("Windows") ? outBase+".exe" : outBase; + String result = TestC.gcc(outBase+".o", null, null, true, exe); System.out.print(result); } } diff --git a/src/test/java/com/seaofnodes/simple/TestArm64.java b/src/test/java/com/seaofnodes/simple/TestArm64.java index a1c02ede..14864d64 100644 --- a/src/test/java/com/seaofnodes/simple/TestArm64.java +++ b/src/test/java/com/seaofnodes/simple/TestArm64.java @@ -1,11 +1,9 @@ package com.seaofnodes.simple; - import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.cpus.arm.arm; -import junit.framework.Assert; -import java.nio.file.Files; -import java.nio.file.Path; +import com.seaofnodes.simple.util.BAOS; import java.io.IOException; import static org.junit.Assert.assertEquals; @@ -13,36 +11,40 @@ import static org.junit.Assert.assertEquals; // Runs 32-bit ARM code in an emulator public class TestArm64 { - public static EvalArm64 build( String file, int arg, int spills, boolean print ) throws IOException { - String dir = "src/test/java/com/seaofnodes/simple/progs"; - String src = Files.readString(Path.of(dir + "/"+file+".smp")); - return build(dir,src, file, arg, spills, print); - } - public static EvalArm64 build( String name, String src, int arg, int spills, boolean print ) throws IOException { - String dir = "src/test/java/com/seaofnodes/simple/progs"; - return build(dir, src ,name, arg, spills, print); - } - - // Compile and run a simple program - public static EvalArm64 build( String dir, String src, String file, int arg, int spills, boolean print ) throws IOException { + public static EvalArm64 build( String main, String src, int arg, int spills, boolean print ) throws IOException { // Compile and export Simple - CodeGen code = new CodeGen(src).driver("arm", "SystemV",null); - if( print ) { code.print_as_hex(); System.out.print(code.asm()); } + CodeGen code = new CodeGen(src).driver("arm", "SystemV",true,false); // Allocation quality not degraded int delta = spills>>3; if( delta == 0 ) delta =1; - if( spills != -1 ) assertEquals("Expect spills:", spills, code._regAlloc._spillScaled, delta); + if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:", spills, code._regAlloc._spillScaled, delta); // Image byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes) EvalArm64 ARM = new EvalArm64(image, 1<<16); - // Code at offset 0 - System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length); + int start = 0; // Code at offset 0 + int cpool = fill(code._encoding._bits ,image,start); + int sdata = fill(code._encoding._cpool,image,cpool); + int end = fill(code._encoding._sdata,image,sdata); + // Initial incoming int arg ARM.regs[arm.X0] = arg; // malloc memory starts at 64K and goes up + + // Look up starting PC or zero if not given + if( main!=null ) + for( FunNode fun : code._linker ) + if( fun != null && !fun.isDead() && fun._compunit == code.compunit() && main.equals(fun._name) ) + ARM._pc = code._encoding.opStart(fun); + return ARM; } + + // Fill bits into image; return end point rounded up. + private static int fill( BAOS bits, byte[] image, int off) { + System.arraycopy(bits.buf(), 0, image, off, bits.size()); + return (off+bits.size() + 15) & -16; + } } diff --git a/src/test/java/com/seaofnodes/simple/TestC.java b/src/test/java/com/seaofnodes/simple/TestC.java index f1831929..7177f765 100644 --- a/src/test/java/com/seaofnodes/simple/TestC.java +++ b/src/test/java/com/seaofnodes/simple/TestC.java @@ -4,9 +4,10 @@ import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.util.Utils; + +import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; @@ -17,77 +18,114 @@ public abstract class TestC { public static final String OS = System.getProperty("os.name"); public static final String CPU = System.getProperty("os.arch"); - public static final String CALL_CONVENTION = OS.startsWith("Windows") ? "Win64" : "SystemV"; + public static final String CALL_CONVENTION = OS.startsWith("Windows") ? "win64" : "SystemV"; public static final String CPU_PORT = switch( CPU ) { case "amd64" -> "x86_64_v2"; default -> throw Utils.TODO("Map Yer CPU Port Here"); }; - - public static void run( String file, int spills ) throws IOException { run(file,"",spills); } - - public static void run( String file, String expected, int spills ) throws IOException { - run("src/test/java/com/seaofnodes/simple/progs",file,expected,spills); + public static final String CPU_ABI = CPU_PORT+"_"+CALL_CONVENTION; + public static final String RELEASE_SYS_DIR = "build/release/"+CPU_ABI; + + public static final String C_DRIVERS_DIR = "src/test/java/com/seaofnodes/simple/progs/"; + + /** + * Compile, link and run + * - WITH a C driver + * - using the default OS/CPU calling convention. + * - no lib sys + */ + public static void runC( String src, String base, String expected, int spills) throws IOException { + String cfile = C_DRIVERS_DIR+base+".c"; + run(src, base, null, CALL_CONVENTION, "", cfile, expected, spills); } - // Compile and run a simple program - public static void run( String dir, String file, TypeInteger arg, String expected, int spills, boolean standalone) throws IOException { - // Files - String cfile = dir+"/"+file+".c" ; - String sfile = dir+"/"+file+".smp"; - String efile = "build/objs/"+file; - - // Compile and export Simple - String src = Files.readString(Path.of(sfile)); - run(src,CALL_CONVENTION,arg, "",standalone? null: cfile,efile,"S",expected,spills); + /** + * Compile, link and run + * - withOUT a C driver + * - using the default OS/CPU calling convention. + * - no lib sys + */ + public static void runSF( String src, String base, String expected, int spills ) throws IOException { + run(src, base, null, CALL_CONVENTION, null,null, expected,spills); } - // link with c and also inline - public static void run(String src, String file, TypeInteger arg, String expected, int spills) throws IOException { - String dir = "src/test/java/com/seaofnodes/simple/progs"; - String cfile = dir+"/"+file+".c"; - String efile = "build/objs/"+file; - run(src, CALL_CONVENTION, arg, "", cfile, efile, "S", expected, spills); + /** + * Compile, link and run + * - withOUT a C driver + * - using the default OS/CPU calling convention. + * - WITH a test lib sys + */ + public static void runSYS( String src, String base, String expected, int spills ) throws IOException { + Ary externPaths = new Ary<>(new String[]{RELEASE_SYS_DIR}); + run(src, base, externPaths, CALL_CONVENTION, null,null, expected,spills); } - public static void run(String dir, String file, String expected, int spills ) throws IOException { - run(dir, file,null, expected, spills, false); - } - // Do not link with c file - no inline. - public static void runS(String file, String expected, int spills ) throws IOException { - run("src/test/java/com/seaofnodes/simple/progs", file, null, expected, spills, true); + /** + * Compile, link and run - general case + * + * @param src Program source to compile + * @param base Compiled binary final base name + * @param externPaths External symbol search path + * @param simple_conv Argument convention when calling Simple code + * @param c_conv Argument convention when calling C code, or null if not + * linking against a C driver program + * @param cfile Associated C program which will drive the Simple program, + * or null if not linking against a C driver program + * @param expected Expected stdout string + * @param spills Expected limit on spill code, used to validate the + * register allocation is reasonable + */ + public static void run( String src, String base, Ary externPaths, String simple_conv, String c_conv, String cfile, String expected, int spills ) throws IOException { + run(src, base, externPaths, simple_conv, c_conv, cfile, null, expected, spills); } - - // Do not link with c file - just inline with source. - public static void runSF(String name, String src, TypeInteger arg, String expected, int spills ) throws IOException { - String efile = "build/objs/"+name; - run(src,CALL_CONVENTION,arg, "",null,efile,"S",expected,spills); - } - - public static void runS(String file, TypeInteger arg, String expected, int spills ) throws IOException { - run("src/test/java/com/seaofnodes/simple/progs", file, arg, expected, spills, true); - } - - - public static void run( String src, String simple_conv, TypeInteger arg, String c_conv, String cfile, String efile, String xtn, String expected, int spills ) throws IOException { - String bin = efile+xtn; - String obj = bin+".o"; - String exe = OS.startsWith("Windows") ? bin+".exe" : bin; + public static void run( String src, String base, Ary externPaths, String simple_conv, String c_conv, String cfile, String stdin, String expected, int spills ) throws IOException { + // Simple file base-name example: + // foo.smp -> + // build/objs/foo.o - object file + // build/objs/foo.exe - linked executable Windows + // build/objs/foo - linked executable Linux + String pathBase = "build/objs/"+base; + String obj = pathBase+".o"; + String exe = pathBase+(OS.startsWith("Windows") ? ".exe" : ""); // Compile simple, emit ELF - CodeGen code = new CodeGen(src, arg).driver( CPU_PORT, simple_conv, obj); + CodeGen code = new CodeGen(null,"build/objs",externPaths,base,src,126L,TypeInteger.BOT); + code.driver( CPU_PORT, simple_conv, false, cfile==null ); - String result = gcc(obj, c_conv, cfile, false, exe ); + String result = gcc(obj, c_conv, cfile, stdin, linkObjs(externPaths), exe ); assertEquals(expected,result); // Allocation quality not degraded int delta = spills>>3; if( delta==0 ) delta = 1; - if( spills != -1 ) + if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); } + // Link with gcc, and execute the resulting binary, returning stdout as a + // String. Any errors *assert* instead of returning some error code; this + // utility is meant to execute inside a JUnit test which will catch the + // assert and flag the test as failed. + public static String gcc( String main, double ignore, String... objs ) throws IOException { + String BLDDIR = "build/objs/test0/"; + String exe = BLDDIR+main+(OS.startsWith("Windows") ? ".exe" : ""); + return gcc(objs[0],null,null,false,exe); + } + + // Link with gcc, and execute the resulting binary, returning stdout as a + // String. Any errors *assert* instead of returning some error code; this + // utility is meant to execute inside a JUnit test which will catch the + // assert and flag the test as failed. public static String gcc( String obj, String c_conv, String cfile, boolean stdin, String... args ) throws IOException { + return gcc(obj,c_conv,cfile,stdin ? "" : null,null,args); + } + + public static String gcc( String obj, String c_conv, String cfile, String stdin, String... args ) throws IOException { + return gcc(obj,c_conv,cfile,stdin,null,args); + } + + public static String gcc( String obj, String c_conv, String cfile, String stdin, Ary linkObjs, String... args ) throws IOException { // Compile the C program. Compiling code and constants in the low // 2Gig. Pointers are 64b BUT since always in the low 2G all the @@ -96,12 +134,14 @@ public abstract class TestC { var params = new Ary<>(String.class); params.add("gcc"); if( cfile!=null ) params.add(cfile); // Associated C driver, usually has a `main` + params.add(obj); + if( linkObjs != null ) + params.addAll(linkObjs.asAry()); params.addAll(new String[] { - obj, - "-lm", // Picks up 'sqrt' for newtonFloat tests to compare - "-g", - "-o", - args[0], + "-lm", // Picks up 'sqrt' for newtonFloat tests to compare + "-g", + "-o", + args[0], }); // Calling convention for C calls, if any if( cfile!=null ) { @@ -128,12 +168,42 @@ public abstract class TestC { // Execute results ProcessBuilder smp = new ProcessBuilder(args); - if( stdin ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT); + File stdinFile = null; + File stdoutFile = File.createTempFile("simple-stdout",".txt"); + smp.redirectOutput(stdoutFile); + if( stdin!=null && stdin.isEmpty() ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT); + else if( stdin!=null ) { + stdinFile = File.createTempFile("simple-stdin",".txt"); + Files.writeString(stdinFile.toPath(),stdin); + smp.redirectInput(stdinFile); + } Process p = smp.start(); + if( stdin==null ) + p.getOutputStream().close(); try { exit = (byte)p.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); } - result = new String(p.getInputStream().readAllBytes()); + result = Files.readString(stdoutFile.toPath()); + if( stdinFile != null ) + stdinFile.delete(); + stdoutFile.delete(); if( exit!=0 ) - System.err.println("exec exit code: "+exit); + return "exec exit code: "+exit; return result; } + + private static Ary linkObjs(Ary externPaths) { + if( externPaths == null ) + return null; + Ary objs = new Ary<>(String.class); + for( String path : externPaths ) { + File file = new File(path); + if( file.isFile() && file.getName().endsWith(".o") ) + objs.add(file.toString()); + else if( file.isDirectory() ) { + File sys = new File(file,"sys.o"); + if( sys.isFile() ) + objs.add(sys.toString()); + } + } + return objs.isEmpty() ? null : objs; + } } diff --git a/src/test/java/com/seaofnodes/simple/TestRisc5.java b/src/test/java/com/seaofnodes/simple/TestRisc5.java index a8f6817e..658909ca 100644 --- a/src/test/java/com/seaofnodes/simple/TestRisc5.java +++ b/src/test/java/com/seaofnodes/simple/TestRisc5.java @@ -1,56 +1,52 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.node.FunNode; -import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.cpus.riscv.riscv; +import com.seaofnodes.simple.util.BAOS; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; + import static org.junit.Assert.*; // Runs 32-bit R5 code in an emulator public abstract class TestRisc5 { - public static EvalRisc5 build( String file, int arg, int spills, boolean print ) throws IOException { - String dir = "src/test/java/com/seaofnodes/simple/progs"; - String src = Files.readString(Path.of(dir + "/"+file+".smp")); - return build(dir,src, file, null, arg, spills, print); - } - - public static EvalRisc5 build( String name, String src, int arg, int spills, boolean print ) throws IOException { - String dir = "src/test/java/com/seaofnodes/simple/progs"; - return build(dir, src ,name, null, arg, spills, print); - } - - // Compile and run a simple program - public static EvalRisc5 build( String dir, String src, String file, String main, int arg, int spills, boolean print ) throws IOException { + public static EvalRisc5 build( String src, String main, int arg, int spills, boolean print ) throws IOException { // Compile and export Simple - CodeGen code = new CodeGen(src).driver("riscv", "SystemV",null); - if( print ) { code.print_as_hex(); System.out.print(code.asm()); } + CodeGen code = new CodeGen(src).driver("riscv", "SystemV",true,false); // Allocation quality not degraded int delta = spills>>3; if( delta==0 ) delta = 1; - if( spills != -1 ) + if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); // Image byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes) EvalRisc5 R5 = new EvalRisc5(image, 1<<16); - // Code at offset 0 - System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length); + int start = 0; // Code at offset 0 + int cpool = fill(code._encoding._bits ,image,start); + int sdata = fill(code._encoding._cpool,image,cpool); + int end = fill(code._encoding._sdata,image,sdata); + // Initial incoming int arg R5.regs[riscv.A0] = arg; // malloc memory starts at 64K and goes up // Look up starting PC or zero if not given if( main!=null ) - for( Node use : code._start._outputs ) - if( use instanceof FunNode fun && main.equals(fun._name) ) - R5._pc = code._encoding._opStart[fun._nid]; + for( FunNode fun : code._linker ) + if( fun != null && !fun.isDead() && fun._compunit == code.compunit() && main.equals(fun._name) ) + R5._pc = code._encoding.opStart(fun); return R5; } + // Fill bits into image; return end point rounded up. + private static int fill( BAOS bits, byte[] image, int off) { + System.arraycopy(bits.buf(), 0, image, off, bits.size()); + return (off+bits.size() + 15) & -16; + } + } diff --git a/src/test/java/com/seaofnodes/simple/TypeTest.java b/src/test/java/com/seaofnodes/simple/TypeTest.java index 0fd56607..e881a2bf 100644 --- a/src/test/java/com/seaofnodes/simple/TypeTest.java +++ b/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -10,18 +10,19 @@ public class TypeTest { // Test basic properties and GLB @Test public void testTypeAdHoc() { - Assert.assertEquals( Type.BOTTOM, TypeInteger.TRUE.meet(TypeNil.NIL) ); - Assert.assertEquals( Type.BOTTOM, TypeInteger.TOP .meet(TypeNil.NIL) ); + Assert.assertEquals( TypeScalar.BOT, TypeInteger.TRUE.meet(TypeNil.NIL) ); + Assert.assertEquals( TypeScalar.BOT, TypeInteger.TOP .meet(TypeNil.NIL) ); TypeStruct s1 = TypeStruct.make("s1",false, - Field.make("a", TypeInteger.BOT,-1, false, false), - Field.make("b", TypeInteger.BOT,-2, false, false) ); + Field.make("a", TypeInteger.BOT,-1, false), + Field.make("b", TypeInteger.BOT,-2, false) ); TypeStruct s2 = TypeStruct.make("s2",false, - Field.make("a", TypeInteger.BOT,-3, false, false), - Field.make("b", TypeInteger.BOT,-4, false, false) ); + Field.make("a", TypeInteger.BOT,-3, false), + Field.make("b", TypeInteger.BOT,-4, false) ); TypeStruct x1ro = (TypeStruct)s1.makeRO(); TypeMemPtr p1 = TypeMemPtr.make(s1); - Assert.assertEquals(x1ro, ((TypeMemPtr)p1.glb(false))._obj); + // GLB no longer forces RO + //Assert.assertEquals(x1ro, ((TypeMemPtr)p1.glb(false))._obj); Assert.assertNotEquals(s1, s1.dual()); TypeStruct s1dglb = ((TypeMemPtr)p1.dual().glb(false))._obj; Assert.assertTrue(x1ro.isa(s1dglb)); @@ -36,12 +37,13 @@ public class TypeTest { Assert.assertNotEquals(m3, m4); Assert.assertEquals(TypeStruct.BOT, s1.meet(s2)); - Assert.assertEquals(TypeMem .BOT, m1.meet(m2)); - Assert.assertEquals(TypeMem.make(1,Type.BOTTOM), m1.meet(m3)); - Assert.assertEquals(TypeMem .BOT, m3.meet(m4)); + TypeMem scalarMemBot = TypeMem.make(1,TypeScalar.BOT,false,false,false,XInt.FULL,XInt.FULL); + Assert.assertEquals(scalarMemBot, ((TypeMem)m1.meet(m2)).makeFrom( XInt.FULL, XInt.FULL)); + Assert.assertEquals(TypeMem.make(1,TypeScalar.BOT), m1.meet(m3)); + Assert.assertEquals(scalarMemBot, ((TypeMem)m3.meet(m4)).makeFrom( XInt.FULL, XInt.FULL)); Assert.assertEquals(TypeMem.make(2,Type.BOTTOM), m1.glb(false)); - Assert.assertEquals(TypeMem.make(2,Type.XNIL), m1.dual()); + Assert.assertEquals(TypeMem.make(2,Type.XNIL,true,true,true, XInt.FULL, XInt.FULL), m1.dual()); Assert.assertEquals(m4.dual(), m4.glb(false).dual()); TypeMemPtr ptr1 = TypeMemPtr.make(s1); @@ -58,14 +60,17 @@ public class TypeTest { Assert.assertNotEquals(ptr1, ptr2); Type p1glb = ptr1.glb(false); - Assert.assertNotEquals(ptr1, p1glb ); + // GLB no longer forces RO + //Assert.assertNotEquals(ptr1, p1glb ); Type p1nro = ptr1nil.makeRO(); - Assert.assertEquals(p1nro, p1glb); + // GLB no longer forces RO + //Assert.assertEquals(p1nro, p1glb); Assert.assertEquals(ptr1, ptr1.dual().dual()); Assert.assertTrue(p1glb.makeRO().isa( ptr1.dual().glb(false))); Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT), ptr1.meet(ptr2nil)); - Assert.assertEquals(p1glb, ptr1.meet(TypeNil.NIL).makeRO()); + // GLB no longer forces RO + //Assert.assertEquals(p1glb, ptr1.meet(TypeNil.NIL).makeRO()); TypeMemPtr TOP = TypeMemPtr.TOP; TypeMemPtr BOT = TypeMemPtr.makeNullable(TypeStruct.BOT); @@ -83,7 +88,7 @@ public class TypeTest { Type nullableptr1_dual = ptr1nil.dual(); // Cyclic check - TypeStruct S1 = ((TypeMemPtr)TypeStruct.SFLT2.field("s1")._t)._obj; + TypeStruct S1 = ((TypeMemPtr)TypeStruct.SFLT1.field("s1")._t)._obj.close(); Assert.assertFalse(s1.isFinal()); Type s1ro = S1.makeRO(); Type s1ro2 = S1.makeRO(); @@ -126,7 +131,7 @@ public class TypeTest { Type t02 = t0.join(t2); Type t12 = t1.join(t2); Type mt = t02.meet(t12); - assertSame(mt,t12); + assertSame(t0+" isa "+t1+" joined "+t2, mt,t12); } } @@ -152,8 +157,8 @@ public class TypeTest { Type d1 = t1.dual(); Type ta = dm.meet(d1); Type tb = dm.meet(d0); - assertSame(ta,d1); - assertSame(tb,d0); + assertSame(t0+" & "+t1+" mt="+mt, ta,d1); + assertSame(t0+" & "+t1+" mt="+mt, tb,d0); } private static void assoc( Type t0, Type t1, Type t2 ) { @@ -161,15 +166,15 @@ public class TypeTest { Type t12 = t1 .meet(t2 ); Type t01_2 = t01.meet(t2 ); Type t0_12 = t0 .meet(t12); - assertSame(t01_2,t0_12); + assertSame(t0+" & "+t1+" & "+t2, t01_2,t0_12); } // Test cyclic types and meets @Test public void testCyclic0() { - Type d0 = TypeStruct.SFLT2.dual(); + Type d0 = TypeStruct.SFLT1.dual(); Type d1 = d0.dual(); - assertSame(TypeStruct.SFLT2,d1); + assertSame(TypeStruct.SFLT1,d1); } @Test @@ -184,8 +189,8 @@ public class TypeTest { public void testList() { TypeStruct list = TypeStruct.open("List"); TypeMemPtr plist = TypeMemPtr.makeNullable(list); - list = list.add(Field.make("next", plist, 2, false, false ) ); - list = list.add(Field.make("x", TypeInteger.BOT, 3, false, false)); + list = list.add(Field.make("next", plist, 2, false) ); + list = list.add(Field.make("x", TypeInteger.BOT, 3, false)); // Make a cyclic type list = list.close(); // Fields are mutable @@ -193,6 +198,17 @@ public class TypeTest { TypeStruct flist = (TypeStruct)list.makeRO(); Assert.assertTrue(flist.isFinal()); Assert.assertNotSame( list, flist ); + } + @Test + public void testOpen() { + TypeStruct open = TypeStruct.make("List",false); + TypeMemPtr plist = TypeMemPtr.makeNullable(open); + TypeStruct list = open.add(Field.make("next", plist, 2, false) ); + list = list.add(Field.make("x", TypeInteger.BOT, 3, false)); + // Make a cyclic type + list = list.close(); + Type mt = open.meet(list); + Assert.assertEquals(list,mt); } } diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java deleted file mode 100644 index 911acc5c..00000000 --- a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ /dev/null @@ -1,382 +0,0 @@ -package com.seaofnodes.simple.evaluator; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.*; - -public class Evaluator { - - private static final Object MEMORY = new Object(); - - public record Obj(TypeStruct struct, Object[] fields) { - private void init(IdentityHashMap objs) { - var oid = objs.get(this); - if (oid != null) { - if (oid != 0) objs.put(this, 0); - return; - } - objs.put(this, -1); - for (var obj : fields) { - if (obj instanceof Obj o) o.init(objs); - } - } - private static int p(StringBuilder sb, IdentityHashMap objs, int id, String indentation, String step, String sep, Object obj) { - if (obj instanceof Obj o) return o.p(sb, objs, id, indentation, step, sep); - sb.append(obj); - return id; - } - private int p(StringBuilder sb, IdentityHashMap objs, int id, String indentation, String step, String sep) { - var cid = objs.get(this); - assert cid != null; - if (cid > 0) { - sb.append("obj@").append(cid); - return id; - } - if (struct._name.equals("[u8]")) { - sb.append('"'); - for (int i=struct._fields.length-1; i= 0x20 && n < 0x80) { - if (n == 0x22 || n == 0x5C) sb.append("\\"); - sb.append((char)n); - } else { - sb.append("\\x").append("0123456789abcdef".charAt(n >> 4)).append("0123456789abcdef".charAt(n & 0xF)); - } - } - sb.append('"'); - return id; - } - sb.append("Obj<").append(struct._name).append(">"); - if (cid == 0) { - cid = id++; - objs.put(this, cid); - sb.append("@").append(cid); - } - sb.append("{"); - if (struct._fields.length == 0) { - assert !struct.isAry(); - sb.append("}"); - return id; - } - String nextIndent = indentation + step; - int e = struct._fields.length - 1; - for(int i=0; i e) { - String innerIndent = nextIndent + step; - sb.append(innerIndent); - id = p(sb, objs, id, nextIndent, step, sep, fields[e]); - for (int i = e+1; i < fields.length; i++) { - sb.append(sep).append(innerIndent); - id = p(sb, objs, id, nextIndent, step, sep, fields[i]); - } - sb.append(nextIndent); - } - sb.append("]"); - } else { - id = p(sb, objs, id, nextIndent, step, sep, fields[e]); - } - sb.append(indentation).append("}"); - return id; - } - private StringBuilder p(StringBuilder sb, String indentation, String step, String sep) { - var objs = new IdentityHashMap(); - init(objs); - p(sb, objs, 1, indentation, step, sep); - return sb; - } - @Override - public String toString() { - if (struct._name.equals("[u8]")) { - var sb = new StringBuilder(); - for (int i=struct._fields.length-1; i= 0 ) return idx; - throw new AssertionError("Field "+memop._name+" not found in struct " + struct._name); - } - - public enum Status { - TIMEOUT, - FALLTHROUGH - } - - /** - * Find the start node from some node in the graph or null if there is no start node - */ - private static StartNode findStart(BitSet visit, Node node) { - if (node == null) return null; - if( visit.get(node._nid) ) return null; - visit.set(node._nid); - StartNode start = node instanceof StartNode ? (StartNode) node : null; - for( Node def : node._inputs ) { - var res = findStart(visit,def); - if( res != null ) start = res; - } - for( Node use : node._outputs ) { - var res = findStart(visit,use); - if( res != null ) start = res; - } - return start; - } - - private final Object[] values; - private final ArrayList phiCache = new ArrayList<>(); - private final StartNode start; - private final Scheduler.Block startBlock; - - public Evaluator(Node graph) { - var visited = new BitSet(); - this.start = findStart(visited, graph); - if (this.start == null) { - this.values = null; - this.startBlock = null; - return; - } - this.startBlock = Scheduler.schedule(this.start); - this.values = new Object[visited.length()]; - } - - private long div(DivNode div) { - long in2 = vall(div.in(2)); - return in2 == 0 ? 0 : vall(div.in(1)) / in2; - } - private double div(DivFNode div) { - double in2 = vald(div.in(2)); - return in2 == 0 ? 0 : vald(div.in(1)) / in2; - } - - private static long offToIdx(long off, TypeStruct t) { - off -= t.aryBase(); - int scale = t.aryScale(); - long mask = (1L << scale)-1; - assert (off & mask) == 0; - return off>>scale; - } - - private Object alloc(NewNode alloc) { - TypeStruct type = alloc._ptr._obj; - Object[] body=null; - int num; - if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); - long n = offToIdx(sz, type); - if( n < 0 ) - throw new NegativeArraySizeException(""+n); - body = new Object[(int)n+1]; // Array body - var elem = type._fields[1]._t; - if (elem instanceof TypeInteger) { - for (int i=0; i 0L; - case TypeFloat tf -> 0; - default -> null; - }; - } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; - } - - private Object load(LoadNode load) { - var from = (Obj)val(load.ptr()); - var off = vall(load.off()); - var idx = getFieldIndex(from.struct, load, (Long)off); - if( idx==from.struct._fields.length-1 && from.struct.isAry() ) { - long len = from.fields.length - from.struct._fields.length + 1; - long i = offToIdx(off, from.struct); - if( i < 0 || i >= len ) - throw new ArrayIndexOutOfBoundsException("Array index out of bounds " + i + " < " + len); - return from.fields[(int)i+from.struct._fields.length-1]; - - } else - return from.fields[idx]; - } - - private Object store(StoreNode store) { - var to = (Obj)val(store.ptr()); - var off = vall(store.off()); - var val = val(store.val()); - var idx = getFieldIndex(to.struct, store, (Long)off); - if( idx==to.struct._fields.length-1 && to.struct.isAry() ) { - long len = to.fields.length - to.struct._fields.length + 1; - long i = offToIdx(off, to.struct); - if( i < 0 || i >= len ) - throw new RuntimeException("Array index out of bounds " + off + " <= " + len); - to.fields[(int)i+to.struct._fields.length-1] = val; - } else - to.fields[idx] = val; - return null; - } - - private static boolean isTrue(Object obj) { - if (obj == null) return false; - if (obj instanceof Number n) return n.longValue() != 0; - return true; - } - - private Object val(Node node) { - return values[node._nid]; - } - - private long vall(Node node) { - var v = val(node); - if (v instanceof Number n) return n.longValue(); - throw new AssertionError("Not a long " + v); - } - private double vald(Node node) { - var v = val(node); - if (v instanceof Number n) return n.doubleValue(); - if( v == null ) return 0; - throw new AssertionError("Not a double " + v); - } - - private Object cons(ConstantNode cons) { - var type = cons.compute(); - if (type instanceof TypeInteger i) return i.isConstant() ? i.value() : Integer.MAX_VALUE; - if (type instanceof TypeFloat i) return i.value(); - return null; - } - - private Object exec(Node node) { - return switch (node) { - case ConstantNode cons -> cons(cons); - case AddNode add -> vall(add.in(1)) + vall(add.in(2)); - case AddFNode add -> vald(add.in(1)) + vald(add.in(2)); - case BoolNode.EQF eq -> vald(eq.in(1)) == vald(eq.in(2)) ? 1L : 0L; - case BoolNode.LEF le -> vald(le.in(1)) <= vald(le.in(2)) ? 1L : 0L; - case BoolNode.LTF lt -> vald(lt.in(1)) < vald(lt.in(2)) ? 1L : 0L; - case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; - case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L; - case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L; - case DivNode div -> div(div); - case DivFNode div -> div(div); - case MinusNode minus -> -vall(minus.in(1)); - case MinusFNode minus -> -vald(minus.in(1)); - case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2)); - case MulFNode mul -> vald(mul.in(1)) * vald(mul.in(2)); - case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L; - case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2)); - case SubFNode sub -> vald(sub.in(1)) - vald(sub.in(2)); - case ShlNode shl -> vall(shl.in(1)) << vall(shl.in(2)); - case ShrNode shr -> vall(shr.in(1)) >>> vall(shr.in(2)); - case SarNode sar -> vall(sar.in(1)) >> vall(sar.in(2)); - case AndNode and -> vall(and.in(1)) & vall(and.in(2)); - case OrNode or -> vall(or .in(1)) | vall(or .in(2)); - case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); - case CastNode cast -> val(cast.in(1)); - case ToFloatNode cast -> (double)vall(cast.in(1)); - case LoadNode load -> load(load); - case StoreNode store -> store(store); - case NewNode alloc -> alloc(alloc); - case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; - case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; - case MemMergeNode mem -> null; - case ReadOnlyNode ro -> val(ro.in(1)); - case CallNode call -> Utils.TODO(); // should not reach here, go the IfNode route - default -> throw new AssertionError("Unexpected node " + node); - }; - } - - /** - * Run the graph until either a return is found or the number of loop iterations are done. - */ - public Object evaluate(long parameter, int loops) { - if (start == null) return Status.TIMEOUT; - var s = new Object[start.compute()._types.length]; - s[1] = MEMORY; - s[2] = parameter; - values[start._nid] = s; - int i=0; - Scheduler.Block block = this.startBlock; - for(;;) { - for(; i 0 && region.nIns() > exit; - block = block.next()[0]; - assert block != null; - for (; i < block.nodes().length; i++) { - if (!(block.nodes()[i] instanceof PhiNode phi)) break; - var val = region instanceof FunNode fun && "main".equals(fun._name) && ((ParmNode)phi)._idx==2 && exit==1 - ? parameter - : val(phi.in(exit)); - phiCache.add(val); - } - for (i=0; i - *
- * This scheduler is used for tests and is not the final scheduler. - *
- *
- * The scheduler schedules not as late as possible. Since it is just for test cases this is fine. - * A better scheduler will try to place nodes outside of loops. Due to the schedule late strategy - * this scheduler might place initialization code in loops. - *
- * We schedule late here since memory edges have some implicit information. There can only be one - * memory edge (for a field) at a time. However, before if statements this might split. - * When scheduling early a store might be moved out of the loop but then there are two edges alive. - * The one through the store node and the adjacent one. This is an invalid schedule and instead of - * handling this case a late scheduler does not have this problem as memory edges are combined - * through phi nodes. - *
- *
- * A bit about this scheduler. It first discovers all alive nodes and allocates side data for them. - * Then the control flow graph is build. Then, all phis are placed and finally all the remaining nodes - * in an order where a node is placed when all alive uses are placed. Finally, the output is produced. - */ -public class Scheduler { - - /** - * A basic block with a schedule of the containing nodes. - * - * @param nodes The nodes in the final schedule of this block - * @param exit The exit node of this block. - * @param exitId In case the next block is a region this gives the entry id into the region. - * @param next The next blocks. - */ - public record Block(Node[] nodes, Node exit, int exitId, Block[] next) {} - - /** - * Definition of a block in the building process. - */ - private static class BasicBlock { - - /** - * Dominator of this block - */ - final BasicBlock dom; - /** - * Id of this block where a.depth < b.depth means that a is before b or - * both are in different branches. - */ - final int depth; - /** - * Previous blocks - */ - final BasicBlock[] prev; - /** - * Entry to this block - */ - final Node entry; - /** - * Schedules node in reverse order. - */ - final ArrayList reverseSchedule = new ArrayList<>(); - - /** - * Initialize the block. - * @param entry The node that started this block - * @param depth The depth of this node. All previous nodes need to have a lower value. - * @param prev The previous blocks. - */ - BasicBlock(Node entry, int depth, BasicBlock... prev) { - BasicBlock dom = null; - int max = -1; - for(var bb: prev) { - max = Math.max(max, bb.depth); - dom = dom==null?bb:dom(dom, bb); - } - assert depth > max; - this.dom = dom; - this.depth = depth; - this.entry = entry; - this.prev = prev; - } - BasicBlock(Node entry, BasicBlock prev) { - this(entry,prev.depth+1,prev); - } - - } - - /** - * Ancillary data for nodes used during the scheduling process. - */ - private static class NodeData { - - /** - * The node this ancillary data is used for. - */ - final Node node; - - /** - * Number of alive users not yet scheduled. - */ - int users = 1; - - /** - * Block into which the node should be placed so far. - * This will be updated with every placed use. - */ - BasicBlock block; - - /** - * Initialize ancillary data for a node - * @param node The node this ancillary data is associated with. - */ - NodeData(Node node) { - this.node = node; - } - - } - - /** - * Ancillary data for nodes. - */ - private final IdentityHashMap data = new IdentityHashMap<>(); - /** - * List of nodes which can be placed since all users are placed. - */ - private final Stack scheduleQueue = new Stack<>(); - - /** - * Get the ancillary data for an alive node. - * @param node The node for which the ancillary data is requested. - * @return The ancillary data for the node. - */ - NodeData d(Node node) { - return od(node).orElseThrow(AssertionError::new); - } - - /** - * Get the ancillary data for a potentially dead node. - * @param node The node for which the ancillary data is requested. - * @return The ancillary data for the node an alive node or - * Optional.empty() for null or dead nodes. - */ - Optional od(Node node) { - return Optional.ofNullable(data.get(node)); - } - - /** - * Checks a node for validity - * @param data The node to check. - * @return true if all placed inputs are before this node. - */ - private boolean isValid(NodeData data) { - return data.node._inputs.stream() - .map(i->i==null?null:d(i)) - .map(d->d==null||d.users>0?null:d.block) - .allMatch(d->d==null||dom(d, data.block)==d); - } - - /** - * Checks that node is not XCtrl. - * @param node The node to check. - * @return true if the node is not XCtrl. - */ - private static boolean isNotXCtrl(Node node) { - return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL) && !(node instanceof XCtrlNode); - } - - /** - * Return the dominator of a and b - * @param a Block a - * @param b Block b - * @return The dominator of a and b - */ - private static BasicBlock dom(BasicBlock a, BasicBlock b) { - while (a!=b) { - if (a.depth >= b.depth) a=a.dom; - if (b.depth > a.depth) b=b.dom; - } - return a; - } - - /** - * Return if a is before b. - * @param a Block a - * @param b Block b - * @return true is a is before b. - */ - private static boolean isBefore(BasicBlock a, BasicBlock b) { - while (b.depth > a.depth) { - while (b.dom.depth > a.depth) b = b.dom; - for (int i=0; i{ - // before might be in a different branch. So check this case with isBefore - // and only refine the placement if it is not in a different branch. - if (isBefore(d.block, data.block)) refinePlacement(data, d.block); - }); - } - - /** - * Schedule all nodes not yet scheduled. - */ - private void doSchedule() { - while (!scheduleQueue.isEmpty()) { - var data = scheduleQueue.pop(); - assert !isPinnedNode(data); - - assert data.block != null; - assert data.users == 0; - - if (data.node instanceof MemOpNode l && l._isLoad) { - assert isValid(data); - // Handle anti-deps of load nodes. - // At this point all anti-dep nodes are scheduled, - // but they did not refine the placement - // so do that now. - var mem = data.node.in(1); - for(var out : mem._outputs) { - if (out instanceof PhiNode p) { - var r = p.in(0); - for (int i = 1; i < p.nIns(); i++) { - if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i)); - } - } else if (!(out instanceof MemOpNode ld && ld._isLoad)) { - optionalRefinePlacement(data, out); - } - } - } - - assert isValid(data); - data.block.reverseSchedule.add(data.node); - - for(var in:data.node._inputs) { - if (in!=null) update(d(in), data.block); - } - if (data.node instanceof MemOpNode s && !s._isLoad) { - // Store nodes have anti-deps to load nodes. - // So decrease the uses of these loads when the store is placed. - for (var out: ((Node)s).in(1)._outputs) { - if (out instanceof MemOpNode ld && ld._isLoad) od(out).ifPresent(this::decUsers); - } - } - } - - // Now all nodes should be placed and have a block assigned - assert this.data.values().stream().noneMatch(d->d.block==null); - } - - /** - * Decrement the not-yet-placed users of a block. - * @param data The node for which the users should be decremented. - */ - private void decUsers(NodeData data) { - assert data.users > 0; - if (--data.users == 0) { - assert data.block != null; - assert isValid(data); - - // When all users are gone this node can be scheduled. - scheduleQueue.push(data); - } - } - - /** - * Update the placement of a node - * @param data The node to update - * @param block The block before which the node should happen. - */ - private void update(NodeData data, BasicBlock block) { - assert block != null; - if (data.users == 0) { - assert isPinnedNode(data); - return; - } - refinePlacement(data, block); - decUsers(data); - } - - /** - * Checks if a CFG node is ready to be placed. This is used for regions joining branches. - * @param node The CFG node to check - * @return true if all parents of a CFG node are placed. - */ - private boolean isCFGNodeReady(Node node) { - if (node instanceof LoopNode l) { - assert d(l.in(1)).block != null; - } else if (node instanceof RegionNode r) { - for(int i=1; id.block==null).orElse(false)) return false; - } - } else if (node instanceof XCtrlNode) { - return false; - } else { - assert d(node.in(0)).block != null; - } - return true; - } - - /** - * Schedule all phi nodes. - * @param phiQueue List of all the phi nodes to schedule. - */ - private void schedulePhis(Stack phiQueue) { - while(!phiQueue.empty()) { - var data = phiQueue.pop(); - var phi = (PhiNode) data.node; - var r = phi.in(0); - data.block = d(r).block; - for(int i=1; iupdate(d(in), d.block)); - } - } - } - - /** - * Build the control flow graph. - * @param start The start node. - */ - private void doBuildCTF(StartNode start) { - var queue = new Stack(); - var phiQueue = new Stack(); - queue.push(d(start)); - while (!queue.isEmpty()) { - var data = queue.pop(); - var node = data.node; - assert node instanceof CFGNode; - BasicBlock block = switch (node) { - case StartNode s -> new BasicBlock(s, 0); - case LoopNode l -> new BasicBlock(l, d(l.in(1)).block); - case RegionNode r -> { - var prev = new ArrayList(); - int depth = 0; - for(int i=1; i d(i.in(0)).block; - case ReturnNode ret -> d(ret .in(0)).block; - case CallNode call -> throw Utils.TODO(); - default -> new BasicBlock(node, d(node.in(0)).block); - }; - data.block = block; - if (node instanceof RegionNode r) { - // Regions might have phis which need to be scheduled. - // Put them on a list for later scheduling. - for (var out:r._outputs) { - if (out instanceof PhiNode p) { - var d = this.data.get(p); - if (d!=null) { - d.users=0; - phiQueue.push(d); - } - } - } - } - if( node instanceof FunNode fun ) { - // Cannot find CFG user from fun via a naive search, because always find - // Return but Return is not always the one CFG user. - CFGNode n = fun.uctrl(); - assert isCFGNodeReady(n) && d(n).block==null; - queue.push(d(n)); - } else if( !(node instanceof ReturnNode) ) { - for (Node n : node._outputs) - if(n instanceof CFGNode && isCFGNodeReady(n) && d(n).block == null) - queue.push(d(n)); - } - var b = block; - for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b)); - } - - // Schedule all phi nodes. - schedulePhis(phiQueue); - } - - /** - * Mark a node alive and create ancillary data for it. - * @param queue List into which this node should be placed if it needs to be visited. - * @param node Node which should be marked alive. - * @param cfg If this node is a CFG node. - */ - private void markAlive(Stack queue, Node node, boolean cfg) { - var nd = od(node).orElse(null); - if (nd != null) { - // Node was already visited, just increase the users. - if (nd.users>0) nd.users++; - return; - } - assert node instanceof CFGNode == cfg; - assert isNotXCtrl(node); - nd = new NodeData(node); - if (cfg) nd.users=0; - data.put(node, nd); - queue.push(nd); - } - - /** - * Mark all alive nodes. - * First all CFG nodes are marked, then all other nodes. - * @param node THe start node. - */ - private void doMarkAlive(Node node) { - var cfgQueue = new Stack(); - var dataQueue = new Stack(); - var mem = new Stack(); - markAlive(cfgQueue, node, true); - // Mark all CFG nodes. - while (!cfgQueue.isEmpty()) { - var data = cfgQueue.pop(); - node = data.node; - assert node instanceof CFGNode; - if (!(node instanceof ReturnNode) ) { - for (var out : node._outputs) if (out!=null && out instanceof CFGNode && isNotXCtrl(out)) markAlive(cfgQueue, out, true); - } - for (var in : node._inputs) if(in!=null && !(in instanceof CFGNode) && isNotXCtrl(in)) markAlive(dataQueue, in, false); - } - // Mark all other nodes. - while (!dataQueue.isEmpty()) { - var data = dataQueue.pop(); - node = data.node; - assert !(node instanceof CFGNode); - if (node instanceof PhiNode phi) { - var r = phi.in(0); - for (int i=1; id.users++); - } - } - } - - /** - * Helper function to append the nodes of a block to an array of nodes - * @param arr The array the nodes of a block should be appended to. - * @param node A potentially separator node - * @param block The block from which the nodes should be appended. - * @return The combined new array. - */ - private static Node[] appendNodes(Node[] arr, Node node, BasicBlock block) { - var idx = arr.length; - arr = Arrays.copyOf(arr, arr.length+block.reverseSchedule.size()+(node==null?0:1)); - if (node != null) arr[idx++] = node; - for(int i=block.reverseSchedule.size()-1;i>=0;i--) { - arr[idx++] = block.reverseSchedule.get(i); - } - return arr; - } - - /** - * Find the CFG output of a node - * @param node The node - * @return The CFG output of the node. - */ - private static CFGNode findSingleCFGOut(Node node) { - if (node instanceof StartNode) { - for(var n:node._outputs) - if (n instanceof FunNode fun && "main".equals(fun._name) ) - return fun; - return null; - } - if( node instanceof FunNode fun ) return fun.uctrl(); - assert node._outputs.stream().filter(x -> x instanceof CFGNode).limit(2).count()<=1; - for(var n:node._outputs) if(n instanceof CFGNode cfg) return cfg; - return null; - } - - /** - * Build the final data structure after all the scheduling happened. - * @param start The start node - * @return The final schedule. - */ - private Block build(Node start) { - var blocks = new IdentityHashMap(); - var queue = new Stack(); - queue.push(d(start)); - - // Visit all CFG nodes and create blocks for them. - // This can combine blocks. - while(!queue.isEmpty()) { - var data = queue.pop(); - var first = data.node; - Node prev; - var last = first; - Node[] arr = new Node[0]; - if (first instanceof RegionNode) { - var nodes = new ArrayList(); - for(var out: first._outputs) { - if (out instanceof PhiNode) { - if(od(out).isPresent()) nodes.add(out); - } - } - arr = nodes.toArray(arr); - } - arr = appendNodes(arr, null, data.block); - assert !(first instanceof IfNode); - for(;;) { - prev = last; - last = findSingleCFGOut(last); - if (last == null) break; - if (last instanceof RegionNode) { - if (blocks.get(last) == null && last != first) - queue.push(d(last)); - break; - } - if (last instanceof ReturnNode) break; - if (last instanceof IfNode if_) { - for(var out:if_._outputs) if (out instanceof CFGNode && blocks.get(out) == null) queue.push(d(out)); - break; - } - data = d(last); - arr = appendNodes(arr, data.node, data.block); - } - blocks.put(first, switch(last) { - case null -> new Block(arr, null, -1, new Block[0]); - case IfNode i -> new Block(arr, i, -1, new Block[2]); - case RegionNode r -> new Block(arr, r, r._inputs.find(prev), new Block[1]); - case ReturnNode r -> new Block(arr, r , -1, new Block[0]); - default -> throw new AssertionError("Unexpected block exit node"); - }); - } - // Update the next pointer of all blocks. - for (var block: blocks.values()) { - switch(block.exit) { - case null: break; - case IfNode i: - for(var out:i._outputs) if (out instanceof CProjNode p) block.next[p._idx] = blocks.get(p); - break; - case RegionNode r: - block.next[0] = blocks.get(r); - break; - default: - assert block.exit instanceof ReturnNode; - } - } - // And return the block for the start node. - return blocks.get(start); - } - - /** - * Create a schedule for the program reachable from start. - * @param start The start node - * @return The final schedule. - */ - public static Block schedule(StartNode start) { - var scheduler = new Scheduler(); - scheduler.doMarkAlive(start); - scheduler.doBuildCTF(start); - scheduler.doSchedule(); - return scheduler.build(start); - } - -} diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java index 77e61b42..c1ffa6c1 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -82,10 +82,10 @@ public class Fuzzer { private static void runCheck(String script, boolean valid) { CodeGen code1; try { - code1 = FuzzerUtils.parse(script, 123, true); + code1 = FuzzerUtils.parse(script, 123); } catch (RuntimeException e1) { try { - FuzzerUtils.parse(script, 456, false); + FuzzerUtils.parse(script, 456); } catch (RuntimeException e2) { if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) { if (!valid || e1.getClass() == Parser.ParseException.class) return; @@ -95,7 +95,7 @@ public class Fuzzer { } throw e1; } - CodeGen code2 = FuzzerUtils.parse(script, 456, false); + CodeGen code2 = FuzzerUtils.parse(script, 456); checkGraphs(code1, code2, 0); checkGraphs(code1, code2, 1); checkGraphs(code1, code2, 10); diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index e08b73fc..9175e380 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -94,13 +94,13 @@ class FuzzerUtils { /** * Parse script with peepholes enabled or disabled */ - public static CodeGen parse(String script, long workListSeed, boolean reset) { + public static CodeGen parse(String script, long workListSeed) { var err = System.err; var out = System.out; try { System.setErr(NULL_PRINT_STREAM); System.setOut(NULL_PRINT_STREAM); - var code = new CodeGen(script, TypeInteger.BOT, workListSeed, reset); + var code = new CodeGen(script, workListSeed); return code.driver(CodeGen.Phase.LocalSched); } finally { System.setErr(err); diff --git a/src/test/java/com/seaofnodes/simple/test0/A.smp b/src/test/java/com/seaofnodes/simple/test0/A.smp new file mode 100644 index 00000000..b94fa147 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test0/A.smp @@ -0,0 +1,6 @@ +// -*- mode: java; -*- + +val _fcn = { -> arg ? { int x -> x+1; } : { int x -> x-1; }; }; + +// Refer to child B is OK because last field of A has been initialized +_fcn()(B.x); diff --git a/src/test/java/com/seaofnodes/simple/test0/A/B.smp b/src/test/java/com/seaofnodes/simple/test0/A/B.smp new file mode 100644 index 00000000..82844f74 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test0/A/B.smp @@ -0,0 +1 @@ +val x=5; diff --git a/src/test/java/com/seaofnodes/simple/test5/par.smp b/src/test/java/com/seaofnodes/simple/test5/par.smp new file mode 100644 index 00000000..f19bd57b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test5/par.smp @@ -0,0 +1,20 @@ +// -*- mode: java; -*- + +// Parent module +val par0 = 3; // Some final field + +// Nested class in parent. +// Makes a field: "val child0 = ..." +struct child0 { + val kid0 = 5; +}; + +// Nested class in parent, comes from disk. +// These are defined in-order +val child1 = child1; + +// Nested class in parent, comes from disk +val child2 = child2; + +// Nested class in parent, comes from disk +val child3 = child3; diff --git a/src/test/java/com/seaofnodes/simple/test5/par/child1.smp b/src/test/java/com/seaofnodes/simple/test5/par/child1.smp new file mode 100644 index 00000000..7bca0cf7 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test5/par/child1.smp @@ -0,0 +1,12 @@ +// -*- mode: java; -*- + +// Child module + +// Same field name as child2 +val kid1 = 7; + +// Can read parent directly +val kid2 = par0; + +// Error to reference a later sibling +// val kid3 = par.child2.kid1; diff --git a/src/test/java/com/seaofnodes/simple/test5/par/child2.smp b/src/test/java/com/seaofnodes/simple/test5/par/child2.smp new file mode 100644 index 00000000..ab5e2ac1 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test5/par/child2.smp @@ -0,0 +1,13 @@ +// -*- mode: java; -*- + +// Child module + +// Same field name as child2 +val kid1 = 11; + +// Can read parent directly. +// Can read defined-in-parent fields. +val kid2 = child0.kid0; + +// OK to reference a past sibling +val kid3 = child1.kid1; diff --git a/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys.smp b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys.smp new file mode 100644 index 00000000..1725c57d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys.smp @@ -0,0 +1,3 @@ +// Reduced multi-compilation-unit reproducer for order-sensitive inlining. +var aryu8 = aryu8; +var aryi64 = aryi64; diff --git a/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/Ary.smp b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/Ary.smp new file mode 100644 index 00000000..e3dd6140 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/Ary.smp @@ -0,0 +1,6 @@ +// Array utilities, discovered indirectly from aryu8. +val copy = { u8[] dst, int doff, u8[~] src, int soff, int len -> + for( int i=0; i buf = b; }; + + val _grow = { int len2 -> + if( len+len2 < buf# ) return self; + int newlen = buf# * 2; + while( len+len2 >= newlen ) newlen *= 2; + i64[] !buf2 = new i64[newlen]; + for( int i=0; i + _grow(1).buf[len++] = c; + self; + }; + + val str = { -> + var str = new aryu8(new u8[1]).add('['); + for( int i=0; i buf = b; }; + + val _grow = { int len2 -> + if( len+len2 < buf# ) return self; + int newlen = buf# * 2; + while( len+len2 >= newlen ) newlen *= 2; + u8[] !buf2 = new u8[newlen]; + Ary.copy(buf2,0,buf,0,buf#); + buf = buf2; + self; + }; + + val clear = { -> len = 0; self; }; + + val add = { u8 c -> + _grow(1).buf[len++] = c; + self; + }; + + val addAry = { u8[~] ary -> + _grow(ary#); + Ary.copy(buf,len,ary,0,ary#); + len += ary#; + self; + }; + + val undo = { int x -> + len = len >= x ? len-x : 0; + self; + }; + + val num = { i64 x -> + if( !x ) { add('0'); return self; } + if( x < 0 ) { add('-'); x = -x; } + i64 ten = 1, log = 0; + while( ten <= x ) { ten *= 10; log++; } + for( int i=0; i