summaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-11-08 14:10:02 -0800
commit44505aa5b75b1046f13f2bc2a52fe0b16d975838 (patch)
treecc558485c807ac3842cd73943cf5e294c4a307de /src/test/java
parent2560096fb4636263798ae56c88687973c72cdf6e (diff)
downloadSimple-linear-chapter30.tar.gz
Simple-linear-chapter30.zip
Chapter 30: Text Formatting and Diagnosticslinear-chapter30
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/com/seaofnodes/simple/BoundaryTest.java12
-rw-r--r--src/test/java/com/seaofnodes/simple/BrainFuckTest.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter04Test.java4
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter05Test.java6
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter07Test.java18
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter08Test.java6
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter09Test.java4
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter10Test.java9
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter13Test.java8
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java6
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter15Test.java4
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter16Test.java4
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter17bTest.java23
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter18Test.java46
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter19Test.java36
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter20Test.java14
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter21Test.java27
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter22Test.java11
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter23Test.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter24Test.java7
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter25Test.java26
-rw-r--r--src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java14
-rw-r--r--src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java10
-rw-r--r--src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java8
-rw-r--r--src/test/java/com/seaofnodes/simple/EscapeFlowTest.java10
-rw-r--r--src/test/java/com/seaofnodes/simple/FinalConstructionTest.java10
-rw-r--r--src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java6
-rw-r--r--src/test/java/com/seaofnodes/simple/Int32Test.java94
-rw-r--r--src/test/java/com/seaofnodes/simple/MergeSortTest.java13
-rw-r--r--src/test/java/com/seaofnodes/simple/MinusNodeTest.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java191
-rw-r--r--src/test/java/com/seaofnodes/simple/PartialReadTest.java6
-rw-r--r--src/test/java/com/seaofnodes/simple/RuntimeArgsTest.java29
-rw-r--r--src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java32
-rw-r--r--src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java8
-rw-r--r--src/test/java/com/seaofnodes/simple/Simple.java30
-rw-r--r--src/test/java/com/seaofnodes/simple/StreamIOTest.java138
-rw-r--r--src/test/java/com/seaofnodes/simple/StringFormatTest.java158
-rw-r--r--src/test/java/com/seaofnodes/simple/TerminalInputTest.java184
-rw-r--r--src/test/java/com/seaofnodes/simple/TestC.java53
-rw-r--r--src/test/java/com/seaofnodes/simple/TicTacToeTest.java92
-rw-r--r--src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java70
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/extern_data.c7
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/int32.c26
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/runtime_args.c35
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/sort.c4
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/stream_io.c49
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/stringHash.c2
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 c2713e4f..166f5c5e 100644
--- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
+++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
@@ -63,7 +63,7 @@ val brain_fuck = { ->
return output;
};
""";
- 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;