diff options
Diffstat (limited to 'src/test/java/com/seaofnodes')
49 files changed, 1349 insertions, 209 deletions
diff --git a/src/test/java/com/seaofnodes/simple/BoundaryTest.java b/src/test/java/com/seaofnodes/simple/BoundaryTest.java index 10b7517e..0b05dc52 100644 --- a/src/test/java/com/seaofnodes/simple/BoundaryTest.java +++ b/src/test/java/com/seaofnodes/simple/BoundaryTest.java @@ -20,7 +20,7 @@ public class BoundaryTest { .driver(CodeGen.Phase.TypeCheck); assertEquals("7",Eval2.eval(factory,0)); } - String src="struct Counter { int x; int y; int z; new Counter={int n -> while(x<n) { int old=x; y=x+1; x=y+1; z=old; }}; }; val c=new Counter(arg); return c.x*100+c.z;"; + String src="struct Counter { int x; int y; int z; new Counter={int n -> while(x<n) { int old=x; y=x+1; x=y+1; z=old; }}; }; val c=new Counter(((arg<<32)>>32)); return c.x*100+c.z;"; CodeGen code=new CodeGen(src).driver(CodeGen.Phase.LocalSched); assertEquals("2018",Eval2.eval(code,19)); assertEquals("0",Eval2.eval(code,0)); @@ -53,11 +53,11 @@ public class BoundaryTest { @Test public void testRecursiveCallback() { String src=""" val run={int n -> - val power={int base,int exponent -> if(exponent<=0) return 1; return base*power(base,exponent-1);}; - val sumPowers={int n,{int int -> int} f -> if(n<=0) return 0; return f(2,n)+sumPowers(n-1,f);}; + val power={int base,int exponent -> if(exponent<=0) return 1; return base*power(base,--exponent);}; + val sumPowers={int n,{int int -> i64} f -> if(n<=0) return 0; return f(2,n)+sumPowers(--n,f);}; return sumPowers(n,power); }; - return run(arg); + return run(((arg<<32)>>32)); """; for( long seed : new long[]{2,13,123} ) { CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.Opto); @@ -74,11 +74,11 @@ public class BoundaryTest { "retain(power); return power(2,n);", "if(n) { retain(power); while(true) {} } return power(2,n);" } ) { - String src="val run={int n -> val power={int base,int exponent -> if(exponent<=0) return 1; return base*power(base,exponent-1);}; { {int int -> int} -> int } retain=\"C\";"+tail+"}; return run(arg);"; + String src="val run={int n -> val power={int base,int exponent -> if(exponent<=0) return 1; return base*power(base,--exponent);}; { {int int -> i64} -> int } retain=\"C\";"+tail+"}; return run(((arg<<32)>>32));"; CodeGen code=new CodeGen(src).driver(CodeGen.Phase.Opto); ParmNode base=code._start.walk(n -> n instanceof ParmNode p && p._label.equals("base") ? p : null); assertNotNull(base); - assertSame(TypeInteger.BOT,base._type); + assertSame(TypeInteger.I32,base._type); code.driver(CodeGen.Phase.TypeCheck); } } diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index d7b259ed..63858df0 100644 --- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -70,7 +70,7 @@ val main = { -> // Retain the original workload for cross-target allocation comparisons. String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/spill/BrainFuckTest-testBrainFuck.smp")); String brain_fuck = "Hello World!\n"; - TestC.runC(src,"brain_fuck", brain_fuck, 40); + TestC.runC(src,"brain_fuck", brain_fuck,165); EvalRisc5 R5 = TestRisc5.build( src, "brain_fuck", 0, 25, false); int trap = R5.step(100000); diff --git a/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/src/test/java/com/seaofnodes/simple/Chapter04Test.java index 1132375e..bfde3b2a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -127,14 +127,14 @@ public class Chapter04Test { public void testBug1() { CodeGen code = new CodeGen("int a=arg+1; int !b=a; b=1; return a+2; "); code.parse(); - assertEquals("return (arg+3);", code.print()); + assertEquals("return ((((arg+1)<<32)>>32)+2);", code.print()); } @Test public void testBug2() { CodeGen code = new CodeGen("int !a=arg+1; a=a; return a; "); code.parse(); - assertEquals("return (arg+1);", code.print()); + assertEquals("return (((arg+1)<<32)>>32);", code.print()); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/src/test/java/com/seaofnodes/simple/Chapter05Test.java index afdb1071..43057c00 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -21,7 +21,7 @@ else { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,(arg+2),(arg-3));", code.print()); + assertEquals("return ((Phi(Region,(arg+2),(arg-3))<<32)>>32);", code.print()); } @Test @@ -65,7 +65,7 @@ else b=a+1; return a+b;"""); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return ((arg<<1)+Phi(Region,2,3));", code.print()); + assertEquals("return ((((Phi(Region,arg,(((arg+1)<<32)>>32))+1)<<32)>>32)+Sar);", code.print()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return ((Phi(Region,(arg<<1),arg)+arg)+Phi(Region,4,5));", code.print()); + assertEquals("return ((((Phi(Region,arg,(((arg+2)<<32)>>32))+1)<<32)>>32)+(((Phi(Region,(((arg+1)<<32)>>32),2)+Phi(Region,Sar,arg))<<32)>>32));", code.print()); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 7094c0d5..20786571 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -35,7 +35,7 @@ if(arg){}else{ return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,1,Phi(Loop,1,(Phi_a+1)));", code.print()); + assertEquals("return Phi(Region,1,Phi(Loop,1,(((Phi_a+1)<<32)>>32)));", code.print()); } @Test @@ -55,7 +55,7 @@ while(i < arg) { return sum; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,0,Phi(Loop,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", code.print()); + assertEquals("return Phi(Loop,0,Phi(Loop,Phi_sum,(((Phi_sum+Phi(Loop,0,(((Phi_j+1)<<32)>>32)))<<32)>>32)));", code.print()); } @Test @@ -90,7 +90,7 @@ while(a < 10) { return b; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,2,(Phi(Region,Phi_b,4)+1));", code.print()); + assertEquals("return Phi(Loop,2,(((Phi(Region,Phi_b,4)+1)<<32)>>32));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -107,7 +107,7 @@ while(a < 10) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); + assertEquals("return Phi(Loop,1,((((((Phi_a+1)<<32)>>32)+2)<<32)>>32));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -123,7 +123,7 @@ while(a < 10) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); + assertEquals("return Phi(Loop,1,((((((Phi_a+1)<<32)>>32)+2)<<32)>>32));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -165,7 +165,7 @@ while(a < 10) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); + assertEquals("return Phi(Loop,1,((((((Phi_a+1)<<32)>>32)+2)<<32)>>32));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -181,7 +181,7 @@ while(a < 10) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); + assertEquals("return Phi(Loop,1,((((((Phi_a+1)<<32)>>32)+2)<<32)>>32));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -198,7 +198,7 @@ while(a < 10) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); + assertEquals("return Phi(Loop,1,((((((Phi_a+1)<<32)>>32)+2)<<32)>>32));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -215,7 +215,7 @@ while(a < 10) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print()); + assertEquals("return Phi(Loop,1,((((((Phi_a+1)<<32)>>32)+2)<<32)>>32));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 376e472b..249beb11 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -46,7 +46,7 @@ while(arg < 10) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,1,Phi(Region,Phi_a,(Phi_a+1),Phi_a));", code.print()); + assertEquals("return Phi(Loop,1,Phi(Region,Phi_a,(((Phi_a+1)<<32)>>32),Phi_a));", code.print()); assertTrue(code.ctrl() instanceof CProjNode); } @@ -185,7 +185,7 @@ while(arg < 10) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,Phi(Loop,1,(Phi_a+1)),Add);", code.print()); + assertEquals("return Phi(Region,Phi(Loop,1,(((Phi_a+1)<<32)>>32)),Sar);", code.print()); assertTrue(code.ctrl() instanceof RegionNode); } @@ -201,7 +201,7 @@ while(1) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return (Phi(Loop,1,Add)+1);", code.print()); + assertEquals("return (((Phi(Loop,1,Sar)+1)<<32)>>32);", code.print()); assertTrue(code.ctrl() instanceof CProjNode prj && prj._idx==1 ); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 7025b98a..6be01487 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -41,7 +41,7 @@ else { return x; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,(arg<<1),(Shl+1));", code.print()); + assertEquals("return Phi(Region,(arg<<1),(((((Shl<<32)>>32)+1)<<32)>>32));", code.print()); Assert.assertEquals( "2", Eval2.eval(code, 1)); Assert.assertEquals("23", Eval2.eval(code, 11)); } @@ -182,7 +182,7 @@ while(arg) { return a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,0,(-(Phi_a+3)));", code.print()); + assertEquals("return Phi(Loop,0,(((-(Phi_a+3))<<32)>>32));", code.print()); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/src/test/java/com/seaofnodes/simple/Chapter10Test.java index eadaf9fe..559def2f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -74,7 +74,8 @@ public class Chapter10Test { CodeGen code = new CodeGen(""" val f = { int n -> (n+1)&7; }; int x = 0; - if (!!(arg && (x=f(arg)))) return x; + int n=arg; + if (!!(n && (x=f(n)))) return x; return -1; """).driver(CodeGen.Phase.TypeCheck); assertEquals("-1",Eval2.eval(code,0)); @@ -181,7 +182,7 @@ struct s0 { int v0; }; arg=0+new s0.0; """); try { code.parse(); fail(); } - catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); } + catch( Exception e ) { assertEquals("Expected an identifier, found '0'",e.getMessage()); } } @Test @@ -196,7 +197,7 @@ while (arg) { return bar.a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return Test.Bar; ]", code.print()); + assertEquals("Stop[ return ((Phi(Loop,0,(Sar+2))<<32)>>32); return Test.Bar; ]", code.print()); } @Test @@ -318,7 +319,7 @@ while( i.x < i.len ) { return sum; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return Test.Iter; ]", code.print()); + assertEquals("Stop[ return Phi(Loop,0,(((Phi_sum+((Phi(Loop,0,(((Sar+1)<<32)>>32))<<32)>>32))<<32)>>32)); return Test.Iter; ]", code.print()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java index c6817308..8b7c5eef 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -127,7 +127,7 @@ N n = new N(null); return n.next; """); try { code.driver(CodeGen.Phase.TypeCheck); fail(); } - catch( Exception e ) { assertEquals("Argument #0 isa null, but must be a *Test.M {i64 !m; }",e.getMessage()); } + catch( Exception e ) { assertEquals("Argument #0 isa null, but must be a *Test.M {i32 !m; }",e.getMessage()); } } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 214bc69e..70cfe25e 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -35,7 +35,7 @@ while( prime <= arg ) { return primeCount; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,0,Phi(Loop,1,Phi(Region,Phi_primeCount,Phi_primeCount,(Phi_primeCount+1))));", code.print()); + assertEquals("return Phi(Region,0,Phi(Loop,1,Phi(Region,Phi_primeCount,Phi_primeCount,(((Phi_primeCount+1)<<32)>>32))));", code.print()); assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below assertEquals("1", Eval2.eval(code, 2)); // 2 assertEquals("2", Eval2.eval(code, 3)); // 2, 3 @@ -116,7 +116,7 @@ if (v1) { return v0.f; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,.f,2);", code.print()); + assertEquals("return ((Phi(Region,((.f<<32)>>32),2)<<32)>>32);", code.print()); } @@ -134,7 +134,7 @@ while (arg) v.f = 2; return i; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return arg;", code.print()); + assertEquals("return ((arg<<32)>>32);", code.print()); } @Test @@ -151,7 +151,7 @@ while(1) { return v.f; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,0,2);", code.print()); + assertEquals("return ((Phi(Loop,0,2)<<32)>>32);", code.print()); for( int arg : new int[]{-1,0,1,3,100} ) assertEquals("0",Eval2.eval(code,arg)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index e058a822..9a3bb87a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -12,8 +12,10 @@ public class Chapter14FloatTest { {"int rounded=arg/2.5; return rounded;", "6", "-6"}, {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, - {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, - {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e100; return rounded;", "-1", "-1"}, + {"int rounded=-1e100; return rounded;", "0", "0"}, + {"i64 rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"i64 rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} }; diff --git a/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/src/test/java/com/seaofnodes/simple/Chapter15Test.java index c2071cb6..97c3af46 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -157,7 +157,7 @@ return a; """); code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return []*Test._A?;", code.print()); - assertEquals("*Test._A {i64 !i; }?[ null,null]", Eval2.eval(code, 0)); + assertEquals("*Test._A {i32 !i; }?[ null,null]", Eval2.eval(code, 0)); } @Test @@ -312,7 +312,7 @@ flt is = new int[2]; return is; """); try { code.driver(CodeGen.Phase.TypeCheck); fail(); } - catch( Exception e ) { assertEquals("Type *[]i64 is not of declared type flt",e.getMessage()); } + catch( Exception e ) { assertEquals("Type *[]i32 is not of declared type flt",e.getMessage()); } } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 2942cfee..09bd9793 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -77,7 +77,7 @@ return z.x; struct _LLI { !_LLI? !next; int i; new _LLI = { !_LLI? !n, int ii -> next=n; i=ii; }; }; !_LLI? !head = null; while( arg ) { - head = new _LLI(head,arg); + int n=arg; head = new _LLI(head,n); arg = arg-1; } if( !head ) return 0; @@ -107,7 +107,7 @@ struct _LLI { !_LLI? !next; int i; new _LLI = { !_LLI? !n, int a -> }; }; !_LLI? !head = null; while( arg ) { - head = new _LLI(head,arg); + int n=arg; head = new _LLI(head,n); arg = arg-1; } if( !head ) return 0; diff --git a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index 98197b77..12e54193 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -409,7 +409,7 @@ var b = arg ? new _Bar : null; return b ? b.x++ + b.x++ : -1; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,((.x<<1)+1),-1);", code.print()); + assertEquals("return Phi(Region,((((.x+1)<<32)>>32)+ld_x),-1);", code.print()); assertEquals("-1", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); @@ -423,7 +423,7 @@ var b = arg ? new _Bar; return b ? b.x++ + b.x++ : -1; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,((.x<<1)+1),-1);", code.print()); + assertEquals("return Phi(Region,((((.x+1)<<32)>>32)+ld_x),-1);", code.print()); assertEquals("-1", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); } @@ -475,7 +475,7 @@ for( int i=0; i<arg; i++ ) return sum; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print()); + assertEquals("return Phi(Loop,0,(((Phi_sum+Phi(Loop,0,(((Phi_i+1)<<32)>>32)))<<32)>>32));", code.print()); assertEquals("3", Eval2.eval(code, 3)); assertEquals("45", Eval2.eval(code, 10)); } @@ -488,7 +488,7 @@ for( ; i<arg; i++ ) return sum; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print()); + assertEquals("return Phi(Loop,0,(((Phi_sum+Phi(Loop,0,(((Phi_i+1)<<32)>>32)))<<32)>>32));", code.print()); assertEquals("3", Eval2.eval(code, 3)); assertEquals("45", Eval2.eval(code, 10)); } @@ -503,7 +503,7 @@ for( int i=0; ; i++ ) { return sum; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print()); + assertEquals("return Phi(Loop,0,(((Phi_sum+Phi(Loop,0,(((Phi_i+1)<<32)>>32)))<<32)>>32));", code.print()); assertEquals("3", Eval2.eval(code, 3)); assertEquals("45", Eval2.eval(code, 10)); } @@ -516,7 +516,7 @@ for( int i=0; i<arg; ) return sum; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print()); + assertEquals("return Phi(Loop,0,(((Phi_sum+Phi(Loop,0,(((Phi_i+1)<<32)>>32)))<<32)>>32));", code.print()); assertEquals("3", Eval2.eval(code, 3)); assertEquals("45", Eval2.eval(code, 10)); } @@ -578,8 +578,9 @@ return new _A(new !B?[0]).array_of_b; CodeGen code = new CodeGen(""" struct _LLI { !_LLI? !next; int i; new _LLI = { !_LLI? !n, int ii -> next=n; i=ii; }; }; !_LLI? !head = null; -while( arg-- ) - head = new _LLI(head,arg); +while( arg-- ) { + int n=arg; head = new _LLI(head,n); +} int sum=0; var ptr = head; // A read-only ptr, to be assigned from read-only next fields for( ; ptr; ptr = ptr.next ) @@ -587,7 +588,7 @@ for( ; ptr; ptr = ptr.next ) return sum; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Loop,0,(Phi_sum+.i));", code.print()); + assertEquals("return Phi(Loop,0,(((Phi_sum+.i)<<32)>>32));", code.print()); assertEquals("45", Eval2.eval(code, 10)); } @@ -618,8 +619,8 @@ for( int j=0; j<nprimes; j++ ) return rez; """); code.driver(CodeGen.Phase.FinalizeIR).driver(CodeGen.Phase.Schedule); - assertEquals("return []i64;", code.print()); - assertEquals("i64[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20)); + assertEquals("return []i32;", code.print()); + assertEquals("i32[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20)); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/src/test/java/com/seaofnodes/simple/Chapter18Test.java index ecb2b934..b1caae96 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -49,9 +49,9 @@ public class Chapter18Test { } @Test public void testFunctionLocalConstantChains() { - String src = "val f = { int x -> x ? f(x-1)*305420988+x/305420988 : 1; }; "+ - "val g = { int x -> x ? g(x-1)/305420988+x*305420988 : 1; }; "+ - "return f(arg)+g(arg);"; + String src = "val f = { int x -> int prev=x-1; x ? f(prev)*305420988+x/305420988 : 1; }; "+ + "val g = { int x -> int prev=x-1; x ? g(prev)/305420988+x*305420988 : 1; }; "+ + "int n=arg; return f(n)+g(n);"; CodeGen code = new CodeGen(src); code.driver(CodeGen.Phase.Schedule,"riscv","SystemV"); var owners = new java.util.IdentityHashMap<CFGNode,Boolean>(); @@ -173,8 +173,8 @@ return x2; }; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return { sq}; return (Parm_x(sq,i64)*x); ]", code.print()); - assertEquals("{ ptr i64 -> i64 #[ 3]}", Eval2.eval(code, 3)); + assertEquals("Stop[ return { sq}; return (Parm_x(sq,i32)*x); ]", code.print()); + assertEquals("{ ptr i32 -> i64 #[ 3]}", Eval2.eval(code, 3)); } @Test @@ -184,10 +184,10 @@ return x2; var _sq = { int x -> x*x; }; -return _sq(arg)+_sq(3); +int n=arg; return _sq(n)+_sq(3); """); code.driver(Phase.LocalSched); - assertEquals("return ((arg*arg)+9);", code.print()); + assertEquals("return ((((arg<<32)>>32)*Sar)+9);", code.print()); assertEquals("13", Eval2.eval(code, 2)); } @@ -200,8 +200,8 @@ int cnt=1; // Global scope; exactly one of these return { -> cnt++; }; // Escape global-scope variable is OK """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return { ptr -> i64 #[ 3]}; return .cnt; ]", code.print()); - assertEquals("{ ptr -> i64 #[ 3]}", Eval2.eval(code, 0)); + assertEquals("Stop[ return { ptr -> i32 #[ 3]}; return .cnt; ]", code.print()); + assertEquals("{ ptr -> i32 #[ 3]}", Eval2.eval(code, 0)); } // Function scope test @@ -244,7 +244,7 @@ var fcn = arg ? { int x -> x*x; } : { int x -> x+x; }; return fcn(3); """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return #2; return (Parm_x($fun3,i64,3)*x); return (Parm_x($fun4,i64,3)<<1); ]", code.print()); + assertEquals("Stop[ return #2; return (Parm_x($fun3,i32,3)*x); return (Parm_x($fun4,i32,3)<<1); ]", code.print()); assertEquals("6", Eval2.eval(code, 0)); assertEquals("9", Eval2.eval(code, 1)); } @@ -252,10 +252,10 @@ 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 -> int prev=x-1; x <= 1 ? 1 : x*_fact(prev); }; int n=arg; return _fact(n);"); code.driver(CodeGen.Phase.TypeCheck); String ir = code.print(); - assertTrue(ir.equals("Stop[ return #2; return Phi(Region,1,(Parm_x(_fact,arg,(x-1))*#2)); ]") || + assertTrue(ir,ir.equals("Stop[ return #2; return Phi(Region,1,(Parm_x(_fact,((arg<<32)>>32),(((x-1)<<32)>>32))*#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)); @@ -270,7 +270,7 @@ return fcn(3); // 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);"); + CodeGen code = new CodeGen("val _fact = { int x -> int prev=x-1; x <= 1 ? 1.0 : x*_fact(prev); }; int n=arg; return _fact(n);"); code.driver(CodeGen.Phase.TypeCheck); assertEquals("1.0", Eval2.eval(code, 0)); assertEquals("1.0", Eval2.eval(code, 1)); @@ -281,7 +281,7 @@ return fcn(3); @Test public void testFcn5Minus() { - CodeGen code = new CodeGen("val _neg = { int x -> x ? -_neg(x-1) : 1; }; return _neg(arg);"); + CodeGen code = new CodeGen("val _neg = { int x -> int prev=x-1; x ? -_neg(prev) : 1; }; int n=arg; return _neg(n);"); code.driver(CodeGen.Phase.TypeCheck); assertEquals( "1", Eval2.eval(code, 0)); assertEquals("-1", Eval2.eval(code, 1)); @@ -324,14 +324,14 @@ return 2; {int -> int}? !_i2i = null; var _id = {{int->int} f-> return f;}; for(;;) { - if (_i2i) return _i2i(arg); + if (_i2i) { int n=arg; return _i2i(n); } var _x = {int i-> return i;}; arg = _x(3); _i2i = _id(_x); } """); code.driver(Phase.LocalSched); - assertEquals("return Phi(Loop,arg,3);", code.print()); + assertEquals("return ((Phi(Loop,arg,3)<<32)>>32);", code.print()); assertEquals("3", Eval2.eval(code, 0)); } @@ -407,7 +407,7 @@ for(;;) { return 0; """); try { code.driver(CodeGen.Phase.TypeCheck); fail(); } - catch( Exception e ) { assertEquals("Might be null calling { ptr i64 -> i64 #[ 3]}?",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null calling { ptr i32 -> i32 #[ 3]}?",e.getMessage()); } } @@ -419,7 +419,7 @@ val f = { int i, int j -> return i+j; }; return f(); """); try { code.driver(CodeGen.Phase.TypeCheck); fail(); } - catch( Exception e ) { assertEquals("Expecting 3 arguments, but found 1",e.getMessage()); } + catch( Exception e ) { assertEquals("Expecting 2 arguments, but found 0",e.getMessage()); } } @@ -457,9 +457,9 @@ return g(); public void testFcnMutRec() { CodeGen code = new CodeGen( """ -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); +val is_even = { int x -> int prev=x-1; x ? is_odd (prev) : true ; }; +val is_odd = { int x -> int prev=x-1; x ? is_even(prev) : false; }; +int n=arg; return is_even(n); """); code.driver(CodeGen.Phase.TypeCheck); assertEquals("1", Eval2.eval(code, 0)); @@ -496,11 +496,11 @@ for(;;) { {{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); +if (_i2i_noInline) { int n=arg; return _i2i_noInline(n); } return _f2f(_o)(1); """); code.driver(Phase.LocalSched); - assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(_i2i_noInline,i64); ]", code.print()); + assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(_i2i_noInline,i32); ]", code.print()); assertEquals("1", Eval2.eval(code, 2)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 50ca8922..50fecdfd 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -32,14 +32,14 @@ public class Chapter19Test { case "u16" -> arg & 0xffffL; case "i16" -> (long)(short)arg; case "u32" -> arg & 0xffffffffL; - case "i32" -> (long)(int)arg; + case "i32", "int" -> (long)(int)arg; case "f32" -> (double)(float)arg; case "flt" -> (double)arg; default -> arg; }; String msg = type+" array="+array+" arg="+arg; String printed = array - ? (type.equals("u8") ? ""+(char)(long)(Long)expected : (type.equals("int") ? "i64" : type)+"[ "+expected+"]") + ? (type.equals("u8") ? ""+(char)(long)(Long)expected : (type.equals("int") ? "i32" : type)+"[ "+expected+"]") : "Test.S{x="+expected+"}"; assertEquals(msg,printed,Eval2.eval(code,arg)); } @@ -118,8 +118,8 @@ val hashCode = { !String self -> hashCode(new String("Hello, World!")); """; CodeGen code = new CodeGen(src).driver(Phase.LocalSched); - assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(((Phi_hash<<5)-Phi_hash)+.[]))); 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)); + assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(((((Phi_hash<<5)-Phi_hash)+.[])<<32)>>32))); 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)+.[])<<32)>>32)))); ]", code.print()); + assertEquals("1498789909", Eval2.eval(code, 2)); } @Test @@ -251,7 +251,7 @@ hashCode(new String("Hello")); int arg1 = arg + 1; return arg1 / arg;"""); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (div,(inc,arg),arg);", code.print()); + assertEquals("return (div,(sari,(shli,(inc,arg))),arg);", code.print()); } @Test @@ -262,7 +262,7 @@ int arg1 = arg + 1; return arg1 * arg; """); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (mul,(inc,arg),arg);", code.print()); + assertEquals("return (mul,(sari,(shli,(inc,arg))),arg);", code.print()); } @Test @@ -272,7 +272,7 @@ int a = arg; return a + 2.0; """ ).driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (addf,(cvtf,arg),2.0f);", code.print()); + assertEquals("return (addf,(cvtf,(sari,(shli,arg))),2.0f);", code.print()); } @Test @@ -287,7 +287,7 @@ else { } return a;"""); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return Phi(Region,(addi,arg),(addi,arg));", code.print()); + assertEquals("return (sari,(shli,Phi(Region,(addi,arg),(addi,arg))));", code.print()); } @Test @@ -302,7 +302,7 @@ else a=b+1; return a+b;"""); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (add,(add,Phi(Region,(shli,arg),arg),arg),Phi(Region,4,5));", code.print()); + assertEquals("return (add,(sari,(shli,(inc,Phi(Region,arg,(sari,(shli,(addi,arg))))))),(sari,(shli,(add,Phi(Region,(sari,(shli,(inc,arg))),2),Phi(Region,sari,arg)))));", code.print()); } @Test @@ -314,7 +314,7 @@ for( int i=0; i<arg; i++ ) sum += i; return sum;"""); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return Phi(Loop,0,(add,Phi_sum,Phi(Loop,0,(inc,Phi_i))));", code.print()); + assertEquals("return Phi(Loop,0,(sari,(shli,(add,Phi_sum,Phi(Loop,0,(sari,(shli,(inc,Phi_i))))))));", code.print()); } @Test @@ -331,21 +331,21 @@ return new _S;"""); public void testLea1() { CodeGen code = new CodeGen("int x = arg/3; return arg+x+7;"); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (lea,arg,(div,arg,3));", code.print()); + assertEquals("return (lea,arg,(sari,(shli,(div,arg,3))));", code.print()); } @Test public void testLea2() { CodeGen code = new CodeGen("int x = arg/3; return arg+x*4+7;"); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (lea,arg,(div,arg,3));", code.print()); + assertEquals("return (lea,arg,(sari,(shli,(div,arg,3))));", code.print()); } @Test public void testLea3() { CodeGen code = new CodeGen("int x = arg/3; return x*4+arg;"); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (lea,arg,(div,arg,3));", code.print()); + assertEquals("return (lea,arg,(sari,(shli,(div,arg,3))));", code.print()); } @Test @@ -359,7 +359,7 @@ return new _S;"""); public void testAlloc3() { CodeGen code = new CodeGen("int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1]+3;"); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return .[];", code.print()); + assertEquals("return (addi,.[]);", code.print()); } @Test @@ -376,7 +376,7 @@ for( int i=0; i<ary#-1; i++ ) return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return .[];", code.print()); + assertEquals("return (add,.[],(muli,.[]));", code.print()); } @Test @@ -479,7 +479,7 @@ 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($fun3,[2-3]),x); return (shli,Parm_x($fun4,[2-3])); ]", code.print()); + assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,(sext32,Parm_x($fun3,[2-3])),sext32); return (shli,(sext32,Parm_x($fun4,[2-3]))); ]", code.print()); } @Test @@ -487,9 +487,9 @@ return _fcn(2)*10 + _fcn(3); CodeGen code = new CodeGen( """ val _sq = { int x -> x*x; }; -return _sq(arg) + _sq(3); +int n=arg; return _sq(n) + _sq(3); """); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); - assertEquals("return (addi,(mul,arg,arg));", code.print()); + assertEquals("return (addi,(mul,(sari,(shli,arg)),sari));", code.print()); } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/src/test/java/com/seaofnodes/simple/Chapter20Test.java index f5eb2b36..273d76ba 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -10,6 +10,7 @@ import static org.junit.Assert.*; public class Chapter20Test { @Test public void testAllocatorTopPhi() { com.seaofnodes.simple.codegen.RegAllocTestSupport.topPhi(); } + @Test public void testIndependentFunctions() { com.seaofnodes.simple.codegen.RegAllocTestSupport.independentFunctions(); } @Test public void testNeverReturnAllocation() { String[] sources = { @@ -141,7 +142,8 @@ val _sqrt = { int x -> guess = next; } }; -return _sqrt(arg) + _sqrt(arg+2); +int x=arg, y=arg+2; +return _sqrt(x) + _sqrt(y); """; testCPU(src,"x86_64_v2", "win64" ,38,null); testCPU(src,"riscv" , "SystemV",19,null); @@ -216,9 +218,9 @@ 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 mov(.[]);"); - testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));"); - testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"x86_64_v2", "SystemV",-1,"return (add,.[],(muli,.[]));"); + testCPU(src,"riscv" , "SystemV",9,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"arm" , "SystemV",13,"return (add,.[],(mul,.[],1000));"); } @@ -259,7 +261,7 @@ val hashCode = { !String self -> """; // Return the public hashCode wrapper; keep its helper private. - testCPU(src,"x86_64_v2", "SystemV",12,null); + testCPU(src,"x86_64_v2", "SystemV",28,null); testCPU(src,"riscv" , "SystemV",8,null); testCPU(src,"arm" , "SystemV",6,null); } @@ -267,7 +269,7 @@ val hashCode = { !String self -> @Test public void testCast() { String src = "struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;"; - testCPU(src,"x86_64_v2", "SystemV",3,null); + testCPU(src,"x86_64_v2", "SystemV",5,null); testCPU(src,"riscv" , "SystemV",6,null); testCPU(src,"arm" , "SystemV",6,null); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/src/test/java/com/seaofnodes/simple/Chapter21Test.java index e15c2581..c6d558eb 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -17,7 +17,7 @@ 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; };"+ + String src = "val float_to_int={ flt x -> i64 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, @@ -138,9 +138,10 @@ public class Chapter21Test { String src = """ val sum = { int n -> if( n<=0 ) return 1; - return sum(n-1)+n; + int prev=n-1; + return sum(prev)+n; }; - val run = { int n -> return sum(n)+sum(n+1); }; + val run = { int n -> int next=n+1; return sum(n)+sum(next); }; """; assertTrue("Ordinary calls must survive optimization",checkArmCalls(src,"run",4,27)>0); } @@ -157,7 +158,7 @@ public class Chapter21Test { buf=buf2; }; val add = { int e -> - if( len>=buf# ) grow(buf#*2); + if( len>=buf# ) { int size=buf#*2; grow(size); } buf[len++]=e; return self; }; @@ -323,9 +324,9 @@ 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 mov(.[]);"); - testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));"); - testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"x86_64_v2", "SystemV",-1,"return (add,.[],(muli,.[]));"); + testCPU(src,"riscv" , "SystemV",9,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"arm" , "SystemV",13,"return (add,.[],(mul,.[],1000));"); } @Test @@ -384,7 +385,7 @@ val hashCode = { !String self -> }; """; // Return the public hashCode wrapper; keep its helper private. - testCPU(src,"x86_64_v2", "SystemV",12,null); + testCPU(src,"x86_64_v2", "SystemV",28,null); testCPU(src,"riscv" , "SystemV",8,null); testCPU(src,"arm" , "SystemV",6,null); } @@ -423,7 +424,7 @@ val hashCode = { !String self -> : (self._hashCode = _hashCodeString(self)); }; """; - TestC.runC(src, "stringHash", "",12); + TestC.runC(src, "stringHash", "",28); } @Test public void testLoop2() { @@ -439,9 +440,9 @@ while(true) { } return i; """; - 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)));"); + testCPU(src,"x86_64_v2", "win64" ,0,"return (!=0)(sari,(shli,(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 ) << #32 ) >> #1056 );"); + testCPU(src,"arm" , "SystemV",0,"return (!=0)(asri,(lsli,(inc,Phi(Loop,0,Phi(Region,(!=0),(!=0),0)))));"); } @Test public void testNewtonExport() throws IOException { @@ -530,7 +531,7 @@ 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.runC(src, "sieve", sprimes, 186); + TestC.runC(src, "sieve", sprimes,344); // Evaluate on RISC5 emulator; expect return of an array of primes in // the simulated heap. diff --git a/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/src/test/java/com/seaofnodes/simple/Chapter22Test.java index e9a668a7..9bebee23 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -18,7 +18,7 @@ import static org.junit.Assert.*; public class Chapter22Test { @Test public void testNarrowCReturnsEmulated() { - String[] types = {"i8","u8","i16","u16","i32","u32","int"}; + String[] types = {"i8","u8","i16","u16","i32","u32","i64","int"}; long[] values = {0xa5a5a5a500000000L,0xa5a5a5a50000007fL,0xa5a5a5a500000080L, 0xa5a5a5a500007fffL,0xa5a5a5a500008000L,0xa5a5a5a57fffffffL, 0xa5a5a5a580000000L,0xa5a5a5a5ffffffffL}; @@ -35,7 +35,7 @@ public class Chapter22Test { long expected = switch(i) { case 0 -> (byte)value; case 1 -> value&255; case 2 -> (short)value; case 3 -> value&65535; - case 4 -> (int)value; case 5 -> value&0xffffffffL; + case 4, 7 -> (int)value; case 5 -> value&0xffffffffL; default -> value; }; byte[] image = new byte[1<<16]; @@ -70,7 +70,7 @@ public class Chapter22Test { {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"; + {i64} c_i64="C"; val test_i8={->return c_i8();}; val test_u8={->return c_u8();}; val test_i16={->return c_i16();}; @@ -344,7 +344,7 @@ val _sum = { i32[~] is -> // final array return _sum(is); """; CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); - assertEquals("return Phi(Loop,0,(Phi_sum+.[]));", code.print()); + assertEquals("return Phi(Loop,0,(((Phi_sum+.[])<<32)>>32));", code.print()); assertEquals("14", Eval2.eval(code, 0)); } @@ -354,7 +354,8 @@ return _sum(is); String src = """ // Echo stdin to stdout. -return sys.io.p( sys.io.stdin() ); +val input=sys.io.stdin(); +return input ? sys.io.p(input) : -1; """; TestC.runSF(src,"echo", "",0); diff --git a/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/src/test/java/com/seaofnodes/simple/Chapter23Test.java index df02d3b4..89e41efa 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter23Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter23Test.java @@ -279,7 +279,7 @@ return _s.peek('q'); CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); 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",31,null); }; @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/src/test/java/com/seaofnodes/simple/Chapter24Test.java index 2bc2ce7f..f4c4f12f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter24Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter24Test.java @@ -20,9 +20,9 @@ public class Chapter24Test { @Test public void testRecursiveWidening() { String[] srcs = { // Parm(x) must widen even though the recursive result is constant. - "val f = { int x, int limit -> x >= limit ? 0 : f(x+1,limit); }; return f(0,arg);", + "val f = { int x, int limit -> x >= limit ? 0 : f(++x,limit); }; int limit=arg; return f(0,limit);", // The growing result has its own cycle, which never crosses Parm(x). - "val f = { int x -> x <= 0 ? 0 : f(x-1)+1; }; return f(3);" + "val f = { int x -> x <= 0 ? 0 : f(--x)+1; }; return f(3);" }; for( int i=0; i<srcs.length; i++ ) for( long seed : new long[]{0,42,126} ) { @@ -32,6 +32,7 @@ public class Chapter24Test { } @Test public void testColdLoopSelfConflict() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coldLoopSelfConflict(); } + @Test public void testTiedLoopBackedges() { com.seaofnodes.simple.codegen.RegAllocTestSupport.tiedLoopBackedges(); } @Test public void testPrintingDeadFunction() throws Exception { var code = new CodeGen("return 0;").parse(); @@ -106,7 +107,7 @@ return (sq_noInline(2) <= score < sq_noInline(3)) ? 0 // Expected : 1; // Error """; - TestC.runSF( src, "stacked_r_3", "", 2); + TestC.runSF( src, "stacked_r_3", "",0); // Evaluate on RISC5 emulator EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_3", 0, 2, false); diff --git a/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/src/test/java/com/seaofnodes/simple/Chapter25Test.java index 5f615282..67e7f982 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter25Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter25Test.java @@ -142,8 +142,9 @@ public class Chapter25Test { CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}), "Client",""" val _other={int n -> n-1;}; - val inc={int n -> n+1;}; - return Lib.apply_noInline(inc,arg); + val inc={int n -> n+=1; return n;}; + int n=arg; + return Lib.apply_noInline(inc,n); """,987654321,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); var clz=(com.seaofnodes.simple.type.TypeStruct)Parser.TYPES.get("class:Lib"); int hidden=((com.seaofnodes.simple.type.TypeFunPtr)clz.field("_hidden_noInline")._t).fidx(); @@ -169,7 +170,7 @@ public class Chapter25Test { String[] sources={ "val _spin={ -> while(1) {} return 1; }; _spin(); return 2;", // Two callers require cloning, including the non-exiting arm. - "val _spin={int x -> if(x) while(1) {} return 7; }; return _spin(arg)+_spin(arg);" + "val _spin={int x -> if(x) while(1) {} return 7; }; int n=arg; return _spin(n)+_spin(n);" }; for( int i=0; i<sources.length; i++ ) { for( long seed : new long[]{1,123,987654321} ) { @@ -191,6 +192,9 @@ public class Chapter25Test { i8 small="C"; f64 fraction="C"; {int} bump="C"; + val extern_matches={i32 x -> counter==x;}; + val extern_less={i32 x -> x<counter;}; + val extern_seven={ -> counter==7;}; val test_global={ -> val before=counter; counter=before; @@ -471,9 +475,9 @@ public class Chapter25Test { // Elf files are sane - // Sys depends on io, libc, char, collections, and array utilities. + // Sys depends on io, libc, text, char, collections, and array utilities. assertArrayEquals(new String[]{"sys/adt", "sys/ary", - "sys/aryi64", "sys/aryu8", "sys/char", "sys/io", "sys/libc", "sys/scan"},sys_elf._deps); + "sys/aryi64", "sys/aryu8", "sys/char", "sys/io", "sys/libc", "sys/rt", "sys/scan", "sys/text"},sys_elf._deps); assertArrayEquals(new String[]{"sys"},sys_elf._units); assertSame("class:sys" ,sys_elf._clz._name); } @@ -496,7 +500,7 @@ public class Chapter25Test { @Test public void testHelloWorldDriver() throws Exception { String out = "build/objs/helloWorldDriver/"; - Simple.main(new String[]{"-L",SYS_BLDDIR,"-o",out,"--norun","src/test/smp/examples/A_helloWorld.smp"}); + Simple.main(new String[]{"-L",SYS_BLDDIR,"-o",out,"--norun","docs/examples/A_helloWorld.smp"}); assertTrue(Files.isRegularFile(Path.of(out,"A_helloWorld.o"))); } @@ -507,7 +511,7 @@ public class Chapter25Test { try( PrintStream capture = new PrintStream(output,true,StandardCharsets.UTF_8) ) { System.setOut(capture); Simple.main(new String[]{"-L",SYS_BLDDIR,"-o","build/objs/helloWorldDriverLibFile/", - "src/test/smp/examples/A_helloWorld.smp"}); + "docs/examples/A_helloWorld.smp"}); } finally { System.setOut(saved); } @@ -554,7 +558,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2); @Test public void testBubbles() throws IOException { - String src = Files.readString( Path.of("src/test/smp/examples/BubbleSort.smp")); + 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", @@ -575,7 +579,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2); @Test public void testCapitalize() throws IOException { - String src = Files.readString(Path.of("src/test/smp/examples/Capitalize.smp")); + 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")); @@ -588,7 +592,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2); @Test public void testDijkstra() throws IOException { - String src = Files.readString(Path.of("src/test/smp/examples/Dijkstra.smp")); + 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); @@ -603,7 +607,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2); @Test public void testFileIO() throws IOException { - String src = Files.readString(Path.of("src/test/smp/examples/FileIO.smp")); + 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/ClassInitDependenciesTest.java b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java index dae64fb0..8f1d1ee8 100644 --- a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java +++ b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java @@ -188,7 +188,7 @@ public class ClassInitDependenciesTest { "B","int value=A.value;"),"A -> B -> A"); cycle("A",Map.of("A","int value=get(); val get={ -> B.value; };", "B","int value=A.value;"),"A -> B -> A"); - cycle("A",Map.of("A","int value=0; if(arg) value=B.value;", + cycle("A",Map.of("A","int value=0; if(((arg<<32)>>32)) value=B.value;", "B","int value=A.value;"),"A -> B -> A"); cycle("A",Map.of("A","val f=arg ? { -> B.value; } : { -> 0; }; int value=f();", "B","int value=A.value;"),"A -> B -> A"); @@ -237,7 +237,7 @@ public class ClassInitDependenciesTest { } @Test public void testRecursionAndNativeCalls() throws Exception { - CodeGen code=compile("A",Map.of("A","val f={int n -> n ? f(n-1) : B.value; }; int value=f(arg);", + CodeGen code=compile("A",Map.of("A","val f={int n -> n ? f(--n) : B.value; }; int value=f(((arg<<32)>>32));", "B","int value=arg|1;"),123); assertTrue(touches(code,new ClassInitDependencies(code),"Root/A","Root/B")); code=compile("A",Map.of("A","{int} native=\"C\"; return native();"),123); @@ -386,8 +386,8 @@ public class ClassInitDependenciesTest { String message=ex.getMessage(); assertCycle(message,"Root.A -> Root.B -> Root.A"); // Inlining must keep Helper's source identity, not substitute A. - assertTrue(message,message.contains("Helper.smp:2:4")); - assertTrue(message,message.contains(" B.value;\n ---^")); + assertTrue(message,message.contains("Helper.smp:2:5")); + assertTrue(message,message.contains(" B.value;\n ____^")); if( name.endsWith("noInline") ) { assertTrue(message,message.contains("A.smp:1:31")); assertTrue(message,message.contains("int value=Helper.read_noInline();")); @@ -401,7 +401,7 @@ public class ClassInitDependenciesTest { CodeGen code=new CodeGen(src).driver(CodeGen.Phase.Parse); CompUnit cu=code._compunits.get("Test"); int pos=src.substring(0,src.indexOf('1')).getBytes(java.nio.charset.StandardCharsets.UTF_8).length; - assertEquals("Test.smp:2:9: error: example\n return 1;\n ---------------^",SourceError.format(cu,pos,"example")); + assertEquals("Test.smp:2:16: error: example\n return 1;\n _______________^",SourceError.format(cu,pos,"example")); assertEquals("Test.smp:3:1: error: example\n \n ^",SourceError.format(cu,src.getBytes(java.nio.charset.StandardCharsets.UTF_8).length,"example")); } @@ -414,8 +414,8 @@ public class ClassInitDependenciesTest { @Test public void testInitializationMemory() throws Exception { String[] sources={ "int value=B.value+B.value;", - "int value; if(arg) value=B.value; else value=B.value+1; value=value+B.value;", - "int value; if(arg) value=B.value; value=value+B.value;" + "int value; if(((arg<<32)>>32)) value=B.value; else value=B.value+1; value=value+B.value;", + "int value; if(((arg<<32)>>32)) value=B.value; value=value+B.value;" }; int[] counts={1,2,2}; for( long seed : new long[]{1,123,987654321} ) diff --git a/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java b/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java index 285d4293..b9d65b1c 100644 --- a/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java +++ b/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java @@ -23,7 +23,7 @@ public class CompoundAssignmentTest { @Test public void testLocals() { for (String op : OPS) for (long arg : new long[]{-8, 0, 5, 64}) { - long expected = apply(op, arg, 3); + long expected = (int)apply(op, arg, 3); check("int x=arg; int r=(x " + op + " 3); return x*10+r;", arg, expected*11); } @@ -31,7 +31,7 @@ public class CompoundAssignmentTest { @Test public void testRhsOnce() { for (String op : OPS) { - long expected = apply(op, 5, 1); + long expected = (int)apply(op, 5, 1); check("int x=arg; int count=0; int r=(x " + op + " (count=count+1)); return count*1000+x*10+r;", 5, 1000+expected*11); } @@ -42,7 +42,7 @@ public class CompoundAssignmentTest { check("u8 x=255; x ^= 256; return x;", 0, 255); check("int x=1; int y=3; x |= y <<= 2; return x*100+y;", 0, 1312); check("int x=-8; x >>= 65; return x;", 0, -4); - check("int x=-8; x >>>= 65; return x;", 0, Long.MAX_VALUE-3); + check("int x=-8; x >>>= 65; return x;", 0, -4); } @Test public void testExistingOperators() { @@ -73,7 +73,7 @@ public class CompoundAssignmentTest { @Test public void testFields() { for (String op : OPS) { - long expected = apply(op, -8, 3); + long expected = (int)apply(op, -8, 3); check("struct S { int x; }; !S !s=new S; s.x=arg; int r=(s.x " + op + " 3); return s.x*10+r;", -8, expected*11); } @@ -81,7 +81,7 @@ public class CompoundAssignmentTest { @Test public void testArrayAddressAndRhsOnce() { for (String op : OPS) { - long expected = apply(op, 5, 1); + long expected = (int)apply(op, 5, 1); check("int[] !a=new int[2]; a[0]=arg; int i=0; int count=0; " + "int r=(a[i++] " + op + " (count=count+1)); " + "return i*1000+count*100+a[0]*10+r;", 5, 1100+expected*11); diff --git a/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java b/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java index e544fcad..1eb64610 100644 --- a/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java +++ b/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java @@ -30,7 +30,7 @@ public class ConstructionGroupTest { @Test public void testCyclesAndIndependentAllocations() { String src = parent("val detached = new Child(null); payload = n+read(detached); child = new Child(self);")+ - "val p = new Parent(arg); val local = new Child(p); val par = local.parent; return (par ? par.payload : 0)+p.child.value;"; + "val p = new Parent(((arg<<32)>>32)); val local = new Child(p); val par = local.parent; return (par ? par.payload : 0)+p.child.value;"; for( long seed : new long[]{1,123,987654321} ) { CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck); assertEquals("17",Eval2.eval(code,7)); @@ -44,7 +44,7 @@ public class ConstructionGroupTest { // Child's own slots are initialized, even though its parent remains // partial. Reading that slot does not complete the parent reference. String src = parent("child = new Child(self); payload = child.parent==null;")+ - "return new Parent(arg).payload;"; + "return new Parent(((arg<<32)>>32)).payload;"; assertEquals("0",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),0)); } @@ -73,7 +73,7 @@ public class ConstructionGroupTest { } @Test public void testAliasesAndLoopBackedges() { - String src = parent("val alias = self; alias.payload = n; child = new Child(alias);")+"return new Parent(arg).child.value;"; + String src = parent("val alias = self; alias.payload = n; child = new Child(alias);")+"return new Parent(((arg<<32)>>32)).child.value;"; assertEquals("5",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),3)); rejects(parent("payload = 1; child = new Child(self); Child? !c = null; while(n--) { if(c) read(c); c = child; }")+ "return 0;","ordinary function"); @@ -116,7 +116,7 @@ public class ConstructionGroupTest { head = p; }; }; - val root = new Root(arg); + val root = new Root(((arg<<32)>>32)); return root.value + root.head.value; """; CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); diff --git a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java b/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java index 71a281b5..5a20388b 100644 --- a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java +++ b/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java @@ -28,7 +28,7 @@ public class EscapeFlowTest { // Start memory rather than a frozen declaration constant. @Test public void testUnknownCallerCallback() { CodeGen code = new CodeGen(""" - val _hidden_noInline = { int n -> return n+2; }; + val _hidden_noInline = { int n -> int result=n+2; return result; }; val apply_noInline = { {int->int} f,int n -> return f(n); }; return apply_noInline; """).parse(); @@ -42,16 +42,16 @@ public class EscapeFlowTest { assertTrue(callback.in(1) instanceof ProjNode); TypeFunPtr type = (TypeFunPtr)callback._type; assertTrue(XInt.isHigh(type.fidxs())); - assertSame(TypeInteger.BOT,apply.ret().expr()._type); + assertSame(TypeInteger.I32,apply.ret().expr()._type); // Only <clinit> and apply escape; forwarding #self must not expose _hidden. assertEquals("Stop[ return { apply_noInline}; return #2; ]",code.print()); } @Test public void testNativeCallbackRoundTrip() { CodeGen code = new CodeGen(""" - val _callback_noInline = { int n -> return n+1; }; + val _callback_noInline = { int n -> int result=n+1; return result; }; { {int->int} -> {int->int} } bounce = "C"; - return bounce(_callback_noInline)(arg); + return bounce(_callback_noInline)(((arg<<32)>>32)); """).driver(CodeGen.Phase.TypeCheck); FunNode callback = named(code,"_callback_noInline"); assertTrue(callback.unknownCallers()); @@ -87,7 +87,7 @@ public class EscapeFlowTest { @Test public void testPublicAllocator() { CodeGen code = new CodeGen(""" struct Box { int n; new Box = { int x -> n=x; }; }; - return new Box(arg).n; + return new Box(((arg<<32)>>32)).n; """).parse(); FunNode allocator = named(code,"Test.Box.<new>"); int[] helpers = XInt.EMPTY; diff --git a/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java b/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java index 66fdacc3..bdb71fc6 100644 --- a/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java +++ b/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java @@ -7,7 +7,7 @@ import static org.junit.Assert.*; public class FinalConstructionTest { private static final String LOOP = """ - val step={int x -> x*2+1;}; + val step={i64 x -> x*2+1;}; struct C { val x=1; new C={int n -> @@ -16,7 +16,7 @@ public class FinalConstructionTest { }; val get={->x;}; }; - return new C(arg).get(); + return new C(((arg<<32)>>32)).get(); """; @Test public void testFinalConstructorFields() throws java.io.IOException { @@ -27,10 +27,10 @@ public class FinalConstructionTest { // Writes are ordinary Stores and may remain inside the loop. String[] sources={ "struct C { val x=1; new C={->x=1; x=2;}; }; return new C().x;", - "struct C { int ~x; new C={int n -> self.x=1; for(int i=0;i<n;i++) self.x*=2;}; }; return new C(arg).x;", - "struct C { val x=1; new C={int n -> for(int i=0;i<n;i++) ++x;}; }; return new C(arg).x;", + "struct C { int ~x; new C={int n -> self.x=1; for(int i=0;i<n;i++) self.x*=2;}; }; return new C(((arg<<32)>>32)).x;", + "struct C { val x=1; new C={int n -> for(int i=0;i<n;i++) ++x;}; }; return new C(((arg<<32)>>32)).x;", "struct C { val x=1.5; new C={->x=2.5;}; }; return new C().x;", - "struct C { val x=\"a\"; new C={int n -> for(int i=0;i<n;i++) x=\"b\";}; }; return new C(arg).x[0];", + "struct C { val x=\"a\"; new C={int n -> for(int i=0;i<n;i++) x=\"b\";}; }; return new C(((arg<<32)>>32)).x[0];", "struct C { val x=new int[1]; new C={->x=new int[2]; x=new int[3];}; }; return new C().x#;" }; String[] results={"2","8","4","2.5","98","3"}; diff --git a/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java b/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java index d8f7ef34..155ebac3 100644 --- a/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java +++ b/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java @@ -68,8 +68,8 @@ public class IncrementalBuildTest { @Test public void testSourceChangeAndCycle() throws Exception { Path root=fixture("bundle-"); Path src=root.resolve("src"), obj=root.resolve("obj"); - write(src,"App/A","val value={int n -> n ? B.value(n-1) : 1; };",1000); - write(src,"App/B","val value={int n -> n ? A.value(n-1) : 5; };",1000); + write(src,"App/A","val value={int n -> n ? B.value(--n) : 1; };",1000); + write(src,"App/B","val value={int n -> n ? A.value(--n) : 5; };",1000); write(src,"App/Main","return A.value(1);",1000); compile(src,obj,"App/A"); assertEquals("One build timestamp for the cycle",Files.getLastModifiedTime(obj.resolve("App/A.o")), @@ -82,7 +82,7 @@ public class IncrementalBuildTest { assertNull(code._compunits.get("App/A")._src); assertNull(code._compunits.get("App/B")._src); - write(src,"App/B","val value={int n -> n ? A.value(n-1) : 7; };",4000); + write(src,"App/B","val value={int n -> n ? A.value(--n) : 7; };",4000); code=client(src,obj); assertEquals("7",Eval2.eval(code,0)); assertNotNull(code._compunits.get("App/A")._src); diff --git a/src/test/java/com/seaofnodes/simple/Int32Test.java b/src/test/java/com/seaofnodes/simple/Int32Test.java new file mode 100644 index 00000000..bea7c116 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Int32Test.java @@ -0,0 +1,94 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import com.seaofnodes.simple.parser.Parser; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeStruct; +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Int32Test { + @Test public void testAliasAndLayout() { + new CodeGen("struct Pair { int x; int y; }; return new Pair;") + .driver(CodeGen.Phase.TypeCheck); + assertSame(TypeInteger.I32,Parser.INIT_TYPES.get("int")); + var pair=(TypeStruct)Parser.TYPES.get("Test.Pair"); + assertSame(TypeInteger.I32,pair.field("x")._t); + assertEquals(8,pair.size()); + assertEquals(4,Math.abs(pair.offset(1)-pair.offset(0))); + } + + @Test public void testNarrowAssignmentsAndUpdates() { + for(String source:new String[]{ + "int x=arg; return x;", + "int x=0; x=arg; return x;", + "struct S { int x; }; val s=new S; s.x=arg; return s.x;", + "val a=new int[2]; a[1]=arg; return a[1];" + }) { + var code=new CodeGen(source).driver(CodeGen.Phase.TypeCheck); + for(long x:new long[]{0,2147483647L,2147483648L,-2147483649L,Long.MAX_VALUE,Long.MIN_VALUE}) + assertEquals(source,Integer.toString((int)x),Eval2.eval(code,x)); + } + var increment=new CodeGen("int x=arg; x++; return x;").driver(CodeGen.Phase.TypeCheck); + assertEquals("-2147483648",Eval2.eval(increment,2147483647)); + var compound=new CodeGen("int x=arg; x+=2; return x;").driver(CodeGen.Phase.TypeCheck); + assertEquals("-2147483647",Eval2.eval(compound,2147483647)); + } + + @Test public void testWideExpressionsAndInference() { + for(String source:new String[]{ + "int x=2147483647; return x+1;", + "var x=2147483647; x++; return x;", + "val x=2147483648; return x;", + "i64 x=2147483648; return x;" + }) assertEquals(source,"2147483648",Eval2.eval(new CodeGen(source).driver(CodeGen.Phase.TypeCheck),0)); + } + + @Test public void testLoopBoundSurvivesWidening() { + for(String type:new String[]{"int","u32"}) { + String source="val f_noInline={"+type+" x -> x;}; "+type+" x=0; while(x<arg) x++; return f_noInline(x);"; + for(long seed:new long[]{1,123,456}) { + var code=new CodeGen(source,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("7",Eval2.eval(code,7)); + } + } + var high=new CodeGen("val f_noInline={u32 x -> x;}; u32 x=2147483646; while(x<arg) x++; return f_noInline(x);") + .driver(CodeGen.Phase.TypeCheck); + assertEquals("2147483650",Eval2.eval(high,2147483650L)); + var wrap=new CodeGen("val f_noInline={u32 x -> x;}; u32 x=4294967294; while(x) x++; return f_noInline(x);") + .driver(CodeGen.Phase.TypeCheck); + assertEquals("0",Eval2.eval(wrap,0)); + } + + @Test public void testNativeLayoutAndParameters() throws IOException { + TestC.runC(""" + val int_echo={int x -> x;}; + val int_wrap={int x -> x++; return x;}; + val wide_echo={i64 x -> x;}; + val narrow_sum={i8 a,u8 b,i16 c,u16 d,int e,u32 f,int g,u32 h -> a+b+c+d+e+f+g+h;}; + struct Pair { int x; int y; }; + val int_pair={ -> val p=new Pair; p.x=-1; p.y=2147483647; return p; }; + val int_array={ -> val a=new int[3]; a[0]=-1; a[1]=2147483647; a[2]=-2147483648; return a; }; + ""","int32","ok\n",-1); + } + + @Test public void testNarrowParameterBackends() throws IOException { + long input=0xa5a5a5a580008080L; + String[] types={"i8","u8","i16","u16","int","u32"}; + long[] expected={(byte)input,input&255,(short)input,input&65535,(int)input,input&0xffffffffL}; + for(int i=0; i<types.length; i++) { + String source="val probe={"+types[i]+" x -> x;};"; + var a=TestArm64.build("probe",source,0,-1,false); + a.regs[arm.X0]=input; + assertEquals(0,a.step(1000)); + assertEquals(types[i],expected[i],a.regs[arm.X0]); + var r=TestRisc5.build(source,"probe",0,-1,false); + r.regs[riscv.A0]=input; + assertEquals(0,r.step(1000)); + assertEquals(types[i],expected[i],r.regs[riscv.A0]); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/src/test/java/com/seaofnodes/simple/MergeSortTest.java index f5d3d146..b177de23 100644 --- a/src/test/java/com/seaofnodes/simple/MergeSortTest.java +++ b/src/test/java/com/seaofnodes/simple/MergeSortTest.java @@ -18,8 +18,9 @@ public class MergeSortTest { """ val merge_sort = { int[] a -> int[] !b = new int[a#]; - _copy_array (a, 0, a#, b); - _split_merge(a, 0, a#, b); + int len=a#; + _copy_array (a, 0, len, b); + _split_merge(a, 0, len, b); }; val _split_merge = { int[] !b, int begin, int end, int[] a -> @@ -57,16 +58,16 @@ val _copy_array = { int[] !a, int begin, int end, int[] b -> for( int prime : primes ) sb.p(prime).p(", "); String sprimes = sb.p("]").toString(); - TestC.runC(src, "sort", sprimes, 34); + TestC.runC(src, "sort", sprimes,70); 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 + R5._heap += (primes.length+1)*4; // 25 primes, and length R5.st4(ps,primes.length); for( int i=0; i<primes.length; i++ ) - R5.st8(ps+(primes.length-1-i+1)*8,primes[i]); + R5.st4(ps+(primes.length-1-i+1)*4,primes[i]); // Pass array base when calling `merge_sort` R5.regs[riscv.A0] = ps; // Array base to be sorted @@ -75,7 +76,7 @@ val _copy_array = { int[] !a, int begin, int end, int[] b -> assertEquals(primes.length,R5.ld4s(ps)); for( int i=0; i<primes.length; i++ ) - assertEquals(primes[i], R5.ld8(ps+(i+1)*8)); + assertEquals(primes[i], R5.ld4s(ps+(i+1)*4)); // TODO: replace with ARM Eval Chapter21Test.testCPU(src,"arm", "SystemV",36,null); diff --git a/src/test/java/com/seaofnodes/simple/MinusNodeTest.java b/src/test/java/com/seaofnodes/simple/MinusNodeTest.java index f7fbde03..1b120e6e 100644 --- a/src/test/java/com/seaofnodes/simple/MinusNodeTest.java +++ b/src/test/java/com/seaofnodes/simple/MinusNodeTest.java @@ -57,7 +57,7 @@ public class MinusNodeTest { } @Test public void testRecursiveFloat() { - CodeGen code = new CodeGen("val _neg = { int x -> x ? -_neg(x-1) : 1.0; }; return _neg(arg);") + CodeGen code = new CodeGen("val _neg = { int x -> x ? -_neg(--x) : 1.0; }; return _neg(((arg<<32)>>32));") .driver(CodeGen.Phase.TypeCheck); for( int i=0; i<4; i++ ) assertEquals((i&1)==0 ? "1.0" : "-1.0",Eval2.eval(code,i)); diff --git a/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java b/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java new file mode 100644 index 00000000..2b7eea78 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java @@ -0,0 +1,191 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.parser.Parser; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import static org.junit.Assert.*; + +public class ParserDiagnosticTest { + private static Parser.ParseException rejected(String source) { + try { + new CodeGen(source).driver(CodeGen.Phase.Parse); + throw new AssertionError("Accepted invalid source"); + } catch( Parser.ParseException e ) { + return e; + } + } + + private static void diagnostic(String source, int line, int column, String message) { + var error=rejected(source); + assertEquals(message,error.getMessage()); + assertEquals("Test.smp:"+line+":"+column+": error: "+message + +"\n "+source.split("\n",-1)[line-1]+"\n "+"_".repeat(column-1)+"^",error.diagnostic()); + } + + @Test public void testNumericLiteralErrors() { + diagnostic("return 9223372036854775808;",1,8,"Integer literal is outside the signed 64-bit range"); + diagnostic("return 1e;",1,8,"Invalid floating-point literal '1e'"); + diagnostic("return 1.2.3f;",1,8,"Invalid floating-point literal '1.2.3f'"); + diagnostic("return 012;",1,8,"Syntax error: integer values cannot start with '0'"); + } + + @Test public void testUnclosedCommentsAndStrings() { + diagnostic("/* never closed",1,1,"Unclosed block comment"); + diagnostic("return 0; /*",1,11,"Unclosed block comment"); + diagnostic("return \"abc\n",1,8,"Unclosed string"); + diagnostic("return \"abc\\",1,8,"Unclosed string"); + // A comment at byte zero used to index -1; a final // was not recognized. + new CodeGen("/* ok */ return 0; //").driver(CodeGen.Phase.Parse); + } + + @Test public void testCharacterLiteralsAndEscapes() { + diagnostic("return '';",1,8,"Empty character literal"); + diagnostic("return 'ab';",1,8,"Character literal must contain exactly one byte"); + diagnostic("return 'a ';",1,8,"Character literal must contain exactly one byte"); + diagnostic("return '",1,8,"Unclosed character literal"); + diagnostic("return 'a\n",1,8,"Unclosed character literal"); + diagnostic("return '\\q';",1,9,"Unknown character escape \\q"); + diagnostic("return \"\\q\";",1,9,"Unknown string escape \\q"); + new CodeGen("return ' ' + '\\'' + '\\\\';").driver(CodeGen.Phase.Parse); + } + + @Test public void testDeferredLiteralErrors() { + diagnostic("struct S {\n new S={ ->\n val x='ab';\n };\n};",3,9, + "Character literal must contain exactly one byte"); + diagnostic("struct S {\n new S={ ->\n val x=\"abc",3,9,"Unclosed string"); + diagnostic("for(int i=0; i<1; i+='ab') {}",1,22, + "Character literal must contain exactly one byte"); + diagnostic("struct S {\n new S={ ->\n val x=\"outer %(\"bad\\q\")\";\n };\n};",3,22, + "Unknown string escape \\q"); + } + + @Test public void testIdentifierAndKeywordLocations() { + diagnostic("int = 1;",1,5,"Expected an identifier, found '='"); + diagnostic("int true=1;",1,5,"Expected an identifier, found 'true'"); + diagnostic("int",1,4,"Expected an identifier, found 'EOF'"); + diagnostic("return if;",1,8,"Syntax error, expected `an identifier or expression` but found `if`"); + diagnostic("\n break;",2,3,"No active loop for a break or continue"); + diagnostic("\n continue;",2,3,"No active loop for a break or continue"); + } + + @Test public void testFinalAssignmentLocations() { + diagnostic("val x=1;\n x=2;",2,3,"Cannot reassign final 'x'"); + diagnostic("val x=1;\n x++;",2,3,"Cannot reassign final 'x'"); + diagnostic("val x=1;\n ++x;",2,5,"Cannot reassign final 'x'"); + } + + @Test public void testParameterAndConstructorLocations() { + diagnostic("val f={int x, int x -> x;};",1,19,"Duplicate parameter 'x'"); + diagnostic("val f={int ^x -> x;};",1,12,"'^' requires a struct parameter"); + diagnostic("struct S { new Wrong={ -> }; };",1,16,"Constructor name 'Wrong' must match type name 'S'"); + diagnostic("int? x;",1,4,"Type i32 cannot be null"); + diagnostic("return new int;",1,12,"Cannot allocate a i32"); + } + + @Test public void testMemberLocation() { + diagnostic("struct S { int x; };\nval s=new S;\nreturn s.missing;",3,10, + "Accessing unknown field 'missing' from '*Test.S'"); + diagnostic("struct S { int x; };\nval s=new S;\nreturn s.;",3,10,"Expected an identifier, found ';'"); + } + + @Test public void testInitializerAndOperatorLocations() { + diagnostic("val x = null;",1,9,"Cannot infer a type from null; declare a nullable type explicitly"); + diagnostic("u8[] bytes=\"C\";",1,6,"C data binding requires an integer or float type"); + diagnostic("return 1 < 2 > 3;",1,14,"Mixing relational directions in a chained relational test"); + var shift=rejected("return 1 << 2.0;"); + assertTrue(shift.getMessage(),shift.getMessage().startsWith("Cannot '<<'")); + assertEquals("Test.smp:1:10: error: "+shift.getMessage() + +"\n return 1 << 2.0;\n _________^",shift.diagnostic()); + } + + @Test public void testCLILexicalErrorsWithoutStackTrace() throws Exception { + checkCLI("return 9223372036854775808;", + ":1:8: error: Integer literal is outside the signed 64-bit range" + +"\n return 9223372036854775808;\n _______^\n"); + checkCLI("/* missing close", + ":1:1: error: Unclosed block comment\n /* missing close\n ^\n"); + } + + @Test public void testMissingInitializer() { + var error=rejected("// declaration\n\nval _validMove() {\n"); + assertEquals("Syntax error, expected `=expression` but found `(`",error.getMessage()); + assertEquals("Test.smp:3:15: error: "+error.getMessage() + +"\n val _validMove() {\n ______________^",error.diagnostic()); + } + + @Test public void testFoundIdentifierPointsAtItsStart() { + var error=rejected("int x=1\nreturn x;"); + assertEquals("Test.smp:2:1: error: Syntax error, expected `;` but found `return`" + +"\n return x;\n ^",error.diagnostic()); + } + + @Test public void testEOF() { + var error=rejected("return 1"); + assertEquals("Test.smp:1:9: error: Syntax error, expected `;` but found `EOF`" + +"\n return 1\n ________^",error.diagnostic()); + } + + @Test public void testCRLFTabsAndUTF8() { + var error=rejected("// π\r\n\tint x=1\r\n\treturn x;"); + assertEquals("Test.smp:3:9: error: Syntax error, expected `;` but found `return`" + +"\n return x;\n ________^",error.diagnostic()); + var unicode=rejected("val x=\"π\" return 1;"); + assertEquals("Test.smp:1:11: error: Syntax error, expected `;` but found `return`" + +"\n val x=\"π\" return 1;\n __________^",unicode.diagnostic()); + } + + @Test public void testDeferredForUpdateLocation() { + // The update is parsed after the body, so lexer line counters can be stale. + var error=rejected("for(int i=0; i<3; i+=) {\n int x=i;\n}\nreturn 0;"); + assertEquals("Test.smp:1:22: error: Syntax error, expected `an identifier or expression` but found `)`" + +"\n for(int i=0; i<3; i+=) {\n _____________________^",error.diagnostic()); + } + + @Test public void testCLIReportsSourceWithoutStackTrace() throws Exception { + checkCLI("// declaration\nval _validMove() {\n", + ":2:15: error: Syntax error, expected `=expression` but found `(`" + +"\n val _validMove() {\n ______________^\n"); + } + + @Test public void testUndefinedNameKeepsItsUseSite() { + var error=rejected("val make={ -> val board=new u8[9]; board; };\n" + +"val read={ ->\n return board[0];\n};\nreturn read();"); + assertEquals("Test.smp:3:12: error: Undefined name 'board'" + +"\n return board[0];\n ___________^",error.diagnostic()); + } + + @Test public void testUndefinedAssignmentPointsAtName() { + var error=rejected("\n board = 1;"); + assertEquals("Test.smp:2:5: error: Undefined name 'board'" + +"\n board = 1;\n ____^",error.diagnostic()); + } + + @Test public void testCLIUndefinedNameWithoutStackTrace() throws Exception { + checkCLI("// unresolved\nreturn board[0];\n", + ":2:8: error: Undefined name 'board'" + +"\n return board[0];\n _______^\n"); + } + + private static void checkCLI(String text, String expected) throws Exception { + Path dir=Files.createDirectories(Path.of("build/diagnostics/source with spaces")); + Path source=dir.resolve("Bad.smp").toAbsolutePath(); + Files.writeString(source,text); + String java=Path.of(System.getProperty("java.home"),"bin","java").toString(); + Process child=new ProcessBuilder(java,"-ea","-cp",System.getProperty("java.class.path"), + Simple.class.getName(),"--norun","-o","build/diagnostics/objects/",source.toString()).start(); + try { + assertTrue("Compiler did not exit",child.waitFor(20,TimeUnit.SECONDS)); + assertEquals(1,child.exitValue()); + assertEquals("",new String(child.getInputStream().readAllBytes(),StandardCharsets.UTF_8)); + String diagnostic=new String(child.getErrorStream().readAllBytes(),StandardCharsets.UTF_8) + .replace("\r\n","\n"); + assertEquals(source+expected,diagnostic); + } finally { + child.destroyForcibly(); + } + } +} diff --git a/src/test/java/com/seaofnodes/simple/PartialReadTest.java b/src/test/java/com/seaofnodes/simple/PartialReadTest.java index a6230b69..427096e5 100644 --- a/src/test/java/com/seaofnodes/simple/PartialReadTest.java +++ b/src/test/java/com/seaofnodes/simple/PartialReadTest.java @@ -11,7 +11,7 @@ public class PartialReadTest { private static String program(String body, String helper) { return "struct _C { int~ x; int~ y; new _C = { int n -> " + body + " }; }; val _read_noInline = { _C ^p -> " + helper + - " }; return new _C(arg).y;"; + " }; return new _C(((arg<<32)>>32)).y;"; } private static void rejects(String source, String message) { try { @@ -108,7 +108,7 @@ public class PartialReadTest { _Child child; new _Parent = { int n -> x=n; child=new _Child(self); }; }; - val p=new _Parent(arg); return p.child.seen+p.child.parent.x; + val p=new _Parent(((arg<<32)>>32)); return p.child.seen+p.child.parent.x; """; assertEquals("22",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),11)); rejects(src.replace("x=n; child=new _Child(self);","child=new _Child(self); x=n;"),"might not be initialized"); @@ -127,7 +127,7 @@ public class PartialReadTest { new _C = { int n -> data=new int[1]; data[0]=n; y=_read_noInline(self); }; }; val _read_noInline={ _C ^p -> return p.data[0]; }; - return new _C(arg).y; + return new _C(((arg<<32)>>32)).y; """; assertEquals("23",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),23)); // A set reference slot is not proof that its referent is initialized. diff --git a/src/test/java/com/seaofnodes/simple/RuntimeArgsTest.java b/src/test/java/com/seaofnodes/simple/RuntimeArgsTest.java new file mode 100644 index 00000000..9086e00c --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/RuntimeArgsTest.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; + +public class RuntimeArgsTest extends TestC { + @Test public void testNativeArguments() throws IOException { + runArgs(""" + val main={ u8[~][] args -> + if(args#!=4 || args[0]#==0) return 1; + if(args[1]#!=0) return 2; + val words=args[2]; + if(words#!=9 || words[3]!=' ' || words[8]!='s') return 3; + val longArg=args[3]; + if(longArg#!=4096) return 4; + for(int i=0; i<longArg#; i++) if(longArg[i]!='x') return 5; + return 0; + }; + ""","runtimeArgs",null,CALL_CONVENTION,"",-1,"","two words","x".repeat(4096)); + } + + @Test public void testArgumentCopiesAndEmptyVector() throws IOException { + String exe="build/objs/runtimeArgsCopy"+(OS.startsWith("Windows") ? ".exe" : ""); + linkExe(runtimeObject("simple_args"),OS.startsWith("Windows") ? "ms_abi" : "sysv_abi", + C_DRIVERS_DIR+"runtime_args.c",null,exe); + assertEquals("ok\n",exec(TEST_TIMEOUT_SECONDS,exe)); + } +} diff --git a/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java index 88c11101..447025f6 100644 --- a/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java +++ b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java @@ -23,7 +23,7 @@ public class SelectiveLoadingTest { new CodeGen(src.toString(),obj.toString(),null,"Lib",null,123,true,TypeInteger.BOT) .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); CodeGen code = new CodeGen(null,null,new Ary<>(new String[]{obj.toString()}), - "Use","return Lib.sum_noInline(arg);",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); + "Use","return Lib.sum_noInline(((arg<<32)>>32));",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); assertNull(code._compunits.get("Lib/Left")._src); assertNull(code._compunits.get("Lib/Right")._src); assertEquals("2000000000000",Eval2.eval(code,0)); @@ -33,11 +33,11 @@ public class SelectiveLoadingTest { @Test public void testUnreachableImportIsNotARoot() throws Exception { Path root = library("dead-import-"); Ary<String> paths = new Ary<>(new String[]{root.toString()}); - object(root,paths,"Unused","val run_noInline = { int n -> n+1; };"); + object(root,paths,"Unused","val run_noInline = { int n -> int result=n+1; return result; };"); for( long seed : new long[]{1,123,987654321} ) { CodeGen code = new CodeGen(null,root.toString(),paths,"Main",""" val _false_noInline = { -> false; }; - if( _false_noInline() ) return Unused.run_noInline(arg); + if( _false_noInline() ) return Unused.run_noInline(((arg<<32)>>32)); return 7; """,seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); CompUnit unused = code._compunits.get("Unused"); @@ -61,8 +61,8 @@ public class SelectiveLoadingTest { object(root,paths,"L"+i,"val relay_noInline = { {int->int} f -> L"+(i-1)+".relay_noInline(f); };"); for( long seed : new long[]{1,123,987654321} ) { CodeGen code = new CodeGen(null,root.toString(),paths,"Main",""" - val _increment_noInline = { int n -> n+1; }; - val _decrement_noInline = { int n -> n-1; }; + val _increment_noInline = { int n -> int result=n+1; return result; }; + val _decrement_noInline = { int n -> int result=n-1; return result; }; val f = L5.relay_noInline(arg ? _increment_noInline : _decrement_noInline); return f(41); """,seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); @@ -78,9 +78,9 @@ public class SelectiveLoadingTest { object(root,paths,"Relay","val relay = { {int->int} f -> f; };"); for( long seed : new long[]{1,123,987654321} ) { CodeGen code = new CodeGen(null,root.toString(),paths,"Main",""" - val _increment_noInline = { int n -> n+1; }; + val _increment_noInline = { int n -> int result=n+1; return result; }; val f = Relay.relay(_increment_noInline); - return f(arg); + return f(((arg<<32)>>32)); """,seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); assertNull(function(code,"relay").summary()); assertEquals("42",Eval2.eval(code,41)); @@ -91,11 +91,11 @@ public class SelectiveLoadingTest { @Test public void testImportedFunctionField() throws Exception { Path root = library("function-field-"); Ary<String> paths = new Ary<>(new String[]{root.toString()}); - object(root,paths,"Targets","var chosen = { int n -> n+1; };"); + object(root,paths,"Targets","var chosen = { int n -> int result=n+1; return result; };"); object(root,paths,"Apply","val run = { int n -> val f = Targets.chosen; return f(n); };"); for( long seed : new long[]{1,123,987654321} ) { CodeGen code = new CodeGen(null,root.toString(),paths,"Main", - "return Apply.run(arg);",seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); + "return Apply.run(((arg<<32)>>32));",seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); assertNull(function(code,"run").summary()); assertEquals("42",Eval2.eval(code,41)); assertEquals("1",Eval2.eval(code,0)); @@ -112,15 +112,15 @@ public class SelectiveLoadingTest { for( String target : new String[]{"apply","apply_noInline","forward_noInline"} ) for( long seed : new long[]{1,123,987654321} ) { CodeGen code = new CodeGen(null,root.toString(),paths,"Main", - "val _increment_noInline = { int n -> n+1; }; "+ + "val _increment_noInline = { int n -> int result=n+1; return result; }; "+ "val forward_noInline = { {int->int} f,int n -> Apply.apply_noInline(f,n); }; return "+ - (target.equals("forward_noInline") ? target : "Apply."+target)+"(_increment_noInline,arg);", + (target.equals("forward_noInline") ? target : "Apply."+target)+"(_increment_noInline,((arg<<32)>>32));", seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); assertEquals("42",Eval2.eval(code,41)); assertEquals("1",Eval2.eval(code,0)); } CodeGen nativeCode = new CodeGen(null,root.toString(),paths,"NativeCallback",""" - val _increment_noInline = { int n -> n+1; }; + val _increment_noInline = { int n -> int result=n+1; return result; }; val run = { int n -> Apply.apply_noInline(_increment_noInline,n); }; """,123,true,TypeInteger.BOT).driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); Path driver = root.resolve("driver.c"); @@ -212,7 +212,7 @@ public class SelectiveLoadingTest { val p={int n -> Work.write_noInline(n);}; val p_noInline={int n -> Work.write_noInline(n);}; """); - write(src,"Lib/Work","val write_noInline={int n -> n+1;};"); + write(src,"Lib/Work","val write_noInline={ int n -> int result=n+1; return result;};"); write(src,"Lib/Unused","val f={int n -> n*2;};"); new CodeGen(src.toString(),lib.toString(),null,"Lib",null,123,true,TypeInteger.BOT) .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); @@ -221,7 +221,7 @@ public class SelectiveLoadingTest { Ary<String> paths = new Ary<>(new String[]{lib.toString()}); CodeGen code = new CodeGen(null,app.toString(),paths,"Main", - "if(arg==0) return -1; return Lib.api.p_noInline(arg);",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); + "if(arg==0) return -1; return Lib.api.p_noInline(((arg<<32)>>32));",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); assertNotNull(function(code,"Lib.Api.p_noInline").summary()); assertNotNull(function(code,"Lib.Work.write_noInline").summary()); assertNotNull(function(code,"Lib.Unused.f").summary()); @@ -233,7 +233,7 @@ public class SelectiveLoadingTest { assertEquals("6",Eval2.eval(code,5)); code=new CodeGen(null,app.toString(),paths,"Main", - "val run={int n -> Lib.api.p(n);}; return run(arg);",123,true,TypeInteger.BOT) + "val run={int n -> Lib.api.p(n);}; return run(((arg<<32)>>32));",123,true,TypeInteger.BOT) .driver(CodeGen.Phase.TypeCheck); assertNull("p requested Api's implementation",function(code,"Lib.Api.p").summary()); assertNotNull("The remaining call needs a signature, not a body",function(code,"Lib.Work.write_noInline").summary()); @@ -255,7 +255,7 @@ public class SelectiveLoadingTest { // fresh program. try { Eval2.eval(new CodeGen(null,app.toString(),paths,"Missing", - "if(arg==0) return -1; return Lib.api.p_noInline(arg);",123,true,TypeInteger.BOT),0); + "if(arg==0) return -1; return Lib.api.p_noInline(((arg<<32)>>32));",123,true,TypeInteger.BOT),0); fail("Missing transitive code must fail before evaluation"); } catch( RuntimeException ex ) { assertTrue(ex.getMessage(),ex.getMessage().contains("Lib/Work")); } } diff --git a/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java index cfc0b7e0..1853aa48 100644 --- a/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java +++ b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java @@ -18,9 +18,9 @@ public class SeparateCompilationTest { val run={int n -> val power={int base,int exponent -> if(exponent<=0) return 1; - return base*power(base,exponent-1); + return base*power(base,--exponent); }; - { {int int -> int} -> int } retain="C"; + { {int int -> i64} -> int } retain="C"; retain(power); return power(2,n); }; @@ -28,14 +28,14 @@ public class SeparateCompilationTest { new CodeGen(null,lib.toString(),null,"Callbacks",src,123,true,TypeInteger.BOT) .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); CodeGen code=new CodeGen(null,null,new Ary<>(new String[]{lib.toString()}), - "Client","return Callbacks.run(arg);",123,true,TypeInteger.BOT) + "Client","return Callbacks.run(((arg<<32)>>32));",123,true,TypeInteger.BOT) .driver(CodeGen.Phase.Opto); // This assertion inspects a body, not just its saved summary. ParseAll.loadCompUnit(code,code._compunits.get("Callbacks")); assertNull(code._compunits.get("Callbacks")._src); ParmNode base=code._start.walk(n -> n instanceof ParmNode p && p._label.equals("base") ? p : null); assertNotNull(base); - assertSame("Native code may call the saved callback with any base",TypeInteger.BOT,base._type); + assertSame("Native code may call the saved callback with any base",TypeInteger.I32,base._type); code.driver(CodeGen.Phase.TypeCheck); } diff --git a/src/test/java/com/seaofnodes/simple/Simple.java b/src/test/java/com/seaofnodes/simple/Simple.java index 1f1e8ab0..78b1e76d 100644 --- a/src/test/java/com/seaofnodes/simple/Simple.java +++ b/src/test/java/com/seaofnodes/simple/Simple.java @@ -3,6 +3,7 @@ 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.parser.Parser; import com.seaofnodes.simple.print.*; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.Ary; @@ -34,6 +35,8 @@ Options: -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 + --link - build a native executable without running it + --norun - emit objects without linking or running --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) @@ -90,10 +93,20 @@ Options: } public static void main(String[] args) throws Exception { + try { + run(args); + } catch( Parser.ParseException e ) { + System.err.println(e.diagnostic()); + System.exit(1); + } + } + + private static void run(String[] args) throws Exception { String root = null; String input_filename = null; boolean do_eval = false; boolean do_run = true; + boolean do_link = false; boolean do_codegen = false; boolean emit_main = false; boolean print_time = false; @@ -134,9 +147,10 @@ Options: 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 "--eval": do_eval = true ; do_run = false; do_link = false; break; case "--run": do_run = true ; break; - case "--norun": do_run = false; break; + case "--link": do_link = true ; do_run = false; break; + case "--norun": do_run = false; do_link = false; break; case "--main": emit_main = true; break; case "--dump-size": print_size = true; break; case "--dump-time": print_time = true; break; @@ -215,7 +229,7 @@ Options: } String outBase = outPath.resolve(srcName).toString(); - if( do_run || print_asm || print_size ) { + if( do_run || do_link || print_asm || print_size ) { if (cpu == null) cpu = TestC.CPU_PORT; if (abi == null) abi = TestC.CALL_CONVENTION; do_codegen = true; @@ -234,7 +248,7 @@ Options: 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(do_eval && !do_codegen ? Phase.TypeCheck : Phase.LastPhase, - cpu,abi, false, emit_main || do_run || do_eval, dump); + cpu,abi, false, emit_main || do_run || do_link || do_eval, dump); if( do_codegen && print_asm ) System.out.println(code.asm()); @@ -253,12 +267,12 @@ Options: if( print_time ) System.out.printf( "EXECUTION TIME: %.3f sec%n", (System.currentTimeMillis() - t) / 1e3); } - if( do_run ) { + if( do_run || do_link ) { if ( !TestC.CPU_PORT.equals( cpu ) || !TestC.CALL_CONVENTION.equals( abi ) ) - throw bad("cannot run code on not native target"); + throw bad("cannot link or run code on a non-native target"); String exe = TestC.OS.startsWith("Windows") ? outBase+".exe" : outBase; - String result = TestC.gcc(code, null, null, "", exe); - System.out.print(result); + TestC.linkExe(TestC.linkObjs(code),null,null,exe); + if( do_run ) TestC.execInteractive(exe); } } } diff --git a/src/test/java/com/seaofnodes/simple/StreamIOTest.java b/src/test/java/com/seaofnodes/simple/StreamIOTest.java new file mode 100644 index 00000000..f3491314 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/StreamIOTest.java @@ -0,0 +1,138 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.Ary; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import static org.junit.Assert.*; + +public class StreamIOTest extends TestC { + @Test public void testReadUntilEOF() throws Exception { + String src=""" + val main={ -> + val bytes=sys.io.stdin(); + if(!bytes) return 1; + return sys.io.write(1,bytes)==bytes# ? 0 : 2; + }; + """; + String exe=compile(src,"streamEcho",new Ary<>(new String[]{RELEASE_SYS_DIR}), + CALL_CONVENTION,null,null,-1); + for(int size:new int[]{0,8192,8193,65537}) { + String input="a\0bc\n".repeat(size/5)+"z".repeat(size%5); + Path in=Files.createTempFile(Path.of("build/objs"),"stream-in-",".txt"); + Path out=Files.createTempFile(Path.of("build/objs"),"stream-out-",".txt"); + Process child=null; + try { + Files.writeString(in,input); + ProcessBuilder builder=new ProcessBuilder(exe).redirectOutput(out.toFile()); + if(size!=8193) builder.redirectInput(in.toFile()); + child=builder.start(); + if(size==8193) { // Also exercise a real pipe, ending only when its writer closes. + child.getOutputStream().write(input.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + child.getOutputStream().close(); + } + assertTrue(child.waitFor(TEST_TIMEOUT_SECONDS,TimeUnit.SECONDS)); + assertEquals(0,child.exitValue()); + assertEquals(input,Files.readString(out)); + } finally { + if(child!=null) child.destroyForcibly(); + Files.deleteIfExists(in); + Files.deleteIfExists(out); + } + } + } + + @Test public void testUnsignedArrayGrowth() throws Exception { + runSYS(""" + val main={ -> + val b=new sys.aryu8(); + val first=b.buf; + b.add('a'); + if(b.buf!=first || b.len!=1) return 1; + b.addAry("bcdefghij"); + if(b.len!=10 || b.buf[0]!='a' || b.buf[9]!='j') return 2; + b.undo(4294967295); + if(b.len!=0) return 3; + b.len=2147483648; + b.undo(1); + if(b.len!=2147483647) return 4; + b.clear(); + val nums=new sys.aryi64(new i64[0]); + nums.add(10).add(20); + if(nums.len!=2 || nums.get(0)!=10 || nums.get(1)!=20) return 5; + nums.len=4294967295; + if(nums.len!=4294967295) return 6; + nums.len=2; + if(sys.ary.capacity(0,1)!=1) return 7; + if(sys.ary.capacity(8,8)!=8 || sys.ary.capacity(8,9)!=16) return 8; + if(sys.ary.capacity(2147483648,2147483649)!=4294967295) return 9; + if(sys.ary.capacity(4294967295,4294967296)!=0) return 10; + if(sys.ary.cmp("abc",4294967295,"",0,4294967295)!=0) return 11; + nums.str().add('\\n').write(1); + return 0; + }; + ""","unsignedArrayGrowth","[10, 20]\n",-1); + } + + @Test public void testShortInterruptedAndFailedIO() throws Exception { + String src=""" + {i32 i64 -> i32} test_io_mode="C"; + {i64} test_io_count="C"; + {i32} test_io_status="C"; + val main={ -> + test_io_mode(1,0); + val bytes=sys.io.read(700); + if(!bytes) return 1; + if(bytes#!=24593) return 2; + for(u32 i=0; i<bytes#; i++) if(bytes[i]!=i-(i/251)*251) return 3; + test_io_mode(1,0); + if(sys.io.write(701,bytes)!=bytes# || test_io_count()!=bytes#) return 4; + if(test_io_status()) return 5; + test_io_mode(1,3); + if(sys.io.writeOff(701,bytes,3,5)!=5 || test_io_count()!=5) return 6; + if(test_io_status()) return 7; + test_io_mode(1,24590); + if(sys.io.writeOff(701,bytes,24590,4294967295)!=3) return 8; + if(test_io_status()) return 9; + test_io_mode(1,0); + if(sys.io.writeOff(701,bytes,24593,0)!=0 || test_io_count()!=0) return 10; + if(sys.io.writeOff(701,bytes,4294967295,1)!=-1) return 11; + if(sys.libc.errno()!=sys.libc.EINVAL || test_io_count()!=0) return 12; + test_io_mode(2,0); + if(sys.io.read(700)!=null || sys.libc.errno()!=sys.libc.EIO) return 13; + test_io_mode(3,0); + if(sys.io.write(701,bytes)!=-1 || sys.libc.errno()!=sys.libc.EIO) return 14; + if(test_io_count()!=7) return 15; + test_io_mode(4,0); + if(sys.io.write(701,bytes)!=-1 || sys.libc.errno()!=sys.libc.EIO) return 16; + test_io_mode(5,0); + if(sys.io.write(701,bytes)!=-1 || test_io_status()) return 17; + if(sys.io.read(-1)!=null) return 18; + val b=new sys.aryu8().addAry(bytes); + test_io_mode(1,0); + if(b.write(701)!=b.len || test_io_count()!=b.len || test_io_status()) return 19; + sys.io.p("ok\\n"); + return 0; + }; + """; + CodeGen code=new CheckedCodeGen(null,"build/objs",new Ary<>(new String[]{RELEASE_SYS_DIR}), + "streamFaults",src,126,true,TypeInteger.BOT); + code.driver(CPU_PORT,CALL_CONVENTION,false,true); + String exe="build/objs/streamFaults"+(OS.startsWith("Windows") ? ".exe" : ""); + Ary<String> gcc=new Ary<>(String.class); + gcc.add("gcc"); + gcc.add(OS.startsWith("Windows") ? "-Wl,--image-base,0x400000" : "-no-pie"); + gcc.add("-Wl,--wrap=read,--wrap=write"); + gcc.add(C_DRIVERS_DIR+"stream_io.c"); + gcc.addAll(linkObjs(code)); + gcc.add(runtimeObject("simple_crt")); + gcc.add(runtimeObject("simple_args")); + gcc.addAll(new String[]{"-lm","-o",exe}); + exec(TEST_TIMEOUT_SECONDS,gcc.asAry()); + assertEquals("ok\n",exec(TEST_TIMEOUT_SECONDS,exe)); + } +} diff --git a/src/test/java/com/seaofnodes/simple/StringFormatTest.java b/src/test/java/com/seaofnodes/simple/StringFormatTest.java new file mode 100644 index 00000000..53a10092 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/StringFormatTest.java @@ -0,0 +1,158 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.parser.Parser; +import com.seaofnodes.simple.node.CallNode; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.Ary; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +public class StringFormatTest extends TestC { + private static void prints(String body, String expected) throws Exception { + runSYS("val main={ -> "+body+" return 0; };", "stringFormat",expected,-1); + } + + @Test public void testDefaultsAndNestedStrings() throws Exception { + prints("sys.io.p(\"CPU plays %(2+3). %(\"nested %(7)\") %(true) %(1.5)\\n\");", + "CPU plays 5. nested 7 1 1.5\n"); + } + + @Test public void testFormats() throws Exception { + prints("sys.io.p(\"%+06d(17)|%#08x(42)|%-5.3s(\"abcdef\")|%.2f(1.25)|%c(65)|%b(false)\\n\");", + "+00017|0x00002a|abc |1.25|A|false\n"); + } + + @Test public void testIntegerLimits() throws Exception { + prints("i64 lo=-9223372036854775807-1; sys.io.p(\"%(lo)|%(9223372036854775807)|%u(-1)|%X(-1)\");", + "-9223372036854775808|9223372036854775807|18446744073709551615|FFFFFFFFFFFFFFFF"); + } + + @Test public void testSignedAndUnsignedRadices() throws Exception { + long[] values={Long.MIN_VALUE,Long.MIN_VALUE+1,Long.MAX_VALUE,-1,-17,0,1,7,8,9,15,16,17,1L<<32}; + StringBuilder source=new StringBuilder("val show_noInline={i64 x -> sys.io.p(\"%(x)|%u(x)|%o(x)|%x(x)\\n\"); }; val main={ -> "); + StringBuilder expected=new StringBuilder(); + for(long x:values) { + source.append("show_noInline(").append(x==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(x)).append(");"); + expected.append(x).append('|').append(Long.toUnsignedString(x)).append('|') + .append(Long.toUnsignedString(x,8)).append('|').append(Long.toUnsignedString(x,16)).append('\n'); + } + source.append("return 0; };"); + runSYS(source.toString(),"stringFormatRadices",expected.toString(),-1); + } + + @Test public void testIntegerPaddingRules() throws Exception { + prints("sys.io.p(\"%#.0o(0)|%#.3o(1)|%#x(0)|%-08d(17)|%08.4d(-17)|%+d(0)|% d(0)\");", + "0|001|0|17 | -0017|+0| 0"); + } + + @Test public void testBuilderNumberUsesFormatter() throws Exception { + prints("val b=new sys.aryu8(); b.num(-9223372036854775807-1).add('|').num(9223372036854775807); sys.io.p(sys.text.end(b));", + "-9223372036854775808|9223372036854775807"); + } + + @Test public void testFormattingInEvaluatorFromSource() { + // No installed sys objects and no native execution: the library builds + // from source, and all non-float formatting executes as ordinary IR. + String source=""" + val s="%#08x(arg)|%b(true)|%-4s("ok")|%c(65)"; + val expected="0x00002a|true|ok |A"; + if(s#!=expected#) return 1; + for(int i=0; i<s#; i++) if(s[i]!=expected[i]) return 2; + return 0; + """; + CodeGen code=new CodeGen("src/main/smp",null,null,"FreshText",source,123,true,TypeInteger.BOT) + .driver(CodeGen.Phase.TypeCheck); + assertEquals("0",Eval2.eval(code,42,10000)); + } + + @Test public void testEvaluationOrderAndLength() throws Exception { + prints("int x=0; val s=\"%(x++)%(x++)%(x++)\"; sys.io.p(\"%(s):%(s#):%(x)|%%|\\%\");", + "012:3:3|%|%"); + prints("val a=new u8[1]; a[0]='A'; sys.io.p(\"%(a)%(a[0]='B')%(a)\");", "A66B"); + } + + @Test public void testConverters() throws Exception { + prints("sys.io.p(sys.text.int(17)); sys.io.p(sys.text.char(65)); sys.io.p(sys.text.bool(true)); sys.io.p(sys.text.flt(1.5));", + "17Atrue1.5"); + prints("struct S { int int=7; }; sys.io.p(\"%((new S).int)\");", "7"); + } + + @Test public void testForwardReturnTypes() throws Exception { + runSYS("val main={ -> sys.io.p(\"%(later())\"); return 0; }; val later={ -> \"ok\"; };", + "stringFormatForward","ok",-1); + } + + @Test public void testFloatAndAlternateFormats() throws Exception { + prints("f32 x=1.25f; sys.io.p(\"%(x)|%f(x)|%.2e(x)|%.2G(x)|%#o(9)|% d(7)|%.0d(0)|%a(1.5)\");", + "1.25|1.250000|1.25e+00|1.2|011| 7||0x1.8p+0"); + runSYS("val format_noInline={ f32 x -> \"%(x)|%f(x)\"; }; val main={ -> sys.io.p(format_noInline(1.25f)); return 0; };", + "stringFormatFloat","1.25|1.250000",-1); + } + + @Test public void testBufferGrowth() throws Exception { + prints("sys.io.p(\"%0100d(17)|%.100f(1.0)\");", "0".repeat(98)+"17|1."+"0".repeat(100)); + } + + @Test public void testLiteralRemainsConstant() { + CodeGen code=new CodeGen("return \"100%% \\%\";").driver(CodeGen.Phase.TypeCheck); + assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); + } + + @Test public void testBytesAndEmptyStrings() throws Exception { + prints("val empty=\"%(\"\")\"; val bytes=\"a\\0b\"; sys.io.p(\"%(empty#):%s(bytes):%.2s(bytes):%c(0)\");", + "0:a\0b:a\0:\0"); + prints("val s=\"π\"; sys.io.p(\"%(s):%(s#):%(s[0]):π\");", "π:2:207:π"); + } + + @Test public void testDeferredParsing() throws Exception { + prints(""" + struct S { u8[~] s; new S={ -> s="%("}) %(3)")"; }; }; + val s=new S; + sys.io.p(s.s); + for(int i=0; i<2; sys.io.p("%(i++) %("(")")) { } + """, "}) 30 (1 ("); + } + + @Test public void testImportedFormatter() throws Exception { + Path lib=Files.createTempDirectory(Path.of("build/objs"),"text-library-"); + new CodeGen(null,lib.toString(),new Ary<>(new String[]{RELEASE_SYS_DIR}),"TextLibrary", + "val format_noInline={int x -> \"value=%04d(x)\"; };",123,true,TypeInteger.BOT) + .driver(CPU_PORT,CALL_CONVENTION,false,false); + run("val main={ -> sys.io.p(TextLibrary.format_noInline(7)); return 0; };", + "stringFormatImported",new Ary<>(new String[]{lib.toString(),RELEASE_SYS_DIR}), + CALL_CONVENTION,null,null,"value=0007",-1); + } + + @Test public void testWrongTypesAndReservedNames() { + rejects("return \"%d(1.5)\";", "must be a"); + rejects("return \"%s(17)\";", "must be a"); + rejects("return \"%(null)\";", "Cannot format"); + rejects("val f={int int -> int;}; return 0;", "Expected an identifier"); + rejects("val f={ -> int int=1; return int; }; return 0;", "Expected an identifier"); + } + + private static void rejects(String source, String expected) { + try { + new CodeGen(null,null,new Ary<>(new String[]{RELEASE_SYS_DIR}),"Test",source,123,true,TypeInteger.BOT) + .driver(CodeGen.Phase.TypeCheck); + fail("Accepted invalid source: "+source); + } catch( Parser.ParseException e ) { + assertTrue(e.getMessage(),e.getMessage().contains(expected)); + } + } + + @Test public void testMalformedFormats() { + for(String format:new String[]{"%name", "%1$d(1)", "%*d(1)", "%n(1)", "%lld(1)", "%"}) + rejects("return \""+format+"\";", "Expected a string format"); + rejects("return \"%X.cliff\";", "Expected '('"); + rejects("return \"%+s(\"x\")\";", "Invalid flags"); + rejects("return \"%.2c(65)\";", "Precision is not supported"); + } + + @Test public void testUnsupportedDefault() { + rejects("struct S { int x; }; return \"%(new S)\";", "Cannot format"); + } +} diff --git a/src/test/java/com/seaofnodes/simple/TerminalInputTest.java b/src/test/java/com/seaofnodes/simple/TerminalInputTest.java new file mode 100644 index 00000000..bd3831b4 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/TerminalInputTest.java @@ -0,0 +1,184 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.util.Ary; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import static org.junit.Assert.*; + +public class TerminalInputTest { + @Test public void testLinkOnlyAndEnterEscape() throws Exception { + String java=Path.of(System.getProperty("java.home"),"bin","java").toString(); + String output="build/objs/linkOnlyGame/"; + // Linking must return without displaying the game or waiting for input. + assertEquals("",TestC.exec(20,java,"-ea","-cp",System.getProperty("java.class.path"), + Simple.class.getName(),"--link","-L",TestC.RELEASE_SYS_DIR,"-o",output, + "docs/examples/TicTacToe.smp")); + Path exe=Path.of(output,"TicTacToe"+(TestC.OS.startsWith("Windows") ? ".exe" : "")); + assertTrue(Files.isRegularFile(exe)); + Process child=new ProcessBuilder(exe.toString()).redirectErrorStream(true).start(); + try { + child.getOutputStream().write(new byte[]{'\r','\n',27}); + child.getOutputStream().flush(); // Keep stdin open: Escape must cause the exit. + assertTrue("Escape did not quit",child.waitFor(10,TimeUnit.SECONDS)); + String text=new String(child.getInputStream().readAllBytes(),StandardCharsets.UTF_8); + assertEquals(text,0,child.exitValue()); + assertTrue(text,text.endsWith("Your move (1-9): \nGoodbye!\n")); + assertFalse(text,text.contains("Choose a square")); + } finally { + child.destroyForcibly(); + } + } + + @Test public void testRedirectedBytesAndEOF() throws IOException { + TestC.run(""" + val main={ u8[~][] args -> + if(sys.io.getch()!='5') return 1; + if(sys.io.getch()!='q') return 2; + if(sys.io.getch()!=-1) return 3; + if(sys.libc.errno()!=sys.libc.EOF) return 4; + sys.io.p("ok\n"); return 0; + }; + ""","getchBytes",new Ary<>(new String[]{TestC.RELEASE_SYS_DIR}), + TestC.CALL_CONVENTION,null,null,"5q","ok\n",-1); + } + + @Test public void testReadError() throws IOException { + TestC.runSYS(""" + val main={ u8[~][] args -> + sys.io.close(sys.libc.STDIN); + if(sys.io.getch()!=-1) return 1; + if(sys.libc.errno()<=0) return 2; + return 0; + }; + ""","getchError","",-1); + } + + @Test public void testGuardsStayInsideFunction() { + String src=""" + val first={ int n -> while(true) { if(n) return 0; return 1; } }; + val second={ -> int sum=0; for(int i=0; i<3; i++) sum+=i; return sum; }; + return first(((arg<<32)>>32))+second(); + """; + for(long seed:new long[]{1,123,456}) { + CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("4",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,1)); + } + } + + @Test public void testForwardReferenceAfterContinue() { + String src=""" + val run={ int n -> + while(n) { + if(n==1) { n=0; continue; } + if(later(n)) return 7; + n=0; + } + return 3; + }; + val later={ int n -> n==2; }; + return run(((arg<<32)>>32)); + """; + for(long seed:new long[]{1,123,456}) { + CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("3",Eval2.eval(code,1)); + assertEquals("7",Eval2.eval(code,2)); + assertEquals("3",Eval2.eval(code,3)); + // The same merge is used by break, including the implicit loop exit. + String breaking=src.replace("return 7;","break;").replace("return 3;","return n+3;"); + CodeGen code2=new CodeGen(breaking,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("3",Eval2.eval(code2,1)); + assertEquals("5",Eval2.eval(code2,2)); + assertEquals("3",Eval2.eval(code2,3)); + } + } + + @Test public void testForwardReferenceWithLoopLocal() { + String src=""" + val run={ int n -> + while(n) { + int key=n; + n--; + if(key==1) continue; + if(later(key)) continue; + return key; + } + return 0; + }; + val later={ int key -> key==2; }; + return run(((arg<<32)>>32)); + """; + String nested=src.replace("if(later(key)) continue;", + "if(later(key)) { int local=key+1; continue; }"); + String breaking=nested.replace("int local=key+1; continue;","int local=key+1; break;") + .replace("return 0;","return n+10;"); + String twoRefs=src.replace("return key;","if(other(key)) continue; return key;") + .replace("return run(((arg<<32)>>32));","val other={ int key -> key==3; }; return run(((arg<<32)>>32));"); + for(long seed:new long[]{1,123,456}) { + for(String variant:new String[]{src,nested}) { + CodeGen code=new CodeGen(variant,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("0",Eval2.eval(code,1)); + assertEquals("0",Eval2.eval(code,2)); + assertEquals("3",Eval2.eval(code,3)); + } + CodeGen code=new CodeGen(breaking,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("10",Eval2.eval(code,1)); + assertEquals("11",Eval2.eval(code,2)); + assertEquals("3",Eval2.eval(code,3)); + CodeGen code2=new CodeGen(twoRefs,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("0",Eval2.eval(code2,3)); + assertEquals("4",Eval2.eval(code2,4)); + } + } + + @Test public void testInteractiveCLIShowsPromptBeforeInput() throws Exception { + // Pipe input rather than a terminal: this catches CLI buffering and + // requires exactly one byte, with no Enter. + String java=Path.of(System.getProperty("java.home"),"bin","java").toString(); + Process child=new ProcessBuilder(java,"-ea","-cp",System.getProperty("java.class.path"), + Simple.class.getName(),"-L",TestC.RELEASE_SYS_DIR,"-o","build/objs/interactiveGame/", + "docs/examples/TicTacToe.smp").redirectErrorStream(true).start(); + try(var reader=Executors.newSingleThreadExecutor()) { + try { + var prompt=reader.submit(() -> readPrompt(child)); + String opening=prompt.get(20,TimeUnit.SECONDS); + assertTrue(opening,opening.contains("You go first.")); + assertTrue(opening,opening.contains(" | | \n")); + assertTrue(child.isAlive()); + child.getOutputStream().write('5'); + child.getOutputStream().flush(); + String turn=reader.submit(() -> readPrompt(child)).get(20,TimeUnit.SECONDS); + assertEquals("5\n | | \n---+---+---\n | X | \n---+---+---\n | | \n" + +"CPU plays 1.\n O | | \n---+---+---\n | X | \n---+---+---\n | | \n" + +"Your move (1-9): ",turn); + assertTrue(child.isAlive()); + child.getOutputStream().write(27); + child.getOutputStream().flush(); + assertTrue("Escape did not quit",child.waitFor(10,TimeUnit.SECONDS)); + assertEquals("\nGoodbye!\n",new String(child.getInputStream().readAllBytes(),StandardCharsets.UTF_8)); + assertEquals(0,child.exitValue()); + } finally { + child.destroyForcibly(); + } + } + } + + private static String readPrompt(Process child) throws IOException { + var bytes=new ByteArrayOutputStream(); + while(bytes.size()<8192) { + int ch=child.getInputStream().read(); + if(ch<0) throw new IOException("Exited before prompt: "+bytes.toString(StandardCharsets.UTF_8)); + bytes.write(ch); + String text=bytes.toString(StandardCharsets.UTF_8); + if(text.endsWith("Your move (1-9): ")) return text; + } + throw new IOException("No prompt: "+bytes.toString(StandardCharsets.UTF_8)); + } +} diff --git a/src/test/java/com/seaofnodes/simple/TestC.java b/src/test/java/com/seaofnodes/simple/TestC.java index 54adf3af..dd7999c9 100644 --- a/src/test/java/com/seaofnodes/simple/TestC.java +++ b/src/test/java/com/seaofnodes/simple/TestC.java @@ -10,10 +10,14 @@ import com.seaofnodes.simple.util.Utils; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.concurrent.TimeUnit; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutionException; import static org.junit.Assert.*; // Call a C driver program, which calls a Simple-generated .o, and self-checks. @@ -32,10 +36,10 @@ public abstract class TestC { public static final String CPU_ABI = CPU_PORT+"_"+CALL_CONVENTION; public static final String RELEASE_SYS_DIR = "build/release/"+CPU_ABI; - public static String runtimeObject() { + public static String runtimeObject(String name) { 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(); + return Path.of(root,CPU_ABI,name+".o").toString(); } public static final String C_DRIVERS_DIR = "src/test/java/com/seaofnodes/simple/progs/"; @@ -176,7 +180,7 @@ public abstract class TestC { linkExe(objects,c_conv,cfile,exe); } - private static void linkExe(Ary<String> objects, String c_conv, String cfile, String exe) throws IOException { + static void linkExe(Ary<String> objects, String c_conv, String cfile, 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 @@ -187,9 +191,12 @@ public abstract class TestC { if( cfile!=null ) params.add(cfile); // Associated C driver, usually has a `main` params.addAll(objects); // 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()); + // runtime owns native main and calls the Simple argument converter + // before making the ABI-qualified entry call. + if( cfile==null ) { + params.add(runtimeObject("simple_crt")); + params.add(runtimeObject("simple_args")); + } params.addAll(new String[] { "-lm", // Picks up 'sqrt' for newtonFloat tests to compare "-g", // debug symbols @@ -215,6 +222,40 @@ public abstract class TestC { return execStdin(null,seconds,args); } + // The CLI must display prompts while the child is waiting for input. + // Keep the capture-to-files path below for bounded, noninteractive tests. + public static void execInteractive(String... args) throws IOException { + Process child = new ProcessBuilder(args).redirectInput(ProcessBuilder.Redirect.INHERIT).start(); + try( var relays = Executors.newFixedThreadPool(2) ) { + var out = relays.submit(() -> { relay(child.getInputStream(),System.out); return null; }); + var err = relays.submit(() -> { relay(child.getErrorStream(),System.err); return null; }); + try { + int status = child.waitFor(); + out.get(); + err.get(); + if( status!=0 ) throw new IOException(String.join(" ",args)+"\nexec exit code: "+status); + } catch( InterruptedException e ) { + child.destroyForcibly(); + Thread.currentThread().interrupt(); + throw new IOException("interrupted",e); + } catch( ExecutionException e ) { + throw new IOException("Cannot relay program output",e.getCause()); + } finally { + if( child.isAlive() ) child.destroyForcibly(); + } + } + } + + private static void relay(InputStream in, PrintStream out) throws IOException { + try( in ) { + byte[] buffer = new byte[4096]; + for( int n; (n=in.read(buffer))!=-1; ) { + out.write(buffer,0,n); + out.flush(); + } + } + } + private static String execStdin( String stdin, int seconds, String... args ) throws IOException { ExecResult result = execute(stdin,seconds,args); if( result.exit!=0 ) diff --git a/src/test/java/com/seaofnodes/simple/TicTacToeTest.java b/src/test/java/com/seaofnodes/simple/TicTacToeTest.java new file mode 100644 index 00000000..1d267935 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/TicTacToeTest.java @@ -0,0 +1,92 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.util.Ary; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +public class TicTacToeTest { + private static String exe; + + @BeforeClass public static void compileGame() throws Exception { + exe=TestC.compile(Files.readString(Path.of("docs/examples/TicTacToe.smp")), + "ticTacToeGames",new Ary<>(new String[]{TestC.RELEASE_SYS_DIR}), + TestC.CALL_CONVENTION,null,null,-1); + } + + private static String play(String keys) throws Exception { + Process child=new ProcessBuilder(exe).redirectErrorStream(true).start(); + try { + child.getOutputStream().write(keys.getBytes(StandardCharsets.US_ASCII)); + child.getOutputStream().close(); + assertTrue("Game did not finish",child.waitFor(10,TimeUnit.SECONDS)); + String text=new String(child.getInputStream().readAllBytes(),StandardCharsets.UTF_8); + assertEquals(text,0,child.exitValue()); + return text; + } finally { + child.destroyForcibly(); + } + } + + private static String board(String squares) { + StringBuilder out=new StringBuilder(); + for(int row=0;row<3;row++) { + if(row>0) out.append("---+---+---\n"); + for(int col=0;col<3;col++) { + if(col>0) out.append('|'); + out.append(' ').append(squares.charAt(row*3+col)).append(' '); + } + out.append('\n'); + } + return out.toString(); + } + + @Test public void testPlayerWin() throws Exception { + String text=play("1879"); + assertTrue(text,text.endsWith(board("X OOO XXX")+"You win!\n")); + assertEquals(3,text.split("CPU plays ",-1).length-1); + } + + @Test public void testCPUWinsEveryLine() throws Exception { + // Three rows, three columns, and both diagonals in complete games. + String[][] games={ + {"4697","OOOXOXX X"}, {"1278","XXOOOOXX "}, {"5234","OXXXX OOO"}, + {"2893","OXXOO OXX"}, {"134","XOXXO O "}, {"5472","OXOXXOX O"}, + {"234","OXXXO O"}, {"124","XXOXO O "} + }; + for(String[] game:games) { + String text=play(game[0]); + assertTrue(game[0]+"\n"+text,text.endsWith(board(game[1])+"CPU wins!\n")); + } + } + + @Test public void testDraw() throws Exception { + String text=play("12768"); + assertTrue(text,text.endsWith(board("XXOOOXXXO")+"It's a draw!\n")); + assertEquals(4,text.split("CPU plays ",-1).length-1); + } + + @Test public void testBusySquaresAndInvalidInputDoNotTakeATurn() throws Exception { + // X takes 5 and O takes 1; neither occupied square can be overwritten. + String text=play("x\r\n551q"); + assertEquals(text,1,text.split("Choose a square",-1).length-1); + assertEquals(text,2,text.split("That square is occupied",-1).length-1); + assertEquals(text,1,text.split("CPU plays ",-1).length-1); + assertTrue(text,text.contains("Your move (1-9): \nChoose a square from 1 to 9: 5\n" + +board(" X "))); + assertTrue(text,text.contains(board("O X "))); + assertTrue(text,text.endsWith("Your move (1-9): " + +"\nThat square is occupied. Your move (1-9): " + +"\nThat square is occupied. Your move (1-9): \nGoodbye!\n")); + } + + @Test public void testCPUBlocksAndEOFQuits() throws Exception { + String text=play("12"); + assertTrue(text,text.contains("CPU plays 3.\n"+board("XXO O "))); + assertTrue(text,text.endsWith("Goodbye!\n")); + } +} diff --git a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index adbb10a5..edb2519c 100644 --- a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -91,12 +91,52 @@ public class RegAllocTestSupport { public static void nullUseMask() { CodeGen code = graph(); Op def = new Op(A,null,null,false,code._start); + new Op(null,null,A,false,code._start); // The dependency must not keep A live across this kill. new Op(null,null,null,false,code._start,def); // Scheduling dependency only. RegAlloc alloc = new RegAlloc(code); assertTrue(BuildLRG.run(0,alloc)); assertTrue(IFG.build(0,alloc)); } + public static void independentFunctions() { + for( boolean reverse : new boolean[]{false,true} ) { + CodeGen code = graph(); + code._cfg.clear(); + code._callerSave = RegMask.FULL; // These graphs only test local constraints. + FunNode masks = new FunNode(null,com.seaofnodes.simple.type.TypeFunPtr.BOT, + "masks",null,null,code._start); + Op a = new Op(A,null,null,false,masks); + Op b = new Op(null,B,null,false,masks,a); // Fails BuildLRG. + ReturnNode ret1 = new ReturnNode(masks,null,null,null,masks); + masks.setRet(ret1); + + FunNode kills = new FunNode(null,com.seaofnodes.simple.type.TypeFunPtr.BOT, + "kills",null,null,code._start); + Op c = new Op(A,null,null,false,kills); + new Op(null,null,A,false,kills); + Op d = new Op(null,A,null,false,kills,c); // Fails IFG, independently of masks. + ReturnNode ret2 = new ReturnNode(kills,null,null,null,kills); + kills.setRet(ret2); + code._cfg.add(reverse ? kills : masks); + code._cfg.add(reverse ? ret2 : ret1); + code._cfg.add(reverse ? masks : kills); + code._cfg.add(reverse ? ret1 : ret2); + RegAlloc alloc = new RegAlloc(code); + alloc.regAlloc(); + assertEquals(0,alloc.regnum(a)); + assertEquals(1,alloc.regnum(b.in(1))); + assertEquals(0,alloc.regnum(c)); + assertEquals(0,alloc.regnum(d.in(1))); + int splits = 0; + for( Node n : kills.outs() ) + if( n instanceof SplitNode split ) { + assertEquals("Another function's mask failure must not delay this split",0,split._round); + splits++; + } + assertTrue("Preserving A across the kill requires transfers",splits>0); + } + } + public static void cloneRegisterClass() { for( RegMask required : new RegMask[]{B,new RegMask(6L)} ) { CodeGen code = graph(); @@ -144,7 +184,7 @@ public class RegAllocTestSupport { assertEquals(op,1,found); code.driver(CodeGen.Phase.RegAlloc,"x86_64_v2","SystemV"); checkRegisters(code); - assertEquals(op+" reversed="+reversed,8,code._regAlloc._spillScaled); + assertEquals(op+" reversed="+reversed,24,code._regAlloc._spillScaled); code.encode(); } } @@ -307,6 +347,34 @@ public class RegAllocTestSupport { } } + public static void tiedLoopBackedges() { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op init = new Op(A,null,null,false,code._start); + LoopNode loop = new LoopNode(null,null,code._start); + PhiNode first = new PhiNode("first",loop,init,null); + PhiNode second = new PhiNode("second",loop,init,null); + first._type = second._type = TypeInteger.BOT; + Op back = new Op(A,A,null,false,loop,first); + Op narrowed = new Op(A,A,null,false,loop,back); + first.setDef(2,back); + second.setDef(2,narrowed); + new Op(null,A,null,false,loop,first,second); + loop.setDef(2,loop); + // Model the union created by two-address operations on the backedge. + LRG lrg = alloc.newLRG(first,null); + lrg._mask = A; + for( Node n : new Node[]{second,init,back,narrowed} ) alloc.union(lrg,n); + lrg.selfConflict(first); + lrg.selfConflict(second); + alloc.splitSelfConflict((byte)0,lrg); + assertSame(back,first.in(2)); + assertSame(narrowed,second.in(2)); + alloc.splitSelfConflict((byte)1,lrg); + assertTrue(first.in(2) instanceof SplitNode); + assertTrue(second.in(2) instanceof SplitNode); + } + public static void copyForward() throws Exception { // Forwarding, fixed operand, two-address tie, intervening clobber, // another user, and a scheduling-only edge. diff --git a/src/test/java/com/seaofnodes/simple/progs/extern_data.c b/src/test/java/com/seaofnodes/simple/progs/extern_data.c index 9325f65e..e8156d0f 100644 --- a/src/test/java/com/seaofnodes/simple/progs/extern_data.c +++ b/src/test/java/com/seaofnodes/simple/progs/extern_data.c @@ -7,8 +7,13 @@ double fraction=1.25; int64_t bump(void) { counter+=5; return counter; } __attribute__((CALL_CONV)) extern int64_t test_global(void); +__attribute__((CALL_CONV)) extern int64_t extern_matches(int32_t); +__attribute__((CALL_CONV)) extern int64_t extern_less(int32_t); +__attribute__((CALL_CONV)) extern int64_t extern_seven(void); int main(void) { + if (!extern_matches(7) || extern_matches(8) || !extern_seven() || + !extern_less(6) || extern_less(7) || extern_less(8)) return 2; 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", @@ -17,6 +22,8 @@ int main(void) { } // A second call must observe C's new values, not a Simple initialization copy. counter=-2; + if (!extern_matches(-2) || extern_matches(7) || extern_seven() || + !extern_less(-3) || extern_less(-2)) return 3; result=test_global(); return result!=-19394 || counter!=6 || small!=-1 || fraction!=2.25; } diff --git a/src/test/java/com/seaofnodes/simple/progs/int32.c b/src/test/java/com/seaofnodes/simple/progs/int32.c new file mode 100644 index 00000000..9694c47b --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/int32.c @@ -0,0 +1,26 @@ +#include <stdint.h> +#include <stdio.h> + +typedef struct { int32_t x, y; } Pair; +typedef struct { uint32_t len; int32_t values[]; } Ints; + +__attribute__((CALL_CONV)) extern int64_t int_echo(int32_t); +__attribute__((CALL_CONV)) extern int64_t int_wrap(int32_t); +__attribute__((CALL_CONV)) extern int64_t wide_echo(int64_t); +__attribute__((CALL_CONV)) extern int64_t narrow_sum(int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,int32_t,uint32_t); +__attribute__((CALL_CONV)) extern Pair *int_pair(void); +__attribute__((CALL_CONV)) extern Ints *int_array(void); + +int main(void) { + if (int_echo(-1) != -1 || int_echo(INT32_MIN) != INT32_MIN) return 1; + if (int_wrap(INT32_MAX) != INT32_MIN) return 2; + if (wide_echo(INT64_MAX) != INT64_MAX || wide_echo(INT64_MIN) != INT64_MIN) return 3; + int64_t expected = -128LL + 255 - 32768 + 65535 + INT32_MIN + UINT32_MAX - 7 + UINT32_MAX; + if (narrow_sum(-128,255,-32768,65535,INT32_MIN,UINT32_MAX,-7,UINT32_MAX) != expected) return 4; + Pair *p = int_pair(); + if (p->x != -1 || p->y != INT32_MAX) return 5; + Ints *a = int_array(); + if (a->len != 3 || a->values[0] != -1 || a->values[1] != INT32_MAX || a->values[2] != INT32_MIN) return 6; + puts("ok"); + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/runtime_args.c b/src/test/java/com/seaofnodes/simple/progs/runtime_args.c new file mode 100644 index 00000000..cd676006 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/runtime_args.c @@ -0,0 +1,35 @@ +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +typedef struct { uint32_t len; unsigned char bytes[]; } Bytes; +typedef struct { uint32_t len, pad; Bytes *args[]; } Args; + +__attribute__((CALL_CONV)) +extern Args *simple_args(int64_t argc, char **argv); + +const char *simple_argv(char **argv, int64_t index) { return argv[index]; } + +int main(void) { + Args *empty = simple_args(0, NULL); + if (!empty || empty->len != 0) return 1; + if (simple_args(-1, NULL) != NULL) return 2; + if (simple_args(4294967296LL, NULL) != NULL) return 6; + + char name[] = "program", blank[] = "", utf8[] = "\xc3\xa9\xcf\x80"; + char *argv[] = { name, blank, utf8, name }; + Args *args = simple_args(4, argv); + if (!args || args->len != 4) return 3; + for (int i = 0; i < 4; i++) { + if (!args->args[i] || args->args[i]->len != strlen(argv[i]) || + memcmp(args->args[i]->bytes, argv[i], args->args[i]->len)) return 4; + } + // Copies survive changes to the native buffers, and repeated argv pointers + // still produce independent Simple arrays. + name[0] = 'X'; + utf8[0] = 0; + args->args[0]->bytes[0] = 'Y'; + if (args->args[3]->bytes[0] != 'p' || args->args[2]->bytes[0] != 0xc3) return 5; + puts("ok"); + return 0; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/sort.c b/src/test/java/com/seaofnodes/simple/progs/sort.c index ad76cc7f..08acf2da 100644 --- a/src/test/java/com/seaofnodes/simple/progs/sort.c +++ b/src/test/java/com/seaofnodes/simple/progs/sort.c @@ -5,14 +5,14 @@ typedef struct AryInt { unsigned int len; - long long data[]; + int data[]; } AryInt; __attribute__ ((CALL_CONV)) extern AryInt* merge_sort(AryInt*); -long long primes[] = { 25, 97, 89, 83, 79, 73, 71, 67, 61, 59, 53, 47, 43, 41, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2 }; +int primes[] = { 25, 97, 89, 83, 79, 73, 71, 67, 61, 59, 53, 47, 43, 41, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2 }; int main() { AryInt *as = (AryInt*)primes; diff --git a/src/test/java/com/seaofnodes/simple/progs/stream_io.c b/src/test/java/com/seaofnodes/simple/progs/stream_io.c new file mode 100644 index 00000000..5cd1096a --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/progs/stream_io.c @@ -0,0 +1,49 @@ +#include <errno.h> +#include <stdint.h> +#include <stddef.h> +#include <unistd.h> + +// Deterministic short I/O and failures; ordinary descriptors still use libc. +extern ssize_t __real_read(int, void *, size_t); +extern ssize_t __real_write(int, const void *, size_t); +static int mode, calls, bad; +static int64_t count, offset; + +int32_t test_io_mode(int32_t next, int64_t start) { + mode = next; + calls = bad = 0; + count = 0; + offset = start; + return 0; +} +int64_t test_io_count(void) { return count; } +int32_t test_io_status(void) { + return bad || (mode == 5 && (errno != EAGAIN || calls != 1)); +} + +ssize_t __wrap_read(int fd, void *buffer, size_t size) { + if (fd != 700) return __real_read(fd, buffer, size); + ++calls; + if (mode == 1 && calls == 1) { errno = EINTR; return -1; } + if (mode == 2 && calls > 1) { errno = EIO; return -1; } + if (count == 24593) return 0; + size_t n = size < 137 ? size : 137; + if (n > (size_t)(24593-count)) n = (size_t)(24593-count); + for (size_t i=0; i<n; ++i) ((unsigned char *)buffer)[i] = (count+i)%251; + count += n; + return (ssize_t)n; +} + +ssize_t __wrap_write(int fd, const void *buffer, size_t size) { + if (fd != 701) return __real_write(fd, buffer, size); + ++calls; + if (mode == 1 && calls == 1) { errno = EINTR; return -1; } + if (mode == 3 && calls > 1) { errno = EIO; return -1; } + if (mode == 4) return 0; + if (mode == 5) { errno = EAGAIN; return -1; } + size_t n = size < 7 ? size : 7; + for (size_t i=0; i<n; ++i) + if (((const unsigned char *)buffer)[i] != (offset+count+i)%251) bad = 1; + count += n; + return (ssize_t)n; +} diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash.c b/src/test/java/com/seaofnodes/simple/progs/stringHash.c index 2c6d0fa9..6578c852 100644 --- a/src/test/java/com/seaofnodes/simple/progs/stringHash.c +++ b/src/test/java/com/seaofnodes/simple/progs/stringHash.c @@ -7,7 +7,7 @@ typedef struct ArrayU8 { } ArrayU8; typedef struct String { ArrayU8 * cs; - long long _hashCode; + int _hashCode; } String; |
