diff options
| author | Cliff Click <cliffc@acm.org> | 2026-08-10 11:11:18 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-08 16:52:27 -0700 |
| commit | d32f97238c903765dec4e9a9f3cbfa5fd12cded6 (patch) | |
| tree | a5ac731801ef7066bab6359c4e85c2bdd20082a6 /src/test | |
| parent | b5ebb2032aa04cf4506d533127d373d4cf5a25c6 (diff) | |
| download | Simple-linear.tar.gz Simple-linear.zip | |
Chapter 25: Modules, Separate Compilation, and SSA Constructionlinear
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: Symious <14933944+symious@users.noreply.github.com>
Co-authored-by: Jun Kawasaki <jun784@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/test')
138 files changed, 3939 insertions, 4294 deletions
diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index a3b98fe0..c2713e4f 100644 --- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -4,10 +4,8 @@ import com.seaofnodes.isa.eval.EvalRisc5; import com.seaofnodes.isa.eval.EvalArm64; -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; @@ -27,24 +25,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]; @@ -52,7 +50,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]; @@ -65,9 +63,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, 25, 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 8923566a..f1e4af12 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -59,7 +59,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 ce808084..1132375e 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -91,7 +91,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 6ff4ac95..79af6ae7 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 @@ -177,7 +180,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 0f4ca169..cdafb43b 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -2,8 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; -import com.seaofnodes.simple.node.*; -import java.util.BitSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -15,35 +13,40 @@ public class Chapter10Test { } ) { String src = "struct S { int x; }; " + body; var code = new CodeGen(src).parse().opto().typeCheck(); + // This chapter also has constructor/class-init Returns in the CompUnit. + var entry = (com.seaofnodes.simple.node.FunNode)code._start.uctrl(); org.junit.Assert.assertTrue(body, - code.expr()._type instanceof com.seaofnodes.simple.type.TypeMemPtr); + entry.ret().expr()._type instanceof com.seaofnodes.simple.type.TypeMemPtr); } } - // Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15. private static final String NULLABLE_POINT_SOURCE = """ - struct Point { int x; }; + struct Point { int x; new Point = { int v -> x = v; }; }; !Point?[] !points = new !Point?[2]; - points[arg] = new Point { x = 42; }; + points[arg] = new Point(42); Point? p = points[1]; """; @Test public void testNullGuards() { + // Issue #246: != null lowers to !!p. Follow every negation, on either arm. for( String body : new String[] { "if (p != null) return p.x; return -1;", "if (null != p) return p.x; return -1;", "if (!!!!p) return p.x; return -1;", "if (!!!p) return -1; return p.x;", - "int b = !!p; if (b) return p.x + b - 1; return -1;", + "val b = !!p; if (b) return p.x + b - 1; return -1;", "if (!(p == null || arg == 0)) return p.x; return -1;" - } ) { - CodeGen code = new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck(); - assertEquals(body,"-1",Eval2.eval(code,0)); - assertEquals(body,"42",Eval2.eval(code,1)); - } + } ) + for( long seed : new long[] { 1, 42, 126 } ) { + CodeGen code = new CodeGen(NULLABLE_POINT_SOURCE+body,seed,true) + .driver(CodeGen.Phase.TypeCheck); + assertEquals(body,"-1",Eval2.eval(code,0)); + assertEquals(body,"42",Eval2.eval(code,1)); + code.driver(CodeGen.Phase.LocalSched); + } } @Test @@ -70,13 +73,10 @@ public class Chapter10Test { "if (!!p) { int x = p.x; } return p.x;" } ) { try { - new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck(); + new CodeGen(NULLABLE_POINT_SOURCE+body).driver(CodeGen.Phase.TypeCheck); fail(body); - } catch( RuntimeException e ) { - // Known null is rejected during parsing in these chapters. - String expected = body.startsWith("if (p == null)") - ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'"; - assertEquals(body,expected,e.getMessage()); + } catch( Parser.ParseException e ) { + assertEquals(body,"Might be null accessing 'x'",e.getMessage()); } } } @@ -109,15 +109,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; @@ -139,7 +139,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;]; return Test.Vector2D; ]", code.print()); } @Test @@ -151,8 +151,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 @@ -177,7 +177,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;]; return Test.Bar; ]", code.print()); } @Test @@ -190,7 +190,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 @@ -216,28 +216,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; @@ -299,7 +299,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;]; return Test.Iter; ]", code.print()); } @@ -318,44 +318,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;]; return Test.s0; ]", 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;]; return Test.s0; ]", code.print()); + } + + @Test public void testBug3() { CodeGen code = new CodeGen(""" @@ -364,7 +383,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 @@ -394,8 +413,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;]; return Test.s0; ]", code.print()); } @Test @@ -423,7 +442,7 @@ s0 v1 = v0; return v1; """); code.parse().opto(); - assertEquals("return (const)s0;", code.print()); + assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:.f0=0;]; return Test.s0; ]", code.print()); } @@ -470,121 +489,4 @@ return 0; assertEquals("return 0;", code.print()); } - @Test public void testReadBeforeStores() { - var code = new CodeGen(""" - struct S { int x; int y; }; - !S !a = new S; !S !b = new S; - a.x = 11; b.x = 22; - !S !p = a; if (arg) p = b; - int before = p.x; - a.y = 55; b.y = 66; - a.x = 33; b.x = 44; - return before*100 + p.x; - """).parse().opto(); - StopNode stop = code._stop; - assertEquals("1133",Eval2.eval(code,0)); - assertEquals("2244",Eval2.eval(code,1)); - } - - static final String NESTED_MEMORY = """ - struct S { int x; int y; int z; }; - !S !s = new S; - s.x = 5; s.y = 7; s.z = 11; - while (arg > 0) { - s.x = s.x + 1; - int j = 3; - while (j > 0) { - j = j - 1; - if (j == 1) continue; - s.y = s.y + arg; - if (arg == 2) break; - s.z = s.z + 1; - } - arg = arg - 1; - } - return s; - """; - - @Test public void testMemoryAcrossNestedLoops() { - var code = new CodeGen(NESTED_MEMORY).parse().opto(); - StopNode stop = code._stop; - for (int arg=0; arg<7; arg++) { - assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+ - ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg)); - } - } - - @Test public void testMemoryAtEarlyReturns() { - var code = new CodeGen(""" - struct S { int x; int y; int z; }; - !S !s = new S; - s.x = 3; s.y = 5; s.z = 7; - if (arg) { s.x = 11; s.z = 13; return s; } - s.y = 17; - return s; - """).parse().opto(); - StopNode stop = code._stop; - for (int arg=0; arg<2; arg++) { - assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg)); - } - } - - @Test public void testKeepLoadsAtMerge() { - var code = new CodeGen(""" - struct S { int x; }; - !S !a = new S; !S !b = new S; - a.x = arg; b.x = arg+1; - !S !p = a; !S !q = b; - if (arg<0) { p=b; q=a; } - int v; - if (arg>1) v=p.x; else v=q.x; - return v; - """).parse().opto(); - StopNode stop = code._stop; - // The one-step safety check stops at the memory aggregate. - assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); - assertEquals("-1",Eval2.eval(code,-1)); - assertEquals("1",Eval2.eval(code,0)); - assertEquals("3",Eval2.eval(code,3)); - } - - @Test public void testDropStores() { - var code = new CodeGen(""" - struct S { int x; }; - !S !s = new S; - if (arg) s.x=arg+1; else s.x=arg+2; - return s; - """).parse().opto(); - StopNode stop = code._stop; - assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); - assertEquals("S{x=2}",Eval2.eval(code,0)); - assertEquals("S{x=4}",Eval2.eval(code,3)); - } - - @Test public void testKeepReadsBeforeWrites() { - var code = new CodeGen(""" - struct S { int x; int y; }; - !S !a = new S; !S !b = new S; - a.x=arg; b.x=arg+1; - !S !p=a; !S !q=b; - if (arg<0) { p=b; q=a; } - int v; - if (arg>1) { v=p.x; p.x=41; p.y=5; } - else { v=q.x; q.x=42; } - return v; - """).parse().opto(); - StopNode stop = code._stop; - assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); - assertEquals("-1",Eval2.eval(code,-1)); - assertEquals("1",Eval2.eval(code,0)); - assertEquals("3",Eval2.eval(code,3)); - } - - private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { - if (n==null || seen.get(n._nid)) return 0; - seen.set(n._nid); - int cnt=kind.isInstance(n) ? 1 : 0; - for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); - return cnt; - } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/src/test/java/com/seaofnodes/simple/Chapter11Test.java deleted file mode 100644 index 3d5d2686..00000000 --- a/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; -import org.junit.Test; -import static org.junit.Assert.*; - -public class Chapter11Test { - @Test public void testIndependentField() { - var code = new CodeGen(""" - struct S { int x; int y; }; - !S !s = new S; - s.x = 42; - while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } - return s.x; - """).parse().opto(); - var stop = code._stop; - assertEquals("return 42;",stop.toString()); - assertEquals("42",Eval2.eval(code,5)); - assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); - } - - @Test public void testParallelMemoryPhis() { - var code = new CodeGen(""" - struct S { int x; int y; int z; }; - !S !a = new S; !S !b = new S; - !S !p = a; if (arg) p = b; - while (arg > 0) { - a.x = p.x + arg; - a.y = p.y + 2; - a.z = p.z + 3; - arg = arg - 1; - } - return a; - """).parse().opto(); - var stop = code._stop; - int[] phis = {0}; - stop.walk(n -> { - // Store sinking can replace field memory Phis with value Phis. - if (n instanceof PhiNode) phis[0]++; - if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) - for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) - assertTrue(a<m.nIns() && m.in(a)!=null); - assertFalse(n instanceof PhiNode && n.isMem() && - !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode)); - return null; - }); - assertTrue(phis[0]>=4); // Three fields and the loop counter. - assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); - } - - @Test public void testLateSliceWorklist() { - // Seed 97 exposed a store selecting a bulk predecessor without queuing - // it. Keep both the worklist assertion and the final heap check. - for (int seed : new int[]{0,97,123,456}) { - var code = new CodeGen(Chapter10Test.NESTED_MEMORY,com.seaofnodes.simple.type.TypeInteger.BOT,seed,false).parse(); - code.opto(); - assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4)); - } - } -} diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java index ac3b81ec..07cd3831 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -12,11 +12,11 @@ public class Chapter12Test { 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 @@ -42,22 +42,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)); } @@ -65,17 +65,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()); } @@ -83,8 +83,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(); @@ -96,12 +96,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 Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 7:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.i=0;]; return Test.N; ]", code.print()); } @Test @@ -109,12 +109,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 @@ -134,20 +134,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(); @@ -159,10 +159,10 @@ return 0; public void testForwardRef0() { CodeGen code = new CodeGen( """ -struct S1 { !S2? !s; }; +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()); } } @@ -170,12 +170,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 Test._S2;", code.print()); } @Test @@ -199,9 +199,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; @@ -211,40 +211,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; @@ -277,7 +277,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/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 62bc1654..7cfa5817 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -1,6 +1,5 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.print.IRPrinter; import org.junit.Test; @@ -46,13 +45,30 @@ return primeCount; } @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; @@ -67,10 +83,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; @@ -88,19 +104,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()); } @@ -108,10 +124,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; @@ -125,27 +141,29 @@ 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()); + for( int arg : new int[]{-1,0,1,3,100} ) + assertEquals("0",Eval2.eval(code,arg)); } @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; """); @@ -157,9 +175,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; @@ -175,9 +193,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; @@ -195,26 +213,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; @@ -225,7 +243,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/Chapter14FloatTest.java b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 6377bc14..c1354e34 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -1,11 +1,9 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.StopNode; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class Chapter14FloatTest { @Test public void testNumericAssignments() { @@ -53,22 +51,23 @@ public class Chapter14FloatTest { 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 @@ -83,8 +82,10 @@ while( 1 ) { } return guess; """); + // TODO: One of the problems with <clinit> 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/Chapter14Test.java b/src/test/java/com/seaofnodes/simple/Chapter14Test.java index c342eeed..086bb824 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -94,12 +94,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 d0a42ccf..f363847a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -26,14 +26,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 @@ -98,34 +98,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; } @@ -141,13 +141,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 @@ -168,22 +168,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?[mutable]? !_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; new _S = { int[] ary -> b=ary; }; }; return 0; """); code.parse().opto(); @@ -209,7 +209,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)); } @@ -227,12 +227,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(); @@ -269,8 +269,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 f73cbf57..91e5cd38 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -48,8 +48,8 @@ return x+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(); @@ -60,8 +60,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(); @@ -74,19 +74,19 @@ return z.x; 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)); @@ -96,28 +96,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)); @@ -127,7 +126,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 @@ -139,34 +138,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)); - } - static final String CONSTRUCTOR_MEMORY = """ - struct S { int x; int y; }; - struct T { int z=arg+40; }; - !T !t = new T; - !S !a = new S { x=11; y=7; }; - !S !b = new S { x=22; y=9; }; - !S !p=a; - if (arg) p=b; - int before=p.x; - !S !c = new S { - x=p.x+1; - { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } - y=p.y; - }; - p.x=33; - return before*10000+c.x*100+c.y+t.z; - """; - - @Test public void testConstructorMemory() { - var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto(); - assertEquals("111249",Eval2.eval(code,0)); - assertEquals("222352",Eval2.eval(code,1)); + 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/Chapter17aTest.java b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java index 8597b799..52acff57 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter17aTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java @@ -24,8 +24,8 @@ return y; @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(); @@ -36,20 +36,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 (const)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; }; -~Point p = new Point { x=5; y=6; }; +struct _Point { int x=3, y=4; new _Point = { int xx, int yy -> x=xx; y=yy; }; }; +~_Point p = new _Point(5,6); p.x++; return p; """); @@ -60,8 +60,8 @@ return p; @Test public void testStructFinal2() { CodeGen code = new CodeGen(""" -struct Point { int x=3, y=4; }; -~Point p = new Point; +struct _Point { int x=3, y=4; }; +~_Point p = new _Point; p.x++; return p; """); @@ -71,18 +71,21 @@ return p; @Test public void testStructFinal3() { - for( String fields : new String[] { "var x; var y;", "int[] !x;", "int[] !x;" } ) { - String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;"; - try { new CodeGen(src).parse().opto(); fail(fields); } - catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); } - } + CodeGen code = new CodeGen(""" +struct _Point { var x; var y; }; +_Point p = new _Point; +p.x++; +return p; +"""); + try { code.parse().opto(); fail(); } + 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; """); @@ -93,14 +96,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)); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index 24dc0229..b6768d6f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -81,8 +81,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; """); @@ -134,13 +134,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() { @@ -158,14 +158,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)); @@ -178,20 +178,20 @@ return s.x; } @Test public void testVar7() { - CodeGen code = new CodeGen("struct S{int x;}; ~S 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; };}; ~S 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)); @@ -209,8 +209,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 @@ -224,16 +224,16 @@ 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 -~Foo xfoo = foo; // Throw away mutability +~_Foo xfoo = foo; // Throw away mutability int x4 = xfoo.bar.x; // Ok to read through xfoo, gets 4 foo.bar.x++; // Bumps to 5 int x5 = xfoo.bar.x; // Ok to read through xfoo, gets 5 @@ -258,41 +258,64 @@ 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 { - if (arg) b = new B; // Constructor ends with partial init of b -}.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()); } + for( String fields : new String[] { + "!B !b; new A = { -> if (arg) b = new B; };", + "!B !b; new A = { -> if (arg) return 0; b = new B; };", + "!B !b; new A = { !B b -> b = new B; };", // Argument shadows field + "new A = { -> }; !B !b;", // Field declared later + } ) { + CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return 0;"); + try { code.parse(); fail(fields); } + catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); } + } + } + + @Test + public void testDefaultConstructorFields() { + for( String field : new String[] { "!B !b;", "!B !b;" } ) { + CodeGen code = new CodeGen("struct B {}; struct A { "+field+" }; return new A;"); + try { code.parse(); fail(field); } + catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' needs to be set in a constructor",e.getMessage()); } + } + for( String fields : new String[] { + "B b = new B;", + "!B !b; b = new B;", + "new A = { -> }; B b = new B;", + "!B !b; new A = { -> if (arg) { b = new B; return 0; } b = new B; };", + } ) { + CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return new A.b != null;"); + code.parse().opto().typeCheck(); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("1",Eval2.eval(code,1)); + } + // No returning path means no object can escape uninitialized. + new CodeGen("struct B {}; struct A { !B !b; new A = { -> while (true) {} }; }; return 0;").parse(); } @Test public void testVar16() { CodeGen code = new CodeGen( """ -struct S{}; +struct S{ new S = { -> x = 2; }; }; val x = 1; -~S s = new S{x = 2;}; +~S 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 @@ -311,7 +334,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,23 +360,36 @@ return arg ? 0 : arg; } @Test + public void testShortCircuitGuard() { + CodeGen code = new CodeGen(""" +struct S { int !x; }; +val get = { !S s -> s.x; }; +!S? !s = arg ? new S; +if( !s || !arg ) return 0; +return get(s); +"""); + code.parse().opto().typeCheck(); + } + + @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(); @@ -366,8 +402,8 @@ 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(); @@ -396,7 +432,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("Cannot return generic scalar",e.getMessage()); } } // --------------------------------------------------------------- @@ -471,7 +507,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()); } } @@ -480,39 +516,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 []*B?;", code.print()); - assertEquals("*B {... }?[]", Eval2.eval(code,0)); + 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 44c365dd..82f99fc0 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -94,7 +94,7 @@ public class Chapter18Test { new CodeGen(test[0]).parse().opto().typeCheck(); fail("Expected incompatible return types: " + test[0]); } catch( Parser.ParseException e ) { - assertEquals(test[1],e.getMessage()); + assertEquals("Cannot return generic scalar",e.getMessage()); } } } @@ -102,10 +102,10 @@ public class Chapter18Test { @Test public void testPrintingForwardReferenceScope() throws Exception { new CodeGen("return 0;").parse(); - var declared = TypeMemPtr.make(TypeStruct.open("PrintForward")); + var declared = TypeMemPtr.make(TypeStruct.make("PrintForward",true,true)); var scope = new ScopeNode(); scope.define("ptr",declared,false,new ConstantNode(TypeInteger.ZERO),null); - Parser.TYPES.put("PrintForward",TypeMemPtr.make(TypeStruct.make("PrintForward",false))); + Parser.TYPES.put("PrintForward",TypeMemPtr.make(TypeStruct.make("PrintForward",false,false))); var field = Var.class.getDeclaredField("_type"); field.setAccessible(true); var v = scope.var(0); @@ -123,7 +123,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)); } @@ -141,7 +141,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)); @@ -158,7 +158,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 ) ); } @@ -171,31 +171,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()); } @@ -210,7 +229,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)); } @@ -223,7 +242,7 @@ var fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; return fcn(3); """); code.parse().opto(); - assertEquals("Stop[ return #2; return (Parm_x($fun21,i64)*x); return (Parm_x($fun22,i64)<<1); ]", 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)); } @@ -231,9 +250,12 @@ 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 #2; return Phi(Region,1,(Parm_x(_fact,arg,(x-1))*#2)); ]") || + 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)); @@ -241,31 +263,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)); } @@ -273,17 +319,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)); } @@ -299,8 +345,8 @@ for(;;) { } """); code.driver(Phase.LocalSched); - assertEquals("return Top;", code._stop.toString()); - assertNull(Eval2.eval(code,0)); // Infinite loop: i2i remains null. + assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print()); + assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, <clinit> never exits } @@ -308,23 +354,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)); } @@ -359,7 +405,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()); } } @@ -371,7 +417,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()); } } @@ -383,16 +429,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( """ @@ -415,8 +459,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)); @@ -438,7 +482,7 @@ for(;;) { } """); code.parse().opto().typeCheck().GCM(); - assertEquals("return 0;", code._stop.toString()); + assertEquals("return 0;", code.print()); assertEquals("0", Eval2.eval(code, 0)); } @@ -447,15 +491,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,1); 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)); } @@ -463,82 +507,16 @@ 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)); } - static final String CALL_MEMORY = """ - struct S { int x; int y; }; - val bump = { !S !s, int d -> int old=s.x; s.x=old+d; return old; }; - !S !a = new S { x=10; y=7; }; - !S !b = new S { x=20; y=9; }; - !S !p=a; if (arg) p=b; - int before=p.x; - int old=bump(p,3); - int after=p.x; - bump(a,5); - return before*1000000+old*10000+after*100+a.x+a.y; - """; - - @Test public void testCallMemory() { - var code = new CodeGen(CALL_MEMORY).parse().opto(); - int[] calls={0}; - code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; }); - assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them. - assertEquals("10101325",Eval2.eval(code,0)); - assertEquals("20202322",Eval2.eval(code,1)); - code._stop.walk(n -> { - if(n instanceof CallNode call) { - assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments. - assertTrue(call.fptr()._type instanceof TypeFunPtr); - } - return null; - }); - } - - static final String RECURSIVE_MEMORY = """ - struct S { int x; }; - val rec = { !S !s, int n -> - if (n==0) return s.x; - int before=s.x; - s.x=before+1; - return before+rec(s,n-1); - }; - !S !s=new S { x=10; }; - int value=rec(s,arg); - return value*100+s.x; - """; - - @Test public void testRecursiveMemory() { - var code = new CodeGen(RECURSIVE_MEMORY).parse().opto(); - for(int n=0; n<7; n++) - assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n)); - } - - static final String INLINE_MEMORY = """ - struct S { int x; int y; }; - val make = { !S s -> - S t = new S { x=s.x+1; y=3; }; - s.y=9; - return t; - }; - !S !a = new S { x=arg+40; y=7; }; - S t=make(a); - return a.x*10000+a.y*100+t.x+t.y; - """; - - @Test public void testInlineAllocationMemory() { - var code = new CodeGen(INLINE_MEMORY).parse().opto(); - assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); - assertEquals("400944",Eval2.eval(code,0)); - assertEquals("420946",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 8a840787..cb7d492f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -6,8 +6,6 @@ import com.seaofnodes.simple.type.*; 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.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -15,14 +13,14 @@ import static org.junit.Assert.*; public class Chapter19Test { @Test public void testPrintingFunctionLookup() { var code = new CodeGen("return 0;").parse(); - var tfp = TypeFunPtr.make((byte)2,false,new Type[]{TypeInteger.constant(987654)},TypeInteger.constant(123456),1L<<30); - var con = new ConstantNode(tfp); + var tfp = TypeFunPtr.make1((byte)2,false,new Type[]{TypeInteger.constant(987654)},TypeInteger.constant(123456),30); + var con = ConstantNode.raw(tfp); var call = new CallNode(null,code._start,con,con); // Diagnostic lookup may construct/intern the return-erased linker key. con.toString(); org.junit.Assert.assertNull(call.name()); - var fun = new FunNode(null,tfp,"printerTarget"); + var fun = new FunNode(null,tfp,"printerTarget",null); fun._name = "printerTarget"; fun.addDef(code._start); code.link(fun); @@ -40,18 +38,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 @@ -64,12 +63,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; @@ -78,107 +71,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[final]); 5:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:._hashCode=0;]; return Test.String; 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 @@ -188,7 +215,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 @@ -199,7 +226,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 @@ -209,7 +236,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 @@ -258,10 +285,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 @@ -349,7 +376,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 ) { @@ -359,15 +386,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 -> @@ -402,8 +430,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)); } @@ -411,97 +439,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,i64),x); return (shli,Parm_x($fun22,i64)); ]", 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()); - } - - @Test public void testSelectedMemory() { - for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) - for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, - Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { - var code = new CodeGen(src).parse().opto().typeCheck(); - var aliases = memoryPhis(code._stop); - code.loopTree().instSelect(cpu,"SystemV"); - assertEquals(aliases,memoryPhis(code._stop)); - code.GCM().localSched(); - code._stop.walk(n -> { - if( !(n instanceof CFGNode) && !(n instanceof ProjNode) ) - assertTrue(n.in(0) instanceof CFGNode); - if( n instanceof NewNode nn ) { - assertEquals(2,((TypeTuple)nn._type)._types.length); - assertTrue(nn.mem() instanceof MemMergeNode); - assertNull(nn.mem().in(1)); // Partial allocation input. - nn.cacheRegs(code); - MachNode mach = (MachNode)nn; - assertNull(mach.regmap(1)); - assertNotNull(mach.regmap(2)); - assertNotNull(mach.outregmap(0)); - assertNull(mach.outregmap(1)); - } - if( n instanceof CallNode call ) { - assertEquals(4,call.nIns()); // ctrl, memory, two args; direct target is embedded. - } - return null; - }); - } - } - - private static java.util.ArrayList<String> memoryPhis(Node stop) { - var phis = new java.util.ArrayList<String>(); - // Count only definitions reachable from Stop, as instruction selection does. - // A dead Phi cycle can still appear through the reverse use edges. - var seen = new java.util.BitSet(); - var work = new java.util.ArrayList<Node>(); - work.add(stop); - for( int i=0; i<work.size(); i++ ) { - Node n = work.get(i); - if( n==null || seen.get(n._nid) ) continue; - seen.set(n._nid); - for( Node def : n._inputs ) work.add(def); - if( n instanceof MemPhiNode phi ) phis.add("alias:"+phi._alias); - if( n instanceof BulkMemPhiNode phi ) phis.add("bulk:"+phi._aliases); - assertFalse(n instanceof PhiNode && n.isMem() && - !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode)); - } - java.util.Collections.sort(phis); - return phis; - } - - @Test public void testFoldedReadBeforeWrites() { - var code = new CodeGen(""" - struct S { int x; }; - !S !a = new S { x=11; }; !S !b = new S { x=22; }; - !S !p = a; if( arg ) p = b; - int before = p.x + arg; - a.x=33; b.x=44; - return before; - """).parse().opto().typeCheck().loopTree().instSelect("x86_64_v2","SystemV").GCM().localSched(); - var read = (MemOpNode)code._stop.walk(n -> - n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); - assertNotNull(read); - int writes = 0; - for( Node use : read.antiDeps() ) - if( use instanceof MemOpNode st && st.isMem() ) { - assertSame(read.cfg0(),st.cfg0()); - assertTrue(st._inputs.find(read)>=0); - assertTrue(read.cfg0()._outputs.find(read)<st.cfg0()._outputs.find(st)); - assertNull(((MachNode)st).regmap(st._inputs.find(read))); - writes++; - } - assertEquals(1,writes); // The next Store follows this first clobber through memory. + 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 4ca1dbda..b46a54b2 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -4,8 +4,6 @@ import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.print.ASMPrinter; -import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -52,11 +50,11 @@ public class Chapter20Test { int stores=0; for( var bb : code._cfg ) for( var node : bb.outs() ) - if( node instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86 st ) { + if( node instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86 st && st._name.equals("x") ) { stores++; assertEquals(-1,st.regmap(4).nextReg((short)15)); } - assertEquals(1,stores); + assertTrue(stores>0); } } @@ -71,14 +69,12 @@ return f(s); // The entry of g can disappear before its Return is folded into f. // Exercise both worklist orders; deleting the entry does not kill 123. for( int seed=0; seed<64; seed++ ) { - CodeGen code = new CodeGen(src,com.seaofnodes.simple.type.TypeInteger.BOT,seed,true).parse().opto().typeCheck(); + CodeGen code = new CodeGen(src,seed,true).parse().opto().typeCheck(); assertEquals("seed "+seed, "123", Eval2.eval(code,0)); } } - @org.junit.Rule public final org.junit.rules.ErrorCollector _errors = new org.junit.rules.ErrorCollector(); - @Test public void testAllocatorMasks() { com.seaofnodes.simple.codegen.RegAllocTestSupport.masks(); } @Test public void testAllocatorUnion() { com.seaofnodes.simple.codegen.RegAllocTestSupport.union(); } @Test public void testAllocatorCopyClobber() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyClobber(); } @@ -97,40 +93,38 @@ return f(s); 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)); } - // Collect differences so every target runs; JUnit still fails the test. - private void testTarget(String src, String cpu, String os, int spills, String stop) { - _errors.checkSucceeds(() -> { testCPU(src,cpu,os,spills,stop); return null; }); - } - - static void testCPU(String src, String cpu, String os, int spills, String stop) { + static void testCPU( String src, String cpu, String os, int spills, String stop ) { CodeGen code = new CheckedCodeGen(src); code.driver(CodeGen.Phase.RegAlloc,cpu,os); - SpillStats.record(code,"Chapter20",cpu,os); - SpillStats.checkSpills(spills,code._regAlloc._spillScaled); - if( stop!=null ) assertEquals(stop,code._stop.toString()); + int delta = spills>>3; + if( delta==0 ) delta = 1; + if( spills != -1 && !CodeGen.iterSeedOverridden() ) + assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); + if( stop != null ) + assertEquals(stop, code.print()); } - private void testAllCPUs( String src, int spills, String stop ) { - testTarget(src,"x86_64_v2", "SystemV",spills,stop); - testTarget(src,"riscv" , "SystemV",spills,stop); - testTarget(src,"arm" , "SystemV",spills,stop); + private static void testAllCPUs( String src, int spills, String stop ) { + testCPU(src,"x86_64_v2", "SystemV",spills,stop); + testCPU(src,"riscv" , "SystemV",spills,stop); + testCPU(src,"arm" , "SystemV",spills,stop); } @Test public void testAlloc0() { - testTarget("return new u8[arg];","x86_64_v2","SystemV",4,"return []u8;"); - testTarget("return new u8[arg];","riscv","SystemV",5,"return []u8;"); - testTarget("return new u8[arg];","arm","SystemV",5,"return []u8;"); + testCPU("return new u8[arg];","x86_64_v2", "SystemV",3,"return []u8;"); + testCPU("return new u8[arg];","riscv" , "SystemV",5,"return []u8;"); + testCPU("return new u8[arg];","arm" , "SystemV",5,"return []u8;"); } @Test public void testBasic1() { String src = "return arg | 2;"; - testTarget(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));"); - testTarget(src,"riscv" , "SystemV",0,"return ( arg | #2 );"); - testTarget(src,"arm" , "SystemV",0,"return (ori,arg);"); + testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));"); + testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );"); + testCPU(src,"arm" , "SystemV",0,"return (ori,arg);"); } @Test @@ -138,50 +132,77 @@ return f(s); 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; } }; -return sqrt(arg) + sqrt(arg+2); +return _sqrt(arg) + _sqrt(arg+2); """; - testTarget(src,"x86_64_v2", "SystemV",40,null); - testTarget(src,"riscv" , "SystemV",17,null); - testTarget(src,"arm" , "SystemV",18,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() { String src = """ -// Newtons approximation to the square root -val 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( next == guess ) return guess; + if( guess-epsilon <= next && next <= guess+epsilon ) + return guess; guess = next; } }; -flt farg = arg; -return sqrt(farg) + sqrt(farg+2.0); +flt farg = arg; return _test_sqrt_noInline(farg) + _test_sqrt_noInline(farg+2.0); """; - testTarget(src,"x86_64_v2", "SystemV",23,null); - testTarget(src,"riscv" , "SystemV",18,null); - testTarget(src,"arm" , "SystemV",18,null); + testCPU(src,"x86_64_v2", "SystemV",40,null); + testCPU(src,"riscv" , "SystemV",17,null); + testCPU(src,"arm" , "SystemV",18,null); + } + + @Test + public void testIterPrettyPrintIsLocalAndReadOnly() { + CodeGen code = new CodeGen(""" + val f_noInline = { flt x -> while(1) { if(x) return x; } }; + flt y=arg; + return f_noInline(y)+f_noInline(y+2.0); + """).driver(CodeGen.Phase.Iter); + assertNull(code._start._ltree); + int pre = code._start._pre; + + String p0 = code.toString(); + String p1 = code.toString(); + assertEquals(p0,p1); + assertNull(code._start._ltree); + assertEquals(pre,code._start._pre); + + int main0 = p0.indexOf("--- class:Test.<clinit>"); + int main1 = p0.indexOf("--- class:Test.<clinit> ----------------------",main0); + int fun0 = p0.indexOf("--- f_noInline ",main1); + int fun1 = p0.indexOf("--- f_noInline ----------------------",fun0); + assertTrue(main0 < main1 && main1 < fun0 && fun0 < fun1); + assertTrue(p0.indexOf("ToFloat",main0) < p0.indexOf("Call",main0)); + assertTrue(p0.indexOf("CallEnd",fun0) == -1 || p0.indexOf("CallEnd",fun0) > fun1); } @Test public void testAlloc2() { String src = "int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];"; - testTarget(src,"x86_64_v2","SystemV",3,"return .[];"); - testTarget(src,"riscv","SystemV",6,"return .[];"); - testTarget(src,"arm","SystemV",6,"return .[];"); + testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];"); + testCPU(src,"riscv" , "SystemV", 6,"return .[];"); + testCPU(src,"arm" , "SystemV", 6,"return .[];"); } + + @Test public void testArray1() { String src = @@ -195,19 +216,23 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; - testTarget(src,"x86_64_v2", "SystemV",5,"return .[];"); - testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));"); - testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"x86_64_v2", "SystemV",-1,"return mov(.[]);"); + testCPU(src,"riscv" , "SystemV", 9,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); } + @Test public void testString() { - String src = """ + String src = +""" struct String { u8[] !cs; int _hashCode; + new String = { u8[] data -> cs=data; }; }; +// Compare two Strings val equals = { !String !self, !String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; @@ -217,6 +242,7 @@ val equals = { !String !self, !String s -> return true; }; +// Return the String hashCode (cached, and never 0) val hashCode = { !String self -> self._hashCode ? self._hashCode @@ -225,30 +251,23 @@ val hashCode = { !String self -> val _hashCodeString = { !String self -> int hash=0; - if( self.cs ) { - for( int i=0; i< self.cs#; i++ ) - hash = hash*31 + self.cs[i]; - } + for( int i=0; i< self.cs#; i++ ) + hash = hash*31 + self.cs[i]; if( !hash ) hash = 123456789; return hash; }; - -!String !s = new String { cs = new u8[17]; }; -s.cs[0] = 67; // C -s.cs[1] = 108; // l -hashCode(s); """; - testTarget(src,"x86_64_v2", "SystemV",9,null); - testTarget(src,"riscv" , "SystemV",3,null); - testTarget(src,"arm" , "SystemV",0,null); + testCPU(src,"x86_64_v2", "SystemV",21,null); + testCPU(src,"riscv" , "SystemV",8,null); + testCPU(src,"arm" , "SystemV",8,null); } @Test public void testCast() { String src = "struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;"; - testTarget(src,"x86_64_v2","SystemV",1,null); - testTarget(src,"riscv","SystemV",2,null); - testTarget(src,"arm","SystemV",2,null); + testCPU(src,"x86_64_v2", "SystemV",3,null); + testCPU(src,"riscv" , "SystemV",6,null); + testCPU(src,"arm" , "SystemV",6,null); } @Test @@ -260,15 +279,15 @@ hashCode(s); @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; """; - testTarget(src,"x86_64_v2", "SystemV",0,"return Phi(Region,1,2,0);"); - testTarget(src,"riscv" , "SystemV",0,"return Phi(Region,1,2,0);"); - testTarget(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 @@ -281,147 +300,8 @@ while (arg > 0) { } return arg; """; - testTarget(src,"x86_64_v2", "SystemV",3,null); - testTarget(src,"riscv" , "SystemV",2,null); - testTarget(src,"arm" , "SystemV",2,null); - } - // Original Chapter 20 allocation workload, kept fixed for cohort comparisons. - @Test - public void testBrainfuck() { - var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(java.nio.charset.StandardCharsets.UTF_8); - var encoded = new StringBuilder("u8[] !program = new u8[").append(program.length).append("];"); - for (int i = 0; i < program.length; i++) { - int value = program[i] & 0xFF; - encoded.append("program[").append(i).append("] = ").append(value).append(";"); - } - - String src = encoded + """ - -int d = 0; -u8[] !output = new u8[0]; -u8[] !data = new u8[100]; - -for( int pc = 0; pc < program#; pc++ ) { - var command = program[pc]; - if (command == 62) { - d++; - } else if (command == 60) { - d--; - } else if (command == 43) { - data[d]++; - } else if (command == 45) { - data[d]--; - } else if (command == 46) { - // 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) { - data[d] = 42; - } else if (command == 91) { - if (data[d] == 0) { - for( int d = 1; d > 0; ) { - command = program[++pc]; - if (command == 91) d++; - if (command == 93) d--; - } - } - } else if (command == 93) { - if (data[d]) { - for( int d = 1; d > 0; ) { - command = program[--pc]; - if (command == 93) d++; - if (command == 91) d--; - } - } - } -} -return output; -"""; - testTarget(src,"x86_64_v2", "SystemV",40,null); - testTarget(src,"riscv" , "SystemV",28,null); - testTarget(src,"arm" , "SystemV",28,null); - //assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000)); - } - // Original Chapter 20 allocation workload, kept fixed for cohort comparisons. - @Test - public void testMergeSort() { - String src = -""" -// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort - -val merge_sort = { int[] !a, int[] !b, int n -> - copy_array(a, 0, n, b); - split_merge(a, 0, n, 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[] !a = new int[arg]; -int[] !b = new int[a#]; - -for (int i = 0; i < a#; i++) - a[i] = a# - i; - -merge_sort(a, b, a#); - -return a; -"""; - testTarget(src,"x86_64_v2", "SystemV",52,null); - testTarget(src,"riscv" , "SystemV",44,null); - testTarget(src,"arm" , "SystemV",41,null); -//assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); - } - - - @Test public void testMemoryAllocation() { - for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) - for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, - Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY, - "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) { - var code = new CodeGen(src).parse().opto().typeCheck().loopTree() - .instSelect(cpu,"SystemV").GCM().localSched().regAlloc(); - com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); - code._stop.walk(n -> { - // Whole memory, precise Phis, and memory projections need no register. - if( n._type instanceof com.seaofnodes.simple.type.TypeMem ) - assertEquals(-1,code._regAlloc.regnum(n)); - if( n instanceof com.seaofnodes.simple.node.NewNode nn ) { - assertTrue(code._regAlloc.regnum(nn.proj(0))>=0); - assertEquals(-1,code._regAlloc.regnum(nn.proj(1))); - } - return null; - }); - } + testCPU(src,"x86_64_v2", "SystemV",3,null); + testCPU(src,"riscv" , "SystemV",2,null); + testCPU(src,"arm" , "SystemV",2,null); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java deleted file mode 100644 index bab7a86a..00000000 --- a/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.seaofnodes.simple; - -import java.io.IOException; -import java.nio.file.*; -import org.junit.Test; - -// Revised Chapter 20 examples introduced with native encoding. -public class Chapter21AllocTest { - @Test - public void testNewtonInteger() { - String src = -""" -// Newtons approximation to the square root -val sqrt = { int x -> - int guess = x; - while( 1 ) { - int next = (x/guess + guess)/2; - if( next == guess ) return guess; - guess = next; - } -}; -return sqrt(arg) + sqrt(arg+2); -"""; - Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,40,null); - Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null); - Chapter21Test.testCPU(src,"arm" , "SystemV",19,null); - } - - @Test - public void testNewtonFloat() throws IOException { - String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp")) - + "flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);"; - Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",39,null); - Chapter21Test.testCPU(src,"riscv" , "SystemV",17,null); - Chapter21Test.testCPU(src,"arm" , "SystemV",18,null); - } - - @Test - public void testArray1() throws IOException { - String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/array1.smp")); - Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];"); - Chapter21Test.testCPU(src,"riscv" , "SystemV", 8,"return (add,.[],(mul,.[],1000));"); - Chapter21Test.testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); - } - - - @Test - public void testString() throws IOException { - String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash21.smp")); - Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); - Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null); - } - -} diff --git a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 5b2c3a54..0cfdb577 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -10,14 +10,33 @@ 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.*; public class Chapter21Test { + @Test public void testFloatToInteger() throws IOException { + String src = "val float_to_int={ flt x -> int i=x; return i; };"+ + "val float_to_u8={ flt x -> u8 i=x; return i; };"; + TestC.runC(src,"floatToInteger","",-1); + for( double x : new double[]{0.0,-0.0,6.92,-6.92,255.75,-257.75, + 0x1p63,Math.nextDown(0x1p63),-0x1p63,Math.nextDown(-0x1p63), + Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NaN} ) { + for( String name : new String[]{"float_to_int","float_to_u8"} ) { + long expected = name.equals("float_to_u8") ? ((long)x)&255 : (long)x; + EvalRisc5 r = TestRisc5.build(src,name,0,-1,false); + r.fregs[riscv.FA0-riscv.F_OFFSET]=x; + assertEquals(0,r.step(1000)); + assertEquals(name+"("+x+")",expected,r.regs[riscv.A0]); + EvalArm64 a = TestArm64.build(name,src,0,-1,false); + a.fregs[arm.D0-arm.D_OFFSET]=x; + assertEquals(0,a.step(1000)); + assertEquals(name+"("+x+")",expected,a.regs[arm.X0]); + } + } + } + @Test public void testReadBeforeConditionalStore() { var code = new CodeGen(""" struct S { int x; }; !S !a=new S; !S !b=new S; @@ -52,35 +71,10 @@ public class Chapter21Test { } - @Test public void testReturnedFunctionEncoding() throws IOException { - String src = "if(arg) return {->42;}; return {->43;};"; - CodeGen code = new CodeGen(src).driver("riscv","SystemV",null); - for( int arg : new int[]{0,1} ) { - byte[] image = new byte[1<<16]; - byte[] bits = code._encoding.bits(); - System.arraycopy(bits,0,image,0,bits.length); - EvalRisc5 cpu = new EvalRisc5(image,1<<15); - cpu._pc = code._encoding._opStart[code.link(code._main)._nid]; - cpu.regs[riscv.A0] = arg; - assertEquals(0,cpu.step(100)); - assertEquals(0,cpu._pc); - cpu._pc = (int)cpu.regs[riscv.A0]; - assertEquals(0,cpu.step(100)); - assertEquals(0,cpu._pc); - assertEquals(arg==0 ? 43 : 42,cpu.regs[riscv.A0]); - } - // Anonymous bodies need code, but no exported ELF name. - new CodeGen(src).driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,"build/objs/anonymous.o"); - } - @Test public void testArmFloatMemory() throws IOException { for( String type : new String[]{"f32","f64"} ) { String src = type+"[] !a=new "+type+"[3]; a[arg]=1.25; return a[1];"; - CodeGen code = new CodeGen(src).driver("arm","SystemV",null); - byte[] image = new byte[1<<20]; - byte[] bits = code._encoding.bits(); - System.arraycopy(bits,0,image,0,bits.length); - EvalArm64 cpu = new EvalArm64(image,1<<16); + EvalArm64 cpu = TestArm64.build("probe","val probe={ int arg -> "+src+" };",1,-1,false); cpu.regs[arm.X0]=1; assertEquals(0,cpu.step(1000)); assertEquals(1.25,cpu.fregs[0],0); @@ -156,6 +150,7 @@ public class Chapter21Test { struct _Vec { u32 !len; int[] !buf; + new _Vec = { int[] initial -> buf=initial; }; val grow = { int sz -> var buf2 = new int[sz]; for( int i=0; i<len; i++ ) buf2[i]=buf[i]; @@ -168,7 +163,7 @@ public class Chapter21Test { }; }; val run = { int arg -> - val v = new _Vec{buf=new int[4];}.add(2).add(3).add(5).add(7).add(11); + val v = new _Vec(new int[4]).add(2).add(3).add(5).add(7).add(11); return v.len*100+v.buf[arg]; }; """; @@ -176,7 +171,7 @@ public class Chapter21Test { } private static int checkArmCalls(String src, String entryName, int arg, int result) throws IOException { - CodeGen code = new CodeGen(src).driver("arm","SystemV",null); + CodeGen code = new CodeGen(src).driver("arm","SystemV",true,false); byte[] image = new byte[1<<20]; System.arraycopy(code._encoding.bits(),0,image,0,code._encoding.bits().length); EvalArm64 cpu = new EvalArm64(image,1<<16); @@ -184,7 +179,7 @@ public class Chapter21Test { int frames=0, calls=0; for( var bb : code._cfg ) { if( bb instanceof com.seaofnodes.simple.node.FunNode fun ) { - int off = code._encoding._opStart[fun._nid]; + int off = code._encoding.opStart(fun); if( entryName.equals(fun._name) ) { cpu._pc=off; entry=true; } int frame=fun._frameAdjust; assertEquals(0,frame&15); @@ -194,7 +189,7 @@ public class Chapter21Test { } } if( bb instanceof com.seaofnodes.simple.node.ReturnNode ret ) { - int off=code._encoding._opStart[ret._nid]; + int off=code._encoding.opStart(ret); int frame=ret.fun()._frameAdjust; if( frame>0 ) { assertEquals(0x910003FF | (frame<<10),cpu.ld4s(off)); // ADD SP,SP,#bytes @@ -220,52 +215,6 @@ public class Chapter21Test { @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } - @Test public void testNarrowStores() throws IOException { - for( String type : new String[]{"i8","u8","i16","u16"} ) { - String src = "struct S { "+type+" x; }; !S !s = new S; s.x = arg; return 0;"; - CodeGen code = new CodeGen(src).driver("riscv","SystemV",null); - int stores=0; - for( var bb : code._cfg ) - for( var node : bb.outs() ) - if( node instanceof com.seaofnodes.simple.node.cpus.riscv.StoreRISC st ) { - stores++; - assertEquals("Byte/short stores have no FP encoding",-1,st.regmap(4).nextReg((short)31)); - } - assertEquals(1,stores); - byte[] image = new byte[1<<20]; - byte[] bits = code._encoding.bits(); - System.arraycopy(bits,0,image,0,bits.length); - EvalRisc5 cpu = new EvalRisc5(image,1<<16); - cpu.regs[riscv.A0] = 0x8765; - assertEquals(0,cpu.step(100)); - assertEquals(type.endsWith("8") ? 0x65 : 0x8765, - type.endsWith("8") ? cpu.ld1z(1<<16) : cpu.ld2z(1<<16)); - code = new CodeGen(src).driver(CodeGen.Phase.Encoding,"x86_64_v2",TestC.CALL_CONVENTION); - stores=0; - for( var bb : code._cfg ) - for( var node : bb.outs() ) - if( node instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86 st ) { - stores++; - assertEquals("Byte/short stores have no XMM encoding",-1,st.regmap(4).nextReg((short)15)); - } - assertEquals(1,stores); - } - } - - @Test public void testNativeExitStatus() throws IOException { - Path source = Path.of("build/objs/nativeExit.c"); - Files.createDirectories(source.getParent()); - Files.writeString(source,"int main() { return 7; }\n"); - try { - TestC.gcc(source.toString(),null,null,false, - "build/objs/nativeExit"+(TestC.OS.startsWith("Windows") ? ".exe" : "")); - } catch( AssertionError error ) { - assertTrue(error.getMessage(),error.getMessage().contains("Program exit status")); - return; - } - fail("A failing native program must fail the test, even with empty stdout"); - } - @Test public void testArmSubtractRegisters() { EvalArm64 cpu = new EvalArm64(new byte[16],16); cpu.st4(0,0xCB020020); // SUB X0,X1,X2, no shift or flag update. @@ -309,21 +258,41 @@ public class Chapter21Test { } - // Frozen Jig runs in Chapter23AllocTest; the revised input is in Chapter24AllocTest. - @Test @Ignore + @Ignore + @Test public void testJig() throws IOException { - String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp")); - testCPU(src,"x86_64_v2", "Win64" ,-1,null); + String src = +""" +struct _s0 { + bool v1; + i16 v2; + int v3; + i8 v4; + byte v5; +}; +while(new _s0.v3) + while(new _s0.v5<<new _s0.v4) {} +if(0) { + if(0) { + flt !P5ZUD4=new _s0.v2; + } + while(0) {} +} +return new _s0.v1; +"""; + testCPU(src,"x86_64_v2", "win64" ,-1,null); testCPU(src,"riscv" , "SystemV",-1,null); testCPU(src,"arm" , "SystemV",-1,null); } static void testCPU( String src, String cpu, String os, int spills, String stop ) { CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); - SpillStats.record(code,"Chapter21",cpu,os); - SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + int delta = spills>>3; + if( delta==0 ) delta = 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()); } @@ -335,14 +304,14 @@ public class Chapter21Test { } @Test public void testInfinite() { - String src = "struct S { int i; }; !S !s = new S; while(1) 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",0,"return Top;"); testCPU(src,"arm" , "SystemV",0,"return Top;"); } @Test - public void testArray1() throws IOException { + public void testArray1() { String src = """ int[] !ary = new int[arg]; @@ -354,19 +323,19 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; - testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];"); - testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"x86_64_v2", "SystemV",-1,"return mov(.[]);"); + testCPU(src,"riscv" , "SystemV", 9,"return (add,.[],(mul,.[],1000));"); testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); } @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 { @@ -374,18 +343,19 @@ 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", 9,"return mov(mov(Test._S));"); + testCPU(src,"riscv" , "SystemV",10,"return mov(mov(Test._S));"); + testCPU(src,"arm" , "SystemV",11,"return mov(mov(Test._S));"); } @Test - public void testString() throws IOException { + public void testString() { String src = """ struct String { u8[] !cs; int _hashCode; + new String = { u8[] data -> cs=data; }; }; // Compare two Strings @@ -413,9 +383,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", 1,null); + testCPU(src,"x86_64_v2", "SystemV",21,null); + testCPU(src,"riscv" , "SystemV",8,null); + testCPU(src,"arm" , "SystemV",8,null); } @Test public void testStringExport() throws IOException { @@ -424,6 +394,7 @@ val _hashCodeString = { !String self -> struct String { u8[] !cs; int _hashCode; + new String = { u8[] data -> cs=data; }; }; // Compare two Strings @@ -451,10 +422,10 @@ val _hashCodeString = { !String self -> return hash; }; """; - TestC.run(src, "stringHash",null, "", 9); + TestC.runC(src, "stringHash", "",21); } - @Test public void testLoop2() throws IOException { + @Test public void testLoop2() { String src = """ int i = 0; @@ -467,9 +438,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 { @@ -498,9 +469,9 @@ val test_sqrt = { flt x -> } }; """; - TestC.run(src, "newtonFloat", null, result, 34); + TestC.runC(src, "newtonFloat", result, TestC.CALL_CONVENTION.equals("win64") ? 42 : 34); - 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); @@ -508,7 +479,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); @@ -558,11 +529,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, 186); + 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, 92, false); + EvalRisc5 R5 = TestRisc5.build( src, "sieve", 100, 90, false); int trap = R5.step(10000); assertEquals(0,trap); // Return register A0 holds sieve(100) @@ -574,7 +545,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, 94, false); + EvalArm64 A5 = TestArm64.build("sieve", src, 100, 88, false); int trap_arm = A5.step(10000); assertEquals(0, trap_arm); int ary_arm = (int)A5.regs[arm.X0]; @@ -587,7 +558,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 ){ @@ -599,9 +570,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 @@ -616,8 +587,19 @@ val fib = {int n -> } @Test public void testPerson() throws IOException { + String src = + """ + struct Person { + i32 age; + }; + + val fcn = { !Person?[] !ps, int x -> + if( ps[x] ) + ps[x].age++; + }; + """; String person = "6\n"; - TestC.run("person21", 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 @@ -628,7 +610,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("person21", ps, 0, false); + EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 2, false); R5.regs[riscv.A1] = 1; // Index 1 R5.st8(ps,3); // Length R5.st8(ps+1*8,p0); @@ -644,7 +626,7 @@ val fib = {int n -> assertEquals(17+1,R5.ld8(p1)); assertEquals(60+0,R5.ld8(p2)); - EvalArm64 A5 = TestArm64.build("person21", ps, 0, false); + EvalArm64 A5 = TestArm64.build("fcn", src, ps, 2, false); A5.regs[arm.X1] = 1; // Index 1 A5.st8(ps, 3); A5.st8(ps+1*8,p0); @@ -662,14 +644,21 @@ 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 -> + return + i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 + + i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ; +}; +"""; String arg_count = "191.000000\n"; - TestC.run("arg_count", arg_count, - TestC.CALL_CONVENTION.equals("Win64") ? 32 : 9); + TestC.runC(src, "arg_count", arg_count, TestC.CALL_CONVENTION.equals("win64") ? 35 : 9); - EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", 0, 0, false); + EvalRisc5 R5 = TestRisc5.build( src, "addAll", 0, 4, false); // Todo: handle stack(imaginary stack in emulator) // pass in float arguments @@ -682,14 +671,13 @@ val fib = {int n -> 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); @@ -699,7 +687,7 @@ val fib = {int n -> assertEquals(22.8, result, 0.00001); // arm - EvalArm64 A5 = TestArm64.build("no_stack_arg_count", 0, 0, 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 c77221bf..7484a6ae 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -5,13 +5,11 @@ import com.seaofnodes.isa.eval.EvalRisc5; import com.seaofnodes.isa.eval.EvalArm64; import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +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.*; @@ -30,8 +28,8 @@ public class Chapter22Test { var enc = code._encoding; // Link the external call to a tiny independent native stub. var call = code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.CallNode ? n : null); - int start = enc._opStart[call._nid]; - ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc._opLen[call._nid],0x1000-start); + int start = enc.opStart(call); + ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc.opLen(call),0x1000-start); for( long value : values ) { long expected = switch(i) { case 0 -> (byte)value; case 1 -> value&255; @@ -45,7 +43,6 @@ public class Chapter22Test { if( target.equals("arm") ) { EvalArm64 cpu = new EvalArm64(image,1<<15); // MOVZ X0,0x1100; LDR X0,[X0]; RET X30. - cpu._pc = enc._opStart[code.link(code._main)._nid]; cpu.st4(0x1000,0xd2822000); cpu.st4(0x1004,0xf9400000); cpu.st4(0x1008,0xd65f03c0); cpu.st8(0x1100,value); @@ -55,7 +52,6 @@ public class Chapter22Test { } else { EvalRisc5 cpu = new EvalRisc5(image,1<<15); // AUIPC a0,0; LD a0,12(a0); RET. - cpu._pc = enc._opStart[code.link(code._main)._nid]; cpu.st4(0x1000,0x00000517); cpu.st4(0x1004,0x00c53503); cpu.st4(0x1008,0x00008067); // RV64 widens even u32 returns by sign-extending bit 31. @@ -83,148 +79,79 @@ public class Chapter22Test { val test_i64={->return c_i64();}; val test_cmp={->return c_i32()==-1;}; """; - for( String conv : new String[]{"SystemV","Win64"} ) - TestC.run(src,conv,com.seaofnodes.simple.type.TypeInteger.BOT,conv.equals("SystemV") ? "sysv_abi" : "ms_abi", - "src/test/java/com/seaofnodes/simple/progs/c_returns.c", - "build/objs/c_returns"+conv,"S","",-1); - } - - - - @Test public void testReturnedFunctions() throws IOException { - for( String src : new String[]{"return {->42;};", "val f={->42;}; return f;", "return sys.io.p;"} ) - for( String target : new String[]{"riscv","arm"} ) { - CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,target,"SystemV").exportELF(null); - byte[] image = new byte[1<<16]; - byte[] bits = code._encoding.bits(); - System.arraycopy(bits,0,image,0,bits.length); - boolean library = src.contains("sys.io.p"); - // u8[]: four-byte length followed by its bytes. - image[0x2000]=2; image[0x2004]='o'; image[0x2005]='k'; - int entry = code._encoding._opStart[code.link(code._main)._nid]; - if( target.equals("riscv") ) { - EvalRisc5 cpu = new EvalRisc5(image,1<<15); - cpu._pc = entry; - assertEquals(0,cpu.step(1000)); - assertEquals(0,cpu._pc); - cpu._pc = (int)cpu.regs[riscv.A0]; // Call the returned address. - cpu.regs[library ? riscv.A1 : riscv.A0] = 0x2000; // Methods also receive self. - assertEquals(0,cpu.step(1000)); - assertEquals(0,cpu._pc); - if( library ) assertEquals("ok",cpu._stdout.toString()); - else assertEquals(42,cpu.regs[riscv.A0]); - } else { - EvalArm64 cpu = new EvalArm64(image,1<<15); - cpu._pc = entry; - assertEquals(0,cpu.step(1000)); - assertEquals(0,cpu._pc); - cpu._pc = (int)cpu.regs[0]; - cpu.regs[library ? 1 : 0] = 0x2000; - assertEquals(0,cpu.step(1000)); - assertEquals(0,cpu._pc); - if( library ) assertEquals("ok",cpu._stdout.toString()); - else assertEquals(42,cpu.regs[0]); - } - } - CodeGen unused = new CodeGen("({->42;}); return 0;").parse().opto(); - assertEquals(1,unused._stop.nIns()); - } - - @Test public void testReturnedFunctionsNative() throws IOException { - String src = "val factory={int x -> if(x) return {->42;}; return {->43;};}; val printer={->return sys.io.p;};"; - String obj = "build/objs/funptr.o"; - new CheckedCodeGen(src).driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,obj); - assertEquals("ok",TestC.gcc(obj,"","src/test/java/com/seaofnodes/simple/progs/funptr.c",false, - "build/objs/funptr"+(TestC.OS.startsWith("Windows") ? ".exe" : ""))); - } - - - @Test public void testRiscvRightShifts() { - // SRAI, SRLI, SRA, SRL: a0 = -32 shifted by 4. - int[] ops = {0x40455513,0x00455513,0x40b55533,0x00b55533}; - for( int i=0; i<ops.length; i++ ) { - EvalRisc5 r5 = new EvalRisc5(new byte[1<<16],1<<15); - r5.st4(0,ops[i]); - r5.regs[riscv.A0] = -32; - r5.regs[riscv.A0+1] = 4; - assertEquals(0,r5.step(1)); - assertEquals((i&1)==0 ? -2L : 0x0fff_ffff_ffff_fffeL,r5.regs[riscv.A0]); - } + for( String conv : new String[]{"SystemV","win64"} ) + TestC.run(src,"c_returns"+conv,null,conv, + conv.equals("SystemV") ? "sysv_abi" : "ms_abi", + TestC.C_DRIVERS_DIR+"c_returns.c","",-1); } - @Test public void testRiscvPointerRelocation() { - CodeGen code = new CheckedCodeGen("return \"x\";").driver(CodeGen.Phase.Encoding,"riscv","SystemV"); - var enc = code._encoding; - assertEquals(1,enc._bigCons.size()); - var ptr = (com.seaofnodes.simple.node.cpus.riscv.TMPRISC)enc._bigCons.keySet().iterator().next(); - int start = enc._opStart[ptr._nid]; - for( int delta : new int[]{0x7ff,0x800,0xfff,0x1000,0x1800,-1,-0x800,-0x801} ) { - ptr.patch(enc,start,enc._opLen[ptr._nid],delta); - // Place the program above zero so backward targets are valid addresses. - byte[] image = new byte[1<<16]; - System.arraycopy(enc.bits(),0,image,0x4000,enc._bits.size()); - EvalRisc5 r5 = new EvalRisc5(image,1<<15); - r5._pc = 0x4000; - assertEquals(0,r5.step(100)); - assertEquals(0x4000+start+delta,r5.regs[riscv.A0]); - } - } @Test public void testSubZeroTypeError() { try { new CodeGen("return null-0;").parse().opto().typeCheck(); fail("Subtraction must reject null even when the other operand is zero"); } catch( Parser.ParseException e ) { - assertEquals("Cannot '-' null",e.getMessage()); + assertEquals("Cannot null - 0",e.getMessage()); } } - @Test public void testInfiniteReturn() { - 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",0,"return Top;"); - testCPU(src,"arm","SystemV",0,"return Top;"); - } - - // Frozen Jig runs in Chapter23AllocTest; the revised input is in Chapter24AllocTest. - @Test @Ignore + @Ignore + @Test public void testJig() throws IOException { - String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp")); - //String src = Files.readString(Path.of("docs/examples/BubbleSort.smp")); - testCPU(src,"x86_64_v2", "Win64" ,-1,null); + String src = """ +struct s0 { + bool v1; + i16 v2; + int v3; + i8 v4; + byte v5; +}; +while(new s0.v3) + while(new s0.v5<<new s0.v4) {} +if(0) { + if(0) { + flt !P5ZUD4=new s0.v2; + } + while(0) {} +} +return new s0.v1; +"""; + testCPU(src,"x86_64_v2", "win64" ,-1,null); testCPU(src,"riscv" , "SystemV",-1,null); testCPU(src,"arm" , "SystemV",-1,null); } static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) { CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); - SpillStats.record(code,"Chapter22",cpu,os); - SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + int delta = spills>>3; + if( delta==0 ) delta = 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(65, testCPUSize(src, "x86_64_v2","win64", 3 )); + assertEquals(76, testCPUSize(src, "riscv", "SystemV", 4 )); + assertEquals(76, 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); @@ -237,24 +164,23 @@ 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); - EvalArm64 A5 = TestArm64.build("sext_str_not_fold_away_2", src, 0, 4, false); + EvalArm64 A5 = TestArm64.build("sext_str_not_fold_away_2", src, 0, 3, false); 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(65, testCPUSize(src, "x86_64_v2","win64",3 )); + assertEquals(76, testCPUSize(src, "riscv", "SystemV",4 )); + assertEquals(76, testCPUSize(src, "arm", "SystemV",4 )); } @@ -262,13 +188,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); @@ -276,9 +202,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(55, testCPUSize(src, "x86_64_v2","win64",3 )); + assertEquals(72, testCPUSize(src, "riscv", "SystemV",5 )); + assertEquals(72, testCPUSize(src, "arm", "SystemV",5)); // do assertEquals here } @@ -287,17 +213,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 @@ -308,7 +234,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, 2, false); R5.regs[riscv.A1] = 1; // Index 1 R5.st8(ps,3); // Length R5.st8(ps+1*8,p0); @@ -324,7 +250,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, 2, false); A5.regs[arm.X1] = 1; // Index 1 A5.st8(ps, 3); A5.st8(ps+1*8,p0); @@ -344,47 +270,62 @@ 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]); assertEquals("Hello, World!",arm._stdout.toString()); } + + @Test + public void testStrLoad() { + CodeGen code = new CodeGen("return \"abc\"[1];"); + code.parse().opto(); + assertEquals("return .[];", code.print()); + } + + @Test public void testFinalArray() { String src = """ @@ -392,16 +333,16 @@ int N=4; i32[] !is = new i32[N]; for( int i=0; i<N; i++ ) is[i] = i*i; -val sum = { i32[~] is -> // final array +val _sum = { i32[~] is -> // final array int sum=0; for( int i=0; i<is#; i++ ) sum += is[i]; return sum; }; -return sum(is); +return _sum(is); """; CodeGen code = new CodeGen(src).parse().opto().typeCheck(); - assertEquals("return Phi(Loop,0,(Phi_sum+.[]));", code._stop.toString()); + assertEquals("return Phi(Loop,0,(Phi_sum+.[]));", code.print()); assertEquals("14", Eval2.eval(code, 0)); } @@ -413,10 +354,10 @@ return sum(is); // Echo stdin to stdout. return sys.io.p( sys.io.stdin() ); """; - TestC.run(src,TestC.CALL_CONVENTION,null, null,null,"build/objs/echo","","",0); + TestC.runSF(src,"echo", "",0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("echo", src, 0, 2, false); + EvalRisc5 R5 = TestRisc5.build( src, "echo", 0, 2, false); int trap = R5.step(100); assertEquals(0,trap); assertEquals(0,R5.regs[riscv.A0]); diff --git a/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java b/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java deleted file mode 100644 index f3cc7512..00000000 --- a/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple; - -import java.io.IOException; -import java.nio.file.*; -import org.junit.Ignore; -import org.junit.Test; - -// Revised or newly enabled allocation examples in Chapter 23. -public class Chapter23AllocTest { - @Test - public void testString() throws IOException { - String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); - Chapter23Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); - Chapter23Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter23Test.testCPU(src,"arm" , "SystemV", 1,null); - } - - @Ignore - @Test - public void testJig21() throws IOException { - String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp")); - Chapter23Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null); - Chapter23Test.testCPU(src,"riscv" , "SystemV",-1,null); - Chapter23Test.testCPU(src,"arm" , "SystemV",-1,null); - } - - @Ignore - @Test - public void testJig22() throws IOException { - String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp")); - //String src = Files.readString(Path.of("docs/examples/BubbleSort.smp")); - Chapter23Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null); - Chapter23Test.testCPU(src,"riscv" , "SystemV",-1,null); - Chapter23Test.testCPU(src,"arm" , "SystemV",-1,null); - } - -} diff --git a/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/src/test/java/com/seaofnodes/simple/Chapter23Test.java index 884e77e5..053621bd 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter23Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter23Test.java @@ -11,7 +11,6 @@ import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.cpus.riscv.riscv; -import com.seaofnodes.simple.type.TypeInteger; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -25,7 +24,7 @@ public class Chapter23Test { @Test public void testDiagnosticTypeAccessors() throws Exception { new CodeGen("return 0;").parse(); var tfp = TypeFunPtr.TEST; - var con = new ConstantNode(tfp); + var con = ConstantNode.raw(tfp); var call = new CallNode(null,con,con,con); var field = Type.class.getDeclaredField("VISIT"); field.setAccessible(true); @@ -45,335 +44,252 @@ public class Chapter23Test { } - @Test - public void testOrRhsLoopLocal() throws IOException { - checkOrRhsLoop(""" - struct S { int f; }; - int x = 0; - int result = (arg & 1) || (new S { - while (x < 3) x++; - f = ++x; - }).f; - return x * 10 + result; - """); - } - - @Test - public void testOrRhsLoopMemory() throws IOException { - checkOrRhsLoop(""" - struct S { int f; }; - !S !s = new S; - int result = (arg & 1) || (new S { - while (s.f < 3) s.f++; - s.f++; - f = s.f; - }).f; - return s.f * 10 + result; - """); - } - - private static void checkOrRhsLoop(String src) throws IOException { - CodeGen code = new CheckedCodeGen(src).driver("riscv", "SystemV", null); - SpillStats.record(code,"Chapter23","riscv","SystemV"); - for (int arg : new int[]{0, 1, 2, 3}) { - byte[] image = new byte[1 << 20]; - System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding._bits.size()); - EvalRisc5 r5 = new EvalRisc5(image, 1 << 16); - r5.regs[riscv.A0] = arg; - assertEquals("Execution must finish", 0, r5.step(1000)); - // False LHS runs the loop and final increment, returning 4. - // True LHS skips all RHS effects and returns the LHS value, 1. - assertEquals("OR result and side effects for arg=" + arg, - (arg & 1) == 0 ? 44 : 1, r5.regs[riscv.A0]); - } - } - - @Ignore - @Test + @Test @Ignore public void testJig() throws IOException { - String src = -""" -var apply = { i64 x, { i64 -> u32 } fcn -> - return fcn(x); -} -"""; + //String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp")); + String src = Files.readString(Path.of("docs/examples/BubbleSort.smp")); + testCPU(src,"x86_64_v2", "win64" ,-1,null); + testCPU(src,"riscv" , "SystemV",-1,null); + testCPU(src,"arm" , "SystemV",-1,null); } static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) { CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); - SpillStats.record(code,"Chapter23",cpu,os); - SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + int delta = spills>>3; + if( delta==0 ) delta = 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 testLogicalValueSemantics() { + assertEquals("42", Eval2.eval(new CodeGen("return 7 && 42;").parse().opto(), 0)); + assertEquals("0", Eval2.eval(new CodeGen("return 0 && 42;").parse().opto(), 0)); + assertEquals("7", Eval2.eval(new CodeGen("return 7 || 42;").parse().opto(), 0)); + assertEquals("42", Eval2.eval(new CodeGen("return 0 || 42;").parse().opto(), 0)); + } + + @Test + public void testMixedScalarNotPhiPredicate() { + CodeGen code = new CodeGen(""" +struct S {}; +S? s = arg ? new S; +return !(!s || !arg) ? 1 : 0; +"""); + code.parse().opto().typeCheck(); + assertEquals("0", Eval2.eval(code, 0)); + assertEquals("1", Eval2.eval(code, 1)); } @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.<clinit>", 1, 12, 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.<clinit>", src, 1, 12, 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, 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 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" ,23,null); + testCPU(src,"x86_64_v2", "win64",23,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] <= ' ' ) @@ -381,23 +297,26 @@ struct Scan { return self; }; }; -~Scan s = new Scan{ buf = " q"; }; +~_Scan s = new _Scan(" q"); return s.skip().x; - """; +"""; try { new CodeGen(src).parse().opto().typeCheck(); fail(); } - catch( Exception e ) { org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa")); } + catch( Exception e ) { + // Bad error message, but basically requires a mutable 'x' field. + org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa")); + } }; @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] <= ' ' ) @@ -408,14 +327,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" ,4,null); + testCPU(src,"x86_64_v2", "win64",4, null); }; diff --git a/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java b/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java deleted file mode 100644 index 7fae2c27..00000000 --- a/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.isa.eval.EvalRisc5; - -import com.seaofnodes.isa.eval.EvalArm64; - -import java.io.IOException; -import java.nio.file.*; -import com.seaofnodes.simple.node.cpus.arm.arm; -import com.seaofnodes.simple.node.cpus.riscv.riscv; -import org.junit.Ignore; -import org.junit.Test; -import static org.junit.Assert.*; - -// Revised inherited programs belong to the chapter which changed their inputs. -public class Chapter24AllocTest { - @Test - public void testNewtonInteger() { - String src = -""" -// Newtons approximation to the square root -val sqrt = { int x -> - int guess = x; - while( 1 ) { - int next = (x/guess + guess)/2; - if( next == guess ) return guess; - guess = next; - } -}; -int cast_int = arg+2; -return sqrt(arg) + sqrt(cast_int); -"""; - Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,40,null); - Chapter24Test.testCPU(src,"riscv" , "SystemV",19,null); - Chapter24Test.testCPU(src,"arm" , "SystemV",19,null); - } - - @Ignore - @Test - public void testJig21() throws IOException { - String src = -""" -struct s0 { - bool v1; - i16 v2; - int v3; - i8 v4; - byte v5; -}; -while(new s0.v3) - while(new s0.v5<<new s0.v4) {} -if(0) { - if(0) { - flt !P5ZUD4=new s0.v2; - } - while(0) {} -} -return new s0.v1; -"""; - Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null); - Chapter24Test.testCPU(src,"riscv" , "SystemV",-1,null); - Chapter24Test.testCPU(src,"arm" , "SystemV",-1,null); - } - - @Test public void testStackArguments() 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. - 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 -> - return - i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 + - i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ; -}; -"""; - String arg_count = "191.000000\n"; - - TestC.run(src, "arg_count", null, arg_count, TestC.CALL_CONVENTION.equals("Win64") ? 32 : 9); - - EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", src, 0, 4, false); - - // Todo: handle stack(imaginary stack in emulator) - // pass in float arguments - R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 1.1; - R5.fregs[riscv.FA1 - riscv.F_OFFSET] = 1.1; - R5.fregs[riscv.FA2 - riscv.F_OFFSET] = 1.1; - R5.fregs[riscv.FA3 - riscv.F_OFFSET] = 1.1; - R5.fregs[riscv.FA4 - riscv.F_OFFSET] = 1.1; - R5.fregs[riscv.FA5 - riscv.F_OFFSET] = 1.1; - 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.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); - - double result = R5.fregs[riscv.FA0 - riscv.F_OFFSET]; - - assertEquals(22.8, result, 0.00001); - - // arm - EvalArm64 A5 = TestArm64.build("no_stack_arg_count", src, 0, 4, false); - - A5.fregs[arm.D0 - arm.D_OFFSET] = 1.1; - A5.fregs[arm.D1 - arm.D_OFFSET] = 1.1; - A5.fregs[arm.D2 - arm.D_OFFSET] = 1.1; - A5.fregs[arm.D3 - arm.D_OFFSET] = 1.1; - A5.fregs[arm.D4 - arm.D_OFFSET] = 1.1; - A5.fregs[arm.D5 - arm.D_OFFSET] = 1.1; - A5.fregs[arm.D6 - arm.D_OFFSET] = 1.1; - A5.fregs[arm.D7 - arm.D_OFFSET] = 1.1; - - A5.regs[arm.X1] = 2; - A5.regs[arm.X2] = 2; - A5.regs[arm.X3] = 2; - A5.regs[arm.X4] = 2; - A5.regs[arm.X5] = 2; - A5.regs[arm.X6] = 2; - A5.regs[arm.X7] = 2; - - int trap_arm = A5.step(100); - assertEquals(0,trap_arm); - - double result1 = A5.fregs[arm.D0 - arm.D_OFFSET]; - assertEquals(22.8, result1, 0.00001); - } - - @Ignore - @Test - public void testJig22() 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; -}; - -fib(10); -"""; - Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null); - Chapter24Test.testCPU(src,"riscv" , "SystemV",-1,null); - Chapter24Test.testCPU(src,"arm" , "SystemV",-1,null); - } - - @Test - public void testBubbleSort() throws IOException { - //String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp")); - String src = Files.readString(Path.of("docs/examples/BubbleSort.smp")); - Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null); - Chapter24Test.testCPU(src,"riscv" , "SystemV",-1,null); - Chapter24Test.testCPU(src,"arm" , "SystemV",-1,null); - } -} diff --git a/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/src/test/java/com/seaofnodes/simple/Chapter24Test.java index 9a657855..3e21c73e 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter24Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter24Test.java @@ -22,14 +22,14 @@ public class Chapter24Test { @Test public void testPrintingDeadFunction() throws Exception { var code = new CodeGen("return 0;").parse(); var tfp = TypeFunPtr.TEST; - var fun = new FunNode(null,tfp,"deadPrinterTarget"); + var fun = new FunNode(null,tfp,"deadPrinterTarget",null); org.junit.Assert.assertTrue(fun.isDead()); code.link(fun); - var con = new ConstantNode(tfp); + var con = ConstantNode.raw(tfp); var call = new CallNode(null,con,con,con); con.toString(); call.name(); - + code.funcName(tfp.fidx()); org.junit.Assert.assertSame(fun,code._link(tfp)); // Optimizer lookup still performs its usual lazy cleanup. org.junit.Assert.assertNull(code.link(tfp)); @@ -41,435 +41,110 @@ 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); } static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) { CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); - SpillStats.record(code,"Chapter24",cpu,os); - SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + int delta = spills>>3; + if( delta==0 ) delta = 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; +return (10 < something_specific < 20) + ? 0 // Expected + : 1; // Error """; - - TestC.runSF("stacked_r_1", src, null, "In Range", 0); + TestC.runSF( src, "stacked_r_1", "", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_1", 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_1", 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 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; -"""; - - TestC.runSF("stacked_r_2", src, null, "In Range", 0); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_2", 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_2", 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 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", "", 2); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_3", src, 0, 5, false); + EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_3", 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_3", src, 0, 5, false); + EvalArm64 arm = TestArm64.build("stacked_r_3", src, 0, 3, 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); + TestC.runSF( src, "stacked_r_4", "", 0); // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_4", src,0, 2, 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("Out of Range",R5._stdout.toString()); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("stacked_r_4", src, 0, 2, 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("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); - - // Evaluate on RISC5 emulator - EvalRisc5 R5 = TestRisc5.build("stacked_r_5", src, 0, 7, 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); - 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]); @@ -483,21 +158,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]); @@ -511,27 +177,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,2); + 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, 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]); @@ -541,21 +200,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]); @@ -572,120 +222,28 @@ 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); // Compile once per target, then check false, true and negative inputs. - EvalRisc5 R5 = TestRisc5.build("stacked_r_13", src2, 1, 0, false); + EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_13", 1, 0, false); for( int arg : new int[]{-1,0,1} ) { R5._pc = 0; - R5.regs[riscv.A0] = arg; + R5.regs[riscv.A1] = arg; assertEquals(0,R5.step(100)); assertEquals(arg==0 ? 1 : 0,R5.regs[riscv.A0]); } EvalArm64 arm = TestArm64.build("stacked_r_13", src2, 1, 0, false); for( int arg : new int[]{-1,0,1} ) { arm._pc = 0; - arm.regs[0] = arg; + arm.regs[1] = arg; assertEquals(0,arm.step(100)); assertEquals(arg==0 ? 1 : 0,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" ,23,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; - }; -}; -~Scan s = new Scan{ buf = " q"; }; -return s.skip().x; -"""; - - try { new CodeGen(src).parse().opto().typeCheck(); fail(); } - catch( Exception e ) { org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa")); } - }; - - - @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" ,4,null); - }; - - @Test public void testSCCP0() { String src = """ @@ -700,7 +258,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 @@ -715,7 +273,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..354c6ec2 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter25Test.java @@ -0,0 +1,459 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +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.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter25Test { + + @Ignore + @Test + public void testJig() { + String src = "int x = arg/17; if( arg == 0 ) return arg + x; return arg + x;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals("return (arg+3);", code.print()); + assertEquals("3",Eval2.eval(code,0)); + } + + @Test public void testNeverReturnPointerRiscV() { + new CodeGen(""" + struct _S { int x; }; + !_S !s = new _S; + if(arg) while(1) { s.x += arg; } + return s; + """).driver(CodeGen.Phase.Encoding,"riscv","SystemV"); + } + + @Test + public void testExternData() throws IOException { + String src = """ + i32 counter="C"; + i8 small="C"; + f64 fraction="C"; + {int} bump="C"; + val test_global={ -> + val before=counter; + counter=before; + counter=++counter+1; + counter+=1; + val mid=bump(); + small++; + fraction=fraction+0.5; + return before*10000+mid*100+counter; + }; + """; + TestC.runC(src,"extern_data","",-1); + } + + @Test + public void testExternDataEncoding() { + for( String cpu : new String[]{"arm","riscv"} ) { + CodeGen code = new CheckedCodeGen("i32 counter=\"C\"; counter=counter+3; return counter;"); + // Exercise signed low-12 relocation arithmetic, past the first page. + int addr = 0x3800; + code._externDataAddresses.put("counter",addr); + code.driver(cpu,"SystemV",true,false); + byte[] image = new byte[1<<20]; + var bits = code._encoding._bits; + System.arraycopy(bits.buf(),0,image,0,bits.size()); + if( cpu.equals("arm") ) { + EvalArm64 arm = new EvalArm64(image,1<<16); + arm.st4(addr,-7); + assertEquals(0,arm.step(1000)); + assertEquals(-4,arm.regs[0]); + assertEquals(-4,arm.ld4s(addr)); + } else { + EvalRisc5 r5 = new EvalRisc5(image,1<<16); + r5.st4(addr,-7); + assertEquals(0,r5.step(1000)); + assertEquals(-4,r5.regs[10]); + assertEquals(-4,r5.ld4s(addr)); + } + } + } + + @Test + public void testExternDataImports() throws IOException { + Path dir = Files.createTempDirectory(Path.of("build/objs"),"extern_data_"); + String globals = "i32 counter=\"C\"; val read={ -> counter; };"; + CodeGen globalCode = new CodeGen(null,dir.toString(),null,"Globals",globals,126,true,TypeInteger.BOT) + .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); + var clz = globalCode.compunit()._clz; + int counter = clz.find("counter"); + assertTrue(clz._fields[counter]._extern); + assertEquals(-1,clz.offset(counter)); + assertEquals(clz.remove(counter).size(),clz.size()); + // Load the serialized declaration and ideal graph from Globals.o. + String src = """ + i32 counter="C"; + val test_global={ -> + val before=Globals.counter; + Globals.counter=before+1; + counter+=2; + return before*100+Globals.read(); + }; + """; + Ary<String> paths = new Ary<>(new String[]{dir.toString()}); + for( boolean imported : new boolean[]{true,false} ) { + Path root = dir.resolve(imported ? "binary" : "source"); + Files.createDirectories(root.resolve("Main")); + if( !imported ) Files.writeString(root.resolve("Main/Globals.smp"),globals); + new CodeGen(root.toString(),root.toString(),imported ? paths : null,"Main",src,126,true,TypeInteger.BOT) + .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); + assertEquals("",TestC.gcc(root+"/Main.o","",TestC.C_DRIVERS_DIR+"extern_data_import.c", + (String)null,new Ary<>(new String[]{dir+"/Globals.o"}),root+"/program"+(TestC.OS.startsWith("Windows") ? ".exe" : ""))); + } + } + + @Test public void testPrintingConstantPool() throws Exception { + com.seaofnodes.simple.codegen.PrintRegTestSupport.checkConstantPool(); + } + + private static final String SYS_BLDDIR = "build/objs/lib_"+TestC.CPU_ABI; + private static final File SYS_FILE = new File(SYS_BLDDIR+"/sys.o"); + + @Test + public void testForwardConstructor() { + CodeGen code = new CodeGen("src/test/java/com/seaofnodes/simple/test_smp/forward_ctor", + "build/objs/forward_ctor_parse",null, + "m",null,123L,true,TypeInteger.BOT); + code.driver(CodeGen.Phase.TypeCheck); + } + + + @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 + public void testCharacterEscapes() { + CodeGen code = new CodeGen("return '\\n'==10 && '\\t'==9 && '\\r'==13;") + .driver(CodeGen.Phase.TypeCheck); + assertEquals("1",Eval2.eval(code,0)); + } + + @Test + public void testFuzzerUnresolvedNumericLoop() { + CodeGen code = new CodeGen(""" + arg=0; + while(arg+0) + while(-arg) + arg=0; + """).driver(CodeGen.Phase.TypeCheck); + assertEquals("return 0;",code.print()); + } + + @Test + public void testFuzzerBadLCA() { + new CodeGen(""" + int v0=arg; + if(v0&&--v0?0:arg) {} + """).driver(CodeGen.Phase.LocalSched); + } + + @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,true,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,true,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,true,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,true,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,true,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(); + } + + + @Test + public void testSys() { + assertTrue("Missing "+SYS_FILE+"; run make tests_sys", SYS_FILE.exists()); + + // Can read the ELF files + CodeGen code1 = new CodeGen("return 0;"); + ElfReader sys_elf = ElfReader.load(SYS_FILE, null); + sys_elf.loadPublicTypes(code1); + + // Elf files are sane + + // Sys depends on io, libc, char, collections, and array utilities. + assertEquals(10,sys_elf._deps.length); + assertSame("sys/aryu8" ,sys_elf._deps[0]); + assertSame("sys/char" ,sys_elf._deps[1]); + assertSame("sys/io" ,sys_elf._deps[2]); + assertSame("sys/ary" ,sys_elf._deps[3]); + assertSame("sys/aryi64",sys_elf._deps[4]); + assertSame("sys/adt/bitset",sys_elf._deps[5]); + assertSame("sys", sys_elf._deps[6]); + assertSame("sys/scan" ,sys_elf._deps[7]); + assertSame("sys/adt" ,sys_elf._deps[8]); + assertSame("sys/libc" ,sys_elf._deps[9]); + assertSame("class:sys" ,sys_elf._clz._name); + } + + @Test + public void testConstantClassInitialization() throws IOException { + TestC.runSF("val x = 42; return 0;","constantClassInt","",0); + TestC.runSF("val f = { -> 7; }; return 0;","constantClassFunction","",0); + } + + @Test + public void testHelloWorld() throws IOException { + String expected = "Hello, World!\n"; + String prog = "return sys.io.p(\""+expected+"\") - "+expected.length()+";"; + // tests_raw1 concurrently builds Chapter22Test's helloWorld executable. + TestC.run(prog,"helloWorldSys",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION, null, null, expected,0); + } + + @Test + public void testHelloWorldDriver() throws Exception { + String out = "build/objs/helloWorldDriver/"; + Simple.main(new String[]{"-L",SYS_BLDDIR,"-o",out,"--norun","docs/examples/A_helloWorld.smp"}); + assertTrue(Files.isRegularFile(Path.of(out,"A_helloWorld.o"))); + } + + @Test + public void testHelloWorldDriverLibFile() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + PrintStream saved = System.out; + try( PrintStream capture = new PrintStream(output,true,StandardCharsets.UTF_8) ) { + System.setOut(capture); + Simple.main(new String[]{"-L",SYS_FILE.toString(),"-o","build/objs/helloWorldDriverLibFile/", + "docs/examples/A_helloWorld.smp"}); + } finally { + System.setOut(saved); + } + assertEquals("Hello, World!\n",output.toString(StandardCharsets.UTF_8)); + } + + @Test + public void testHelloWorldNoInline() throws Exception { + String base = "helloWorldNoInline"; + String expected = "Hello, World!\n"; + String prog = "return sys.io.p_noInline(\""+expected+"\") - "+expected.length()+";"; + CodeGen code = new CheckedCodeGen(null,"build/objs",new Ary<>(new String[]{SYS_BLDDIR}), + base,prog,123L,true,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 = TestC.exec(TestC.TEST_TIMEOUT_SECONDS,"nm",obj); + assertTrue(syms, syms.contains(" U sys.io.p_noInline")); + + TestC.linkExe(obj,null,null,new Ary<>(new String[]{SYS_FILE.toString()}),exe); + String rez = TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe); + assertEquals(expected,rez); + } + + @Test + public void testRedirectedRead() throws IOException { + 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 testErrnoAccessor() throws IOException { + String src = "val status=sys.libc.close(-1); return status != -1 || sys.libc.errno()==0;"; + TestC.run(src,"errnoAccessor",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION,null,null,"",-1); + } + + @Test + public void testBubbles() throws IOException { + String src = Files.readString( Path.of("docs/examples/BubbleSort.smp")); + String exe = TestC.compile(src,"BubbleSort",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION,null,null,-1); + assertEquals("[-17, 2, 3, 999]\n", + TestC.exec(exe,"[3, 2,-17, 999 ]")); + assertEquals("[1, 2, 3, 4, 4, 5]\n", + TestC.exec(exe,"[4, 5, 3, 1, 4, 2]")); + assertEquals("[1, 2, 3, 4, 5]\n", + TestC.exec(exe,"[1, 2, 3, 4, 5]")); + assertEquals("[1, 2, 3, 4, 5, 6, 7, 8, 9]\n", + TestC.exec(exe,"[9, 8, 7, 6, 5, 4, 3, 2, 1]")); + + String usage = "Usage: please provide a list of at least two integers to sort in the format \"[1, 2, 3, 4, 5]\"\n"; + assertEquals(usage,TestC.exec(exe)); + assertEquals(usage,TestC.exec(exe,"")); + assertEquals(usage,TestC.exec(exe,"[1]")); + assertEquals(usage,TestC.exec(exe,"[4 5 3]")); + } + + @Test + public void testCapitalize() throws IOException { + String src = Files.readString(Path.of("docs/examples/Capitalize.smp")); + String exe = TestC.compile(src,"Capitalize",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION,null,null,-1); + assertEquals("Hello world\n",TestC.exec(exe,"hello world")); + assertEquals("Hello World\n",TestC.exec(exe,"Hello World")); + assertEquals("123 apples\n",TestC.exec(exe,"123 apples")); + assertEquals("Usage: please provide a string\n",TestC.exec(exe)); + assertEquals("Usage: please provide a string\n",TestC.exec(exe,"")); + assertEquals("Use quotes around multiple strings.\n",TestC.exec(exe,"hello","world")); + } + + @Test + public void testDijkstra() throws IOException { + String src = Files.readString(Path.of("docs/examples/Dijkstra.smp")); + String matrix = "[0, 2, 0, 6, 0, 2, 0, 3, 8, 5, 0, 3, 0, 0, 7, 6, 8, 0, 0, 9, 0, 5, 7, 9, 0]"; + String exe = TestC.compile(src,"Dijkstra",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION,null,null,-1); + assertEquals("2\n",TestC.exec(exe,matrix,"0","1")); + String usage = "Usage: please provide three inputs: a serialized matrix, a source node and a destination node\n"; + assertEquals("7\n",TestC.exec(exe,matrix,"0","4")); + assertEquals(usage,TestC.exec(exe)); + assertEquals(usage,TestC.exec(exe,"","","")); + assertEquals(usage,TestC.exec(exe,"[1, 0, 3, 0, 5, 1]","1","2")); + assertEquals(usage,TestC.exec(exe,"[0, 0, 0, 0]","0","1")); + } + + @Test + public void testFileIO() throws IOException { + String src = Files.readString(Path.of("docs/examples/FileIO.smp")); + String exe = TestC.compile(src,"FileIO",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION,null,null,-1); + assertEquals("File I/O succeeded\n",TestC.exec(exe)); + } +} 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<String>(); - 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 <stdio.h> - - 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 98e0f18a..21e677ce 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,13 +134,21 @@ 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 <clinit> / 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 <clinit> self parameter to the + // singleton class structure. + F.put(fun.parm(2),alloc(clz)); + } // ------------ // Evaluate until exit or timeout. Each outer loop step computes // all data nodes under some new Control. while( true ) { - assert BB!=null; + if( BB==null ) return null; traceCtrl(BB,trace); if( trace!=null ) System.out.println(F.p()); @@ -170,6 +178,7 @@ public abstract class Eval2 { case RegionNode r : break; default: throw Utils.TODO(); } + if( BB==null ) return null; assert !BB.isDead(); } } @@ -183,28 +192,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 <clinit> 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 <clinit> 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; } @@ -253,36 +271,42 @@ 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 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(2))==0 ? 0L : 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 -> 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 RoundF32Node rnd -> (double)(float)d(rnd.in(1)); case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); @@ -291,12 +315,6 @@ public abstract class Eval2 { }; } - private static Object not( NotNode not ) { - Object n = val(not.in(1)); - return ( n==null || (n instanceof Double D && D==0.0) || n instanceof Long L && L==0 ) - ? 1L : 0L; - } - // Fetch without unboxing, searching up-Frame static Object val( Node n ) { return F.get(n); } // Fetch and unbox as primitive long @@ -304,7 +322,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); } @@ -316,8 +336,6 @@ public abstract class Eval2 { case TypeFunPtr tfp -> tfp; case TypeMem mem -> "MEM"; case TypeMemPtr tmp -> { - // Unknown-caller inputs are type placeholders, not heap constants. - if( !tmp.isConstant() ) yield tmp; if( tmp._obj.isAry() ) { // Constant array ptr Type elem = tmp._obj.field("[]")._t; if( elem instanceof TypeConAry con ) { @@ -332,16 +350,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(); @@ -351,10 +386,10 @@ 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.size()); + long sz = (Long)val(alloc.in(1)); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) @@ -371,6 +406,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<num; i++ ) @@ -378,15 +417,16 @@ public abstract class Eval2 { return ptr; } + private static Object not( NotNode not ) { + Object n = val(not.in(1)); + return ( n==null || (n instanceof Double D && D==0.0) || n instanceof Long L && L==0 ) + ? 1L : 0L; + } + private static Object load( LoadNode ld ) { Object f = val(ld.ptr()); // Check for dense constant array if( f instanceof TypeMemPtr tmp ) { - //assert tmp._obj._con != TypeConAry.BOT; - //if( ld._name.equals("#") ) - // return (long)tmp._obj._con.len(); - //int idx = offToIdx(x(ld.off()),tmp._obj); - //return tmp._obj._con.at(idx); throw Utils.TODO(); } Object[] fs = (Object[])f; @@ -445,12 +485,11 @@ public abstract class Eval2 { case TypeMemPtr tmp -> { 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); @@ -475,9 +514,9 @@ public abstract class Eval2 { case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { - if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr ) + if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) // Assume a NewNode - yield _print( tt._types[0], x, sb, visit ); + yield _print( tt._types[1], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { diff --git a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java index b022bec9..68bbe918 100644 --- a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java +++ b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java @@ -7,19 +7,37 @@ import org.junit.Test; import static org.junit.Assert.assertTrue; /** - * Normal tests run chapter-local regression seeds. Open failures and exploratory - * fuzzing are opt-in (run directly in the IDE or temporarily remove Ignore). - * Seeds depend on this chapter's generator; do not copy them between chapters. + * To use the fuzzer run the <code>fuzzPeeps</code> methods. + * This can be done from IntelliJ Gui or remove the `@Ignore` annotation and start the test case from the command line + * via <code>mvn clean test -Dtest=com.seaofnodes.simple.Fuzzer08Test#fuzzPeeps</code> */ public class FuzzerWrap { private static final long[] REGRESSION_SEEDS = { + 375135762521757909L, // bad LCA + -148471672577312953L, // bulk mem + -5037182906211190034L, // dead Guard control + -6359653295501938199L, // monotonicity, escape is dead + 6506797708065910879L, // required field without a user constructor + 650692970082394944L, // recursive bulk mem split + 8335100609598232836L, // ternary result killed by region cleanup + 2520958273643234516L, // nested guard merge + -3772838984504063325L, // fuzzer regression + -4628356252269023530L, // idepth cache version wrap + -4022199781524079249L, // fuzzer regression + 8824787517620928178L, // unknown-mode unary minus during SCCP + -673501011619761901L, // peephole killed self + -8212834489697770130L, // bad keep/unkeep in parser + 478567486335112458L, // dying escape node + -5114548365532699915L, // dominate flags dependencies + -4710931876754133070L, // Tests monotonic phi widening }; private static final long[] OPEN_FAILING_SEEDS = { }; - @Test public void fuzzPeepsRegression () { fuzzPeepsSeeds(REGRESSION_SEEDS); } + + @Test public void fuzzPeepsRegression () { fuzzPeepsSeeds( REGRESSION_SEEDS); } @Test @Ignore public void fuzzPeepsOpenFailures() { fuzzPeepsSeeds(OPEN_FAILING_SEEDS); } private static void fuzzPeepsSeeds(long... seeds) { @@ -30,29 +48,31 @@ public class FuzzerWrap { @Ignore @Test - public void fuzzPeepsLarge() { + public void fuzzPeepsRandom() { + Random R = new Random(System.currentTimeMillis()); var fuzzer = new Fuzzer(); - for (int i=0; i<1000000; i++) - fuzzer.fuzzPeeps(i); + for (int i=0; i<100; i++) + fuzzer.fuzzPeeps(R.nextLong()); assertTrue(fuzzer.noExceptions()); } - @Test @Ignore - public void fuzzPeepsRandom() { - Random R = new Random(System.currentTimeMillis()); + @Ignore + @Test + public void fuzzPeepsLarge() { var fuzzer = new Fuzzer(); - for (int i=0; i<100; i++) - fuzzer.fuzzPeeps( R.nextLong()); + for (int i=0; i<1000000; i++) + fuzzer.fuzzPeeps(i); assertTrue(fuzzer.noExceptions()); } - @Test @Ignore + @Test public void fuzzPeepTiming() { var fuzzer = new Fuzzer(); int max_nid=0; for (int i=0; i<1000000; i++) max_nid = fuzzer.fuzzPeepTiming(i, max_nid); } + } diff --git a/src/test/java/com/seaofnodes/simple/LiteralTest.java b/src/test/java/com/seaofnodes/simple/LiteralTest.java deleted file mode 100644 index 09ee30f5..00000000 --- a/src/test/java/com/seaofnodes/simple/LiteralTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.seaofnodes.simple; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.TypeConAryB; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMemPtr; -import org.junit.Test; -import static org.junit.Assert.*; - -public class LiteralTest { - private static final String[] ESCAPES = {"\\0", "\\n", "\\t", "\\r", "\\\\", "\\\"", "\\'"}; - private static final int[] VALUES = {0, 10, 9, 13, 92, 34, 39}; - - @Test public void testStringEscapes() { - for (int i = 0; i < ESCAPES.length; i++) - assertString(ESCAPES[i], new byte[]{(byte)VALUES[i]}); - assertString("a\\\"b\\\\c\\0d", new byte[]{97, 34, 98, 92, 99, 0, 100}); - assertString("", new byte[]{}); - assertString("plain", new byte[]{112, 108, 97, 105, 110}); - } - - private static void assertString(String text, byte[] expected) { - var code = new CodeGen("return \"" + text + "\";").parse().opto().typeCheck(); - TypeMemPtr ptr = (TypeMemPtr)code.expr()._type; - TypeConAryB bytes = (TypeConAryB)ptr._obj.field("[]")._t; - assertArrayEquals(text, expected, bytes._ary); - } - - @Test public void testCharacterEscapes() { - for (int i = 0; i < ESCAPES.length; i++) - assertCharacter(ESCAPES[i], VALUES[i]); - assertCharacter("A", 65); - } - - private static void assertCharacter(String text, int expected) { - var code = new CodeGen("return '" + text + "';").parse().opto().typeCheck(); - assertEquals(text, expected, ((TypeInteger)code.expr()._type).value()); - } - - @Test public void testMalformedStrings() { - assertBadLiteral("return \"\\q\";", "Unknown string escape"); - assertBadLiteral("return \"abc", "Unclosed string"); - assertBadLiteral("return \"abc\\", "Unclosed string"); - // The quote closes the string at EOF; the missing token is the semicolon. - assertBadLiteral("return \"plain\"", ";"); - } - - @Test public void testMalformedCharacters() { - assertBadLiteral("return '\\q';", "Unknown character escape"); - assertBadLiteral("return '", "Unclosed character"); - assertBadLiteral("return '\\", "Unclosed character"); - assertBadLiteral("return '';", "Syntax error"); - assertBadLiteral("return 'ab';", "Syntax error"); - assertBadLiteral("return 'a", "Syntax error"); - } - - private static void assertBadLiteral(String src, String message) { - try { - new CodeGen(src).parse(); - fail("Accepted malformed literal: " + src); - } catch (Parser.ParseException e) { - assertTrue(e.getMessage(), e.getMessage().contains(message)); - } - } -} diff --git a/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java index e97194af..9406f480 100644 --- a/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java +++ b/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -1,18 +1,8 @@ package com.seaofnodes.simple; -import com.seaofnodes.isa.eval.EvalRisc5; - -import com.seaofnodes.isa.eval.EvalArm64; - import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.RegAllocTestSupport; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.arm.arm; -import com.seaofnodes.simple.node.cpus.riscv.riscv; -import com.seaofnodes.simple.type.TypeMemPtr; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.BitSet; import org.junit.Test; import static org.junit.Assert.*; @@ -65,117 +55,50 @@ public class MemoryEncodingTest { return sum; """ }; - for( int p=0; p<sources.length; p++ ) { - CodeGen code=new CodeGen(sources[p]).parse().opto(); - if( p==0 ) assertNull(code._stop.walk(n -> - n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && - ptr.in(0) instanceof NewNode nn && - nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); - for( int arg=0; arg<7; arg++ ) { - long expected=switch(p) { - case 0 -> 106*arg+(arg>=2 ? 2 : 0); - case 1 -> (arg&1)==0 ? 140 : 60; - case 2 -> 130; - default -> 16; - }; - assertEquals(Long.toString(expected),Eval2.eval(code,arg)); - } - } - } - - private static final String[] SOURCES = { - Chapter16Test.CONSTRUCTOR_MEMORY, - Chapter18Test.CALL_MEMORY, - Chapter18Test.RECURSIVE_MEMORY, - Chapter18Test.INLINE_MEMORY, - "int[] !a = new int[7]; a[arg]=arg+3; return a[0]+1;", - """ - int[] !a = new int[7]; - for( int i=0; i<7; i++ ) a[i]=i+arg; - for( int i=0; i<6; i++ ) a[i+1]+=a[i]; - return a[6]; - """ - }; - - private static long expected(int program, long arg) { - return switch(program) { - case 0 -> arg==0 ? 111249 : 222351+arg; - case 1 -> arg==0 ? 10101325 : 20202322; - case 2 -> (10*(arg+1)+arg*(arg+1)/2)*100+10+arg; - case 3 -> 400944+arg*10001; - case 4 -> arg==0 ? 4 : 1; - case 5 -> 21+7*arg; - default -> throw new AssertionError(); - }; - } - - @Test public void testEmulatedMemory() throws IOException { - for( String target : new String[]{"riscv","arm"} ) - for( int p=0; p<SOURCES.length; p++ ) { - CodeGen code = new RegAllocTestSupport.CheckedCodeGen(SOURCES[p]).driver(target,"SystemV",null); - int entry=0; - for( var n : code._start._outputs ) - if( n instanceof FunNode fun && "main".equals(fun._name) ) - entry=code._encoding._opStart[fun._nid]; + // Exercise both unresolved Store aliases and nested value-Phi cleanup. + for( long seed : new long[]{0,9,126} ) + for( int p=0; p<sources.length; p++ ) { + CodeGen code=new CodeGen(sources[p],seed,true).parse().opto(); + if( p==0 ) assertNull(code._stop.walk(n -> + n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && + ptr.in(0) instanceof NewNode nn && + nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); for( int arg=0; arg<7; arg++ ) { - byte[] image = new byte[1<<20]; - byte[] bits = code._encoding.bits(); - System.arraycopy(bits,0,image,0,bits.length); - long result; - if( target.equals("riscv") ) { - EvalRisc5 cpu = new EvalRisc5(image,1<<16); - cpu._pc=entry; - cpu.regs[riscv.A0]=arg; - assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); - result=cpu.regs[riscv.A0]; - } else { - EvalArm64 cpu = new EvalArm64(image,1<<16); - cpu._pc=entry; - cpu.regs[arm.X0]=arg; - assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); - result=cpu.regs[arm.X0]; - } - assertEquals(target+" program "+p+" arg "+arg,expected(p,arg),result); + long expected=switch(p) { + case 0 -> 106*arg+(arg>=2 ? 2 : 0); + case 1 -> (arg&1)==0 ? 140 : 60; + case 2 -> 130; + default -> 16; + }; + assertEquals(Long.toString(expected),Eval2.eval(code,arg)); } } } - @Test public void testNeverMemory() { - // A varying pointer prevents store sinking: the synthetic exit needs both slices. - for( String target : new String[]{"x86_64_v2","riscv","arm"} ) { - CodeGen code = new CodeGen(""" - struct S { int x; int y; }; - !S !a=new S; !S !b=new S; !S !p=a; - if( arg ) while( 1 ) { p.x+=arg; p.y+=p.x; p=p==a ? b : a; } - return a.x+a.y; - """).driver(CodeGen.Phase.LoopTree); - assertNotNull(code._stop.walk(n -> { - if( n instanceof MemMergeNode mem && - mem.in(0) instanceof CProjNode exit && exit.in(0) instanceof NeverNode ) { - var fields = ((TypeMemPtr)Parser.TYPES.get("S"))._obj._fields; - assertTrue(mem.alias(fields[0]._alias) instanceof MemPhiNode); - assertTrue(mem.alias(fields[1]._alias) instanceof MemPhiNode); - return mem; - } - return null; - })); - code.instSelect(target,"SystemV").GCM().localSched().regAlloc(); - RegAllocTestSupport.checkRegisters(code); - code.encode(); - } + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + !S !a = new S; !S !b = new S; + a.x=arg; b.x=arg+1; + !S !p=a; !S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); } - @Test public void testNativeMemory() throws IOException { - Files.createDirectories(Path.of("build/objs")); - for( int p=0; p<SOURCES.length; p++ ) { - String file="build/objs/memory"+p; - StringBuilder c = new StringBuilder("extern long long memory(long long);\nint main() {\n"); - for( int arg=0; arg<7; arg++ ) - c.append("if(memory(").append(arg).append(")!=").append(expected(p,arg)).append("LL) return ").append(arg+1).append(";\n"); - c.append("return 0;\n}\n"); - Files.writeString(Path.of(file+".c"),c); - TestC.run("val memory = { int arg -> "+SOURCES[p]+" };", - TestC.CALL_CONVENTION,null,"",file+".c",file,"S","",-1); - } + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; } } diff --git a/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/src/test/java/com/seaofnodes/simple/MergeSortTest.java index 453bd35c..f5d3d146 100644 --- a/src/test/java/com/seaofnodes/simple/MergeSortTest.java +++ b/src/test/java/com/seaofnodes/simple/MergeSortTest.java @@ -2,12 +2,10 @@ package com.seaofnodes.simple; import com.seaofnodes.isa.eval.EvalRisc5; -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; @@ -17,52 +15,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, 34); + 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/MutabilityTest.java b/src/test/java/com/seaofnodes/simple/MutabilityTest.java index 7ae80314..60e4c5aa 100644 --- a/src/test/java/com/seaofnodes/simple/MutabilityTest.java +++ b/src/test/java/com/seaofnodes/simple/MutabilityTest.java @@ -22,20 +22,20 @@ public class MutabilityTest { } @Test public void testIndependentBindingsAndViews() { - ok("struct P { int x; }; !P ~p=new P; p.x=7; return p.x;", "7"); - ok("struct P { int x; }; ~P !p=new P{x=3;}; p=new P{x=7;}; return p.x;", "7"); - bad("struct P { int x; }; !P ~p=new P; p=new P; return 0;"); - bad("struct P { int x; }; ~P !p=new P; p.x=7; return 0;"); + ok("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); p.x=7; return p.x;", "7"); + ok("struct P { int x; new P = { int v -> x=v; }; }; ~P !p=new P(3); p=new P(7); return p.x;", "7"); + bad("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); p=new P(0); return 0;"); + bad("struct P { int x; new P = { int v -> x=v; }; }; ~P !p=new P(0); p.x=7; return 0;"); bad("int ~x=3; x=7; return x;"); ok("int !x=3; x=7; return x;", "7"); } @Test public void testDeepViewAndAliases() { - ok("struct P { int x; }; !P ~p=new P; ~P ~r=p; p.x=7; return r.x;", "7"); - bad("struct P { int x; }; struct Box { !P p; }; !P q=new P; Box b=new Box{p=q;}; b.p.x=7; return 0;"); - bad("struct P { int x; }; P p=new P; !P q=p; q.x=7; return 0;"); - bad("struct P { P? next; int x; }; !P p=new P{next=new P;}; p.next.x=7; return 0;"); - bad("struct P { !P? next; int x; }; P p=new P{next=new P;}; p.next.x=7; return 0;"); + ok("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); ~P ~r=p; p.x=7; return r.x;", "7"); + bad("struct P { int x; new P = { int v -> x=v; }; }; struct Box { !P p; new Box = { !P v -> p=v; }; }; !P p=new P(0); Box b=new Box(p); b.p.x=7; return 0;"); + bad("struct P { int x; new P = { int v -> x=v; }; }; P p=new P(0); !P q=p; q.x=7; return 0;"); + bad("struct P { P? next; int x; new P = { P? v -> next=v; }; }; !P p=new P(new P(null)); p.next.x=7; return 0;"); + bad("struct P { !P? next; int x; new P = { !P? v -> next=v; }; }; P p=new P(new P(null)); p.next.x=7; return 0;"); } @Test public void testInferencePreservesAccess() { @@ -53,18 +53,12 @@ public class MutabilityTest { bad("struct P { int x; }; val f={ P?[] a -> return a#; }; !P?[] a=new !P?[1]; return f(a);"); } - @Test public void testConstructorScope() { - ok("struct P { int ~x; }; P p=new P{if(arg) x=3; else x=7;}; return p.x;", "7"); - bad("int ~outer=1; struct P { int x; }; P p=new P{outer=7;}; return outer;"); - bad("struct P { int x; }; P p=new P{int ~local=1; local=7;}; return p.x;"); - } - @Test public void testArrayLayers() { bad("struct P { int x; }; !P?[] rw=new !P?[1]; P?[] ro=rw; rw[0]=new P; ro[0].x=7; return 0;"); ok("u8[] a=new u8[2]; a[0]=7; return a[0];", "7"); - ok("struct P { int x; }; P?[] a=new P?[1]; a[0]=new P{x=7;}; return a[0].x;", "7"); - bad("struct P { int x; }; P?[] a=new P?[1]; a[0]=new P; a[0].x=7; return 0;"); - ok("struct P { int x; }; !P?[] a=new !P?[1]; a[0]=new P; a[0].x=7; return a[0].x;", "7"); + ok("struct P { int x; new P = { int v -> x=v; }; }; P?[] a=new P?[1]; a[0]=new P(7); return a[0].x;", "7"); + bad("struct P { int x; new P = { int v -> x=v; }; }; P?[] a=new P?[1]; a[0]=new P(0); a[0].x=7; return 0;"); + ok("struct P { int x; new P = { int v -> x=v; }; }; !P?[] a=new !P?[1]; a[0]=new P(0); a[0].x=7; return a[0].x;", "7"); bad("u8[~] a=new u8[1]; a[0]=7; return 0;"); ok("u8[~]?[] rows=new u8[~]?[1]; rows[0]=new u8[1]; return rows#;", "1"); bad("u8[~]?[] rows=new u8[~]?[1]; rows[0]=new u8[1]; rows[0][0]=7; return 0;"); diff --git a/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java b/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java new file mode 100644 index 00000000..f3f65183 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java @@ -0,0 +1,69 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +public class NativeExecutionTest { + // A subprocess with deterministic stdout, stderr, and process status. + public static class Child { + public static void main(String[] args) { + System.out.println("before exit"); + System.err.println("child diagnostic"); + System.exit(Integer.parseInt(args[0])); + } + } + + private static String child(int exit) throws IOException { + String java = Path.of(System.getProperty("java.home"),"bin","java").toString(); + return TestC.exec(java,"-cp",System.getProperty("java.class.path"), + Child.class.getName(),Integer.toString(exit)); + } + + private static void checkFailure(int exit) throws IOException { + try { + child(exit); + fail("Accepted process exit "+exit); + } catch( IOException e ) { + assertTrue(e.getMessage(),e.getMessage().contains("exit code: "+exit)); + assertTrue(e.getMessage(),e.getMessage().contains("before exit")); + assertTrue(e.getMessage(),e.getMessage().contains("child diagnostic")); + } + } + + @Test public void testNonzeroExit() throws IOException { checkFailure(7); } + + @Test public void testFullWindowsExitCode() throws IOException { + assumeTrue(TestC.OS.startsWith("Windows")); + checkFailure(256); // Narrowing to a byte incorrectly reports success. + checkFailure(2816); // Observed Cygwin status for the Hello World crash. + } + + @Test public void testSuccessfulOutput() throws IOException { + assertEquals("before exit"+System.lineSeparator(),child(0)); + } + + @Test public void testHelloWorldArtifacts() throws IOException { + new Chapter22Test().testHelloWorld(); + new Chapter25Test().testHelloWorld(); + // Model a parallel run paused between Chapter 22's link and execution. + // Chapter 25 must not replace the executable Chapter 22 is about to run. + String exe = "build/objs/helloWorld"+(TestC.OS.startsWith("Windows") ? ".exe" : ""); + assertEquals("Hello, World!",TestC.exec(exe)); + } + + @Test public void testDriverNonzeroExit() throws Exception { + Path dir = Files.createTempDirectory(Path.of("build/objs"),"driver-exit-"); + Path src = dir.resolve("fail.smp"); + Files.writeString(src,"return 7;"); + try { + Simple.main(new String[]{src.toString()}); + fail("Driver accepted a failed executable"); + } catch( IOException e ) { + assertTrue(e.getMessage(),e.getMessage().contains("exit code: 7")); + } + } +} 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 3b19857a..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 18804ebd..b7e10122 100644 --- a/src/test/java/com/seaofnodes/simple/Simple.java +++ b/src/test/java/com/seaofnodes/simple/Simple.java @@ -1,66 +1,65 @@ 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_PASS_NAME = 1<<30; - static final int DUMP_AFTER_ALL = 0xff | DUMP_PASS_NAME; - - static final int DUMP_DISASSEMBLE = 1<<31; - static void usage() { System.out.println( """ -simple [options] <input-file> ...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 <cpu-name> - use specific CPU (x86_64_v2, riscv, arm) + --abi <abi-name> - 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 - -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 <cpu-name> - use specific CPU (x86_64_v2, riscv, arm) - --abi <abi-name> - use speific ABI variant (SystemV) - --target - print native CPU and ABI - --version - --help + --dump - dump final intermediate representation only """ ); 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); @@ -73,71 +72,34 @@ Options: System.out.println(TestC.CALL_CONVENTION); } - static void dump(CodeGen code, int dump, int pass) { - if ((dump & pass) != 0) { - 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<code._times.length; i++ ) + System.out.printf("%20s %.3f sec%n", Phase.values()[i], times[i]); } - static String getInput() throws IOException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { - return reader.lines().collect(Collectors.joining("\n")); - } + static String getInput() { + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + return reader.lines().collect(Collectors.joining("\n")); } public static void main(String[] args) throws Exception { + String root = null; String input_filename = null; boolean do_eval = false; boolean do_run = true; boolean do_codegen = false; - boolean do_print_size = false; - boolean do_print_time = false; + boolean emit_main = false; + boolean print_time = false; + boolean print_size = false; + boolean print_asm = false; int dump = 0; int first_arg = 0; String src = null; String cpu = null; String abi = null; + String out = null; + ArrayList<String> libPaths = new ArrayList<>(); // Parse command line int i; for( i = 0; i < args.length; i++ ) { @@ -149,23 +111,32 @@ 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 "-S": dump |= DUMP_DISASSEMBLE; break; + case "--dump-after-parse": dump |= 1<<Phase.Parse .ordinal() ; break; + case "--dump-after-iter": dump |= 1<<Phase.Iter .ordinal() ; break; + case "--dump-after-opto": dump |= 1<<Phase.Opto .ordinal() ; break; + case "--dump-after-type-check": dump |= 1<<Phase.TypeCheck .ordinal() ; break; + case "--dump-after-loop-tree": dump |= 1<<Phase.LoopTree .ordinal() ; break; + case "--dump-after-select": dump |= 1<<Phase.Select .ordinal() ; break; + case "--dump-after-gcm": dump |= 1<<Phase.Schedule .ordinal() ; break; + case "--dump-after-local-sched": dump |= 1<<Phase.LocalSched.ordinal() ; break; + case "--dump-after-reg-alloc": dump |= 1<<Phase.RegAlloc .ordinal() ; break; + case "--dump-after-encode": dump |= 1<<Phase.Encoding .ordinal() ; break; + case "--dump-after-export": dump |= 1<<Phase.Export .ordinal() ; break; + case "--dump": dump |= 1<<Phase.LastPhase .ordinal() ; break; + case "--dump-after-all": dump |= (2<<Phase.LastPhase .ordinal())-1; break; + case "-S": print_asm = true; break; case "--eval": do_eval = true ; do_run = false; break; case "--run": do_run = true ; break; case "--norun": do_run = false; break; - case "--dump-size": do_print_size = true; break; - case "--dump-time": do_print_time = true; break; + case "--main": emit_main = true; break; + case "--dump-size": print_size = true; break; + case "--dump-time": print_time = true; break; + case "-o": if (out != null || i + 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; @@ -179,16 +150,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 { @@ -196,70 +219,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<String> externPaths = libPaths.isEmpty() ? null : new Ary<>(libPaths.toArray(new String[0])); + CodeGen code = new CodeGen(modPath.toString(), outPath.toString(), externPaths, srcName, src, 456, true, 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, "", TestC.linkObjs(externPaths), exe); System.out.print(result); } } diff --git a/src/test/java/com/seaofnodes/simple/SpillStats.java b/src/test/java/com/seaofnodes/simple/SpillStats.java index ad044a52..d43f1a36 100644 --- a/src/test/java/com/seaofnodes/simple/SpillStats.java +++ b/src/test/java/com/seaofnodes/simple/SpillStats.java @@ -1,13 +1,15 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +import com.seaofnodes.simple.type.TypeInteger; +import java.nio.file.*; import java.util.Map; import java.util.TreeMap; import org.junit.runner.*; import org.junit.runner.notification.*; -// Run existing tests and sum actual allocations, including ones without goldens. -// Helpers call record after allocation; ordinary JUnit runs collect nothing. +// Earlier cohorts freeze source/target membership; Chapter 25 retains native checks. public class SpillStats extends RunListener { private static SpillStats ACTIVE; private Description _test; @@ -15,30 +17,25 @@ public class SpillStats extends RunListener { private final Map<String,long[]> _totals = new TreeMap<>(); @Override public void testStarted(Description test) { _test = test; } - public static void record(CodeGen code, String cohort, String cpu, String abi) { - if( ACTIVE==null ) return; - String test = ACTIVE._test.getTestClass().getSimpleName()+"."+ACTIVE._test.getMethodName(); + @Override public void testFinished(Description test) { _test = null; } + + public static void record25(CodeGen code) { + if( ACTIVE==null || ACTIVE._test==null || ACTIVE._test.getTestClass()!=Chapter25Test.class ) return; + ACTIVE.record(code,"Chapter25",ACTIVE._test.getMethodName(),code._mach.getClass().getSimpleName(),code._callingConv); + } + + private void record(CodeGen code, String cohort, String test, String cpu, String abi) { for( String key : new String[]{cohort+","+cpu+","+abi,cohort+",ALL,ALL"} ) { - long[] sum = ACTIVE._totals.computeIfAbsent(key,k -> new long[3]); + long[] sum = _totals.computeIfAbsent(key,k -> new long[3]); sum[0]++; sum[1] += code._regAlloc._spills; sum[2] += code._regAlloc._spillScaled; } System.out.println("allocation,"+cohort+","+test+","+cpu+","+abi+","+ code._regAlloc._spills+","+code._regAlloc._spillScaled); } - // The same native helpers serve old and new program cohorts. - public static void recordNative(CodeGen code, String cpu, String abi) { - String cohort = ACTIVE!=null && (ACTIVE._test.getTestClass()==Chapter24Test.class || ACTIVE._test.getTestClass()==Chapter24AllocTest.class) - ? "Chapter24" : ACTIVE!=null && ACTIVE._test.getTestClass()==Chapter23Test.class - ? "Chapter23" : ACTIVE!=null && ACTIVE._test.getTestClass()==Chapter22Test.class - ? "Chapter22" : "Chapter21"; - record(code,cohort,cpu,abi); - } - - // Keep running every CPU and native result check when measuring quality. - // A changed golden still makes spill-stats fail at the end. + // Keep native result checks running during a quality comparison. public static void checkSpills(int expected, int actual) { - if( expected==-1 ) return; + if( expected==-1 || CodeGen.iterSeedOverridden() ) return; try { org.junit.Assert.assertEquals("Expect spills:",expected,actual,Math.max(1,expected>>3)); } catch( AssertionError error ) { if( ACTIVE==null ) throw error; @@ -47,27 +44,49 @@ public class SpillStats extends RunListener { } } - public static void main(String[] args) throws ClassNotFoundException { - if( args.length==0 ) args = new String[]{"Chapter20Test","Chapter21Test","Chapter21AllocTest","Chapter22Test","Chapter23Test","Chapter23AllocTest","Chapter24Test","Chapter24AllocTest","BrainFuckTest","MergeSortTest"}; - Class<?>[] tests = new Class<?>[args.length]; - for( int i=0; i<args.length; i++ ) - tests[i] = Class.forName("com.seaofnodes.simple."+args[i]); + public static void main(String[] args) throws Exception { ACTIVE = new SpillStats(); + Path dir = Path.of("src/test/java/com/seaofnodes/simple/spill"); + int failures=0, attempts=0; + for( String line : Files.readAllLines(dir.resolve("cohorts.tsv")) ) { + if( line.startsWith("#") || line.isBlank() ) continue; + attempts++; + String[] row = line.split("\t"); + try { + String src = Files.readString(dir.resolve(row[5])); + CodeGen code = new CheckedCodeGen(null,null,null,null,src,123L,true,TypeInteger.BOT) + .driver(CodeGen.Phase.RegAlloc,row[2],row[3]); + ACTIVE.record(code,row[0],row[1],row[2],row[3]); + } catch( Throwable error ) { + failures++; + System.err.println(line); + error.printStackTrace(); + } + } + // Include the library's allocation even when make's sys.o is up to date. + try { + CodeGen code = new CheckedCodeGen("src/main/smp","build/spill-sys",null,"sys", + Files.readString(Path.of("src/main/smp/sys.smp")),456L,true,TypeInteger.BOT) + .driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION); + ACTIVE.record(code,"Chapter25","sys",TestC.CPU_PORT,TestC.CALL_CONVENTION); + } catch( Throwable error ) { + failures++; + error.printStackTrace(); + } JUnitCore junit = new JUnitCore(); junit.addListener(ACTIVE); - Result result = junit.run(tests); - boolean passed = result.wasSuccessful() && ACTIVE._qualityFailures==0; - if( !result.wasSuccessful() ) System.out.println("FAILED test run: totals are not a complete comparison."); - else if( !passed ) System.out.println("Spill expectations differ; allocation and runtime checks passed."); + Result result = junit.run(Chapter25Test.class); + for( Failure failure : result.getFailures() ) System.err.println(failure.getTrace()); + failures += result.getFailureCount(); + if( failures!=0 ) System.out.println("FAILED: totals are not a complete comparison."); System.out.println("cohort,cpu,abi,allocations,splits,scaled"); for( var row : ACTIVE._totals.entrySet() ) { long[] sum = row.getValue(); System.out.println(row.getKey()+","+sum[0]+","+sum[1]+","+sum[2]); } - for( Failure failure : result.getFailures() ) System.err.println(failure.getTrace()); - System.out.println("Tests: "+result.getRunCount()+", failures: "+result.getFailureCount()); - System.out.println("Spill golden failures: "+ACTIVE._qualityFailures); + System.out.println("Historical allocations: "+attempts+"; native tests: "+result.getRunCount()+"; failures: "+failures+"; spill golden failures: "+ACTIVE._qualityFailures); + failures += ACTIVE._qualityFailures; ACTIVE = null; - if( !passed ) System.exit(1); + if( failures!=0 ) System.exit(1); } -} +}
\ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/StaticDataTest.java b/src/test/java/com/seaofnodes/simple/StaticDataTest.java new file mode 100644 index 00000000..070b7a9e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/StaticDataTest.java @@ -0,0 +1,112 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.StaticData; +import com.seaofnodes.simple.type.TypeStruct; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.isa.eval.EvalArm64; +import com.seaofnodes.isa.eval.EvalRisc5; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +public class StaticDataTest { + private static final String SRC="val n=42; val f={int x -> x+n;}; return 0;"; + + private static StaticData.ObjectData root(CodeGen code) { + return code._encoding._data.entries().stream() + .filter(obj -> obj.symbol.endsWith("Test.$class")).findFirst().orElseThrow(); + } + + // Read the image before <clinit>, then call the function through its data slot. + @Test public void testEmulatorImage() { + for( String cpu : new String[]{"arm","riscv"} ) { + CodeGen code=new CodeGen(SRC).driver(cpu,"SystemV",true,false); + var e=code._encoding; + var obj=root(code); + TypeStruct ts=(TypeStruct)obj.type; + int cp=(e._bits.size()+15)&-16, sd=(cp+e._cpool.size()+15)&-16; + byte[] image=new byte[1<<20]; + System.arraycopy(e._bits.buf(),0,image,0,e._bits.size()); + System.arraycopy(e._cpool.buf(),0,image,cp,e._cpool.size()); + System.arraycopy(e._sdata.buf(),0,image,sd,e._sdata.size()); + int n=sd+obj.offset+ts.offset(ts.find("n")); + int f=sd+obj.offset+ts.offset(ts.find("f")); + if( cpu.equals("arm") ) { + var arm=new EvalArm64(image,1<<16); + assertEquals(42,arm.ld4s(n)); + arm._pc=arm.ld4s(f); + arm.regs[0]=7; + assertEquals(0,arm.step(1000)); + assertEquals(49,arm.regs[0]); + } else { + var r5=new EvalRisc5(image,1<<16); + assertEquals(42,r5.ld4s(n)); + r5._pc=r5.ld4s(f); + r5.regs[10]=7; + assertEquals(0,r5.step(1000)); + assertEquals(49,r5.regs[10]); + } + } + } + + @Test public void testNativeImage() throws Exception { + Path dir=Path.of("build/objs/static-data"); + Files.createDirectories(dir); + CodeGen code=new CodeGen(null,dir.toString(),null,"Test",SRC,123L,true, + TypeInteger.BOT) + .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); + TypeStruct ts=(TypeStruct)root(code).type; + Path driver=dir.resolve("driver.c"); + Files.writeString(driver,""" + #include <stdint.h> + #include <string.h> + extern unsigned char data[] __asm__("Test.$class"); + typedef int64_t (__attribute__((CALL_CONV)) *fun)(int64_t); + int main(void) { + int64_t n; uint32_t f; + memcpy(&n,data+%d,sizeof(n)); + memcpy(&f,data+%d,sizeof(f)); + return n!=42 || ((fun)(uintptr_t)f)(7)!=49; + } + """.formatted(ts.offset(ts.find("n")),ts.offset(ts.find("f")))); + String exe=dir.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString(); + TestC.linkExe(dir.resolve("Test.o").toString(), + TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi", + driver.toString(),null,exe); + assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe)); + } + + // Two separately compiled users must reference the same mutable class object. + @Test public void testSharedClass() throws Exception { + Path dir=Path.of("build/objs/static-data-shared"); + Files.createDirectories(dir); + Path lib=dir.resolve("lib"); + Files.createDirectories(lib); + var paths=new Ary<>(new String[]{lib.toString()}); + String[] names={"Globals","A","B"}; + String[] sources={"int count=0;", + "val a={ -> Globals; };", "val b={ -> Globals; };"}; + for( int i=0; i<names.length; i++ ) + new CodeGen(null,(i==0?lib:dir).toString(),i==0?null:paths,names[i],sources[i],123L,true,TypeInteger.BOT) + .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); + Path driver=dir.resolve("driver.c"); + Files.writeString(driver,""" + #include <stdint.h> + __attribute__((CALL_CONV)) extern int64_t *a(void),*b(void); + int main(void) { + int64_t *x=a(),*y=b(); + if(x!=y) return 1; + *x=37; + return *y!=37; + } + """); + String exe=dir.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString(); + TestC.linkExe(lib.resolve("Globals.o").toString(), + TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi", + driver.toString(),new Ary<>(new String[]{dir+"/A.o",dir+"/B.o"}),exe); + assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe)); + } +} diff --git a/src/test/java/com/seaofnodes/simple/TestArm64.java b/src/test/java/com/seaofnodes/simple/TestArm64.java index 086bf9c7..f1089cf0 100644 --- a/src/test/java/com/seaofnodes/simple/TestArm64.java +++ b/src/test/java/com/seaofnodes/simple/TestArm64.java @@ -3,13 +3,10 @@ package com.seaofnodes.simple; import com.seaofnodes.isa.eval.EvalArm64; import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; - - 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; @@ -17,35 +14,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 CheckedCodeGen(src).driver("arm", "SystemV",null); - SpillStats.recordNative(code,"arm","SystemV"); - if( print ) { code.print_as_hex(); System.out.print(code.asm()); } + CodeGen code = new CheckedCodeGen(src).driver("arm", "SystemV",true,false); // Allocation quality not degraded - SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + int delta = spills>>3; + if( delta == 0 ) delta =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) 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 3b3aabc0..b08be824 100644 --- a/src/test/java/com/seaofnodes/simple/TestC.java +++ b/src/test/java/com/seaofnodes/simple/TestC.java @@ -5,6 +5,8 @@ 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; @@ -14,93 +16,171 @@ import static org.junit.Assert.*; // Call a C driver program, which calls a Simple-generated .o, and self-checks. // Expects GCC from the default CLI, compiles C, compiles Simple, links and runs. public abstract class TestC { + public static final int TEST_TIMEOUT_SECONDS = 60; 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 final String CPU_ABI = CPU_PORT+"_"+CALL_CONVENTION; + public static final String RELEASE_SYS_DIR = "build/release/"+CPU_ABI; - 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 String runtimeObject() { + String home = System.getenv("SIMPLE_HOME"); + String root = home==null || home.isEmpty() ? "build/release" : home; + return Path.of(root,CPU_ABI,"simple_crt.o").toString(); } - // 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; + public static final String C_DRIVERS_DIR = "src/test/java/com/seaofnodes/simple/progs/"; - // 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 + * - 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); } - // 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. + * - 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); } - public static void run(String dir, String file, String expected, int spills ) throws IOException { - run(dir, file,null, expected, spills, false); + /** + * 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<String> externPaths = new Ary<>(new String[]{RELEASE_SYS_DIR}); + run(src, base, externPaths, CALL_CONVENTION, null,null, expected,spills); } - // 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<String> 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 runArgs( String src, String base, Ary<String> externPaths, String simple_conv, + String expected, int spills, String... programArgs ) throws IOException { + run0(src,base,externPaths,simple_conv,null,null,null,expected,spills,programArgs); } - 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 base, Ary<String> externPaths, String simple_conv, String c_conv, String cfile, String stdin, String expected, int spills ) throws IOException { + run0(src,base,externPaths,simple_conv,c_conv,cfile,stdin,expected,spills,new String[0]); } + private static void run0( String src, String base, Ary<String> externPaths, String simple_conv, String c_conv, + String cfile, String stdin, String expected, int spills, String[] programArgs ) throws IOException { + String exe = compile(src,base,externPaths,simple_conv,c_conv,cfile,spills); - 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; + String[] execArgs = new String[programArgs.length+1]; + execArgs[0] = exe; + System.arraycopy(programArgs,0,execArgs,1,programArgs.length); + String result = execStdin(stdin, 0, execArgs ); + assertEquals(expected,result); + } + + public static String compile( String src, String base, Ary<String> externPaths, String simple_conv, String c_conv, + String cfile, 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 CheckedCodeGen(src, arg).driver( CPU_PORT, simple_conv, obj); - SpillStats.recordNative(code,CPU_PORT,simple_conv); + CodeGen code = new CheckedCodeGen(null,"build/objs",externPaths,base,src,126L,true,TypeInteger.BOT); + code.driver( CPU_PORT, simple_conv, false, cfile==null ); - String result = gcc(obj, c_conv, cfile, false, exe ); - assertEquals(expected,result); + linkExe(obj, c_conv, cfile, linkObjs(externPaths), exe); // Allocation quality not degraded SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + return 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 main, double ignore, String... objs ) throws IOException { + String BLDDIR = "build/objs/test0/"; + String exe = BLDDIR+main+(OS.startsWith("Windows") ? ".exe" : ""); + linkExe(objs[0],null,null,null,exe); + // Module tests intentionally use the program exit code as their result. + ExecResult result = execute(null,0,exe); + return result.exit==0 ? result.out : "exec exit code: "+result.exit; + } + + // 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<String> linkObjs, String... args ) throws IOException { + linkExe(obj,c_conv,cfile,linkObjs,args[0]); + return execStdin(stdin,0,args); + } + public static void linkExe( String obj, String c_conv, String cfile, Ary<String> linkObjs, String exe ) 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 // high bits are zero - and Simple code can be emitted treating // pointers as 4bytes. var params = new Ary<>(String.class); params.add("gcc"); + params.add(OS.startsWith("Windows") ? "-Wl,--image-base,0x400000" : "-no-pie"); if( cfile!=null ) params.add(cfile); // Associated C driver, usually has a `main` + params.add(obj); + // Standalone Simple programs export `simple_main`; the small C + // runtime owns the native main symbol and converts argc/argv into a + // normal Simple array before making the ABI-qualified call. + if( cfile==null ) params.add(runtimeObject()); + 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", + exe, }); // Calling convention for C calls, if any if( cfile!=null ) { @@ -109,29 +189,85 @@ public abstract class TestC { } // Run GCC to link (optionally compile C driver code) - Process gcc = new ProcessBuilder(params.asAry()).redirectErrorStream(true).start(); - int exit; - try { - boolean normal = gcc.waitFor(2, TimeUnit.SECONDS); - exit = normal ? gcc.exitValue() : -1; // no exit??? - } catch( InterruptedException e ) { - throw new IOException("interrupted"); - } - String result = new String(gcc.getInputStream().readAllBytes()); - if( exit!=0 ) { - System.err.println("gcc error code: "+exit); - System.err.println(result); - } - assertEquals( 0, exit ); - //assertTrue(result.isEmpty()); // No data in error stream + exec(TEST_TIMEOUT_SECONDS,params.asAry()); + } + + public static String exec( String... args ) throws IOException { + return execStdin(null,0,args); + } + + // Tests can bound execution without imposing a deadline on the CLI driver. + public static String exec( int seconds, String... args ) throws IOException { + return execStdin(null,seconds,args); + } + private static String execStdin( String stdin, int seconds, String... args ) throws IOException { + ExecResult result = execute(stdin,seconds,args); + if( result.exit!=0 ) + throw new IOException(String.join(" ",args)+"\nexec exit code: "+result.exit+ + "\nstdout:\n"+result.out+"\nstderr:\n"+result.err); + return result.out; + } + + private record ExecResult(int exit, String out, String err) {} + + private static ExecResult execute( String stdin, int seconds, String... args ) throws IOException { // Execute results ProcessBuilder smp = new ProcessBuilder(args); - if( stdin ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT); - Process p = smp.start(); - try { exit = p.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); } - result = new String(p.getInputStream().readAllBytes()); - assertEquals("Program exit status",0,exit); - return result; + Path stdinFile = null; + Path stdoutFile = Files.createTempFile("simple-stdout",".txt"); + Path stderrFile = Files.createTempFile("simple-stderr",".txt"); + Process p = null; + try { + smp.redirectOutput(stdoutFile.toFile()); + smp.redirectError(stderrFile.toFile()); + if( stdin!=null && stdin.isEmpty() ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT); + else if( stdin!=null ) { + stdinFile = Files.createTempFile("simple-stdin",".txt"); + Files.writeString(stdinFile,stdin); + smp.redirectInput(stdinFile.toFile()); + } + p = smp.start(); + if( stdin==null ) + p.getOutputStream().close(); + if( seconds>0 && !p.waitFor(seconds,TimeUnit.SECONDS) ) { + String process = "pid="+p.pid()+", cpu="+ + p.info().totalCpuDuration().map(Object::toString).orElse("unknown"); + p.destroyForcibly(); + boolean stopped = p.waitFor(5,TimeUnit.SECONDS); + throw new IOException(String.join(" ",args)+"\ntimed out after "+seconds+" seconds ("+process+")"+ + (stopped ? "" : "; process did not terminate")+ + "\nstdout:\n"+Files.readString(stdoutFile)+ + "\nstderr:\n"+Files.readString(stderrFile)); + } + // Windows statuses have 32 bits; narrowing can turn a failure into 0. + int exit = seconds>0 ? p.exitValue() : p.waitFor(); + return new ExecResult(exit,Files.readString(stdoutFile),Files.readString(stderrFile)); + } catch( InterruptedException e ) { + if( p!=null ) p.destroyForcibly(); + Thread.currentThread().interrupt(); + throw new IOException("interrupted",e); + } finally { + if( stdinFile!=null ) Files.deleteIfExists(stdinFile); + Files.deleteIfExists(stdoutFile); + Files.deleteIfExists(stderrFile); + } + } + + static Ary<String> linkObjs(Ary<String> externPaths) { + if( externPaths == null ) + return null; + Ary<String> 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 f55e7d0b..10c75d3f 100644 --- a/src/test/java/com/seaofnodes/simple/TestRisc5.java +++ b/src/test/java/com/seaofnodes/simple/TestRisc5.java @@ -4,54 +4,52 @@ import com.seaofnodes.isa.eval.EvalRisc5; import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; 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 CheckedCodeGen(src).driver("riscv", "SystemV",null); - SpillStats.recordNative(code,"riscv","SystemV"); - if( print ) { code.print_as_hex(); System.out.print(code.asm()); } + CodeGen code = new CheckedCodeGen(src).driver("riscv", "SystemV",true,false); // Allocation quality not degraded - SpillStats.checkSpills(spills,code._regAlloc._spillScaled); + int delta = spills>>3; + if( delta==0 ) delta = 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 fd3fea9d..f6099607 100644 --- a/src/test/java/com/seaofnodes/simple/TypeTest.java +++ b/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -27,11 +27,11 @@ public class TypeTest { @Test public void testCyclicLeafKinds() { TypeStruct bottom = TypeStruct.make("B14LeafKinds",false, - Field.make("x",Type.BOTTOM,2,false, false)); + Field.make("x",Type.BOTTOM,2,false)); TypeStruct one = TypeStruct.make("B14LeafKinds",false, - Field.make("x",TypeFloat.constant(1.0),2,false, false)); + Field.make("x",TypeFloat.constant(1.0),2,false)); TypeStruct two = TypeStruct.make("B14LeafKinds",false, - Field.make("x",TypeFloat.constant(2.0),2,false, false)); + Field.make("x",TypeFloat.constant(2.0),2,false)); // Meeting creates a new struct while cyclic equality is active. Its // float leaf must compare unequal to the previously interned BOTTOM. @@ -39,29 +39,25 @@ public class TypeTest { assertSame(TypeFloat.F32,meet._fields[0]._t); assertSame(meet,two.meet(one)); assertSame(meet,TypeStruct.make("B14LeafKinds",false, - Field.make("x",TypeFloat.F32,2,false, false))); + Field.make("x",TypeFloat.F32,2,false))); assertSame(bottom,meet.meet(bottom)); assertSame(meet.dual(),one.dual().join(two.dual())); } - // Test basic properties and GLB + // Test basic type properties @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) ); - TypeStruct x1ro = (TypeStruct)s1.makeRO(); + Field.make("a", TypeInteger.BOT,-3, false), + Field.make("b", TypeInteger.BOT,-4, false) ); TypeMemPtr p1 = TypeMemPtr.make(s1); - 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)); TypeMem m1 = TypeMem.make(2,TypeNil.NIL); TypeMem m2 = TypeMem.make(3,TypeInteger.U16); @@ -73,13 +69,12 @@ 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(m4.dual(), m4.glb(false).dual()); + Assert.assertEquals(TypeMem.make(2,Type.XNIL,true,true,true, XInt.FULL, XInt.FULL), m1.dual()); TypeMemPtr ptr1 = TypeMemPtr.make(s1); Assert.assertEquals(s1, ptr1._obj); @@ -94,15 +89,8 @@ public class TypeTest { Assert.assertEquals(s2, ptr2nil._obj); Assert.assertNotEquals(ptr1, ptr2); - Type p1glb = ptr1.glb(false); - Assert.assertNotEquals(ptr1, p1glb ); - Assert.assertTrue(((TypeMemPtr)p1glb)._ro); - Assert.assertTrue(ptr1nil.isa(p1glb)); - Assert.assertEquals(ptr1, ptr1.dual().dual()); - Assert.assertTrue(p1glb.makeRO().isa( ptr1.dual().glb(false))); Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT).withAccess(false), ptr1.meet(ptr2nil)); - Assert.assertEquals(ptr1nil, ptr1.meet(TypeNil.NIL)); TypeMemPtr TOP = TypeMemPtr.TOP; TypeMemPtr BOT = TypeMemPtr.makeNullable(TypeStruct.BOT); @@ -120,7 +108,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(); @@ -128,9 +116,6 @@ public class TypeTest { Assert.assertTrue(s1ro.isFinal()); Assert.assertFalse(S1.isConstant()); - Type s1glb = S1.field("s2")._t.glb(false); - Type s1glb2 = S1.field("s2")._t.glb(false); - Assert.assertSame(s1glb,s1glb2); } // Test theoretical properties. @@ -163,7 +148,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); } } @@ -189,8 +174,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 ) { @@ -198,31 +183,23 @@ 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); - } - - @Test - public void testGLB() { - Type[] ts = Type.gather(); - for( Type t0 : ts ) - if( !(t0 instanceof Field || t0 instanceof TypeStruct || t0 instanceof TypeTuple || t0 instanceof TypeConAry ) ) - Assert.assertTrue(t0.isa(t0.glb(false))); + assertSame(TypeStruct.SFLT1,d1); } @Test 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 @@ -230,6 +207,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/codegen/PrintRegTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java index b9b49c9f..b255bcf7 100644 --- a/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java +++ b/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java @@ -7,9 +7,9 @@ public class PrintRegTestSupport { var code = new CodeGen("return 0;").parse(); var alloc = new RegAlloc(code); var n = new ConstantNode(TypeInteger.ZERO); - var a = new LRG((short)1); - var b = new LRG((short)2); - var c = new LRG((short)3); + var a = new LRG((short)1,null); + var b = new LRG((short)2,null); + var c = new LRG((short)3,null); a._reg = -1; c._leader = b; b._leader = a; @@ -23,4 +23,54 @@ public class PrintRegTestSupport { assertSame(b,c._leader); assertSame(a,b._leader); } + public static void checkConstantPool() throws Exception { + var code = new CodeGen("return 0;").parse(); + code._phase = CodeGen.Phase.Encoding; + var enc = new Encoding(code); + var t = com.seaofnodes.simple.type.TypeStruct.make("PrintPool",false, + com.seaofnodes.simple.type.Field.make("x",TypeInteger.ZERO,2,true)); + var n = new ConstantNode(TypeInteger.ZERO); + enc.largeConstant(n,t,0,2); + int size = t.size(); // Encoding lays out data before printing the pool. + for( int i=0; i<size; i++ ) enc._cpool.write(0); + // Layout queries are forbidden during printing, even if they would + // happen to return a cached answer without changing the type. + boolean[] printing = {false}; + var scalar = new com.seaofnodes.simple.type.Type(com.seaofnodes.simple.type.Type.BOTTOM._type) { + @Override public int alignment() { + assertFalse("alignment queried by printer",printing[0]); + return 0; + } + @Override public int size() { + assertFalse("size queried by printer",printing[0]); + return 1; + } + @Override public String str() { return "LayoutProbe"; } + }; + var scalarNode = new ConstantNode(TypeInteger.TRUE); + enc._bigCons.put(scalarNode,new Encoding.Relo(scalarNode,scalar,(byte)0,(byte)2)); + enc._cpool.write(0); + printing[0] = true; + var offsets = com.seaofnodes.simple.type.TypeStruct.class.getDeclaredField("_offs"); + offsets.setAccessible(true); + offsets.set(t,null); // Printer must not lazily rebuild the cached layout. + var field = com.seaofnodes.simple.type.Type.class.getDeclaredField("VISIT"); + field.setAccessible(true); + var visit = (java.util.Map<Object,Object>)field.get(null); + var marker = new Object(); + visit.put(marker,t); + var method = com.seaofnodes.simple.print.ASMPrinter.class.getDeclaredMethod( + "printConstantPool",int.class,com.seaofnodes.simple.util.SB.class, + com.seaofnodes.simple.util.BAOS.class,java.util.HashMap.class,boolean.class,String.class); + method.setAccessible(true); + try { + var sb = new com.seaofnodes.simple.util.SB(); + method.invoke(null,0,sb,enc._cpool,enc._bigCons,true,"Constant Pool"); + assertTrue(sb.toString().contains("PrintPool")); + assertTrue(sb.toString().contains("LayoutProbe")); + assertNull(offsets.get(t)); + assertSame(t,visit.get(marker)); + assertEquals(1,visit.size()); + } finally { visit.clear(); } + } } diff --git a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index 1ffdb3d0..93380c87 100644 --- a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -33,8 +33,8 @@ public class RegAllocTestSupport { CodeGen code = graph(); RegAlloc alloc = new RegAlloc(code); Op def = new Op(new RegMask(3L),null,null,false,code._start); - LRG lrg = alloc.newLRG(def); - LRG neighbor = new LRG((short)99); + LRG lrg = alloc.newLRG(def,null); + LRG neighbor = new LRG((short)99,null); neighbor._mask = A; lrg.addNeighbor(neighbor); // Uncolored, but its only possible color is A. var bias = IFG.class.getDeclaredMethod("biasColorNeighbors",RegAlloc.class,Node.class,RegMask.class); @@ -59,7 +59,7 @@ public class RegAllocTestSupport { public static void union() { CodeGen code = graph(); Op use = new Op(null,B,null,false,code._start); - LRG a = new LRG((short)1), b = new LRG((short)2); + LRG a = new LRG((short)1,null), b = new LRG((short)2,null); a._mask = new RegMask(3L); b._mask = new RegMask(6L); b.machUse(use,(short)2,true); @@ -113,7 +113,7 @@ public class RegAllocTestSupport { @Override public boolean commutes() { return true; } }; RegionNode region = new RegionNode(null,code._start,code._start); - new PhiNode("sum",TypeInteger.BOT,region,add,a); + new PhiNode("sum",region,add,a); code._cfg.add(region); assertTrue(BuildLRG.run(0,new RegAlloc(code))); } @@ -131,7 +131,7 @@ public class RegAllocTestSupport { RegAlloc alloc = new RegAlloc(code); RegionNode block = new RegionNode(null,code._start,code._start); Op def = new Op(new RegMask(7L),null,null,false,block); - LRG lrg = alloc.newLRG(def); + LRG lrg = alloc.newLRG(def,null); lrg._machDef = def; lrg._mask = new RegMask(0L); Op ab = new Op(null,new RegMask(3L),null,false,block,def); @@ -163,10 +163,14 @@ public class RegAllocTestSupport { // Check the scheduled graph before encoding adds untyped branches or rewrites tail calls. public static class CheckedCodeGen extends CodeGen { public CheckedCodeGen(String src) { super(src); } - public CheckedCodeGen(String src, TypeInteger arg) { super(src,arg); } + public CheckedCodeGen(String modDir, String buildDir, com.seaofnodes.simple.util.Ary<String> externPaths, + String srcName, String src, long seed, boolean reset, TypeInteger arg) { + super(modDir,buildDir,externPaths,srcName,src,seed,reset,arg); + } @Override public CodeGen regAlloc() { super.regAlloc(); checkRegisters(this); + com.seaofnodes.simple.SpillStats.record25(this); return this; } } @@ -180,7 +184,7 @@ public class RegAllocTestSupport { Op def = new Op(A,null,null,false,code._start); Node top = new ConstantNode(com.seaofnodes.simple.type.Type.TOP); RegionNode region = new RegionNode(null,code._start,code._start); - PhiNode phi = new PhiNode("value",TypeInteger.BOT,region,slot==1 ? top : def,slot==2 ? top : def); + PhiNode phi = new PhiNode("value",region,slot==1 ? top : def,slot==2 ? top : def); phi._type = TypeInteger.BOT; new Op(null,A,null,false,region,phi); code._cfg.add(region); @@ -220,14 +224,14 @@ public class RegAllocTestSupport { CodeGen code = graph(); RegAlloc alloc = new RegAlloc(code); Op def = new Op(A,null,null,false,code._start); - alloc.newLRG(def)._reg=0; - SplitNode hi = code._mach.split("test",(byte)0,null); + alloc.newLRG(def,null)._reg=0; + SplitNode hi = code._mach.split(null,"test",(byte)0); hi.setDef(0,code._start); hi.setDef(1,def); - alloc.newLRG(hi)._reg=1; + alloc.newLRG(hi,null)._reg=1; new Op(null,null,killed ? A : null,false,code._start); - SplitNode lo = code._mach.split("test",(byte)0,null); + SplitNode lo = code._mach.split(null,"test",(byte)0); lo.setDef(0,code._start); lo.setDef(1,hi); - alloc.newLRG(lo)._reg=0; + alloc.newLRG(lo,null)._reg=0; Op use = new Op(null,A,null,false,code._start,lo); var post = RegAlloc.class.getDeclaredMethod("postColor"); post.setAccessible(true); post.invoke(alloc); @@ -236,10 +240,10 @@ public class RegAllocTestSupport { CodeGen pre = graph(); RegAlloc uncolored = new RegAlloc(pre); Op def0 = new Op(A,null,null,false,pre._start); - uncolored.newLRG(def0)._mask=A; + uncolored.newLRG(def0,null)._mask=A; Op fixed = new Op(A,null,null,false,pre._start); - uncolored.newLRG(fixed)._mask=A; - SplitNode copy = pre._mach.split("test",(byte)0,null); + uncolored.newLRG(fixed,null)._mask=A; + SplitNode copy = pre._mach.split(null,"test",(byte)0); copy.setDef(0,pre._start); copy.setDef(1,def0); assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy)); } @@ -249,13 +253,14 @@ public class RegAllocTestSupport { CodeGen code = graph(); RegAlloc alloc = new RegAlloc(code); Op init = new Op(A,null,null,false,code._start); - LoopNode loop = new LoopNode(null,code._start); - PhiNode phi = new PhiNode("loop",TypeInteger.BOT,loop,init,null); + LoopNode loop = new LoopNode(null,null,code._start); + PhiNode phi = new PhiNode("loop",loop,init,null); + phi._type = TypeInteger.BOT; Op back = new Op(A,A,null,false,loop,phi); phi.setDef(2,back); Op use = new Op(null,A,null,false,loop,phi); loop.setDef(2,loop); - LRG lrg = alloc.newLRG(phi); + LRG lrg = alloc.newLRG(phi,null); lrg._mask = A; alloc.union(lrg,init); alloc.union(lrg,back); lrg.selfConflict(back); @@ -278,10 +283,10 @@ public class RegAllocTestSupport { CodeGen code = graph(); RegAlloc alloc = new RegAlloc(code); Op def = new Op(A,null,null,false,code._start); - alloc.newLRG(def)._reg=0; - SplitNode copy = code._mach.split("test",(byte)0,null); + alloc.newLRG(def,null)._reg=0; + SplitNode copy = code._mach.split(null,"test",(byte)0); copy.setDef(0,code._start); copy.setDef(1,def); - alloc.newLRG(copy)._reg=1; + alloc.newLRG(copy,null)._reg=1; if( kind==3 ) new Op(null,null,A,false,code._start); int twoAddress = kind==2 ? 1 : 0; RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); @@ -302,15 +307,15 @@ public class RegAllocTestSupport { CodeGen code = graph(); RegAlloc alloc = new RegAlloc(code); Op def = new Op(new RegMask(3L),null,null,false,code._start); - SplitNode copy = code._mach.split("test",(byte)0,null); + SplitNode copy = code._mach.split(null,"test",(byte)0); copy.setDef(0,code._start); copy.setDef(1,def); Op use = new Op(null,new RegMask(3L),null,false,code._start,copy); - LRG from = alloc.newLRG(def), to = alloc.newLRG(copy); + LRG from = alloc.newLRG(def,null), to = alloc.newLRG(copy,null); from._mask = kind==1 ? A : new RegMask(3L); to._mask = kind==1 ? B : new RegMask(3L); from.machDef(def,false); from.machUse(copy,(short)1,false); to.machDef(copy,false); to.machUse(use,(short)1,false); - LRG left = new LRG((short)100), right = new LRG((short)101); + LRG left = new LRG((short)100,null), right = new LRG((short)101,null); if( kind==2 ) { from.addNeighbor(to); to.addNeighbor(from); } if( kind>=3 ) { from.addNeighbor(left); left.addNeighbor(from); diff --git a/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java b/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java deleted file mode 100644 index 0914d916..00000000 --- a/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.cpus.x86_64_v2.CmpMemX86; -import com.seaofnodes.simple.node.cpus.x86_64_v2.NotX86; -import com.seaofnodes.simple.node.cpus.x86_64_v2.MulIX86; -import org.junit.Test; -import static org.junit.Assert.*; - -public class X86EncodingTest { - - @Test - public void testLogicalNotRegisters() { - CodeGen code = new CodeGen("return !arg;") - .driver(CodeGen.Phase.Select, "x86_64_v2", "SystemV"); - var not = code._stop.walk(n -> n instanceof NotX86 ? (NotX86)n : null); - assertNotNull("Must exercise logical not", not); - int[][] registers = {{0,0}, {1,1}, {7,7}, {9,9}, {1,9}, {9,1}, {6,0}, {15,8}}; - // TEST src,src; SETZ dstb; MOVZX dst32,dstb. Include overlapping - // registers, low-byte registers requiring REX, and extended registers. - String[] expected = { - "4885c00f94c00fb6c0", "4885c90f94c10fb6c9", - "4885ff400f94c7400fb6ff", "4d85c9410f94c1450fb6c9", - "4d85c90f94c10fb6c9", "4885c9410f94c1450fb6c9", - "4885c0400f94c6400fb6f6", "4d85c0410f94c7450fb6ff" - }; - for (int i = 0; i < registers.length; i++) { - int dst = registers[i][0], src = registers[i][1]; - var enc = new FixedRegisterEncoding(code, not, dst, not.in(1), src); - not.encoding(enc); - assertArrayEquals("Logical not dst=" + dst + ", src=" + src, - java.util.HexFormat.of().parseHex(expected[i]), enc._bits.toByteArray()); - } - } - - @Test - public void testLogicalNotExecution() throws java.io.IOException { - var dir = java.nio.file.Path.of("build/objs"); - java.nio.file.Files.createDirectories(dir); - java.nio.file.Files.writeString(dir.resolve("x86LogicalNot.smp"), - "val logicalNot = { int x -> return !x; };"); - java.nio.file.Files.writeString(dir.resolve("x86LogicalNot.c"), """ - #include <limits.h> - extern long long logicalNot(long long); - int main(void) { - long long inputs[] = {0, 1, -1, LLONG_MIN, LLONG_MAX, 1LL << 32}; - for (unsigned i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++) - if (logicalNot(inputs[i]) != (inputs[i] == 0)) return 1; - return 0; - } - """); - com.seaofnodes.simple.TestC.run("build/objs", "x86LogicalNot", "", -1); - } - - @Test - public void testMultiplyImmediateRegisters() { - // Destination, source, expected REX and ModRM. Cross the register-8 - // boundary in both directions, with low/low and high/high controls. - int[][] cases = {{1, 9, 0x49, 0xC9}, {9, 1, 0x4C, 0xC9}, - {1, 2, 0x48, 0xCA}, {9, 10, 0x4D, 0xCA}}; - for (int immediate : new int[]{11, -11, 123456789, -123456789}) { - CodeGen code = new CodeGen("return arg * " + immediate + ";") - .driver(CodeGen.Phase.Select, "x86_64_v2", "SystemV"); - var mul = code._stop.walk(n -> n instanceof MulIX86 ? n : null); - assertNotNull("Must exercise immediate multiply", mul); - for (int[] regs : cases) { - var enc = new FixedRegisterEncoding( - code, mul, regs[0], mul.in(1), regs[1]); - ((MulIX86)mul).encoding(enc); - boolean small = immediate == 11 || immediate == -11; - assertEquals("REX for dst=" + regs[0] + ", src=" + regs[1], regs[2], enc.read1(0)); - assertEquals(small ? 0x6B : 0x69, enc.read1(1)); - assertEquals(regs[3], enc.read1(2)); - assertEquals(small ? 4 : 7, enc._bits.size()); - assertEquals(immediate, small ? (byte)enc.read1(3) : enc.read4(3)); - } - } - } - @Test - public void testMemoryCompare64Immediate32() { - for (int immediate : new int[]{123456789, -123456789}) { - String src = "int[] !a = new int[2]; a[0] = arg; return a[arg & 1] == " + immediate + ";"; - CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding, "x86_64_v2", "SystemV"); - var cmp = code._stop.walk(n -> n instanceof CmpMemX86 ? n : null); - assertNotNull("Must exercise a memory comparison", cmp); - var enc = code._encoding; - int start = enc._opStart[cmp._nid]; - - // REX.W, 81 /7, SIB with scale 8, disp8, and a sign-extended imm32. - assertEquals(0x48, enc.read1(start) & 0xF8); - assertEquals(0x81, enc.read1(start + 1)); - assertEquals(0x7C, enc.read1(start + 2)); - assertEquals(0xC0, enc.read1(start + 3) & 0xC0); - assertEquals(8, enc.read1(start + 4)); - assertEquals(immediate, enc.read4(start + 5)); - assertEquals("CMP r/m64 uses four immediate bytes", 9, enc._opLen[cmp._nid]); - - // SETE must immediately follow the CPU's nine-byte CMP instruction. - assertEquals(0x0F, enc.read1(start + 9)); - assertEquals(0x94, enc.read1(start + 10)); - } - } - - /** Exercise instruction encoders with explicit registers, independent of allocator choices. */ - private static class FixedRegisterEncoding extends Encoding { - private final Node _dst, _src; - private final short _dstReg, _srcReg; - - FixedRegisterEncoding(CodeGen code, Node dst, int dstReg, Node src, int srcReg) { - super(code); - _dst = dst; - _src = src; - _dstReg = (short)dstReg; - _srcReg = (short)srcReg; - } - - @Override public short reg(Node n) { - if (n == _dst) return _dstReg; - if (n == _src) return _srcReg; - throw new AssertionError("Unexpected register lookup"); - } - } -} 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 20aac367..00000000 --- a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ /dev/null @@ -1,380 +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<Obj, Integer> 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<Obj, Integer> 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<Obj, Integer> 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<fields.length; i++) { - var v = fields[i]; - if (v == null) v = 0; - assert v instanceof Number; - var n = ((Number)v).byteValue() & 0xFF; - if (n >= 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; i++) { - sb.append(nextIndent).append(struct._fields[i]._fname).append("="); - id = p(sb, objs, id, nextIndent, step, sep, fields[i]); - sb.append(sep); - } - sb.append(nextIndent).append(struct._fields[e]._fname).append("="); - if (struct.isAry()) { - sb.append("["); - if (fields.length > 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<Obj, Integer>(); - 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<fields.length; i++) { - var v = fields[i]; - if (v == null) v = 0; - assert v instanceof Number; - var n = ((Number)v).byteValue() & 0xFF; - sb.append((char)n); - } - return sb.toString(); - } - return p(new StringBuilder(), "", "", ",").toString(); - } - public String pretty() { - return p(new StringBuilder(), "\n", " ", "").toString(); - } - } - - private static int getFieldIndex(TypeStruct struct, MemOpNode memop, long off) { - int idx = struct.find(memop._name); - if( idx >= 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<Object> 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.size()); - 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<n; i++) body[i+1] = 0L; - } else if (elem instanceof TypeFloat) { - for (int i = 0; i < n; i++) body[i+1] = 0D; - } else { - assert elem instanceof TypeMemPtr; - } - // Length value - body[0] = n; - } else { - body = new Object[num = type._fields.length]; - for( int i=0; i<num; i++ ) - body[i] = switch( alloc._ptr._obj._fields[i]._t ) { - case TypeInteger ti -> 0L; - case TypeFloat tf -> 0D; - default -> null; - }; - } - return new Object[]{new Obj(type,body),null}; - } - - 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 RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); - case ToIntegerNode cvt -> (long)vald(cvt.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<block.nodes().length;i++) values[block.nodes()[i]._nid] = exec(block.nodes()[i]); - i=0; - switch (block.exit()) { - case null: - return Status.FALLTHROUGH; - case ReturnNode ret : return val(ret .expr()); - case IfNode ifn: - block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; - if (block == null) return Status.FALLTHROUGH; - break; - case RegionNode region: - if (loops-- <= 0) return Status.TIMEOUT; - int exit = block.exitId(); - assert exit > 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<phiCache.size(); i++) { - values[block.nodes()[i]._nid] = phiCache.get(i); - } - phiCache.clear(); - break; - default: - throw Utils.TODO("Unexpected control node " + block.exit()); - } - } - } - - public static Object evaluate(StopNode graph) { - return evaluate(graph, 0); - } - - public static Object evaluate(StopNode graph, long parameter) { - return evaluate(graph, parameter, 1000); - } - - public static Object evaluate(StopNode graph, long parameter, int loops) { - var res = evaluateWithResult(graph, parameter, loops); - if (res == Status.TIMEOUT) throw new RuntimeException("Timeout"); - return res; - } - - public static Object evaluateWithResult(StopNode graph, long parameter, int loops) { - return new Evaluator(graph).evaluate(parameter, loops); - } - -} diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java deleted file mode 100644 index c837bd6f..00000000 --- a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ /dev/null @@ -1,605 +0,0 @@ -package com.seaofnodes.simple.evaluator; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Utils; -import java.util.*; - -/** - * A late scheduler for test cases. - * <br> - * <br> - * This scheduler is used for tests and is not the final scheduler. - * <br> - * <br> - * 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. - * <br> - * 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 <code>if</code> 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. - * <br> - * <br> - * 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 <code>a.depth < b.depth</code> 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<Node> 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<Node, NodeData> data = new IdentityHashMap<>(); - /** - * List of nodes which can be placed since all users are placed. - */ - private final Stack<NodeData> 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 - * <code>Optional.empty()</code> for <code>null</code> or dead nodes. - */ - Optional<NodeData> 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 <code>a</code> and <code>b</code> - * @param a Block a - * @param b Block b - * @return The dominator of <code>a</code> and <code>b</code> - */ - 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<b.prev.length-1; i++) { - if (isBefore(a, b.prev[i])) return true; - } - b = b.prev[b.prev.length-1]; - } - return a == b; - } - - /** - * Is node placed during the control flow graph build. - * @param data Node to check. - * @return true if node is placed during the control flow graph build. - */ - private static boolean isPinnedNode(NodeData data) { - return data.node instanceof CFGNode || data.node instanceof PhiNode; - } - - /** - * Refine placement of a node. - * @param data The node to refine. - * @param block The block before the node should be scheduled. - */ - private void refinePlacement(NodeData data, BasicBlock block) { - assert !isPinnedNode(data); - data.block = data.block == null ? block : dom(data.block, block); - assert isValid(data); - } - - /** - * Refine placement if before is happening before data - * @param data The node for which the placement should be refined. - * @param before The node before which the data node should be placed. - */ - private void optionalRefinePlacement(NodeData data, Node before) { - od(before).ifPresent(d->{ - // 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; i<r.nIns(); i++) { - if (od(r.in(i)).map(d->d.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<NodeData> 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; i<phi.nIns(); i++) { - var in = phi.in(i); - od(r.in(i)).ifPresent(d->update(d(in), d.block)); - } - } - } - - /** - * Build the control flow graph. - * @param start The start node. - */ - private void doBuildCTF(StartNode start) { - var queue = new Stack<NodeData>(); - var phiQueue = new Stack<NodeData>(); - 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<BasicBlock>(); - int depth = 0; - for(int i=1; i<r.nIns(); i++) { - if( !od(r.in(i)).isPresent() ) continue; - BasicBlock bb = d(r.in(i)).block; - prev.add(bb); - depth = Math.max(depth,bb.depth); - } - yield new BasicBlock(r, depth+1, prev.toArray(BasicBlock[]::new)); - } - case IfNode 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<NodeData> 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<NodeData>(); - var dataQueue = new Stack<NodeData>(); - var mem = new Stack<NodeData>(); - 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; i<phi.nIns(); i++) { - if (od(r.in(i)).isPresent()) markAlive(dataQueue, phi.in(i), false); - } - } else { - for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) markAlive(dataQueue, in, false); - } - if (node instanceof MemOpNode s && !s._isLoad) mem.push(data); - } - // Handle store nodes and increase load with an anti-dep to the store. - while (!mem.isEmpty()) { - var data = mem.pop(); - node = data.node; - for(var out:node.in(1)._outputs) { - if (out instanceof MemOpNode ld && ld._isLoad) od(out).ifPresent(d->d.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<Node, Block>(); - var queue = new Stack<NodeData>(); - 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<Node>(); - 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 b29eb2c3..ec9e3910 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -85,7 +85,7 @@ public class Fuzzer { code1 = FuzzerUtils.parse(script, 123, true); } catch (RuntimeException e1) { try { - FuzzerUtils.parse(script, 456, false); + FuzzerUtils.parse(script, 456, true); } catch (RuntimeException e2) { if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) { if (!valid || e1.getClass() == Parser.ParseException.class) return; @@ -163,26 +163,30 @@ public class Fuzzer { check(sb.toString(), valid, seed); } - /** - * Check that no exceptions happened. - * @return true if no exception happened + * Run one seed as a regression test. Unlike the exploratory fuzzer path, + * this deliberately does not reduce on failure; old bad seeds should fail + * fast with the seed visible in the JUnit assertion. */ - public boolean noExceptions() { - return exceptions.isEmpty(); - } - - /** Run a fixed chapter-local seed without reducing or swallowing failures. */ public void fuzzPeepsRegression(long seed) { var rand = new Random(seed); var sb = new StringBuilder(); var valid = new ScriptGenerator(rand, sb, true).genProgram(); try { runCheck(sb.toString(), valid); - } catch (Throwable e) { - AssertionError ae = new AssertionError("Fuzzer regression seed failed: " + seed); + } catch( Throwable e ) { + AssertionError ae = new AssertionError("Fuzzer regression seed failed: "+seed); ae.initCause(e); throw ae; } } + + + /** + * Check that no exceptions happened. + * @return true if no exception happened + */ + public boolean noExceptions() { + return exceptions.isEmpty(); + } } diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index 45558541..9fe2e405 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 and optimize using the given worklist seed */ - public static CodeGen parse(String script, long workListSeed, boolean reset) { + public static CodeGen parse(String script, long workListSeed, boolean resetTypes ) { 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, resetTypes); return code.driver(CodeGen.Phase.LocalSched); } finally { System.setErr(err); diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index 20815d5d..c5e94047 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -83,18 +83,11 @@ public class ScriptGenerator { } } - private static final List<TypeInt> INTTYPES = new ArrayList<>(); - static { - for( var e : Parser.TYPES.entrySet() ) { - if( e.getValue() instanceof TypeInteger ) { - INTTYPES.add(new TypeInt(e.getKey())); - } - } - } - - private static final Type TYPE_INT = INTTYPES.stream().filter(t->t.name.equals("int")).findAny().get(); - private static final Type TYPE_BOOL = INTTYPES.stream().filter(t->t.name.equals("bool")).findAny().get(); + private static final TypeInt TYPE_INT = new TypeInt("int"); + private static final TypeInt TYPE_BOOL = new TypeInt("bool"); + private static final TypeInt TYPE_U8 = new TypeInt("u8"); + private static final List<TypeInt> INTTYPES = new ArrayList<>(){{add(TYPE_INT); add(TYPE_BOOL); add(TYPE_U8); }}; private static class Variable { final String name; @@ -937,4 +930,4 @@ public class ScriptGenerator { else sb.append(v.name); } -}
\ No newline at end of file +} diff --git a/src/test/java/com/seaofnodes/simple/progs/extern_data.c b/src/test/java/com/seaofnodes/simple/progs/extern_data.c new file mode 100644 index 00000000..9325f65e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/extern_data.c @@ -0,0 +1,22 @@ +#include <stdint.h> +#include <stdio.h> + +int32_t counter=7; +int8_t small=-3; +double fraction=1.25; + +int64_t bump(void) { counter+=5; return counter; } +__attribute__((CALL_CONV)) extern int64_t test_global(void); + +int main(void) { + int64_t result=test_global(); + if(result!=71515 || counter!=15 || small!=-2 || fraction!=1.75) { + printf("result=%lld counter=%d small=%d fraction=%g\n", + (long long)result,counter,small,fraction); + return 1; + } + // A second call must observe C's new values, not a Simple initialization copy. + counter=-2; + result=test_global(); + return result!=-19394 || counter!=6 || small!=-1 || fraction!=2.25; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c b/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c new file mode 100644 index 00000000..f65e5c84 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c @@ -0,0 +1,14 @@ +#include <stdint.h> +#include <stdio.h> + +int32_t counter=7; +__attribute__((CALL_CONV)) extern int64_t test_global(void); + +int main(void) { + int64_t result=test_global(); + if(result!=710 || counter!=10) { + printf("result=%lld counter=%d\n",(long long)result,counter); + return 1; + } + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c b/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c new file mode 100644 index 00000000..cf9390d8 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c @@ -0,0 +1,25 @@ +#include <stdint.h> +#include <math.h> +#include <stdio.h> + +extern int64_t float_to_int(double); +extern int64_t float_to_u8(double); + +int main(void) { + double values[] = {0.0,-0.0,6.92,-6.92,0.99,-0.99,255.75,-257.75, + 0x1p63,0x1.fffffffffffffp62,-0x1p63,-0x1.0000000000001p63, + INFINITY,-INFINITY,NAN,-NAN}; + for( unsigned i=0; i<sizeof(values)/sizeof(values[0]); i++ ) { + double x=values[i]; + int64_t expected=isnan(x) ? 0 : x>=0x1p63 ? INT64_MAX + : x<=-0x1p63 ? INT64_MIN : (int64_t)x; + int64_t actual=float_to_int(x), narrow=float_to_u8(x); + if( actual!=expected || narrow!=(expected&255) ) { + fprintf(stderr,"conversion of %g: got %lld / %lld, expected %lld / %lld\n", + x,(long long)actual,(long long)narrow, + (long long)expected,(long long)(expected&255)); + return 1; + } + } + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/funptr.c b/src/test/java/com/seaofnodes/simple/progs/funptr.c deleted file mode 100644 index 2abe6e74..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/funptr.c +++ /dev/null @@ -1,15 +0,0 @@ -#include <assert.h> -#include <stdint.h> - -typedef int64_t (*Function)(void); -typedef int64_t (*Printer)(void *, void *); -extern Function factory(int64_t); -extern Printer printer(void); - -int main(void) { - assert(factory(1)() == 42); - assert(factory(0)() == 43); - struct { uint32_t length; char data[2]; } text = {2, {'o','k'}}; - printer()(0, &text); // Unused class receiver, then the byte array. - return 0; -} diff --git a/src/test/java/com/seaofnodes/simple/progs/jig.smp b/src/test/java/com/seaofnodes/simple/progs/jig.smp deleted file mode 100644 index 29987d8e..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/jig.smp +++ /dev/null @@ -1,16 +0,0 @@ -// -*- mode: java; -*- -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; -}; - -_fib(10); diff --git a/src/test/java/com/seaofnodes/simple/progs/person21.c b/src/test/java/com/seaofnodes/simple/progs/person21.c deleted file mode 100644 index df95e1ca..00000000 --- a/src/test/java/com/seaofnodes/simple/progs/person21.c +++ /dev/null @@ -1,34 +0,0 @@ -#include <stdio.h> -#include <stdbool.h> -#include <stdlib.h> - -typedef struct Person { - int age; -} Person; - -typedef struct PersonArray { - unsigned int length; - int pad; - Person* data[]; -} PersonArray; - -__attribute__ ((CALL_CONV)) -extern void fcn(PersonArray* people, int index); - -int main() { - Person base[5] = {{25},{1},{18},{45},{60}}; - unsigned char buf[8+8*5]; - PersonArray* people_array = (PersonArray*)buf; - people_array->length=5; - for (int i=0; i<people_array->length; i++) people_array->data[i] = &base[i]; - - fcn(people_array, 1); - fcn(people_array, 1); - fcn(people_array, 1); - fcn(people_array, 1); - fcn(people_array, 1); - - printf("%d\n", people_array->data[1]->age); // expected: 6 - - return 0; -}
\ No newline at end of file diff --git a/src/test/java/com/seaofnodes/simple/spill/BrainFuckTest-testBrainFuck.smp b/src/test/java/com/seaofnodes/simple/spill/BrainFuckTest-testBrainFuck.smp new file mode 100644 index 00000000..71af2b7a --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/BrainFuckTest-testBrainFuck.smp @@ -0,0 +1,46 @@ +// -*- mode: java; -*- +val brain_fuck = { -> + var !program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."; + int d = 0; + u8[] !output = new u8[0]; + u8[] !data = new u8[100]; + + for( int pc = 0; pc < program#; pc++ ) { + var command = program[pc]; + if (command == 62) { + d++; + } else if (command == 60) { + d--; + } else if (command == 43) { + data[d]++; + } else if (command == 45) { + data[d]--; + } else if (command == 46) { + // 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) { + data[d] = 42; + } else if (command == 91) { + if (data[d] == 0) { + for( int d = 1; d > 0; ) { + command = program[++pc]; + if (command == 91) d++; + if (command == 93) d--; + } + } + } else if (command == 93) { + if (data[d]) { + for( int d = 1; d > 0; ) { + command = program[--pc]; + if (command == 93) d++; + if (command == 91) d--; + } + } + } + } + return output; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc0.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc0.smp new file mode 100644 index 00000000..d85e39e5 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc0.smp @@ -0,0 +1 @@ +return new u8[arg]; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc2.smp new file mode 100644 index 00000000..864bcdc8 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc2.smp @@ -0,0 +1 @@ +int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1]; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testArray1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testArray1.smp new file mode 100644 index 00000000..7f3bd007 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testArray1.smp @@ -0,0 +1,8 @@ +int[] !ary = new int[arg]; +// Fill [0,1,2,3,4,...] +for( int i=0; i<ary#; i++ ) + ary[i] = i; +// Fill [0,1,3,6,10,...] +for( int i=0; i<ary#-1; i++ ) + ary[i+1] += ary[i]; +return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBasic1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBasic1.smp new file mode 100644 index 00000000..671e5b74 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBasic1.smp @@ -0,0 +1 @@ +return arg | 2; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBrainfuck.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBrainfuck.smp new file mode 100644 index 00000000..4e5ab81d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBrainfuck.smp @@ -0,0 +1,43 @@ +u8[] !program = new u8[106];program[0] = 43;program[1] = 43;program[2] = 43;program[3] = 43;program[4] = 43;program[5] = 43;program[6] = 43;program[7] = 43;program[8] = 91;program[9] = 62;program[10] = 43;program[11] = 43;program[12] = 43;program[13] = 43;program[14] = 91;program[15] = 62;program[16] = 43;program[17] = 43;program[18] = 62;program[19] = 43;program[20] = 43;program[21] = 43;program[22] = 62;program[23] = 43;program[24] = 43;program[25] = 43;program[26] = 62;program[27] = 43;program[28] = 60;program[29] = 60;program[30] = 60;program[31] = 60;program[32] = 45;program[33] = 93;program[34] = 62;program[35] = 43;program[36] = 62;program[37] = 43;program[38] = 62;program[39] = 45;program[40] = 62;program[41] = 62;program[42] = 43;program[43] = 91;program[44] = 60;program[45] = 93;program[46] = 60;program[47] = 45;program[48] = 93;program[49] = 62;program[50] = 62;program[51] = 46;program[52] = 62;program[53] = 45;program[54] = 45;program[55] = 45;program[56] = 46;program[57] = 43;program[58] = 43;program[59] = 43;program[60] = 43;program[61] = 43;program[62] = 43;program[63] = 43;program[64] = 46;program[65] = 46;program[66] = 43;program[67] = 43;program[68] = 43;program[69] = 46;program[70] = 62;program[71] = 62;program[72] = 46;program[73] = 60;program[74] = 45;program[75] = 46;program[76] = 60;program[77] = 46;program[78] = 43;program[79] = 43;program[80] = 43;program[81] = 46;program[82] = 45;program[83] = 45;program[84] = 45;program[85] = 45;program[86] = 45;program[87] = 45;program[88] = 46;program[89] = 45;program[90] = 45;program[91] = 45;program[92] = 45;program[93] = 45;program[94] = 45;program[95] = 45;program[96] = 45;program[97] = 46;program[98] = 62;program[99] = 62;program[100] = 43;program[101] = 46;program[102] = 62;program[103] = 43;program[104] = 43;program[105] = 46; +int d = 0; +u8[] !output = new u8[0]; +u8[] !data = new u8[100]; + +for( int pc = 0; pc < program#; pc++ ) { + var command = program[pc]; + if (command == 62) { + d++; + } else if (command == 60) { + d--; + } else if (command == 43) { + data[d]++; + } else if (command == 45) { + data[d]--; + } else if (command == 46) { + // 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) { + data[d] = 42; + } else if (command == 91) { + if (data[d] == 0) { + for( int d = 1; d > 0; ) { + command = program[++pc]; + if (command == 91) d++; + if (command == 93) d--; + } + } + } else if (command == 93) { + if (data[d]) { + for( int d = 1; d > 0; ) { + command = program[--pc]; + if (command == 93) d++; + if (command == 91) d--; + } + } + } +} +return output; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp new file mode 100644 index 00000000..d1368767 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp @@ -0,0 +1 @@ +struct _Bar { int x; }; var b = arg ? new _Bar; return b ? b.x++ + b.x++ : -1; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags1.smp new file mode 100644 index 00000000..f630e959 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags1.smp @@ -0,0 +1,5 @@ +bool b1 = arg == 1; +bool b2 = arg == 2; +if (b2) if (b1) return 1; +if (b1) return 2; +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags2.smp new file mode 100644 index 00000000..70bfc226 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags2.smp @@ -0,0 +1,6 @@ +bool b1 = arg == 1; +while (arg > 0) { + arg--; + if (b1) arg--; +} +return arg; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFltArg.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFltArg.smp new file mode 100644 index 00000000..caa11f12 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFltArg.smp @@ -0,0 +1 @@ +return {int i, flt f, int j->return i+f+j;}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp new file mode 100644 index 00000000..417eba72 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp @@ -0,0 +1,46 @@ +// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort + +val _merge_sort = { int[] a, int[] b, int n -> + _copy_array(a, 0, n, b); + _split_merge(a, 0, n, 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[] !a = new int[arg]; +int[] !b = new int[a#]; + +for (int i = 0; i < a#; i++) + a[i] = a# - i; + +_merge_sort(a, b, a#); + +return a; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp new file mode 100644 index 00000000..fd76478f --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp @@ -0,0 +1,11 @@ +// Newtons approximation to the square root +val _sqrt = { flt x -> + flt guess = x; + while( 1 ) { + flt next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } +}; +flt farg = arg; +return _sqrt(farg) + _sqrt(farg+2.0); diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp new file mode 100644 index 00000000..8912dcd9 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp @@ -0,0 +1,10 @@ +// Newtons approximation to the square root +val _sqrt = { int x -> + int guess = x; + while( 1 ) { + int next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } +}; +return _sqrt(arg) + _sqrt(arg+2); diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp new file mode 100644 index 00000000..951cdc32 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp @@ -0,0 +1,35 @@ +struct _String { + u8[] cs; + new _String={ u8[] b -> cs=b; }; + int _hashCode; +}; + +val _equals = { !_String !self, !_String s -> + if( self == s ) return true; + if( self.cs# != s.cs# ) return false; + for( int i=0; i< self.cs#; i++ ) + if( self.cs[i] != s.cs[i] ) + return false; + return true; +}; + +val _hashCode = { !_String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; + +val _hashCodeString = { !_String self -> + int hash=0; + if( self.cs ) { + for( int i=0; i< self.cs#; i++ ) + hash = hash*31 + self.cs[i]; + } + if( !hash ) hash = 123456789; + return hash; +}; + +!_String !s = new _String(new u8[17]); +s.cs[0] = 67; // C +s.cs[1] = 108; // l +_hashCode(s); diff --git a/src/test/java/com/seaofnodes/simple/progs/array1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testArray1.smp index 6b10f92c..6b10f92c 100644 --- a/src/test/java/com/seaofnodes/simple/progs/array1.smp +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testArray1.smp diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp new file mode 100644 index 00000000..58449f9c --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp @@ -0,0 +1,13 @@ +// -*- mode: java; -*- +// Newtons approximation to the square root +val _test_sqrt = { 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; + guess = next; + } +}; +flt farg = arg; return _test_sqrt(farg) + _test_sqrt(farg+2.0); diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash21.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp index 257f79f9..a18a5b88 100644 --- a/src/test/java/com/seaofnodes/simple/progs/stringHash21.smp +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp @@ -1,11 +1,12 @@ // -*- mode: java; -*- -struct String { +struct _String { u8[] cs; int _hashCode; + new _String = { u8[] data -> cs=data; }; }; // Compare two Strings -val equals = { !String !self, !String s -> +val equals = { !_String !self, !_String s -> if( self == s ) return true; if( self.cs# != s.cs# ) return false; for( int i=0; i< self.cs#; i++ ) @@ -15,13 +16,13 @@ val equals = { !String !self, !String s -> }; // Return the String hashCode (cached, and never 0) -val hashCode = { !String self -> +val hashCode = { !_String self -> self._hashCode ? self._hashCode : (self._hashCode = _hashCodeString(self)); }; -val _hashCodeString = { !String self -> +val _hashCodeString = { !_String self -> int hash=0; if( self.cs ) for( int i=0; i< self.cs#; i++ ) @@ -29,3 +30,6 @@ val _hashCodeString = { !String self -> if( !hash ) hash = 123456789; return hash; }; + +// This fixture exports its API, not the last declaration as a result. +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp new file mode 100644 index 00000000..315cc5b6 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp @@ -0,0 +1,10 @@ +struct _S { int f; }; +var v0 = new _S; +!_S? !v1; +if (arg) v1 = new _S; +if (v1) { + v0.f = v1.f; +} else { + v0.f = 2; +} +return v0; diff --git a/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount-2.smp index 20a05445..20a05445 100644 --- a/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount-2.smp diff --git a/src/test/java/com/seaofnodes/simple/progs/arg_count.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount.smp index 6b6bc0ae..6b6bc0ae 100644 --- a/src/test/java/com/seaofnodes/simple/progs/arg_count.smp +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount.smp diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testFibExport.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testFibExport.smp new file mode 100644 index 00000000..149872a7 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testFibExport.smp @@ -0,0 +1,10 @@ +val fib = {int n -> + int f1=1; + int f2=1; + while( n-- > 1 ){ + int temp = f1+f2; + f1=f2; + f2=temp; + } + return f2; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp new file mode 100644 index 00000000..1804ad59 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp @@ -0,0 +1 @@ +struct _S { int i; }; !_S !s = new _S; while(1) s.i++; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testLoop2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testLoop2.smp new file mode 100644 index 00000000..cc54f790 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testLoop2.smp @@ -0,0 +1,9 @@ +int i = 0; +while(true) { + i += 1; + if( i==0 ) continue; + if( i==1 ) continue; + if( i==2 ) break; + continue; +} +return i; diff --git a/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testNewtonExport.smp index be2dffec..2786558e 100644 --- a/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testNewtonExport.smp @@ -1,5 +1,3 @@ -// -*- mode: java; -*- -// Newtons approximation to the square root val test_sqrt = { flt x -> flt epsilon = 1e-15; flt guess = x; diff --git a/src/test/java/com/seaofnodes/simple/progs/person21.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp index b5e6ee90..09285034 100644 --- a/src/test/java/com/seaofnodes/simple/progs/person21.smp +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp @@ -6,4 +6,4 @@ struct Person { val fcn = { !Person?[] ps, int x -> if( ps[x] ) ps[x].age++; -};
\ No newline at end of file +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testSieve.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testSieve.smp new file mode 100644 index 00000000..beb0b3ad --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testSieve.smp @@ -0,0 +1,30 @@ +val sieve = { int N -> + // The main Sieve array + bool[] !ary = new bool[N]; + // The primes less than N + u32[] !primes = new u32[N>>1]; + // Number of primes so far, searching at index p + int nprimes = 0, p=2; + // Find primes while p^2 < N + while( p*p < N ) { + // skip marked non-primes + while( ary[p] ) p++; + // p is now a prime + primes[nprimes++] = p; + // Mark out the rest non-primes + for( int i = p + p; i < ary#; i+= p ) + ary[i] = true; + p++; + } + + // Now just collect the remaining primes, no more marking + for( ; p < N; p++ ) + if( !ary[p] ) + primes[nprimes++] = p; + + // Copy/shrink the result array + u32[] !rez = new u32[nprimes]; + for( int j=0; j < nprimes; j++ ) + rez[j] = primes[j]; + return rez; +}; diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp index 7344fcdd..ee5a0560 100644 --- a/src/test/java/com/seaofnodes/simple/progs/stringHash.smp +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp @@ -1,7 +1,7 @@ -// -*- mode: java; -*- struct String { u8[] cs; int _hashCode; + new String = { u8[] data -> cs=data; }; }; // Compare two Strings @@ -28,3 +28,6 @@ val _hashCodeString = { !String self -> if( !hash ) hash = 123456789; return hash; }; + +// This fixture exports its API, not the last declaration as a result. +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testHelloWorld.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testHelloWorld.smp new file mode 100644 index 00000000..1bcac36d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testHelloWorld.smp @@ -0,0 +1,4 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +_p("Hello, World!"); +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp new file mode 100644 index 00000000..cc549808 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp @@ -0,0 +1 @@ +struct _S { int i; }; !_S !s = new _S; while(1) s.i++; return s.i; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp new file mode 100644 index 00000000..21293a7a --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp @@ -0,0 +1,12 @@ +{i8} c_i8="C"; {u8} c_u8="C"; +{i16} c_i16="C"; {u16} c_u16="C"; +{i32} c_i32="C"; {u32} c_u32="C"; +{int} c_i64="C"; +val test_i8={->return c_i8();}; +val test_u8={->return c_u8();}; +val test_i16={->return c_i16();}; +val test_u16={->return c_u16();}; +val test_i32={->return c_i32();}; +val test_u32={->return c_u32();}; +val test_i64={->return c_i64();}; +val test_cmp={->return c_i32()==-1;}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp new file mode 100644 index 00000000..f860091d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp @@ -0,0 +1,8 @@ +struct Person { + i32 age; +}; + +val fcn = { !Person?[] ps, int x -> + if( ps[x] ) + ps[x].age++; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp new file mode 100644 index 00000000..ec9c6d50 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp @@ -0,0 +1,4 @@ +struct _Person { i32 age;}; +!_Person !p = new _Person; +p.age = (arg<<17)>>17; +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp new file mode 100644 index 00000000..d5264aa2 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp @@ -0,0 +1,5 @@ + + struct _Person { i32 age;}; + !_Person !p = new _Person; + p.age = (arg<<48)>>48; + return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp new file mode 100644 index 00000000..c4398a89 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp @@ -0,0 +1,5 @@ +// Should fold away sign extend +struct _Person { i8 age;}; +!_Person !p = new _Person; +p.age = (arg<<48)>>48; +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp new file mode 100644 index 00000000..49c80a99 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp @@ -0,0 +1,34 @@ +// -*- mode: java; -*- +struct _String { + u8[] cs; + int _hashCode; + new _String = { u8[] data -> cs=data; }; +}; + +// Compare two Strings +val equals = { !_String !self, !_String s -> + if( self == s ) return true; + if( self.cs# != s.cs# ) return false; + for( int i=0; i< self.cs#; i++ ) + if( self.cs[i] != s.cs[i] ) + return false; + 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; + for( int i=0; i< self.cs#; i++ ) + hash = hash*31 + self.cs[i]; + if( !hash ) hash = 123456789; + return hash; +}; + +// This fixture exports its API, not the last declaration as a result. +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAnd.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAnd.smp new file mode 100644 index 00000000..da85ef0e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAnd.smp @@ -0,0 +1,11 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +int a = 1; +int b = 1; + +if(a && b) { + _p("And"); +} else { + _p("Or"); +} +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp new file mode 100644 index 00000000..a1e4e9ab --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp @@ -0,0 +1,12 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +struct _S { !_S? !fld; new _S={ !_S? f -> fld=f; }; }; + +val ptr = arg == 1 ? null : new _S(arg==1 ? null : new _S(null)); +if( ptr && ptr.fld ) { + _p("true"); +} else { + _p("false"); +} + +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffAnd.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffAnd.smp new file mode 100644 index 00000000..1a6b5cf3 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffAnd.smp @@ -0,0 +1,17 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +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) { + _p("Effected"); +} else { + _p("Not effected"); +} +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffOr.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffOr.smp new file mode 100644 index 00000000..f2bb16c5 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffOr.smp @@ -0,0 +1,24 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +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) { + _p("Effected"); +} else { + _p("Not effected"); +} +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp new file mode 100644 index 00000000..eac0a542 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp @@ -0,0 +1,20 @@ +// A hypothetical scanner class +struct _Scan { + int !x; + u8[~] buf; + new _Scan={ u8[~] b -> buf=b; }; + // Skip whitespace + val skip = { -> + while( buf[x] <= ' ' ) + x++; + }; + // Peek a character; if matched consume it, else false. + val peek = { u8 c -> + skip(); + if( buf[x] != c ) return false; + x++; + return true; + }; +}; +!_Scan !s = new _Scan(" q"); +return s.peek('q'); diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp new file mode 100644 index 00000000..8a7b90d9 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp @@ -0,0 +1,16 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +// -*- mode: java; -*- +int a = 1; +int b = 1; + +var _sq = { int x -> + x*x; +}; + +if(a && _sq(0)) { + _p("And"); +} else { + _p("Or"); +} +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp new file mode 100644 index 00000000..ced649e3 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp @@ -0,0 +1,17 @@ +// A hypothetical scanner class +struct _Scan { + int !x; + u8[~] buf; + new _Scan={ u8[~] b -> buf=b; }; + // Skip whitespace + val skip = { -> + while( buf[x] <= ' ' ) + x++; + }; + val require = { u8 ch -> + skip(); + buf[x++]==ch; + }; +}; +!_Scan !s = new _Scan(" [1,2]"); +return s.require('['); diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOr.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOr.smp new file mode 100644 index 00000000..dbab1ae5 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOr.smp @@ -0,0 +1,13 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +int a = 1; +int b = 0; + +if(a++ || b++ ) { + if(b == 0 && a == 2) { + _p("Or"); + } +} else{ + _p("And"); +} +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp new file mode 100644 index 00000000..c4e4f9b5 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp @@ -0,0 +1,10 @@ +struct _S { int f; }; +int x=0; +int result=arg&1; +if(!result) { + !_S !s=new _S; + while(x<3) x++; + s.f=++x; + result=s.f; +} +return x*10+result; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp new file mode 100644 index 00000000..79478408 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp @@ -0,0 +1,11 @@ +struct _S { int f; }; +!_S !s=new _S; +int result=arg&1; +if(!result) { + !_S !t=new _S; + while(s.f<3) s.f++; + s.f++; + t.f=s.f; + result=t.f; +} +return s.f*10+result; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp new file mode 100644 index 00000000..f292254e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp @@ -0,0 +1,217 @@ + + i32 errno = "C"; + // fd buf len -> len + { i32 i64 u32 -> u32 } write = "C"; + // fd buf len -> len + { i32 i64 u32 -> u32 } read = "C"; + // status + { i32 -> i32 } _exit = "C"; + // addr len prot flags fd off -> void* + { i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C"; + // mmap flags + i32 PROT_EXEC = 4; + i32 PROT_READ = 1; + i32 PROT_WRITE= 2; + i32 PROT_NONE = 0; + i32 MAP_PRIVATE = 2; + i32 MAP_ANON = 32; + + val _copy = { u8[] dst, int doff, u8[~] src, int soff, int len -> + for( int i=0; i<len; i++ ) + dst[i+doff] = src[i+soff]; + return dst; + }; + struct _aryu8 { + u32 !len = 0; + u8[] !buf; + new _aryu8={ u8[] b -> buf=b; }; + // Grow as needed + 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]; + _copy(buf2,0,buf,0,buf#); + buf = buf2; + self; + }; + + // Add a character to aryu8 + val add = { u8 c -> + _grow(1).buf[len++] = c; + self; + }; + + // Add a plain array to the aryu8 + val addAry = { u8[~] ary -> + _grow(ary#); + _copy(buf,len,ary,0,ary#); + len += ary#; + self; + }; + + // Remove x chars from the tail. + val undo = { int x -> + len = len >= x ? len-x : 0; + self; + }; + + // Print ASCII number into aryu8 + 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<log; i++ ) { + ten /= 10; + i64 q = x/ten; + u8 digit = q+'0'; + add(digit); + x -= q*ten; + } + self; + }; + + // Write to fd, returns the number of bytes written (len). + val write = { i32 fd -> i64 ptr = buf; write(fd,ptr,len); }; + }; +struct _Scan { + int !x; + u8[~] buf; + new _Scan={ u8[~] b -> buf=b; }; + + // Skip whitespace + val skip = { -> + while( buf[x] <= ' ' ) + x++; + return self; + }; + + // True if find required character, and skip it; else False. + val match = { u8 c -> + skip(); + if( buf[x] != c ) return false; + x++; + return true; + }; + + // Parse a simple number + val number = { -> + skip(); + bool neg = match('-'); + i64 n = 0; + while( '0' <= buf[x] & buf[x] <= '9' ) + n = n*10 + (buf[x++] - '0'); + return neg ? -n : n; + }; + }; +struct _aryi64 { + u32 !len = 0; + i64[] !buf; + new _aryi64={ i64[] b -> buf=b; }; + // Grow as needed + 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<buf#; i++ ) + buf2[i] = buf[i]; + buf = buf2; + self; + }; + + // Add a i64 to aryu8 + val add = { i64 c -> + _grow(1).buf[len++] = c; + self; + }; + + // Write to aryu8 + val str = { -> + // TODO: clunky default constructor syntax + var str = new _aryu8(new u8[1]).add('['); + for( int i=0; i<len; i++ ) + str.num(buf[i]).add(',').add(' '); + str.undo(2).add(']'); + }; + }; + val _parseAryI64 = { u8[~] str -> + !_Scan !s = new _Scan(str); + if( !s.match('[') ) return null; + // Array of parsed results + var nums = new _aryi64(new i64[1]); + // Parse a list of numbers + if( !s.match(']') ) { // Handle the empty list + // Parse "123, " until a something else + while( s.x < s.buf# ) { + nums.add(s.number()); + if( !s.match(',') ) + break; + }; + if( !s.match(']') ) return null; // Close the list + } + return nums; + }; + + + // Print string to stdout + val _p = { u8[~] str -> + i64 ptr = str; // cast array base to i64 + return write(1,ptr,str#); + }; + + // Return stdin as a u8[] + val _stdin = { -> + var buf = new u8[8*1024]; + i64 ptr = buf; + int len = 0; + while( true ) { + int rez = read(0,ptr,buf#); + if( rez < 0 ) _exit(errno); + len += rez; + if( rez < buf# ) + break; + _exit(-2); // Need to implement + } + // Compress + var dst = new u8[len]; + return _copy(dst,0,buf,0,len); + }; + // -*- mode: java; -*- + +// An implementation of +// https://sampleprograms.io/projects/bubble-sort/ +// +// Bubble sort, with reading (parsing) and writing (formatting) the data. + +val _sort = { !_aryi64 nums -> + var done = false; + while( !done ) { + done = true; + for( var i = 0; i < nums.len-1; i++ ) + if( nums.buf[i] > nums.buf[i+1] ) { + int tmp = nums.buf[i]; + nums.buf[i] = nums.buf[i+1]; + nums.buf[i+1] = tmp; // Swap + done = false; + } + } +}; + +// Read from stdin a line of the form "[ 4, 3, 2, 1]" and print it sorted. +val main = { -> + // Parse array of numbers from stdin + var nums = _parseAryI64( _stdin() ); + if( !nums ) { + _p("stdin must look like [4, 3, 2, 1]"); + return 1; + } + + // Bubble sort array of integers + _sort(nums); + + // Print a list of numbers to stdout + nums.str().write(1); + return 0; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp new file mode 100644 index 00000000..572e4ad7 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp @@ -0,0 +1,11 @@ +// Newtons approximation to the square root +val _sqrt = { int x -> + int guess = x; + while( 1 ) { + int next = (x/guess + guess)/2; + if( next == guess ) return guess; + guess = next; + } +}; +int cast_int = arg+2; +return _sqrt(arg) + _sqrt(cast_int); diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testStackArguments.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testStackArguments.smp new file mode 100644 index 00000000..b7d52dc5 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testStackArguments.smp @@ -0,0 +1,5 @@ +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 -> + return + i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 + + i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack1.smp new file mode 100644 index 00000000..ded92426 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack1.smp @@ -0,0 +1,10 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +int something_specific = 11; + +if (10 < something_specific < 20) { + _p("In Range"); +} else { + _p("Out of Range"); +} +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10-2.smp new file mode 100644 index 00000000..d9fb38d5 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10-2.smp @@ -0,0 +1 @@ +return (0 < 1) == (1>0); diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10.smp new file mode 100644 index 00000000..f92e5b00 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10.smp @@ -0,0 +1,3 @@ +var stack10 = { int x -> + (x < 1) == (1>x); +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11-2.smp new file mode 100644 index 00000000..b5aac70b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11-2.smp @@ -0,0 +1 @@ +return 0 < arg < arg+1 < 4; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11.smp new file mode 100644 index 00000000..f50e3512 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11.smp @@ -0,0 +1,3 @@ +var stack11 = { int x -> + 0 < x < x+1 < 4; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12-2.smp new file mode 100644 index 00000000..575d1b4e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12-2.smp @@ -0,0 +1 @@ +return 0 != arg != 2; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12.smp new file mode 100644 index 00000000..b4f36c2e --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12.smp @@ -0,0 +1,3 @@ +var stack12 = { int x -> + 0 != x != 2; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13-2.smp new file mode 100644 index 00000000..9bde9687 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13-2.smp @@ -0,0 +1 @@ +return 0 == arg == 1; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13.smp new file mode 100644 index 00000000..ae7b38a3 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13.smp @@ -0,0 +1,3 @@ +var stack13 = { int x -> + 0 == x == 1; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack2.smp new file mode 100644 index 00000000..5590c42f --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack2.smp @@ -0,0 +1,10 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +int score = 75; + +if (60 <= score < 90) { + _p("In Range"); +} else { +_p("Out of range"); +} +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp new file mode 100644 index 00000000..4700f8ce --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp @@ -0,0 +1,11 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +var _sq = { int x -> + x*x; +}; + +int score = 5; + +val str = (_sq(2) <= score < _sq(3)) ? "In Range" : "Out of range"; +_p(str); +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack4.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack4.smp new file mode 100644 index 00000000..47623b30 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack4.smp @@ -0,0 +1,10 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +int something_specific = 9; + +if (10 < something_specific < 20) { + _p("In Range"); +} else { + _p("Out of Range"); +} +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp new file mode 100644 index 00000000..3c2c4208 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp @@ -0,0 +1,15 @@ +{i32 i64 u32 -> u32} write="C"; +val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);}; +var _sq = { int x -> + x*x; +}; + +int score = 5; + +if (_sq(2) <= score < _sq(3)) { +_p("In Range"); +} else { +_p("Out of range"); +} + +return 0; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9-2.smp new file mode 100644 index 00000000..f54558f5 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9-2.smp @@ -0,0 +1 @@ +return (0 < 1) == 1; diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9.smp new file mode 100644 index 00000000..15979be6 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9.smp @@ -0,0 +1,3 @@ +var stack9 = { int x -> + (x < 1) == 1; +}; diff --git a/src/test/java/com/seaofnodes/simple/spill/MergeSortTest-testMergeSort0.smp b/src/test/java/com/seaofnodes/simple/spill/MergeSortTest-testMergeSort0.smp new file mode 100644 index 00000000..e6478eeb --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/MergeSortTest-testMergeSort0.smp @@ -0,0 +1,35 @@ + 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]; + }; diff --git a/src/test/java/com/seaofnodes/simple/spill/README.ja.md b/src/test/java/com/seaofnodes/simple/spill/README.ja.md new file mode 100644 index 00000000..5ae019dd --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/README.ja.md @@ -0,0 +1,50 @@ +# 過去の割り当てコホート + +[English](README.md) | 日本語 + +この文書は [Historical allocation cohorts](README.md) の日本語訳です。 + +`cohorts.tsv` は、第24章の `SpillStats` で測定した212件のコンパイル項目を固定したものです。 +コホート20〜24の項目数は、それぞれ39、52、24、30、67です。 +ソースファイルは内容で重複を除き、最初に参照したテストの名前を付けています。 +マニフェストには、テストの識別情報、ターゲット、ABI、元の停止フェーズを保持しています。 +`Win64` は、第25章で使う ABI の表記 `win64` に変更しています。 +引数の型は i64、再実行のシードは全項目で123です。 + +これらの入力は、この最終監査で新たな正しさの回帰テストを追加する前に、 +第24章のテストから取得しました。比較のための安定したコーパスです。 +変更する場合は、比較可能性が失われる理由を説明する必要があります。 +人工的なアロケータの回帰テストや命令の回帰テストでの割り当ては、これらのコホートには含めません。 + +第25章では、以前のソースをすべて無変更で解析できるわけではありません。適応のための変更を明記します。 + +- String、Scan、連結された S のコンストラクタでは、`new S { field=...; }` を、 + 宣言されたコンストラクタとその引数に置き換えています。 + レシーバーを明示するスキャナ関数は、操作を保ったままインスタンスメソッドにしています。 +- 保存した `Chapter21Test-testStringExport`、`Chapter21AllocTest-testString`、 + `Chapter23AllocTest-testString` のソースは、必須の非 null フィールド `cs` を初期化するために、 + `new String = { u8[] data -> cs=data; };` を宣言しています。 + これによって10件の再実行項目(4件、3件、3件)を修正していますが、 + 等価性・ハッシュの本体やマニフェストの構成項目は変えていません。 + これらの適応済みソースが新しい比較基準です。以前のコンストラクタなしのフィクスチャと + 同一入力として、その集計値を比較することはできません。 +- 短絡評価の右辺で初期化する2つのループは、同等のガード付きブロックにしています。 + 割り当て、副作用、最終結果を保持しています。 +- 単純な I/O のケースには、以前の `write` の束縛と表示用の補助関数を直接含めています。 + Bubble Sort には、名前空間の接頭辞を平坦化し、コンストラクタを明示し、 + 置換用バッファを可変にした、以前のライブラリの本体を含めています。 + これにより、第25章で大幅に改訂した例に差し替えるのではなく、以前のワークロードを保持しています。 +- 元の Newton の浮動小数点数版2種類を含め、ほかのソース本文は保持しています。 + +すべての項目を **RegAlloc まで**再実行し、スケジューリング済みのレジスタ制約を確認します。 +過去のフェーズの列は来歴情報であり、第25章で以前のネイティブ実行ハーネスを動かすことを約束するものではありません。 +適応済み Bubble Sort は、割り当ての比較可能性のために、過去の `i32 errno="C"` 宣言を保持しています。 +直接のデータ束縛は現在、3つのターゲットすべてでエンコードできますが、 +errno がスレッドローカルまたはアクセサ経由の libc とは、この宣言をリンクできません。 +現在のシステムライブラリのコードは、代わりに `libc.errno()` を使います。 +このコーパスを、過去のランタイムの実行カバレッジとして数えてはいけません。 + +その後 `SpillStats` は、シード456で現在のシステムライブラリをエンコーディングまでコンパイルし、 +既存のシードとネイティブ検査を使って Chapter25Test を実行します。 +これらを合わせてコホート25とします。ライブラリまたはテストが失敗すると、レポートも失敗します。 +部分的な集計値では改善を示せません。現在のコンパイラの実行検証は、通常の章全体のテストスイートが担います。 diff --git a/src/test/java/com/seaofnodes/simple/spill/README.md b/src/test/java/com/seaofnodes/simple/spill/README.md new file mode 100644 index 00000000..b5ab9d80 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/README.md @@ -0,0 +1,78 @@ +# Historical allocation cohorts + +English | [日本語](README.ja.md) + +`cohorts.tsv` freezes 199 compilation entries matching Chapter 24's +`SpillStats`: 39, 52, 26, 24, and 58 entries for cohorts 20-24. The source files +are deduplicated by contents and named for their first referring test. The +manifest preserves test identity, target, ABI, and the original stopping phase. +`Win64` becomes `win64`, the Chapter 25 ABI spelling. Argument type is i64 and +the replay seed is 123 throughout. + +The original 212 entries were captured from the Chapter 24 tests before the +allocator audit's new correctness regressions. The 2026-10-07 refresh adds +Chapter22Test.testNarrowCReturns on x86 SystemV and Win64, matching the two +entries already measured in Chapters 22-24. The source is unchanged; only the +manifest's ABI spelling changes. The [cross-chapter matrix](../../../../../../../../docs/regalloc-spills.md) +checks matching test/CPU/ABI membership, including repeated compilations. +The same refresh removes all 15 Jig entries: six from cohort 23 and nine from +cohort 24. Jig methods are ignored scratch pads for debugging, not regression +tests or allocation benchmarks. Their frozen source copies were removed too. +These inputs are a stable comparison corpus; changing one +requires explaining the resulting break in comparability. Synthetic allocator +and instruction regressions do not contribute allocations to these cohorts. + +Chapter 25 cannot parse all earlier sources unchanged. Adaptations are explicit: + +- String, Scan, and linked S constructors replace `new S { field=...; }` with + declared constructors and arguments. The explicit-receiver scanner functions + become instance methods, preserving their operations. +- The saved `Chapter21Test-testStringExport`, `Chapter21AllocTest-testString`, + and `Chapter23AllocTest-testString` sources declare + `new String = { u8[] data -> cs=data; };` to initialize the required non-null + `cs` field. This repairs ten replay entries (four, three, and three), without + changing their equality/hash bodies or manifest membership. These adapted + sources are a new comparison baseline; their totals cannot be compared as + identical inputs with the earlier constructor-free fixtures. +- The two short-circuit RHS initializer loops become equivalent guarded blocks, + preserving allocation, side effects, and the final result. Their RHS assigns + the field value directly: Simple's `||` returns an operand, not a normalized + boolean. The 2026-10-07 cleanup removes an erroneous `!!` in those adaptations; + both fixtures return 44 for even arguments and 1 for odd arguments. +- Simple I/O cases include the old `write` binding and print helper directly. + Bubble Sort includes the old library bodies, with namespace prefixes flattened, + explicit constructors, and mutable replacement buffers. This retains the old + workload instead of substituting Chapter 25's substantially revised example. +- Local implementation names are private in eleven fixtures: the four Newton + variants that call their helper from the top level, Chapter 20's MergeSort, + String and Cast programs, and Chapter 22's three sign-extension programs and + InfiniteReturn. Their helpers/classes acquire an underscore prefix; operations, + inputs and final results are unchanged. This avoids exporting implementation + functions and allocation factories solely because Chapter 25 treats top-level + declarations as module members. Export/API fixtures, including NewtonExport, + StringExport, Person and the C-return wrappers, retain their public names. + These visibility adaptations change the generated workload and are recorded + separately from the allocator improvement in the cross-chapter comparison. +- The remaining visibility cleanup makes local classes/functions private in + AntiDeps1, Infinite, the Chapter 21/23 allocation String cases, the Chapter 23 + scanner/pointer/short-circuit/function cases, and Stack3/Stack5. Bubble Sort's + copied library implementation is private; its `main` stays public. The three + adapted String API fixtures end in `return 0;`, avoiding an accidental return + of the last declared function. Their equality/hash APIs stay public, and the + named StringExport fixture also keeps its public String factory. No cohort + entries, workload operations, or intentional export APIs are removed. +- Other source texts are retained. + +All entries replay **through RegAlloc**, checking scheduled register constraints. +The historical phase column is provenance, not a promise to run those old native +harnesses in Chapter 25. The adapted Bubble Sort retains its historical +`i32 errno="C"` declaration for allocation comparability. Direct data bindings +now encode on all three targets, but this declaration cannot link to a libc +whose errno is thread-local or accessor-based. Current system-library code uses +`libc.errno()` instead. Do not count this corpus as historical runtime coverage. + +`SpillStats` then compiles the current system library through encoding at seed +456, and runs Chapter25Test with its existing seeds and native checks. Together +these form cohort 25. A library or test failure makes the report fail; partial +totals cannot establish an improvement. The ordinary full chapter suite remains +the execution validation for the current compiler. diff --git a/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv b/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv new file mode 100644 index 00000000..83b19d2a --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv @@ -0,0 +1,200 @@ +# cohort test cpu abi original phase source +Chapter20 Chapter20Test.testMergeSort x86_64_v2 SystemV RegAlloc Chapter20Test-testMergeSort.smp +Chapter20 Chapter20Test.testMergeSort riscv SystemV RegAlloc Chapter20Test-testMergeSort.smp +Chapter20 Chapter20Test.testMergeSort arm SystemV RegAlloc Chapter20Test-testMergeSort.smp +Chapter20 Chapter20Test.testNewtonInteger x86_64_v2 SystemV RegAlloc Chapter20Test-testNewtonInteger.smp +Chapter20 Chapter20Test.testNewtonInteger riscv SystemV RegAlloc Chapter20Test-testNewtonInteger.smp +Chapter20 Chapter20Test.testNewtonInteger arm SystemV RegAlloc Chapter20Test-testNewtonInteger.smp +Chapter20 Chapter20Test.testCast x86_64_v2 SystemV RegAlloc Chapter20Test-testCast.smp +Chapter20 Chapter20Test.testCast riscv SystemV RegAlloc Chapter20Test-testCast.smp +Chapter20 Chapter20Test.testCast arm SystemV RegAlloc Chapter20Test-testCast.smp +Chapter20 Chapter20Test.testNewtonFloat x86_64_v2 SystemV RegAlloc Chapter20Test-testNewtonFloat.smp +Chapter20 Chapter20Test.testNewtonFloat riscv SystemV RegAlloc Chapter20Test-testNewtonFloat.smp +Chapter20 Chapter20Test.testNewtonFloat arm SystemV RegAlloc Chapter20Test-testNewtonFloat.smp +Chapter20 Chapter20Test.testBrainfuck x86_64_v2 SystemV RegAlloc Chapter20Test-testBrainfuck.smp +Chapter20 Chapter20Test.testBrainfuck riscv SystemV RegAlloc Chapter20Test-testBrainfuck.smp +Chapter20 Chapter20Test.testBrainfuck arm SystemV RegAlloc Chapter20Test-testBrainfuck.smp +Chapter20 Chapter20Test.testAlloc0 x86_64_v2 SystemV RegAlloc Chapter20Test-testAlloc0.smp +Chapter20 Chapter20Test.testAlloc0 riscv SystemV RegAlloc Chapter20Test-testAlloc0.smp +Chapter20 Chapter20Test.testAlloc0 arm SystemV RegAlloc Chapter20Test-testAlloc0.smp +Chapter20 Chapter20Test.testAlloc2 x86_64_v2 SystemV RegAlloc Chapter20Test-testAlloc2.smp +Chapter20 Chapter20Test.testAlloc2 riscv SystemV RegAlloc Chapter20Test-testAlloc2.smp +Chapter20 Chapter20Test.testAlloc2 arm SystemV RegAlloc Chapter20Test-testAlloc2.smp +Chapter20 Chapter20Test.testArray1 x86_64_v2 SystemV RegAlloc Chapter20Test-testArray1.smp +Chapter20 Chapter20Test.testArray1 riscv SystemV RegAlloc Chapter20Test-testArray1.smp +Chapter20 Chapter20Test.testArray1 arm SystemV RegAlloc Chapter20Test-testArray1.smp +Chapter20 Chapter20Test.testBasic1 x86_64_v2 SystemV RegAlloc Chapter20Test-testBasic1.smp +Chapter20 Chapter20Test.testBasic1 riscv SystemV RegAlloc Chapter20Test-testBasic1.smp +Chapter20 Chapter20Test.testBasic1 arm SystemV RegAlloc Chapter20Test-testBasic1.smp +Chapter20 Chapter20Test.testFlags1 x86_64_v2 SystemV RegAlloc Chapter20Test-testFlags1.smp +Chapter20 Chapter20Test.testFlags1 riscv SystemV RegAlloc Chapter20Test-testFlags1.smp +Chapter20 Chapter20Test.testFlags1 arm SystemV RegAlloc Chapter20Test-testFlags1.smp +Chapter20 Chapter20Test.testFlags2 x86_64_v2 SystemV RegAlloc Chapter20Test-testFlags2.smp +Chapter20 Chapter20Test.testFlags2 riscv SystemV RegAlloc Chapter20Test-testFlags2.smp +Chapter20 Chapter20Test.testFlags2 arm SystemV RegAlloc Chapter20Test-testFlags2.smp +Chapter20 Chapter20Test.testFltArg x86_64_v2 SystemV RegAlloc Chapter20Test-testFltArg.smp +Chapter20 Chapter20Test.testFltArg riscv SystemV RegAlloc Chapter20Test-testFltArg.smp +Chapter20 Chapter20Test.testFltArg arm SystemV RegAlloc Chapter20Test-testFltArg.smp +Chapter20 Chapter20Test.testString x86_64_v2 SystemV RegAlloc Chapter20Test-testString.smp +Chapter20 Chapter20Test.testString riscv SystemV RegAlloc Chapter20Test-testString.smp +Chapter20 Chapter20Test.testString arm SystemV RegAlloc Chapter20Test-testString.smp +Chapter21 Chapter21Test.testFibExport x86_64_v2 win64 Export Chapter21Test-testFibExport.smp +Chapter21 Chapter21Test.testFibExport riscv SystemV Export Chapter21Test-testFibExport.smp +Chapter21 Chapter21Test.testFibExport arm SystemV Export Chapter21Test-testFibExport.smp +Chapter21 Chapter21Test.testAntiDeps1 x86_64_v2 SystemV Encoding Chapter21Test-testAntiDeps1.smp +Chapter21 Chapter21Test.testAntiDeps1 riscv SystemV Encoding Chapter21Test-testAntiDeps1.smp +Chapter21 Chapter21Test.testAntiDeps1 arm SystemV Encoding Chapter21Test-testAntiDeps1.smp +Chapter21 Chapter21Test.testLoop2 x86_64_v2 win64 Encoding Chapter21Test-testLoop2.smp +Chapter21 Chapter21Test.testLoop2 riscv SystemV Encoding Chapter21Test-testLoop2.smp +Chapter21 Chapter21Test.testLoop2 arm SystemV Encoding Chapter21Test-testLoop2.smp +Chapter21 Chapter21Test.testSieve x86_64_v2 win64 Export Chapter21Test-testSieve.smp +Chapter21 Chapter21Test.testSieve riscv SystemV Export Chapter21Test-testSieve.smp +Chapter21 Chapter21Test.testSieve arm SystemV Export Chapter21Test-testSieve.smp +Chapter21 Chapter21Test.testArgCount x86_64_v2 win64 Export Chapter21Test-testArgCount.smp +Chapter21 Chapter21Test.testArgCount riscv SystemV Export Chapter21Test-testArgCount-2.smp +Chapter21 Chapter21Test.testArgCount arm SystemV Export Chapter21Test-testArgCount-2.smp +Chapter21 Chapter21Test.testStringExport x86_64_v2 win64 Export Chapter21Test-testStringExport.smp +Chapter21 Chapter21Test.testInfinite x86_64_v2 SystemV Encoding Chapter21Test-testInfinite.smp +Chapter21 Chapter21Test.testInfinite riscv SystemV Encoding Chapter21Test-testInfinite.smp +Chapter21 Chapter21Test.testInfinite arm SystemV Encoding Chapter21Test-testInfinite.smp +Chapter21 Chapter21Test.testArray1 x86_64_v2 SystemV Encoding Chapter20Test-testArray1.smp +Chapter21 Chapter21Test.testArray1 riscv SystemV Encoding Chapter20Test-testArray1.smp +Chapter21 Chapter21Test.testArray1 arm SystemV Encoding Chapter20Test-testArray1.smp +Chapter21 Chapter21Test.testBasic1 x86_64_v2 SystemV Encoding Chapter20Test-testBasic1.smp +Chapter21 Chapter21Test.testBasic1 riscv SystemV Encoding Chapter20Test-testBasic1.smp +Chapter21 Chapter21Test.testBasic1 arm SystemV Encoding Chapter20Test-testBasic1.smp +Chapter21 Chapter21Test.testNewtonExport x86_64_v2 win64 Export Chapter21Test-testNewtonExport.smp +Chapter21 Chapter21Test.testNewtonExport riscv SystemV Export Chapter21Test-testNewtonExport.smp +Chapter21 Chapter21Test.testNewtonExport arm SystemV Export Chapter21Test-testNewtonExport.smp +Chapter21 Chapter21Test.testPerson x86_64_v2 win64 Export Chapter21Test-testPerson.smp +Chapter21 Chapter21Test.testPerson riscv SystemV Export Chapter21Test-testPerson.smp +Chapter21 Chapter21Test.testPerson arm SystemV Export Chapter21Test-testPerson.smp +Chapter21 Chapter21Test.testString x86_64_v2 SystemV Encoding Chapter21Test-testStringExport.smp +Chapter21 Chapter21Test.testString riscv SystemV Encoding Chapter21Test-testStringExport.smp +Chapter21 Chapter21Test.testString arm SystemV Encoding Chapter21Test-testStringExport.smp +Chapter21 Chapter21AllocTest.testNewtonInteger x86_64_v2 win64 Encoding Chapter20Test-testNewtonInteger.smp +Chapter21 Chapter21AllocTest.testNewtonInteger riscv SystemV Encoding Chapter20Test-testNewtonInteger.smp +Chapter21 Chapter21AllocTest.testNewtonInteger arm SystemV Encoding Chapter20Test-testNewtonInteger.smp +Chapter21 Chapter21AllocTest.testNewtonFloat x86_64_v2 SystemV Encoding Chapter21AllocTest-testNewtonFloat.smp +Chapter21 Chapter21AllocTest.testNewtonFloat riscv SystemV Encoding Chapter21AllocTest-testNewtonFloat.smp +Chapter21 Chapter21AllocTest.testNewtonFloat arm SystemV Encoding Chapter21AllocTest-testNewtonFloat.smp +Chapter21 Chapter21AllocTest.testArray1 x86_64_v2 SystemV Encoding Chapter21AllocTest-testArray1.smp +Chapter21 Chapter21AllocTest.testArray1 riscv SystemV Encoding Chapter21AllocTest-testArray1.smp +Chapter21 Chapter21AllocTest.testArray1 arm SystemV Encoding Chapter21AllocTest-testArray1.smp +Chapter21 Chapter21AllocTest.testString x86_64_v2 SystemV Encoding Chapter21AllocTest-testString.smp +Chapter21 Chapter21AllocTest.testString riscv SystemV Encoding Chapter21AllocTest-testString.smp +Chapter21 Chapter21AllocTest.testString arm SystemV Encoding Chapter21AllocTest-testString.smp +Chapter22 Chapter22Test.testSextFail x86_64_v2 win64 Encoding Chapter22Test-testSextFail.smp +Chapter22 Chapter22Test.testSextFail riscv SystemV Encoding Chapter22Test-testSextFail.smp +Chapter22 Chapter22Test.testSextFail arm SystemV Encoding Chapter22Test-testSextFail.smp +Chapter22 Chapter22Test.testSextFail riscv SystemV Export Chapter22Test-testSextFail.smp +Chapter22 Chapter22Test.testSextFail arm SystemV Export Chapter22Test-testSextFail.smp +Chapter22 Chapter22Test.testSextSuccess riscv SystemV Export Chapter22Test-testSextSuccess.smp +Chapter22 Chapter22Test.testSextSuccess arm SystemV Export Chapter22Test-testSextSuccess.smp +Chapter22 Chapter22Test.testSextSuccess x86_64_v2 win64 Encoding Chapter22Test-testSextSuccess.smp +Chapter22 Chapter22Test.testSextSuccess riscv SystemV Encoding Chapter22Test-testSextSuccess.smp +Chapter22 Chapter22Test.testSextSuccess arm SystemV Encoding Chapter22Test-testSextSuccess.smp +Chapter22 Chapter22Test.testInfiniteReturn x86_64_v2 SystemV Encoding Chapter22Test-testInfiniteReturn.smp +Chapter22 Chapter22Test.testInfiniteReturn riscv SystemV Encoding Chapter22Test-testInfiniteReturn.smp +Chapter22 Chapter22Test.testInfiniteReturn arm SystemV Encoding Chapter22Test-testInfiniteReturn.smp +Chapter22 Chapter22Test.testSextFail2 riscv SystemV Export Chapter22Test-testSextFail2.smp +Chapter22 Chapter22Test.testSextFail2 arm SystemV Export Chapter22Test-testSextFail2.smp +Chapter22 Chapter22Test.testSextFail2 x86_64_v2 win64 Encoding Chapter22Test-testSextFail2.smp +Chapter22 Chapter22Test.testSextFail2 riscv SystemV Encoding Chapter22Test-testSextFail2.smp +Chapter22 Chapter22Test.testSextFail2 arm SystemV Encoding Chapter22Test-testSextFail2.smp +Chapter22 Chapter22Test.testPerson x86_64_v2 win64 Export Chapter22Test-testPerson.smp +Chapter22 Chapter22Test.testPerson riscv SystemV Export Chapter22Test-testPerson.smp +Chapter22 Chapter22Test.testPerson arm SystemV Export Chapter22Test-testPerson.smp +Chapter22 Chapter22Test.testHelloWorld x86_64_v2 win64 Export Chapter22Test-testHelloWorld.smp +Chapter22 Chapter22Test.testHelloWorld riscv SystemV Export Chapter22Test-testHelloWorld.smp +Chapter22 Chapter22Test.testHelloWorld arm SystemV Export Chapter22Test-testHelloWorld.smp +Chapter23 Chapter23Test.testAnd x86_64_v2 win64 Export Chapter23Test-testAnd.smp +Chapter23 Chapter23Test.testAnd riscv SystemV Export Chapter23Test-testAnd.smp +Chapter23 Chapter23Test.testAnd arm SystemV Export Chapter23Test-testAnd.smp +Chapter23 Chapter23Test.testOr x86_64_v2 win64 Export Chapter23Test-testOr.smp +Chapter23 Chapter23Test.testOr riscv SystemV Export Chapter23Test-testOr.smp +Chapter23 Chapter23Test.testOr arm SystemV Export Chapter23Test-testOr.smp +Chapter23 Chapter23Test.testCondSideEffAnd x86_64_v2 win64 Export Chapter23Test-testCondSideEffAnd.smp +Chapter23 Chapter23Test.testCondSideEffAnd riscv SystemV Export Chapter23Test-testCondSideEffAnd.smp +Chapter23 Chapter23Test.testCondSideEffAnd arm SystemV Export Chapter23Test-testCondSideEffAnd.smp +Chapter23 Chapter23Test.testFRefFields x86_64_v2 win64 Encoding Chapter23Test-testFRefFields.smp +Chapter23 Chapter23Test.testAndPtr riscv SystemV Export Chapter23Test-testAndPtr.smp +Chapter23 Chapter23Test.testAndPtr arm SystemV Export Chapter23Test-testAndPtr.smp +Chapter23 Chapter23Test.testOrRhsLoopLocal riscv SystemV Export Chapter23Test-testOrRhsLoopLocal.smp +Chapter23 Chapter23Test.testMethod x86_64_v2 win64 Encoding Chapter23Test-testMethod.smp +Chapter23 Chapter23Test.testOrRhsLoopMemory riscv SystemV Export Chapter23Test-testOrRhsLoopMemory.smp +Chapter23 Chapter23Test.testCondSideEffOr x86_64_v2 win64 Export Chapter23Test-testCondSideEffOr.smp +Chapter23 Chapter23Test.testCondSideEffOr riscv SystemV Export Chapter23Test-testCondSideEffOr.smp +Chapter23 Chapter23Test.testCondSideEffOr arm SystemV Export Chapter23Test-testCondSideEffOr.smp +Chapter23 Chapter23Test.testFuncCall x86_64_v2 win64 Export Chapter23Test-testFuncCall.smp +Chapter23 Chapter23Test.testFuncCall riscv SystemV Export Chapter23Test-testFuncCall.smp +Chapter23 Chapter23Test.testFuncCall arm SystemV Export Chapter23Test-testFuncCall.smp +Chapter23 Chapter23AllocTest.testString x86_64_v2 SystemV Encoding Chapter23AllocTest-testString.smp +Chapter23 Chapter23AllocTest.testString riscv SystemV Encoding Chapter23AllocTest-testString.smp +Chapter23 Chapter23AllocTest.testString arm SystemV Encoding Chapter23AllocTest-testString.smp +Chapter24 Chapter24Test.testAnd x86_64_v2 win64 Export Chapter23Test-testAnd.smp +Chapter24 Chapter24Test.testAnd riscv SystemV Export Chapter23Test-testAnd.smp +Chapter24 Chapter24Test.testAnd arm SystemV Export Chapter23Test-testAnd.smp +Chapter24 Chapter24Test.testOr x86_64_v2 win64 Export Chapter23Test-testOr.smp +Chapter24 Chapter24Test.testOr riscv SystemV Export Chapter23Test-testOr.smp +Chapter24 Chapter24Test.testOr arm SystemV Export Chapter23Test-testOr.smp +Chapter24 Chapter24Test.testCondSideEffAnd x86_64_v2 win64 Export Chapter23Test-testCondSideEffAnd.smp +Chapter24 Chapter24Test.testCondSideEffAnd riscv SystemV Export Chapter23Test-testCondSideEffAnd.smp +Chapter24 Chapter24Test.testCondSideEffAnd arm SystemV Export Chapter23Test-testCondSideEffAnd.smp +Chapter24 Chapter24Test.testFRefFields x86_64_v2 win64 Encoding Chapter23Test-testFRefFields.smp +Chapter24 Chapter24Test.testAndPtr riscv SystemV Export Chapter23Test-testAndPtr.smp +Chapter24 Chapter24Test.testAndPtr arm SystemV Export Chapter23Test-testAndPtr.smp +Chapter24 Chapter24Test.testMethod x86_64_v2 win64 Encoding Chapter23Test-testMethod.smp +Chapter24 Chapter24Test.testStack1 x86_64_v2 win64 Export Chapter24Test-testStack1.smp +Chapter24 Chapter24Test.testStack1 riscv SystemV Export Chapter24Test-testStack1.smp +Chapter24 Chapter24Test.testStack1 arm SystemV Export Chapter24Test-testStack1.smp +Chapter24 Chapter24Test.testStack2 x86_64_v2 win64 Export Chapter24Test-testStack2.smp +Chapter24 Chapter24Test.testStack2 riscv SystemV Export Chapter24Test-testStack2.smp +Chapter24 Chapter24Test.testStack2 arm SystemV Export Chapter24Test-testStack2.smp +Chapter24 Chapter24Test.testStack3 x86_64_v2 win64 Export Chapter24Test-testStack3.smp +Chapter24 Chapter24Test.testStack3 riscv SystemV Export Chapter24Test-testStack3.smp +Chapter24 Chapter24Test.testStack3 arm SystemV Export Chapter24Test-testStack3.smp +Chapter24 Chapter24Test.testStack4 x86_64_v2 win64 Export Chapter24Test-testStack4.smp +Chapter24 Chapter24Test.testStack4 riscv SystemV Export Chapter24Test-testStack4.smp +Chapter24 Chapter24Test.testStack4 arm SystemV Export Chapter24Test-testStack4.smp +Chapter24 Chapter24Test.testStack5 x86_64_v2 win64 Export Chapter24Test-testStack5.smp +Chapter24 Chapter24Test.testStack5 riscv SystemV Export Chapter24Test-testStack5.smp +Chapter24 Chapter24Test.testStack5 arm SystemV Export Chapter24Test-testStack5.smp +Chapter24 Chapter24Test.testStack9 x86_64_v2 win64 Export Chapter24Test-testStack9.smp +Chapter24 Chapter24Test.testStack9 riscv SystemV Export Chapter24Test-testStack9-2.smp +Chapter24 Chapter24Test.testStack9 arm SystemV Export Chapter24Test-testStack9-2.smp +Chapter24 Chapter24Test.testCondSideEffOr x86_64_v2 win64 Export Chapter23Test-testCondSideEffOr.smp +Chapter24 Chapter24Test.testCondSideEffOr riscv SystemV Export Chapter23Test-testCondSideEffOr.smp +Chapter24 Chapter24Test.testCondSideEffOr arm SystemV Export Chapter23Test-testCondSideEffOr.smp +Chapter24 Chapter24Test.testStack10 x86_64_v2 win64 Export Chapter24Test-testStack10.smp +Chapter24 Chapter24Test.testStack10 riscv SystemV Export Chapter24Test-testStack10-2.smp +Chapter24 Chapter24Test.testStack10 arm SystemV Export Chapter24Test-testStack10-2.smp +Chapter24 Chapter24Test.testStack11 x86_64_v2 win64 Export Chapter24Test-testStack11.smp +Chapter24 Chapter24Test.testStack11 riscv SystemV Export Chapter24Test-testStack11-2.smp +Chapter24 Chapter24Test.testStack11 arm SystemV Export Chapter24Test-testStack11-2.smp +Chapter24 Chapter24Test.testStack12 x86_64_v2 win64 Export Chapter24Test-testStack12.smp +Chapter24 Chapter24Test.testStack12 riscv SystemV Export Chapter24Test-testStack12-2.smp +Chapter24 Chapter24Test.testStack12 arm SystemV Export Chapter24Test-testStack12-2.smp +Chapter24 Chapter24Test.testStack13 x86_64_v2 win64 Export Chapter24Test-testStack13.smp +Chapter24 Chapter24Test.testStack13 riscv SystemV Export Chapter24Test-testStack13-2.smp +Chapter24 Chapter24Test.testStack13 arm SystemV Export Chapter24Test-testStack13-2.smp +Chapter24 Chapter24Test.testFuncCall x86_64_v2 win64 Export Chapter23Test-testFuncCall.smp +Chapter24 Chapter24Test.testFuncCall riscv SystemV Export Chapter23Test-testFuncCall.smp +Chapter24 Chapter24Test.testFuncCall arm SystemV Export Chapter23Test-testFuncCall.smp +Chapter24 Chapter24AllocTest.testNewtonInteger x86_64_v2 win64 Encoding Chapter24AllocTest-testNewtonInteger.smp +Chapter24 Chapter24AllocTest.testNewtonInteger riscv SystemV Encoding Chapter24AllocTest-testNewtonInteger.smp +Chapter24 Chapter24AllocTest.testNewtonInteger arm SystemV Encoding Chapter24AllocTest-testNewtonInteger.smp +Chapter24 Chapter24AllocTest.testStackArguments x86_64_v2 win64 Export Chapter24AllocTest-testStackArguments.smp +Chapter24 Chapter24AllocTest.testStackArguments riscv SystemV Export Chapter24AllocTest-testStackArguments.smp +Chapter24 Chapter24AllocTest.testStackArguments arm SystemV Export Chapter24AllocTest-testStackArguments.smp +Chapter24 Chapter24AllocTest.testBubbleSort x86_64_v2 win64 Encoding Chapter24AllocTest-testBubbleSort.smp +Chapter24 Chapter24AllocTest.testBubbleSort riscv SystemV Encoding Chapter24AllocTest-testBubbleSort.smp +Chapter24 Chapter24AllocTest.testBubbleSort arm SystemV Encoding Chapter24AllocTest-testBubbleSort.smp +Chapter21 BrainFuckTest.testBrainFuck x86_64_v2 win64 Export BrainFuckTest-testBrainFuck.smp +Chapter21 BrainFuckTest.testBrainFuck riscv SystemV Export BrainFuckTest-testBrainFuck.smp +Chapter21 BrainFuckTest.testBrainFuck arm SystemV Export BrainFuckTest-testBrainFuck.smp +Chapter21 MergeSortTest.testMergeSort0 x86_64_v2 win64 Export MergeSortTest-testMergeSort0.smp +Chapter21 MergeSortTest.testMergeSort0 riscv SystemV Export MergeSortTest-testMergeSort0.smp +Chapter21 MergeSortTest.testMergeSort0 arm SystemV Encoding MergeSortTest-testMergeSort0.smp +Chapter22 Chapter22Test.testNarrowCReturns x86_64_v2 SystemV Export Chapter22Test-testNarrowCReturns.smp +Chapter22 Chapter22Test.testNarrowCReturns x86_64_v2 win64 Export Chapter22Test-testNarrowCReturns.smp 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/forward_ctor/m.smp b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m.smp new file mode 100644 index 00000000..569f38be --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m.smp @@ -0,0 +1,2 @@ +val a = a; +val b = b; diff --git a/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/a.smp b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/a.smp new file mode 100644 index 00000000..97113b71 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/a.smp @@ -0,0 +1 @@ +val f = { -> new b(1); }; diff --git a/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/b.smp b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/b.smp new file mode 100644 index 00000000..43770b60 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/b.smp @@ -0,0 +1,4 @@ +struct b { + int x; + new b = { int y -> x = y; }; +}; 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<len; i++ ) + dst[i+doff] = src[i+soff]; + return dst; +}; diff --git a/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryi64.smp b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryi64.smp new file mode 100644 index 00000000..53c93666 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryi64.smp @@ -0,0 +1,29 @@ +struct aryi64 { + u32 !len = 0; + i64[] !buf; + + new aryi64 = { i64[] b -> 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<buf#; i++ ) + buf2[i] = buf[i]; + buf = buf2; + self; + }; + + val add = { i64 c -> + _grow(1).buf[len++] = c; + self; + }; + + val str = { -> + var str = new aryu8(new u8[1]).add('['); + for( int i=0; i<len; i++ ) + str.num(buf[i]).add(',').add(' '); + str.undo(2).add(']'); + }; +}; diff --git a/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryu8.smp b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryu8.smp new file mode 100644 index 00000000..e6b708a4 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryu8.smp @@ -0,0 +1,50 @@ +struct aryu8 { + u32 !len = 0; + u8[] !buf; + + new aryu8 = { u8[] b -> 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[~] xs -> + _grow(xs#); + ary.copy(buf,len,xs,0,xs#); + len += xs#; + 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<log; i++ ) { + ten /= 10; + i64 q = x/ten; + u8 digit = q+'0'; + add(digit); + x -= q*ten; + } + self; + }; +}; |
