summaryrefslogtreecommitdiff
path: root/chapter25/src/test
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
commit51f1f8f4b871e3b52eb0c5d18b03f362a2b37791 (patch)
tree0d734f9d53fa58acfd0ad2875f6c753c78d523a1 /chapter25/src/test
parentac3cfbfd58a2e7fdc61995c83a587778cb4e2919 (diff)
downloadSimple-51f1f8f4b871e3b52eb0c5d18b03f362a2b37791.tar.gz
Simple-51f1f8f4b871e3b52eb0c5d18b03f362a2b37791.zip
Chapter 25: add separate compilation and simplify SSA construction
Introduce Chapter 25 with separately compiled Simple modules, serialized IR, cross-compilation-unit linking, and native system-library integration. This also completes a broad parser and type-system cleanup needed to make graph construction independent of incomplete parse-time type information. Separate compilation and linking - Add compilation-unit Start/Stop nodes and track functions, classes, aliases, and global indices across compilation-unit boundaries. - Serialize canonical types and ideal graph nodes into ELF objects, including dependency metadata, relocations, global/local bitset remapping, and dense memory-alias information. - Load dependent objects on demand, recursively resolve source and object paths, relink calls and function pointers, and preserve external C symbols. - Distinguish the post-optimization ownership hook from unknown callers so private call graphs can remain precise while public functions remain externally callable. - Add command-line and build support for compiling modules and the reusable `sys` object library. Parser, constructors, and SSA - Replace inline constructor expansion with a hidden default `<init>` followed by an explicit user constructor declared as `new Type = { ... }`. - Keep the default constructor private, pass private object memory through constructor calls, and require user constructors to initialize non-default fields before the object escapes. - Track possibly-uninitialized fields in parser `Var` state and diagnose early reads without encoding parser-only constraints in Load and Store nodes. - Make ordinary parser memory bulk and recover precise alias slices in the optimizer using `MemMerge`, `MemPhi`, and `BulkMemPhi` nodes. - Remove parser-supplied type constraints from Loads and ordinary Phis, defer integer/float arithmetic selection, and keep call shape driven by syntax instead of provisional inferred types. - Preserve chained field declarations as static parser information when flow types are not yet available, including postfix field updates through calls. Types, functions, and escape analysis - Add `TypeScalar` as the common lattice family for integer, float, memory pointer, and function-pointer values, with scalar TOP/BOT values for generic Loads, Stores, parameters, and invalid intermediate expressions. - Represent unresolved struct definitions directly with the `TypeStruct` forward-reference flag while retaining the separate open-structure concept; close and canonicalize recursive structures without side maps. - Add explicit `FunPtrNode` references so constant function pointers retain a graph edge to their Return, and keep referenced functions alive until Opto proves the pointer dead. - Refine public-function and class-initializer escape handling, including public-only fallback escape sets and monotone compilation-unit summaries. - Add guards, proof casts, read-only propagation, pointer-to-integer conversion for non-null FFI arguments, and consistent deep mutability checks at calls. Optimization and code generation - Make weak and unresolved graph inputs compute conservatively and monotonically throughout iterative peepholes and SCCP. - Preserve independent memory slices when sharpening Stores, retain control on conditional Stores, and freeze Store widths from their target field types. - Extend serialization for new node kinds, scalar types, Store modes, function pointers, and memory partitions, preserving serialize/deserialize bijection. - Complete native selection and encoding support across x86-64, RISC-V, and ARM, including FFI pointer conversion and missing integer operations. - Fix x86 immediate multiply REX encoding and assorted scheduling, register allocation, spilling, relocation, and constant-pool issues exposed by the larger linked graphs. System library, tests, and documentation - Compile and link the Simple `sys` library, including libc bindings, I/O, scanners, strings, vectors, array helpers, and executable entry points. - Add Chapter 25 module, serialization, constructor, forward-reference, postfix-update, system, Hello World, and Bubble Sort coverage; update golden graphs and spill expectations for the new IR shape. - Add module and type-system documentation, parser-simplification design notes, grammar updates, examples, diagrams, and graph visualization assets. The resulting compiler passes the raw Chapter 1-24 suite and the Chapter 25 system and separate-compilation tests, including a freshly rebuilt `sys.o` and the native Bubble Sort example.
Diffstat (limited to 'chapter25/src/test')
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/BrainFuckTest.java83
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter01Test.java95
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter02Test.java61
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter03Test.java73
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter04Test.java131
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java186
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java305
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java222
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java208
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java299
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java418
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java281
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java62
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java281
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java226
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java358
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java258
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java558
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java412
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java435
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java198
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java464
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java278
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java288
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java247
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java277
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Eval2.java525
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java766
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java368
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java43
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Main.java18
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/MergeSortTest.java81
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Simple.java251
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java50
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TestC.java209
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java52
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TypeTest.java214
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java174
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java111
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java177
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java938
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/arg_count.c10
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/brain_fuck.c18
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/fib.c16
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/loop2.c7
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c16
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/person.c34
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/sieve.c19
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/sort.c28
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_10x.c13
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_11x.c13
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_12x.c13
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_13x.c13
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_9x.c13
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/stringHash.c30
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/test0/A.smp6
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/test0/A/B.smp1
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/test5/par.smp20
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/test5/par/child1.smp12
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/test5/par/child2.smp13
60 files changed, 10976 insertions, 0 deletions
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter25/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
new file mode 100644
index 00000000..0ebee2c4
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
@@ -0,0 +1,83 @@
+package com.seaofnodes.simple;
+
+import java.io.IOException;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class BrainFuckTest {
+
+ @Test public void testBrainFuck() throws IOException {
+ String brain_fuck = "Hello World!\n";
+ String src =
+"""
+// -*- mode: java; -*-
+val brain_fuck = { ->
+ var !program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.";
+ int d = 0;
+ u8[] !output = new u8[0];
+ u8[] !data = new u8[100];
+
+ for( int pc = 0; pc < program#; pc++ ) {
+ var command = program[pc];
+ if (command == '>') {
+ d++;
+ } else if (command == '<') {
+ d--;
+ } else if (command == '+') {
+ data[d]++;
+ } else if (command == '-') {
+ data[d]--;
+ } else if (command == '.') {
+ // Output a byte; increase the output array size
+ var old = output;
+ output = new u8[output# + 1];
+ for( int i = 0; i < old#; i++ )
+ output[i] = old[i];
+ output[old#] = data[d]; // Add the extra byte on the end
+ } else if (command == ',') {
+ data[d] = 42;
+ } else if (command == '[') {
+ if (data[d] == 0) {
+ for( int d = 1; d > 0; ) {
+ command = program[++pc];
+ if (command == 91) d++;
+ if (command == 93) d--;
+ }
+ }
+ } else if (command == ']') {
+ if (data[d]) {
+ for( int d = 1; d > 0; ) {
+ command = program[--pc];
+ if (command == 93) d++;
+ if (command == 91) d--;
+ }
+ }
+ }
+ }
+ return output;
+};
+""";
+ TestC.runC(src,"brain_fuck", brain_fuck, 40);
+
+ EvalRisc5 R5 = TestRisc5.build( src, "brain_fuck", 0, 28, false);
+ int trap = R5.step(100000);
+ assertEquals(0,trap);
+ int ptr = (int)R5.regs[com.seaofnodes.simple.node.cpus.riscv.riscv.A0];
+ assertEquals(brain_fuck.length(),R5.ld4s(ptr));
+ for( int i=0; i<brain_fuck.length(); i++ )
+ assertEquals(brain_fuck.charAt(i), R5.ld1z(ptr+4+i));
+
+ EvalArm64 A5 = TestArm64.build("brain_fuck", src, 0, 28, false);
+ int trap_arm = A5.step(100000);
+ assertEquals(0,trap_arm);
+ assertEquals(0,trap);
+ int ptr_arm = (int)A5.regs[com.seaofnodes.simple.node.cpus.arm.arm.X0];
+ assertEquals(brain_fuck.length(),A5.ld4s(ptr_arm));
+
+ for( int i=0; i<brain_fuck.length(); i++ )
+ assertEquals(brain_fuck.charAt(i), A5.ld1z(ptr+4+i));
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter01Test.java
new file mode 100644
index 00000000..fa7fb68a
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter01Test.java
@@ -0,0 +1,95 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter01Test {
+
+ @Test
+ public void testSimpleProgram() {
+ CodeGen code = new CodeGen("1;").parse();
+ Node expr = code.expr();
+ if( expr instanceof ConstantNode con ) {
+ assertEquals(code._start,con.in(0));
+ assertEquals(TypeInteger.constant(1), con._type);
+ } else {
+ fail();
+ }
+ }
+
+ @Test
+ public void testZero() {
+ CodeGen code = new CodeGen("return 0;");
+ code.parse();
+ Node use = code.expr();
+ if( use instanceof ConstantNode con && con._type instanceof TypeInteger )
+ assertEquals(TypeInteger.constant(0),con._type);
+ }
+
+ @Test
+ public void testBad1() {
+ try {
+ new CodeGen("ret;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Undefined name 'ret'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testBad2() {
+ try {
+ new CodeGen("return 0123;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNotBad3() {
+ // this test used to fail in chapter 1
+ assertEquals("return 12;", new CodeGen("return - -12;").parse().print());
+ }
+
+ @Test
+ public void testBad4() {
+ try {
+ new CodeGen("return 100").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected `;` but found `EOF`",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNotBad5() {
+ // this test used to fail in chapter 1
+ assertEquals("return -100;", new CodeGen("return -100;").parse().print());
+ }
+
+ @Test
+ public void testBad6() {
+ try {
+ new CodeGen("return100;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Undefined name 'return100'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testBad7() {
+ try {
+ new CodeGen("return 1;}").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, unexpected but found `}`",e.getMessage());
+ }
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter02Test.java
new file mode 100644
index 00000000..631e1487
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter02Test.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter02Test {
+
+ @Test
+ public void testParseGrammar() {
+ CodeGen code = new CodeGen("return 1+2*3+-5;");
+ code.parse(); // No more disable peepholes
+ assertEquals("return 2;", code.print());
+ }
+
+ @Test
+ public void testAddPeephole() {
+ CodeGen code = new CodeGen("return 1+2;");
+ code.parse();
+ assertEquals("return 3;", code.print());
+ }
+
+ @Test
+ public void testSubPeephole() {
+ CodeGen code = new CodeGen("return 1-2;");
+ code.parse();
+ assertEquals("return -1;", code.print());
+ }
+
+ @Test
+ public void testMulPeephole() {
+ CodeGen code = new CodeGen("return 2*3;");
+ code.parse();
+ assertEquals("return 6;", code.print());
+ }
+
+ @Test
+ public void testDivPeephole() {
+ CodeGen code = new CodeGen("return 6/3;");
+ code.parse();
+ assertEquals("return 2;", code.print());
+ }
+
+ @Test
+ public void testMinusPeephole() {
+ CodeGen code = new CodeGen("return 6/-3;");
+ code.parse();
+ assertEquals("return -2;", code.print());
+ }
+
+ @Test
+ public void testExample() {
+ CodeGen code = new CodeGen("return 1+2*3+-5;");
+ code.parse();
+ assertEquals("return 2;", code.print());
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter03Test.java
new file mode 100644
index 00000000..c96acddc
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter03Test.java
@@ -0,0 +1,73 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter03Test {
+
+ @Test
+ public void testVarDecl() {
+ CodeGen code = new CodeGen("int a=1; return a;");
+ code.parse(); assertEquals("return 1;", code.print());
+ }
+
+ @Test
+ public void testVarAdd() {
+ CodeGen code = new CodeGen("int a=1; int b=2; return a+b;");
+ code.parse();
+ assertEquals("return 3;", code.print());
+ }
+
+ @Test
+ public void testVarScope() {
+ CodeGen code = new CodeGen("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c;");
+ code.parse();
+ assertEquals("return 4;", code.print());
+ }
+
+ @Test
+ public void testVarScopeNoPeephole() {
+ CodeGen code = new CodeGen("int a=1; int b=2; int !c=0; { int b=3; c=a+b; } return c; ");
+ code.parse(); // Peepholes always run now
+ assertEquals("return 4;", code.print());
+ }
+
+ @Test
+ public void testVarDist() {
+ CodeGen code = new CodeGen("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); ");
+ code.parse();
+ assertEquals("return 8;", code.print());
+ }
+
+ @Test
+ public void testSelfAssign() {
+ try {
+ new CodeGen("int a=a; return a;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Undefined name 'a'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testRedeclareVar() {
+ try {
+ new CodeGen("int a=1; int b=2; int c=0; int b=3; c=a+b;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Redefining name 'b'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testBad1() {
+ try {
+ new CodeGen("int a=1; int b=2; int !c=0; { int b=3; c=a+b;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected `}` but found `EOF`",e.getMessage());
+ }
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter04Test.java
new file mode 100644
index 00000000..eaa959cb
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter04Test.java
@@ -0,0 +1,131 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter04Test {
+
+ @Test
+ public void testPeephole() {
+ CodeGen code = new CodeGen("return 1+arg+2; ");
+ code.parse();
+ assertEquals("return (arg+3);", code.print());
+ }
+
+ @Test
+ public void testPeephole2() {
+ CodeGen code = new CodeGen("return (1+arg)+2;");
+ code.parse();
+ assertEquals("return (arg+3);", code.print());
+ }
+
+ @Test
+ public void testAdd0() {
+ CodeGen code = new CodeGen("return 0+arg;");
+ code.parse();
+ assertEquals("return arg;", code.print());
+ }
+
+ @Test
+ public void testAddAddMul() {
+ CodeGen code = new CodeGen("return arg+0+arg;");
+ code.parse();
+ assertEquals("return (arg<<1);", code.print());
+ }
+
+ @Test
+ public void testPeephole3() {
+ CodeGen code = new CodeGen("return 1+arg+2+arg+3; ");
+ code.parse();
+ assertEquals("return ((arg<<1)+6);", code.print());
+ }
+
+ @Test
+ public void testMul1() {
+ CodeGen code = new CodeGen("return 1*arg;");
+ code.parse();
+ assertEquals("return arg;", code.print());
+ }
+
+ @Test
+ public void testVarArg() {
+ CodeGen code = new CodeGen("return arg; ");
+ code.parse();
+ assertTrue(code.ctrl() instanceof FunNode);
+ assertTrue(code.expr() instanceof ParmNode);
+ }
+
+ // Adding functions means `main` does not constant fold the incoming arg value
+ @Ignore @Test
+ public void testConstantArg() {
+ CodeGen code = new CodeGen("return arg; ", TypeInteger.constant(2)).parse();
+ assertEquals("return 2;", code.expr().in(1).toString());
+ }
+
+ @Test
+ public void testCompEq() {
+ CodeGen code = new CodeGen("return 3==3; ");
+ code.parse();
+ assertEquals("return 1;", code.print());
+ }
+
+ @Test
+ public void testCompEq2() {
+ CodeGen code = new CodeGen("return 3==4; ");
+ code.parse();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testCompNEq() {
+ CodeGen code = new CodeGen("return 3!=3; ");
+ code.parse();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testCompNEq2() {
+ CodeGen code = new CodeGen("return 3!=4; ");
+ code.parse();
+ assertEquals("return 1;", code.print());
+ }
+
+ @Test
+ 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());
+ }
+
+ @Test
+ public void testBug2() {
+ CodeGen code = new CodeGen("int !a=arg+1; a=a; return a; ");
+ code.parse();
+ assertEquals("return (arg+1);", code.print());
+ }
+
+ @Test
+ public void testBug3() {
+ try { new CodeGen("inta=1; return a;").parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Undefined name 'inta'",e.getMessage()); }
+ }
+
+ @Test
+ public void testBug4() {
+ CodeGen code = new CodeGen("return -arg;");
+ code.parse();
+ assertEquals("return (-arg);", code.print());
+ }
+
+ @Test
+ public void testBug5() {
+ CodeGen code = new CodeGen("return arg--2;");
+ code.parse();
+ assertEquals("return (arg--2);", code.print());
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java
new file mode 100644
index 00000000..51ff20ba
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java
@@ -0,0 +1,186 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter05Test {
+
+ @Test
+ public void testIfStmt() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+if (arg == 1)
+ a = arg+2;
+else {
+ a = arg-3;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,(arg+2),(arg-3));", code.print());
+ }
+
+ @Test
+ public void testTest() {
+ CodeGen code = new CodeGen(
+"""
+int c = 3;
+int b = 2;
+if (arg == 1) {
+ b = 3;
+ c = 4;
+}
+return c;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,4,3);", code.print());
+ }
+
+ @Test
+ public void testReturn2() {
+ CodeGen code = new CodeGen(
+"""
+if( arg==1 )
+ return 3;
+else
+ return 4;
+""");
+ code.parse();
+ assertEquals("return Phi(Region,3,4);", code.print());
+ }
+
+ @Test
+ public void testIfMergeB() {
+ CodeGen code = new CodeGen(
+"""
+int a=arg+1;
+int b=0;
+if( arg==1 )
+ b=a;
+else
+ b=a+1;
+return a+b;""");
+ code.parse().opto();
+ assertEquals("return ((arg<<1)+Phi(Region,2,3));", code.print());
+ }
+
+ @Test
+ public void testIfMerge2() {
+ CodeGen code = new CodeGen(
+"""
+int a=arg+1;
+int b=arg+2;
+if( arg==1 )
+ b=b+a;
+else
+ a=b+1;
+return a+b;""");
+ code.parse().opto();
+ assertEquals("return ((Phi(Region,(arg<<1),arg)+arg)+Phi(Region,4,5));", code.print());
+ }
+
+ @Test
+ public void testMerge3() {
+ CodeGen code = new CodeGen(
+"""
+int a=1;
+if( arg==1 )
+ if( arg==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,3,5,2,4);", code.print());
+ }
+
+ @Test
+ public void testMerge4() {
+ CodeGen code = new CodeGen(
+"""
+int a=0;
+int b=0;
+if( arg )
+ a=1;
+if( arg==0 )
+ b=2;
+return arg+a+b;
+""");
+ code.parse().opto();
+ assertEquals("return ((arg+Phi(Region,1,0))+Phi(Region,2,0));", code.print());
+ }
+
+ @Test
+ public void testMerge5() {
+ CodeGen code = new CodeGen(
+"""
+int a=arg==2;
+if( arg==1 )
+{
+ a=arg==3;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return (arg==Phi(Region,3,2));", code.print());
+ }
+
+ @Test
+ public void testTrue() {
+ CodeGen code = new CodeGen("return true;").parse();
+ assertEquals("return 1;",code.print());
+ }
+
+ @Test
+ public void testHalfDef() {
+ try { new CodeGen("if( arg==1 ) int b=2; return b;").parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Cannot define a 'b' on one arm of an if",e.getMessage()); }
+ }
+
+ @Test
+ public void testHalfDef2() {
+ try { new CodeGen("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Undefined name 'b'",e.getMessage()); }
+ }
+
+ @Test
+ public void testRegress1() {
+ try { new CodeGen("if(arg==2) int a=1; else int b=2; return a;").parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Cannot define a 'a' on one arm of an if",e.getMessage()); }
+ }
+
+
+ @Test
+ public void testBadNum() {
+ try { new CodeGen("return 1-;").parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Syntax error, expected `an identifier or expression` but found `;`",e.getMessage()); }
+ }
+
+ @Test
+ public void testKeyword1() {
+ try { new CodeGen("int true=0; return true;").parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Expected an identifier, found 'true'",e.getMessage()); }
+ }
+
+ @Test
+ public void testKeyword2() {
+ try { new CodeGen("int else=arg; if(else) else=2; else else=1; return else;").parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Expected an identifier, found 'else'",e.getMessage()); }
+ }
+
+ @Test
+ public void testKeyword3() {
+ try { new CodeGen("int a=1; ififif(arg);a=2;return a;").parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Undefined name 'ififif'",e.getMessage()); }
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java
new file mode 100644
index 00000000..fdf6963f
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java
@@ -0,0 +1,305 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter06Test {
+
+ @Test
+ public void testPeepholeReturn() {
+ CodeGen code = new CodeGen("""
+if( true ) return 2;
+return 1;
+""");
+ code.parse().opto();
+ assertEquals("return 2;", code.print());
+ assertTrue(code.ctrl() instanceof FunNode);
+ }
+
+ @Test
+ public void testPeepholeRotate() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+if (arg)
+ a = 2;
+return (arg < a) < 3; // Because (arg < a) is a bool/uint1/[0-1], its always less than 3
+""");
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ }
+
+ @Test
+ public void testPeepholeCFG() {
+ CodeGen code = new CodeGen(
+"""
+int a=1;
+if( true )
+ a=2;
+else
+ a=3;
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return 2;", code.print());
+ assertTrue(code.ctrl() instanceof FunNode);
+ }
+
+ @Test
+ public void testIfIf() {
+ CodeGen code = new CodeGen(
+"""
+int a=1;
+if( arg!=1 )
+ a=2;
+else
+ a=3;
+int b=4;
+if( a==2 )
+ b=42;
+else
+ b=5;
+return b;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,42,5);", code.print());
+ }
+
+ @Test
+ public void testIfArgIf() {
+ CodeGen code = new CodeGen(
+"""
+int a=1;
+if( 1==1 )
+ a=2;
+else
+ a=3;
+int b=4;
+if( arg==2 )
+ b=a;
+else
+ b=5;
+return b;""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,2,5);", code.print());
+ }
+
+ @Test
+ public void testMerge3With2() {
+ CodeGen code = new CodeGen(
+"""
+arg=2;
+int a=1;
+if( arg==1 )
+ if( arg==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.constant(2)).parse().opto();
+ assertEquals("return 5;", code.print());
+ }
+
+ @Test
+ public void testMerge3With1() {
+ CodeGen code = new CodeGen(
+"""
+arg=1;
+int a=1;
+if( arg==1 )
+ if( arg==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.constant(1)).parse().opto();
+ assertEquals("return 3;", code.print());
+ }
+
+ @Test
+ public void testMerge3Peephole() {
+ CodeGen code = new CodeGen(
+"""
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,3,5,4);", code.print());
+ }
+
+ @Test
+ public void testMerge3Peephole1() {
+ CodeGen code = new CodeGen(
+"""
+arg=1;
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.constant(1));
+ code.parse().opto();
+ assertEquals("return 3;", code.print());
+ }
+
+ @Test
+ public void testMerge3Peephole3() {
+ CodeGen code = new CodeGen(
+"""
+arg=3;
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.constant(3));
+ code.parse().opto();
+ assertEquals("return 4;", code.print());
+ }
+
+ @Test
+ public void testDemo1NonConst() {
+ CodeGen code = new CodeGen("""
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,4,1);", code.print());
+ }
+
+
+ @Test
+ public void testDemo1True() {
+ CodeGen code = new CodeGen(
+"""
+arg=1;
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""", TypeInteger.constant(1));
+ code.parse().opto();
+ assertEquals("return 4;", code.print());
+ }
+
+ @Test
+ public void testDemo1False() {
+ CodeGen code = new CodeGen(
+"""
+arg=0;
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""", TypeInteger.constant(0));
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ }
+
+ @Test
+ public void testDemo2NonConst() {
+ CodeGen code = new CodeGen("""
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,6,1,5);", code.print());
+ }
+
+
+ @Test
+ public void testDemo2True() {
+ CodeGen code = new CodeGen(
+"""
+arg=1;
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""", TypeInteger.constant(1));
+ code.parse().opto();
+ assertEquals("return 6;", code.print());
+ }
+
+ @Test
+ public void testDemo2arg2() {
+ CodeGen code = new CodeGen(
+"""
+arg=2;
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""", TypeInteger.constant(2));
+ code.parse().opto();
+ assertEquals("return 5;", code.print());
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java
new file mode 100644
index 00000000..2ca6f8dd
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java
@@ -0,0 +1,222 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter07Test {
+
+ @Test
+ public void testExample() {
+ CodeGen code = new CodeGen(
+"""
+while(arg < 10) {
+ arg = arg + 1;
+}
+return arg;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,arg,(Phi_arg+1));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testRegression() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+if(arg){}else{
+ while(a < 10) {
+ a = a + 1;
+ }
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,1,Phi(Loop,1,(Phi_a+1)));", code.print());
+ }
+
+ @Test
+ public void testWhileNested() {
+ CodeGen code = new CodeGen(
+"""
+int sum = 0;
+int i = 0;
+while(i < arg) {
+ i = i + 1;
+ int j = 0;
+ while( j < arg ) {
+ sum = sum + j;
+ j = j + 1;
+ }
+}
+return sum;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,0,Phi(Loop,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", code.print());
+ }
+
+ @Test
+ public void testWhileScope() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ if (a == 2) a = 3;
+ else b = 4;
+}
+return b;
+""");
+ code.parse();
+ assertEquals("return Phi(Loop,2,Phi(Region,Phi_b,4));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testWhileNestedIfAndInc() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ if (a == 2) a = 3;
+ else b = 4;
+ b = b + 1;
+ a = a + 1;
+}
+return b;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,2,(Phi(Region,Phi_b,4)+1));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+
+ @Test
+ public void testWhile() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+while(a < 10) {
+ a = a + 1;
+ a = a + 2;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testWhilePeep() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+while(a < 10) {
+ a = a + 1;
+ a = a + 2;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testWhile2() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+while(arg) a = 2;
+return a;
+""");
+ code.parse();
+ assertEquals("return Phi(Loop,1,2);", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testWhile2Peep() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+while(arg) a = 2;
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,1,2);", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testWhile3() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testWhile3Peep() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testWhile4() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testWhile4Peep() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java
new file mode 100644
index 00000000..09669cc7
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java
@@ -0,0 +1,208 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter08Test {
+
+ @Test
+ public void testEx6() {
+ CodeGen code = new CodeGen(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ break;
+ if (arg == 6)
+ break;
+}
+return arg;
+ """);
+ code.parse().opto();
+ assertEquals("return Phi(Region,(Phi(Loop,arg,Add)+1),Add,Phi_arg);", code.print());
+ assertTrue(code.ctrl() instanceof RegionNode);
+ Assert.assertEquals( "5", Eval2.eval(code, 1));
+ Assert.assertEquals("10", Eval2.eval(code, 6));
+ }
+
+
+ @Test
+ public void testEx5() {
+ CodeGen code = new CodeGen(
+ """
+int a = 1;
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ continue;
+ if (arg == 7)
+ continue;
+ a = a + 1;
+}
+return a;
+ """);
+ code.parse().opto();
+ assertEquals("return Phi(Loop,1,Phi(Region,Phi_a,(Phi_a+1),Phi_a));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+
+ @Test
+ public void testEx4() {
+ CodeGen code = new CodeGen(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ continue;
+ if (arg == 6)
+ break;
+}
+return arg;
+ """);
+ code.parse().opto();
+ assertEquals("return Phi(Region,Phi(Loop,arg,(Phi_arg+1)),Add);", code.print());
+ assertTrue(code.ctrl() instanceof RegionNode);
+ }
+
+ @Test
+ public void testEx3() {
+ CodeGen code = new CodeGen(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 6)
+ break;
+}
+return arg;
+ """);
+ code.parse().opto();
+ assertEquals("return Phi(Region,Phi(Loop,arg,(Phi_arg+1)),Add);", code.print());
+ assertTrue(code.ctrl() instanceof RegionNode);
+ }
+
+
+ @Test
+ public void testEx2() {
+ CodeGen code = new CodeGen(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ continue;
+ if (arg == 6)
+ continue;
+}
+return arg;
+ """);
+ code.parse().opto();
+ assertEquals("return Phi(Loop,arg,(Phi_arg+1));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+
+ @Test
+ public void testEx1() {
+ CodeGen code = new CodeGen(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ continue;
+}
+return arg;
+ """);
+ code.parse().opto();
+ assertEquals("return Phi(Loop,arg,(Phi_arg+1));", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode);
+ }
+
+ @Test
+ public void testRegress1() {
+ CodeGen code = new CodeGen(
+ """
+while( arg < 10 ) {
+ int a = arg+2;
+ if( a > 4 )
+ break;
+}
+return arg;
+ """);
+ code.parse().opto();
+ assertEquals("return arg;", code.print());
+ }
+
+ @Test
+ public void testRegress2() {
+ CodeGen code = new CodeGen("if(1) return 0; else while(arg>- -arg) arg=arg+1; return 0;");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testBreakOutsideLoop() {
+ try {
+ new CodeGen("""
+if(arg <= 10) {
+ break;
+ arg = arg + 1;
+}
+return arg;
+""").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("No active loop for a break or continue",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testRegress3() {
+ CodeGen code = new CodeGen("""
+while(arg < 10) {
+ break;
+}
+return arg;
+""");
+ code.parse().opto();
+ assertEquals("return arg;", code.print());
+ }
+
+ @Test
+ public void testRegress4() {
+ CodeGen code = new CodeGen("""
+int a = 1;
+while(arg < 10) {
+ a = a + 1;
+ if (arg > 2) {
+ int a = 17;
+ break;
+ }
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,Phi(Loop,1,(Phi_a+1)),Add);", code.print());
+ assertTrue(code.ctrl() instanceof RegionNode);
+ }
+
+ @Test
+ public void testRegress5() {
+ CodeGen code = new CodeGen("""
+int a = 1;
+while(1) {
+ a = a + 1;
+ if (a<10) continue;
+ break;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return (Phi(Loop,1,Add)+1);", code.print());
+ assertTrue(code.ctrl() instanceof CProjNode prj && prj._idx==1 );
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java
new file mode 100644
index 00000000..d36cdf7f
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java
@@ -0,0 +1,299 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter09Test {
+
+ @Test
+ @Ignore
+ public void testJig() {
+ CodeGen code = new CodeGen("""
+int v0=0;
+arg=0;
+while(v0) {
+ while(1) if(arg*arg*0==0) {}
+ while(0) {}
+ arg=1;
+}
+return 0;
+ """);
+ code.parse().opto();
+ }
+
+ @Test
+ public void testGVN1() {
+ CodeGen code = new CodeGen(
+ """
+int x = arg + arg;
+if(arg < 10) {
+ return arg + arg;
+}
+else {
+ x = x + 1;
+}
+return x;
+ """);
+ code.parse().opto();
+ assertEquals("return Phi(Region,(arg<<1),(Shl+1));", code.print());
+ Assert.assertEquals( "2", Eval2.eval(code, 1));
+ Assert.assertEquals("23", Eval2.eval(code, 11));
+ }
+
+ @Test
+ public void testGVN2() {
+ CodeGen code = new CodeGen("return arg*arg-arg*arg;");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ Assert.assertEquals("0", Eval2.eval(code, 1, 1));
+ }
+
+ @Test
+ public void testWorklist1() {
+ CodeGen code = new CodeGen("""
+int step = 1;
+while (arg < 10) {
+ arg = arg + step + 1;
+}
+return arg;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,arg,(Phi_arg+2));", code.print());
+ Assert.assertEquals("11", Eval2.eval(code, 1));
+ }
+
+ @Test
+ public void testWorklist2() {
+ CodeGen code = new CodeGen(
+ """
+int cond = 0;
+int one = 1;
+while (arg < 10) {
+ if (cond) one = 2;
+ arg = arg + one*3 + 1;
+}
+return arg;
+ """);
+ code.parse().opto();
+ assertEquals("return Phi(Loop,arg,(Phi_arg+4));", code.print());
+ Assert.assertEquals("13", Eval2.eval(code, 1));
+ }
+
+ @Test
+ public void testWorklist3() {
+ CodeGen code = new CodeGen(
+ """
+int v1 = 0;
+int v2 = 0;
+int v3 = 0;
+int v4 = 0;
+int v5 = 0;
+int v6 = 0;
+int v7 = 0;
+int v8 = 0;
+while (arg) {
+ if (v1) v2 = 1;
+ if (v2) v3 = 1;
+ if (v3) v4 = 1;
+ if (v4) v5 = 1;
+ if (v5) v6 = 1;
+ if (v6) v7 = 1;
+ if (v7) v8 = 1;
+ arg = arg + v8 + 1;
+}
+return arg;
+ """);
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testRegionPeepBug() {
+ CodeGen code = new CodeGen(
+ """
+int v0=0;
+int v1=0;
+while(v1+arg) {
+ arg=0;
+ int v2=v0;
+ while(arg+1) {}
+ v0=1;
+ v1=v2;
+}
+return 0;
+ """);
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testWhile0() {
+ CodeGen code = new CodeGen("while(0) continue; if(0) arg=0; return arg;");
+ code.parse().opto();
+ assertEquals("return arg;", code.print());
+ }
+
+ @Test
+ public void testWhile1() {
+ CodeGen code = new CodeGen("""
+if(0) while(0) {
+ int arg=arg;
+ while(0) {}
+}
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+
+ @Test
+ public void testPrecedence() {
+ CodeGen code = new CodeGen("return 3-1+2;");
+ code.parse().opto();
+ assertEquals("return 4;", code.print());
+ }
+
+ @Test
+ public void testSwap2() {
+ CodeGen code = new CodeGen("return 1+(1+1);");
+ code.parse().opto();
+ assertEquals("return 3;", code.print());
+ }
+
+
+ @Test
+ public void testFuzz0() {
+ CodeGen code = new CodeGen("""
+int one = 1;
+int a = 0;
+int zero = 0;
+while(arg) {
+ a = -(one + a + 2);
+ arg = arg + 1;
+ one = one + zero;
+}
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,0,(-(Phi_a+3)));", code.print());
+ }
+
+ @Test
+ public void testFuzz1() {
+ CodeGen code = new CodeGen("""
+while(1) {}
+while(arg) break;
+while(arg) arg=0;
+arg=0;
+int v0=0!=0<-0;
+return -0+0+0;
+""");
+ code.parse().opto();
+ assertEquals("return Top;", code.print());
+ }
+
+
+ @Test
+ public void testFuzz2() {
+ CodeGen code = new CodeGen("return 0+-0;");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testFuzz3() {
+ CodeGen code = new CodeGen("int v0=0; while(0==69) while(v0) return 0; return 1;");
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ }
+
+ @Test
+ public void testFuzz4() {
+ CodeGen code = new CodeGen("""
+while(1) {
+ arg=0<=0;
+ if(1<0) while(arg==-0) arg=arg-arg;
+}
+""");
+ code.parse().opto();
+ assertEquals("return Top;", code.print());
+ }
+
+ @Test
+ public void testFuzz5() {
+ CodeGen code = new CodeGen("""
+{
+ int v0=0;
+ while(1)
+ int v1=0--0;
+ while(v0)
+ break;
+ while(-v0) {
+ while(0+0+v0) continue;
+ break;
+ }
+ if(-0!=-0+0+v0) while(0+0+0+0)
+ break;
+}
+return 0!=0;
+""");
+ code.parse().opto();
+ assertEquals("return Top;", code.print());
+ }
+
+ @Test
+ public void testFuzz6() {
+ CodeGen code = new CodeGen(
+"""
+int v0=0;
+while(0==1) while(v0)
+ v0=1+v0;
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testFuzz7() {
+ CodeGen code = new CodeGen("""
+while(1) {}
+int v0=0;
+while(v0)
+ {}
+int v1=0;
+while(1)
+ v1=1;
+return v1+v0;
+ """);
+ code.parse().opto();
+ assertEquals("return Top;", code.print());
+ }
+
+ @Test
+ public void testFuzz8() {
+ CodeGen code = new CodeGen("while(arg) arg = arg - 1; return arg;");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testMeet() {
+ Type t1 = Type.TOP;
+ Type t2 = TypeInteger.TOP;
+ Assert.assertEquals(TypeInteger.TOP, t1.meet(t2));
+ Assert.assertEquals(TypeInteger.TOP, t2.meet(t1));
+ t1 = Type.BOTTOM;
+ t2 = TypeInteger.BOT;
+ Assert.assertEquals(Type.BOTTOM, t1.meet(t2));
+ Assert.assertEquals(Type.BOTTOM, t2.meet(t1));
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java
new file mode 100644
index 00000000..6b237b18
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -0,0 +1,418 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class Chapter10Test {
+
+ @Test
+ public void testFuzzer() {
+ CodeGen code = new CodeGen(
+"""
+int a = arg/3;
+int b = arg*5;
+int x = arg*7;
+int y = arg/11;
+int p; int g; int h;
+if( (arg/13)==0 ) {
+ p = x + y;
+ g = x;
+ h = y;
+} else {
+ p = a + b;
+ g = a;
+ h = b;
+}
+int r = g+h;
+return p-r;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testStruct() {
+ CodeGen code = new CodeGen("""
+struct _Bar {
+ int a;
+ int b;
+};
+struct _Foo {
+ int x;
+};
+_Foo? foo = null;
+_Bar !bar = new _Bar;
+bar.a = 1;
+bar.a = 2;
+return bar.a;
+""");
+ code.parse().opto();
+ assertEquals("return 2;", code.print());
+ }
+
+ @Test
+ public void testExample() {
+ CodeGen code = new CodeGen("""
+struct Vector2D { int x; int y; };
+Vector2D !v = new Vector2D;
+v.x = 1;
+if (arg)
+ v.y = 2;
+else
+ v.y = 3;
+return v;
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return Test.Vector2D; return MEM[ 2:.x=0; 3:.y=0;]; ]", code.print());
+ }
+
+ @Test
+ public void testBug() {
+ CodeGen code = new CodeGen("""
+struct s0 {
+ int v0;
+};
+s0? v1=null;
+int v3=v1.zAicm;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Might be null accessing 'zAicm'",e.getMessage()); }
+ }
+
+ @Test
+ public void testBug2() {
+ CodeGen code = new CodeGen("""
+struct s0 { int v0; };
+arg=0+new s0.0;
+""");
+ try { code.parse(); fail(); }
+ catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); }
+ }
+
+ @Test
+ public void testLoop() {
+ CodeGen code = new CodeGen("""
+struct Bar { int a; };
+Bar !bar = new Bar;
+while (arg) {
+ bar.a = bar.a + 2;
+ arg = arg + 1;
+}
+return bar.a;
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MEM[ 2:.a=0;]; ]", code.print());
+ }
+
+ @Test
+ public void testIf() {
+ CodeGen code = new CodeGen("""
+struct Bar { int a; };
+Bar !bar = new Bar;
+if (arg) bar = null;
+bar.a = 1;
+return bar.a;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Type null is not of declared type *Test.Bar",e.getMessage()); }
+ }
+
+ @Test
+ public void testIf2() {
+ CodeGen code = new CodeGen("""
+struct Bar { int a; };
+Bar? !bar = null;
+if (arg) bar = new Bar;
+bar.a = 1;
+return bar.a;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Might be null accessing 'a'",e.getMessage()); }
+ }
+
+ @Test
+ public void testIf3() {
+ CodeGen code = new CodeGen("""
+struct Bar { int a; };
+Bar bar = null;
+if (arg) bar = null;
+bar.a = 1;
+return bar.a;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Type null is not of declared type *Test.Bar", e.getMessage()); }
+ }
+
+ @Test
+ public void testIfOrNull() {
+ CodeGen code = new CodeGen("""
+struct _Bar { int a; };
+_Bar? !bar = new _Bar;
+if (arg) bar = null;
+if( bar ) bar.a = 1;
+return bar.a;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,1,.a);", code.print());
+ }
+
+ @Test
+ public void testIfOrNull2() {
+ CodeGen code = new CodeGen(
+"""
+struct _Bar { int a; };
+_Bar? !bar = new _Bar;
+if (arg) bar = null;
+int rez = 3;
+if( !bar ) rez=4;
+else bar.a = 1;
+return rez;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,4,3);", code.print());
+ }
+
+ @Test
+ public void testWhileWithNullInside() {
+ CodeGen code = new CodeGen("""
+struct s0 {int v0;};
+s0? !v0 = new s0;
+int ret = 0;
+while(arg) {
+ ret = v0.v0;
+ v0 = null;
+ arg = arg - 1;
+}
+return ret;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) {
+ assertEquals("Might be null accessing 'v0'", e.getMessage()); }
+ }
+
+ @Test
+ public void testRedeclareStruct() {
+ CodeGen code = new CodeGen("""
+struct s0 {
+ int v0;
+};
+s0? v1=new s0;
+s0? v1;
+v1=new s0;
+""");
+ try { code.parse(); fail(); }
+ catch( Exception e ) { assertEquals("Redefining name 'v1'", e.getMessage()); }
+ }
+
+ @Test
+ public void testIter() {
+ // Build and use an iterator
+ CodeGen code = new CodeGen(
+"""
+struct Iter {
+ int x;
+ int len;
+};
+Iter !i = new Iter;
+i.len = arg;
+int sum=0;
+while( i.x < i.len ) {
+ sum = sum + i.x;
+ i.x = i.x + 1;
+}
+return sum;
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MEM[ 2:.x=0; 3:.len=0;]; ]", code.print());
+ }
+
+
+ @Test
+ public void test1() {
+ CodeGen code = new CodeGen("""
+struct s0 {int v0;};
+s0 !ret = new s0;
+while(arg) {
+ s0 !v0 = new s0;
+ v0.v0 = arg;
+ arg = arg-1;
+ if (arg==5) ret=v0;
+
+}
+return ret;
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MEM[ 2:.v0=0;]; ]", code.print());
+ }
+
+ @Test
+ public void test2() {
+ CodeGen code = new CodeGen("""
+struct _s0 {int v0;};
+_s0 !ret = new _s0;
+_s0 !v0 = new _s0;
+while(arg) {
+ v0.v0 = arg;
+ arg = arg-1;
+ if (arg==5) ret=v0;
+}
+return ret.v0;
+""");
+ code.parse().opto();
+ assertEquals("return .v0;", code.print());
+ }
+
+
+ @Test
+ public void test3() {
+ CodeGen code = new CodeGen("""
+struct _s0 {int v0;};
+_s0 !ret = new _s0;
+while(arg < 10) {
+ _s0 !v1 = new _s0;
+ if (arg == 5) ret=v1;
+ arg = arg + 1;
+}
+return ret.v0;
+""");
+ code.parse().opto();
+ assertEquals("return .v0;", code.print());
+ }
+
+
+
+ @Test
+ public void testModGlobalInConstructor() {
+ CodeGen code = new CodeGen(
+"""
+int cnt = 1;
+// cnt is incremented exactly once, it is not part of s0 constructor
+// hence s0.x is always 1, and cnt remains at 2
+struct s0 { int x = cnt++; };
+s0 a = new s0();
+s0 b = new s0();
+// a.x==1, b.x==1, cnt==2
+return a.x * 100 + b.x * 10 + cnt;
+""");
+ code.parse().iter().opto();
+ assertEquals("Stop[ return 112; return MEM[ 2:___ 3:.x=1;]; ]", code.print());
+ }
+
+
+ @Test
+ public void testBug3() {
+ CodeGen code = new CodeGen("""
+struct s0 { int f0; };
+return new s0;
+int v0=null.f0;
+""");
+ try { code.parse(); fail(); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `;` but found `.`",e.getMessage()); }
+ }
+
+ @Test
+ public void testBug4() {
+ CodeGen code = new CodeGen("""
+if(0) {
+ while(0) if(arg) continue;
+ int v0=0;
+ while(1) {
+ int v2=-arg;
+ v0=v2;
+ }
+}
+return 0;
+ """);
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testBug5() {
+ CodeGen code = new CodeGen("""
+struct s0 {
+ int f0;
+};
+if(0) return 0;
+else return new s0;
+if(new s0.f0) return 0;
+ """);
+ code.parse().iter().opto().typeCheck();
+ assertEquals("Stop[ return Test.s0; return MEM[ 2:.f0=0;]; ]", code.print());
+ }
+
+ @Test
+ public void testBug6MissedWorklist() {
+ CodeGen code = new CodeGen("""
+while(0) {}
+int v4=0;
+while(0<arg) {
+ v4=v4+1;
+ while(1) v4=-v4;
+ while(0) arg=-1;
+}
+return 0;
+ """);
+ code.parse().opto();
+ }
+
+ @Test
+ public void testBug7() {
+ CodeGen code = new CodeGen("""
+struct s0 { int f0; };
+s0 v0 = new s0;
+while(v0.f0) {}
+s0 v1 = v0;
+return v1;
+ """);
+ code.parse().opto();
+ assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:.f0=0;]; ]", code.print());
+ }
+
+
+ @Test
+ public void testBug8() {
+ CodeGen code = new CodeGen("""
+int v2=0;
+while(0)
+while(0) {}
+{
+ {
+ {
+ int v36=0;
+ {
+ while(0) {
+ {
+ while(-v2) {
+ {
+ while(v36) {
+ while(v2) return 0;
+ break;
+ } }
+ if(-v2) break;
+ }
+ }
+ }
+ }
+ } }
+}
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testBug9() {
+ CodeGen code = new CodeGen("""
+int v0=arg==0;
+while(v0) continue;
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java
new file mode 100644
index 00000000..ab730b45
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java
@@ -0,0 +1,281 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class Chapter11Test {
+
+ @Test
+ public void testPrimes() {
+ CodeGen code = new CodeGen(
+"""
+if( arg < 2 ) return 0;
+int primeCount = 1;
+int prime = 3;
+while( prime <= arg ) {
+ int isPrime = 1;
+ // Check for even case, so the next loop need only check odds
+ if( (prime/2)*2 == prime )
+ continue;
+ // Check odds up to sqrt of prime
+ int j = 3;
+ while( j*j <= prime ) {
+ if( (prime/j)*j == prime ) {
+ isPrime = 0;
+ break;
+ }
+ j = j + 2;
+ }
+ if( isPrime )
+ primeCount = primeCount + 1;
+ prime = prime + 2;
+}
+return primeCount;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,0,Phi(Loop,1,Phi(Region,Phi_primeCount,Phi_primeCount,(Phi_primeCount+1))));", 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
+ assertEquals("2", Eval2.eval(code, 4)); // 2, 3
+ assertEquals("3", Eval2.eval(code, 5)); // 2, 3, 5
+ assertEquals("4", Eval2.eval(code, 10)); // 2, 3, 5, 7
+ }
+
+ @Test
+ public void testClassField() {
+ CodeGen code = new CodeGen(
+"""
+if( arg < 2 ) return 0;
+int primeCount = 1;
+while( arg-- )
+ primeCount++;
+return primeCount;
+""");
+ code.driver(CodeGen.Phase.LocalSched);
+ assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below
+ assertEquals("3", Eval2.eval(code, 2)); //
+}
+
+
+ @Test
+ public void testAntiDeps1() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v=new _S;
+v.f = 2;
+_S q=new _S;
+int i=q.f;
+i=v.f;
+if (arg) v.f=1;
+return i;
+""");
+ code.parse().opto();
+ assertEquals("return 2;", code.print());
+ assertEquals("2", Eval2.eval(code, 0));
+ assertEquals("2", Eval2.eval(code, 1));
+ }
+
+ @Test
+ public void testAntiDeps2() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v = new _S;
+v.f = arg;
+_S !t = new _S;
+int i = 0;
+if (arg) {
+ if (arg+1) v = t;
+ i = v.f;
+} else {
+ v.f = 2;
+}
+return i;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,.f,0);", code.print());
+ }
+
+ @Test
+ public void testAntiDeps3() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v0 = new _S;
+_S? v1;
+if (arg) v1 = new _S;
+if (v1) {
+ v0.f = v1.f;
+} else {
+ v0.f = 2;
+}
+return v0.f;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,.f,2);", code.print());
+ }
+
+
+ @Test
+ public void testAntiDeps4() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v = new _S;
+v.f = arg;
+_S t = new _S;
+int i = v.f;
+if (arg+1) arg= 0;
+while (arg) v.f = 2;
+return i;
+""");
+ code.parse().opto();
+ assertEquals("return arg;", code.print());
+ }
+
+ @Test
+ public void testAntiDeps5() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v = new _S;
+while(1) {
+ while(arg+1) { arg=arg-1; }
+ if (arg) break;
+ v.f = 2;
+}
+return v.f;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,0,2);", code.print());
+ }
+
+ @Test
+ public void testAntiDeps6() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int v; };
+_S !ptr=new _S;
+while( -arg )
+ ptr = new _S;
+while(1)
+ arg = arg+ptr.v;
+""");
+ code.parse().opto();
+ assertEquals("return Top;", code.print());
+ }
+
+ @Test
+ public void testAntiDeps7() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v = new _S;
+_S t = new _S;
+int i = v.f;
+while (arg) {
+ v.f = arg;
+ arg = arg-1;
+}
+return i;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testAntiDeps8() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v = new _S;
+_S t = new _S;
+while(arg) {
+ arg=arg-1;
+ int f = v.f;
+ v.f = 2;
+ if (arg) arg = f;
+ else v.f = 3;
+}
+return arg;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testAntiDeps9() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v = new _S;
+_S t = new _S;
+if (arg) {
+ v.f=2;
+ int i=t.f;
+ v.f=i;
+}
+return v.f;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testExample2() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int f; };
+_S !v = new _S;
+int i = arg;
+while (arg > 0) {
+ int j = i/3;
+ if (arg == 5)
+ v.f = j;
+ arg = arg - 1;
+}
+return v;
+ """);
+ code.parse().opto();
+ //assertEquals("return new _S;", code.print());
+ }
+
+ @Test
+ public void testScheduleUse() {
+ CodeGen code = new CodeGen(
+"""
+int v0=0;
+while(0>=0) {
+ u1 v1=0;
+ v1=v0;
+ if(v1*0)
+ v0=-v1;
+}
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return Top;", code.print());
+ }
+
+ @Test
+ public void testLoopCarriedDep() {
+ CodeGen code = new CodeGen(
+"""
+u32 v0=0;
+{
+ int v1=0;
+ while(v1) {
+ v1=1>>>v0!=0;
+ v0=v1/0;
+ }
+ return v1;
+}
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
new file mode 100644
index 00000000..85317644
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -0,0 +1,62 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class Chapter12Test {
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen(
+"""
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testFloat() {
+ CodeGen code = new CodeGen(
+"""
+flt x = 3.14;
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testSquareRoot() {
+ CodeGen code = new CodeGen(
+"""
+flt guess = arg;
+while( 1 ) {
+ flt next = (arg/guess + guess)/2;
+ if( next == guess ) break;
+ guess = next;
+}
+return guess;
+""");
+ // TODO: One of the problems with <clinit> returning an OS error code,
+ // is it chops all results to int.
+ code.parse().opto();
+ assertEquals("return Phi(Loop,(flt)arg,((Phi_guess+(ToFloat/Phi_guess))*0.5f));", code.print());
+ assertEquals("3.0", Eval2.eval(code, 9));
+ assertEquals("1.414213562373095", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testFPOps() {
+ CodeGen code = new CodeGen(
+"""
+flt x = arg;
+return x+1==x;
+""");
+ code.parse().opto();
+ assertEquals("return ((flt)arg==(ToFloat+1.0f));", code.print());
+ assertEquals("0", Eval2.eval(code, 1));
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java
new file mode 100644
index 00000000..8bbcf21c
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java
@@ -0,0 +1,281 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class Chapter13Test {
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen(
+"""
+return 3;
+""");
+ code.parse().opto();
+ assertEquals("return 3;", code.print());
+ assertEquals("3", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testLinkedList0() {
+ CodeGen code = new CodeGen(
+"""
+struct LLI { LLI? next; int i; };
+LLI? !head = null;
+while( arg ) {
+ LLI !x = new LLI;
+ x.next = head;
+ x.i = arg;
+ head = x;
+ arg = arg-1;
+}
+return head.next.i;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); }
+ }
+
+ @Test
+ public void testLinkedList1() {
+ CodeGen code = new CodeGen(
+"""
+struct _LLI { _LLI? next; int q; };
+_LLI? !head = null;
+while( arg ) {
+ _LLI !x = new _LLI;
+ x.next = head;
+ x.q = arg;
+ head = x;
+ arg = arg-1;
+}
+if( !head ) return 0;
+_LLI? next = head.next;
+if( next==null ) return 1;
+return next.q;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return Phi(Region,1,.q,0);", code.print());
+ assertEquals("2", Eval2.eval(code, 3));
+ }
+
+ @Test
+ public void testCoRecur() {
+ CodeGen code = new CodeGen(
+"""
+struct _int0 { int i; Test._flt0? f; };
+struct _flt0 { flt f; _int0? i; };
+_int0 !i0 = new _int0;
+i0.i = 17;
+_flt0 !f0 = new _flt0;
+f0.f = 3.14;
+i0.f = f0;
+f0.i = i0;
+return f0.i.f.i.i;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return 17;", code.print());
+ }
+
+ @Test
+ public void testNullRef0() {
+ CodeGen code = new CodeGen(
+"""
+struct _N { _N? next; int i; };
+_N n = new _N;
+return n.next;
+""");
+ code.parse().opto();
+ assertEquals("return null;", code.print());
+ }
+
+ @Test
+ public void testNullRef1() {
+ CodeGen code = new CodeGen(
+"""
+struct M { int m; };
+struct N { M next; int i; new N = { M m -> next = m; }; };
+N n = new N(new M);
+return n.next;
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return (const)Test.M; return MEM[ 2:.m=0;]; return MEM[ 2:___ 3:___ 4:___ 5:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 6:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:.i=0;]; ]", code.print());
+ }
+
+ @Test
+ public void testNullRef2() {
+ CodeGen code = new CodeGen(
+"""
+struct M { int m; };
+struct N { M next; int i; new N = { M m -> next = m; }; };
+N n = new N(null);
+return n.next;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Argument #0 isa null, but must be a *Test.M {i64 !m; }",e.getMessage()); }
+ }
+
+ @Test
+ public void testNullRef3() {
+ CodeGen code = new CodeGen(
+"""
+struct N { N? next; int i; };
+N !n = new N;
+n.i = 3.14;
+return n.i;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Type 3.14 is not of declared type i64",e.getMessage()); }
+ }
+
+ @Test
+ public void testNullRef4() {
+ CodeGen code = new CodeGen("return -null-5/null-5;");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot 5 // null",e.getMessage()); }
+ }
+
+ @Test public void testNullRef5() {
+ CodeGen code = new CodeGen("return null+42;");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot null + 42",e.getMessage()); }
+ }
+
+ @Test
+ public void testEmpty() {
+ CodeGen code = new CodeGen(
+"""
+struct _S{};
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testForwardRef0() {
+ CodeGen code = new CodeGen(
+"""
+struct S1 { S2? s; };
+return new S2;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); }
+ }
+
+ @Test
+ public void testForwardRef1() {
+ CodeGen code = new CodeGen(
+"""
+struct _S1 { _S2? s; };
+struct _S2 { int x; };
+return (new _S1).s=new _S2;
+""");
+ code.parse().opto();
+ assertEquals("return _S2;", code.print());
+ }
+
+ @Test
+ public void testcheckNull() {
+ CodeGen code = new CodeGen(
+"""
+struct I { int i; };
+struct P { I? pi; };
+P !p1 = new P;
+P !p2 = new P;
+p2.pi = new I;
+p2.pi.i = 2;
+if (arg) p1 = new P;
+return p1.pi.i + 1;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); }
+ }
+
+ @Test
+ public void testCoRecur2() {
+ CodeGen code = new CodeGen(
+"""
+struct _A { Test._B? f0; Test._C? f1; }; _A !a = new _A;
+struct _B { Test._C? f0; Test._A? f1; }; _B !b = new _B;
+struct _C { Test._A? f0; Test._B? f1; }; _C !c = new _C;
+
+a.f0=b; a.f1=c;
+b.f0=c; b.f1=a;
+c.f0=a; c.f1=b;
+
+return a.f0.f1.f0.f1.f0;
+
+""");
+ code.parse().opto().typeCheck().GCM().localSched();
+ assertEquals("return Test._B;", code.print());
+ assertEquals("Test._B{f0=Test._C{f0=Test._A{f0=$cyclic,f1=$cyclic},f1=$cyclic},f1=$cyclic}", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testCoRecur3() {
+ CodeGen code = new CodeGen(
+"""
+struct _A { Test._L? a; Test._T? b; Test._F? c; }; _A !a = new _A;
+struct _B { Test._M? a; Test._U? b; Test._G? c; }; _B !b = new _B;
+struct _C { Test._N? a; Test._V? b; Test._H? c; }; _C !c = new _C;
+struct _D { Test._O? a; Test._W? b; Test._I? c; }; _D !d = new _D;
+struct _E { Test._P? a; Test._X? b; Test._J? c; }; _E !e = new _E;
+struct _F { Test._Q? a; Test._Y? b; Test._K? c; }; _F !f = new _F;
+struct _G { Test._R? a; Test._Z? b; Test._L? c; }; _G !g = new _G;
+struct _H { Test._S? a; Test._A? b; Test._M? c; }; _H !h = new _H;
+struct _I { Test._T? a; Test._B? b; Test._N? c; }; _I !i = new _I;
+struct _J { Test._U? a; Test._C? b; Test._O? c; }; _J !j = new _J;
+struct _K { Test._V? a; Test._D? b; Test._P? c; }; _K !k = new _K;
+struct _L { Test._W? a; Test._E? b; Test._Q? c; }; _L !l = new _L;
+struct _M { Test._X? a; Test._F? b; Test._R? c; }; _M !m = new _M;
+struct _N { Test._Y? a; Test._G? b; Test._S? c; }; _N !n = new _N;
+struct _O { Test._Z? a; Test._H? b; Test._T? c; }; _O !o = new _O;
+struct _P { Test._A? a; Test._I? b; Test._U? c; }; _P !p = new _P;
+struct _Q { Test._B? a; Test._J? b; Test._V? c; }; _Q !q = new _Q;
+struct _R { Test._C? a; Test._K? b; Test._W? c; }; _R !r = new _R;
+struct _S { Test._D? a; Test._L? b; Test._X? c; }; _S !s = new _S;
+struct _T { Test._E? a; Test._M? b; Test._Y? c; }; _T !t = new _T;
+struct _U { Test._F? a; Test._N? b; Test._Z? c; }; _U !u = new _U;
+struct _V { Test._G? a; Test._O? b; Test._A? c; }; _V !v = new _V;
+struct _W { Test._H? a; Test._P? b; Test._B? c; }; _W !w = new _W;
+struct _X { Test._I? a; Test._Q? b; Test._C? c; }; _X !x = new _X;
+struct _Y { Test._J? a; Test._R? b; Test._D? c; }; _Y !y = new _Y;
+struct _Z { Test._K? a; Test._S? b; Test._E? c; }; _Z !z = new _Z;
+
+a.a=l; a.b=t; a.c=f;
+b.a=m; b.b=u; b.c=g;
+c.a=n; c.b=v; c.c=h;
+d.a=o; d.b=w; d.c=i;
+e.a=p; e.b=x; e.c=j;
+f.a=q; f.b=y; f.c=k;
+g.a=r; g.b=z; g.c=l;
+h.a=s; h.b=a; h.c=m;
+i.a=t; i.b=b; i.c=n;
+j.a=u; j.b=c; j.c=o;
+k.a=v; k.b=d; k.c=p;
+l.a=w; l.b=e; l.c=q;
+m.a=x; m.b=f; m.c=r;
+n.a=y; n.b=g; n.c=s;
+o.a=z; o.b=h; o.c=t;
+p.a=a; p.b=i; p.c=u;
+q.a=b; q.b=j; q.c=v;
+r.a=c; r.b=k; r.c=w;
+s.a=d; s.b=l; s.c=x;
+t.a=e; t.b=m; t.c=y;
+u.a=f; u.b=n; u.c=z;
+v.a=g; v.b=o; v.c=a;
+w.a=h; w.b=p; w.c=b;
+x.a=i; x.b=q; x.c=c;
+y.a=j; y.b=r; y.c=d;
+z.a=k; z.b=s; z.c=e;
+
+return a.b.c.a.b.c.a.b.c.a.b.c.a.b.c.a.b.c;
+
+""");
+ code.parse().opto().typeCheck().GCM().localSched();
+ assertEquals("return Test._R;", code.print());
+ assertEquals("Test._R{a=Test._C{a=Test._N{a=Test._Y{a=Test._J{a=Test._U{a=Test._F{a=Test._Q{a=Test._B{a=Test._M{a=Test._X{a=Test._I{a=Test._T{a=Test._E{a=Test._P{a=Test._A{a=Test._L{a=Test._W{a=Test._H{a=Test._S{a=Test._D{a=Test._O{a=Test._Z{a=Test._K{a=Test._V{a=Test._G{a=$cyclic,b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic}", Eval2.eval(code, 0));
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java
new file mode 100644
index 00000000..807905a8
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -0,0 +1,226 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class Chapter14Test {
+
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen(
+"""
+return 3.14;
+""");
+ code.parse().opto();
+ assertEquals("return 3.14;", code.print());
+ assertEquals("3.14", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testRange() {
+ CodeGen code = new CodeGen(
+"""
+int b;
+if( arg ) b=1; else b=0;
+int c = 99;
+if( b < 0 ) c = -1;
+if( b > 2 ) c = 1;
+return c;
+""");
+ code.parse().opto();
+ assertEquals("return 99;", code.print());
+ assertEquals("99", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testU8() {
+ CodeGen code = new CodeGen(
+"""
+u8 b = 123;
+b = b + 456;// Truncate
+return b;
+""");
+ code.parse().opto();
+ assertEquals("return 67;", code.print());
+ assertEquals("67", Eval2.eval(code, 0));
+ }
+
+
+ @Test
+ public void testU8While() {
+ CodeGen code = new CodeGen(
+"""
+u8 b = 123;
+while( b ) b = b + 456;// Truncate
+return b;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testU1() {
+ CodeGen code = new CodeGen(
+"""
+bool b = 123;
+b = b + 456;// Truncate
+u1 c = b; // No more truncate needed
+return c;
+""");
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testAnd() {
+ CodeGen code = new CodeGen(
+"""
+int b = 123;
+b = b+456 & 31; // Precedence
+return b;
+""");
+ code.parse().opto();
+ assertEquals("return 3;", code.print());
+ assertEquals("3", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testRefLoad() {
+ CodeGen code = new CodeGen(
+"""
+struct _Foo { u1 b; };
+_Foo !f = new _Foo;
+f.b = 123;
+return f.b;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testSigned() {
+ CodeGen code = new CodeGen(
+"""
+i8 b = 255; // Chopped
+return b; // Sign extend
+""");
+ code.parse().opto();
+ assertEquals("return -1;", code.print());
+ assertEquals("-1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testI8() {
+ CodeGen code = new CodeGen(
+"""
+i8 b = arg;
+b = b + 1;// Truncate
+return b;
+""");
+ code.parse().opto();
+ assertEquals("return (((((arg<<56)>>56)+1)<<56)>>56);", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ assertEquals("-128", Eval2.eval(code, 127));
+ }
+
+ @Test
+ public void testMask() {
+ CodeGen code = new CodeGen(
+"""
+u16 mask = (1<<16)-1; // AND mask
+int c = 123456789 & mask;
+return c; //
+""");
+ code.parse().opto();
+ assertEquals("return 52501;", code.print());
+ assertEquals("52501", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testOr() {
+ CodeGen code = new CodeGen(
+"""
+return (arg | 123 ^ 456) >>> 1;
+""");
+ code.parse().opto();
+ assertEquals("return (((arg|123)^456)>>>1);", code.print());
+ assertEquals("217", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testMaskFloat() {
+ CodeGen code = new CodeGen(
+"""
+flt f = arg;
+arg = f & 1;
+return arg;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot '&' flt",e.getMessage()); }
+ }
+
+ @Test
+ public void testCloneAnd() {
+ CodeGen code = new CodeGen("""
+int v0=0;
+u32 v1 = 1&(1<<arg)&(1<<arg);
+while(arg) v1=-v0;
+while(v1) break;
+return v1;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,(((1<<arg)&1)&Shl),0);", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testAndHigh() {
+ CodeGen code = new CodeGen("""
+int v0=0;
+if(0&0>>>0) {
+ while(0) {
+ u8 v1=0;
+ v0=0>>>0;
+ v1=arg;
+ while(v1+0) {}
+ }
+}
+return v0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testTypes() {
+ CodeGen code = new CodeGen(
+"""
+i8 xi8 = 123456789; if( xi8 != 21 ) return -8;
+i16 xi16 = 123456789; if( xi16 != -13035 ) return -16;
+i32 xi32 = 123456789; if( xi32 != 123456789 ) return -32;
+i64 xi64 = 123456789; if( xi64 != 123456789 ) return -64;
+int xint = 123456789; if( xint != 123456789 ) return -64;
+
+u1 ui1 = 123456789; if( ui1 != 1 ) return 1;
+u8 ui8 = 123456789; if( ui8 != 21 ) return 8;
+u16 ui16 = 123456789; if( ui16 != 52501 ) return 16;
+u32 ui32 = 123456789; if( ui32 != 123456789 ) return 32;
+
+flt fflt = 3.141592653589793; if( fflt != 3.141592653589793 ) return 3;
+f64 ff64 = 3.141592653589793; if( ff64 != 3.141592653589793 ) return 3;
+f32 ff32 = 3.141592653589793; if( ff32 != 3.1415927410125732) return 5;
+
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
new file mode 100644
index 00000000..d4790b3b
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -0,0 +1,358 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.Ignore;
+import static org.junit.Assert.*;
+
+public class Chapter15Test {
+
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen(
+"""
+return 3.14;
+""");
+ code.parse().opto();
+ assertEquals("return 3.14;", code.print());
+ assertEquals("3.14", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testCyclic() {
+ CodeGen code = new CodeGen(
+"""
+struct _C { _C? l; };
+_C !c = new _C;
+c.l = c;
+return c;
+""");
+ code.parse().opto();
+ assertEquals("return Test._C;", code.print());
+ assertEquals("Test._C{l=$cyclic}", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testSafetyCheck() {
+ CodeGen code = new CodeGen(
+"""
+u8[] old = new u8[0];
+u8[] !output = new u8[1];
+int i = 0;
+while (i < old#) {
+ output[i] = old[i];
+ i = i + 1;
+}
+output[i] = 1;
+return output;
+""");
+ code.parse().opto();
+ assertEquals("return []u8;", code.print());
+ assertEquals("\u0001", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testBasic1() {
+ CodeGen code = new CodeGen(
+"""
+int[] is = new int[2];
+return is[1];
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testBasic2() {
+ CodeGen code = new CodeGen(
+"""
+int[] is = new int[2];
+int[] is2 = new int[2];
+return is[1];
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testBasic3() {
+ CodeGen code = new CodeGen(
+"""
+int[] !a = new int[2];
+a[0] = 1;
+a[1] = 2;
+return a[0];
+""");
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testBasic4() {
+ CodeGen code = new CodeGen(
+"""
+struct _A { int i; };
+_A?[] !a = new _A?[2];
+return a;
+""");
+ code.parse().opto();
+ assertEquals("return []*Test._A?;", code.print());
+ assertEquals("*Test._A {i64 !i; }?[ null,null]", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testBasic5() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int x; flt y; };
+// A new _S
+_S !s = new _S; s.x=99; s.y = 3.14;
+
+// Double-d array of _Ss. Fill in one row.
+_S?[]?[] !iss = new _S?[]?[2];
+iss[0] = new _S?[7];
+iss[0][2] = s;
+
+// Now pull out the filled-in value, with null checks
+flt rez;
+_S?[]? is = iss[arg];
+if( !is ) rez = 1.2;
+else {
+ _S? i = is[2];
+ if( !i ) rez = 2.3;
+ else rez = i.y;
+}
+return rez;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,1.2,.y,2.3);", code.print());
+ assertEquals("3.14", Eval2.eval(code, 0));
+ assertEquals("1.2", Eval2.eval(code, 1));
+ }
+
+ @Test
+ public void testBasic6() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int x; flt y; };
+// A new _S
+_S !s = new _S; s.x=99; s.y = 3.14;
+
+// Double-d array of _Ss. Fill in one row.
+_S?[]?[] !iss = new _S?[]?[2];
+iss[0] = new _S?[7];
+iss[0][2] = s;
+
+// Now pull out the filled-in value, with null checks
+flt rez = 1.2;
+if( iss[arg] )
+ if( iss[arg][2] )
+ rez = iss[arg][2].y;
+return rez;""");
+ code.parse().opto().typeCheck();
+ assertEquals("return Phi(Region,1.2,1.2,.y);", code.print());
+ assertEquals("3.14", Eval2.eval(code, 0));
+ try { Eval2.eval(code, 10); fail(); } // Fails AIOOBE in Eval2, no range checks
+ catch( Exception e ) { assertEquals("Index 10 out of bounds for length 2",e.getMessage()); }
+ }
+
+ @Test
+ public void testTree() {
+ CodeGen code = new CodeGen(
+"""
+// Can we define a forward-reference array?
+struct _Tree { _Tree?[]? _kids; };
+_Tree !root = new _Tree;
+root._kids = new _Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES????
+root._kids[0] = new _Tree;
+return root;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return Test._Tree;", code.print());
+ assertEquals("Test._Tree{_kids=*Test._Tree {*[]*Test._Tree?? !_kids; }?[ Test._Tree{_kids=null},null]}", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testNestedStructAddMemProj() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int a; int[] b; };
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testRollingSum() {
+ CodeGen code = new CodeGen(
+"""
+int[] !ary = new int[arg];
+// Fill [0,1,2,3,4,...]
+int i=0;
+while( i < ary# ) {
+ ary[i] = i;
+ i = i+1;
+}
+// Fill [0,1,3,6,10,...]
+i=0;
+while( i < ary# - 1 ) {
+ ary[i+1] = ary[i+1] + ary[i];
+ i = i+1;
+}
+return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return (.[]+(.[]*1000));", code.print());
+ assertEquals("1006", Eval2.eval(code, 4));
+ }
+
+ @Test @Ignore
+ public void sieveOEratosthenes() throws IOException {
+ String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/sieve.smp"));
+ CodeGen code = new CodeGen(src+"return sieve(arg);");
+ code.parse().opto();
+ assertEquals("return []u32;", code.print());
+ assertEquals("u32[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20));
+ }
+
+ @Test
+ public void testNewNodeInit() {
+ CodeGen code = new CodeGen(
+"""
+struct _S {int i; flt f;};
+_S !s1 = new _S;
+_S !s2 = new _S;
+s2.i = 3;
+s2.f = 2.0;
+if (arg) s1 = new _S;
+return s1.i + s1.f;
+""");
+ code.parse().opto();
+ assertEquals("return ((flt).i+.f);", code.print());
+ assertEquals("0.0", Eval2.eval(code, 0));
+ }
+
+
+ @Test
+ public void testBad0() {
+ CodeGen code = new CodeGen(
+"""
+return new flt;
+""");
+ try { code.parse().opto(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot allocate a flt",e.getMessage()); }
+ }
+
+ @Test
+ public void testBad1() {
+ CodeGen code = new CodeGen(
+"""
+flt is = new int[2];
+return is;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Type *[]i64 is not of declared type flt",e.getMessage()); }
+ }
+
+ @Test
+ public void testBad2() {
+ CodeGen code = new CodeGen(
+"""
+int[] is = new int[3.14];
+return is[1];
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot '<<' 3.14",e.getMessage()); }
+ }
+
+ @Test
+ public void testBad3() {
+ CodeGen code = new CodeGen("""
+int[] is = new int[arg];
+return is[1];
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 4));
+ try { Eval2.eval(code,-1); fail(); }
+ catch( NegativeArraySizeException e ) { assertEquals("-1",e.getMessage()); }
+ // This test will not pass (i.e., failed range-check) until we have range checks.
+ // Without the range check, the load `is[1]` optimizes against the `new int[]`
+ // and assumes it loads a zero then folds away, and the Eval2 never sees an OOB load.
+ //try { Eval2.eval(code,0); fail(); }
+ //catch( ArrayIndexOutOfBoundsException e ) { assertEquals("Array index 1 out of bounds for array length 0",e.getMessage()); }
+ }
+
+ @Test
+ public void testProgress() {
+ CodeGen code = new CodeGen("""
+i8 v1=0&0;
+u8 v2=0;
+byte v4=0;
+if(0) {}
+while(v2<0) {
+ v4=0-v1;
+ break;
+}
+int v5=0&0;
+while(v5+(0&0)) {
+ int v7=0&0;
+ while(v7)
+ v4=0>>>v5;
+ while(v1)
+ return 0;
+}
+return v1;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testSharpNot() {
+ CodeGen code = new CodeGen(
+"""
+if(0>>0) {}
+while(0) {}
+u32 v7=0;
+int v8=0;
+while(0<--1>>>---(v7*0==v8)) {}
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+
+
+ @Test
+ public void testProgress2() {
+ CodeGen code = new CodeGen(
+"""
+if(1) {}
+else {
+ while(arg>>>0&0>>>0) {}
+ byte v3=0>>>0;
+ while(0) {}
+ int v7=0>>>0;
+ while(v7<0>>>0) {
+ while(0+v7<=0) if(1) arg=-12;
+ if(arg) {
+ v3=arg+v3+0;
+ arg=0;
+ }
+ }
+}
+return 0;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java
new file mode 100644
index 00000000..80b0a2bf
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -0,0 +1,258 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class Chapter16Test {
+
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen(
+"""
+return 3.14;
+""");
+ code.parse().opto();
+ assertEquals("return 3.14;", code.print());
+ assertEquals("3.14", Eval2.eval(code, 0));
+ }
+
+
+ @Test
+ public void testMulti0() {
+ CodeGen code = new CodeGen(
+"""
+int x, y;
+return x+y;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+ @Test
+ public void testMulti1() {
+ CodeGen code = new CodeGen(
+"""
+int x=2, y=x+1;
+return x+y;
+""");
+ code.parse().opto();
+ assertEquals("return 5;", code.print());
+ assertEquals("5", Eval2.eval(code, 0));
+ }
+
+// @Test
+// public void testFinal0() {
+// CodeGen code = new CodeGen(
+//"""
+//int !x=2;
+//x=3;
+//return x;
+//""");
+// try { code.parse().opto(); fail(); }
+// catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); }
+// }
+
+ @Test
+ public void testFinal1() {
+ CodeGen code = new CodeGen(
+"""
+int x=2, y=3;
+if( arg ) { int x = y; x = x*x; y=x; } // Shadow final x
+return y;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,9,3);", code.print());
+ assertEquals("3", Eval2.eval(code, 0));
+ assertEquals("9", Eval2.eval(code, 1));
+ }
+
+ @Test
+ public void testConstruct0() {
+ CodeGen code = new CodeGen("""
+struct _X { int x=3; };
+_X z = new _X;
+return z.x;
+""");
+ code.parse().opto();
+ assertEquals("return 3;", code.print());
+ assertEquals("3", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testConstruct1() {
+ CodeGen code = new CodeGen("""
+struct _X { int !x; new _X = { int xx -> x=xx; }; };
+_X z = new _X(3);
+return z.x;
+""");
+ code.parse().opto();
+ assertEquals("return 3;", code.print());
+ assertEquals("3", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testConstruct2() {
+ CodeGen code = new CodeGen("""
+struct _X { int x=3; new _X = { int xx -> x=xx; }; };
+_X z = new _X(4);
+return z.x;
+""");
+ code.parse().opto();
+ assertEquals("return 4;", code.print());
+ assertEquals("4", Eval2.eval(code, 0));
+ }
+
+
+ @Test
+ public void testStructFinal0() {
+ CodeGen code = new CodeGen("""
+struct _Point { int !x, !y; new _Point = { int xx, int yy -> x=xx; y=yy; }; };
+_Point p = new _Point(3,4);
+return p;
+""");
+ code.parse().opto();
+ assertEquals("return (const)Test._Point;", code.print());
+ assertEquals("Test._Point{x=3,y=4}", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testStructFinal1() {
+ CodeGen code = new CodeGen("""
+struct _Point { int x=3, y=4; new _Point = { int xx, int yy -> x=xx; y=yy; }; };
+val p = new _Point(5,6);
+p.x++;
+return p;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
+ }
+
+ @Test
+ public void testStructFinal2() {
+ CodeGen code = new CodeGen("""
+struct _Point { int x=3, y=4; };
+val p = new _Point;
+p.x++;
+return p;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
+ }
+
+ @Test
+ public void testStructFinal3() {
+ CodeGen code = new CodeGen("""
+struct _Point { var x; var y; };
+_Point p = new _Point;
+p.x++;
+return p;
+""");
+ try { code.parse().opto(); fail(); }
+ catch( Exception e ) { assertEquals("'Test._Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
+
+ @Test
+ public void testStructFinal4() {
+ CodeGen code = new CodeGen("""
+struct _Point { val x=2; val y=4; };
+_Point p = new _Point;
+p.x++;
+return p;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
+ }
+
+ @Test
+ public void testStructFinal5() {
+ CodeGen code = new CodeGen("""
+struct _Point { var x=3; var y=4; };
+_Point !p = new _Point;
+p.x++;
+return p;
+""");
+ code.parse().opto();
+ assertEquals("return Test._Point;", code.print());
+ assertEquals("Test._Point{x=4,y=4}", Eval2.eval(code, 0));
+ }
+
+ // Same as the Chapter13 test with the same name, but using the new
+ // constructor syntax
+ @Test
+ public void testLinkedList1() {
+ 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);
+ arg = arg-1;
+}
+if( !head ) return 0;
+_LLI? next = head.next;
+if( !next ) return 1;
+return next.i;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,1,.i,0);", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ assertEquals("1", Eval2.eval(code, 1));
+ assertEquals("2", Eval2.eval(code, 3));
+ }
+
+ @Test
+ public void testLinkedList2() {
+ CodeGen code = new CodeGen(
+"""
+struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int a ->
+ next=n;
+ int !tmp=a;
+ while( a > 10 ) {
+ tmp = tmp + a;
+ a = a - 1;
+ }
+ i=tmp;
+}; };
+_LLI? !head = null;
+while( arg ) {
+ head = new _LLI(head,arg);
+ arg = arg-1;
+}
+if( !head ) return 0;
+_LLI? next = head.next;
+if( !next ) return 1;
+return next.i;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,1,.i,0);", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ assertEquals("1", Eval2.eval(code, 1));
+ assertEquals("2", Eval2.eval(code, 11));
+ }
+
+ @Test
+ public void testSquare() {
+ CodeGen code = new CodeGen(
+"""
+struct _Square {
+ flt !side = arg;
+ // Newtons approximation to the square root, computed in a constructor.
+ // The actual allocation will copy in this result as the initial
+ // value for 'diag'.
+ flt !diag = arg*arg/2;
+ while( 1 ) {
+ flt next = (side/diag + diag)/2;
+ if( next == diag ) break;
+ diag = next;
+ }
+};
+return new _Square;
+""");
+ code.parse().opto();
+ assertEquals("return Test._Square;", code.print());
+ assertEquals("Test._Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3));
+ assertEquals("Test._Square{side=4.0,diag=2.0}", Eval2.eval(code, 4));
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java
new file mode 100644
index 00000000..24daa164
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java
@@ -0,0 +1,558 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class Chapter17Test {
+
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen(
+"""
+return 1;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+
+ // ---------------------------------------------------------------
+ @Test
+ public void testInc0() {
+ CodeGen code = new CodeGen("""
+return arg++;
+""");
+ code.parse().opto();
+ assertEquals("return arg;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testInc1() {
+ CodeGen code = new CodeGen("""
+return arg+++arg++;
+""");
+ code.parse().opto();
+ assertEquals("return ((arg<<1)+1);", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testInc2() {
+ CodeGen code = new CodeGen("""
+// -(arg--)-(arg--)
+return -arg---arg--;
+""");
+ code.parse().opto();
+ assertEquals("return (-((arg<<1)+-1));", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testInc3() {
+ CodeGen code = new CodeGen("""
+int[] !xs = new int[arg];
+xs[0]++;
+xs[1]++;
+return xs[0];
+""");
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testInc4() {
+ CodeGen code = new CodeGen("""
+u8[] !xs = new u8[1];
+xs[0]--;
+return xs[0];
+""");
+ code.parse().opto();
+ assertEquals("return 255;", code.print());
+ assertEquals("255", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testInc5() {
+ CodeGen code = new CodeGen("""
+struct _S { u16 x; };
+_S !s = new _S;
+s.x--;
+return s.x;
+""");
+ code.parse().opto();
+ assertEquals("return 65535;", code.print());
+ assertEquals("65535", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testInc6() {
+ CodeGen code = new CodeGen("return --arg;");
+ code.parse().opto();
+ assertEquals("return (arg+-1);", code.print());
+ assertEquals("-1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testInc7() {
+ CodeGen code = new CodeGen("u8 x=0; return --x;");
+ code.parse().opto();
+ assertEquals("return 255;", code.print());
+ assertEquals("255", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testInc8() {
+ CodeGen code = new CodeGen("int x; x+=2; return x+=3;");
+ code.parse().opto();
+ assertEquals("return 5;", code.print());
+ assertEquals("5", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testInc9() {
+ CodeGen code = new CodeGen("flt x = 1.5; x++; return x;");
+ code.parse().opto();
+ assertEquals("return 2.5f;", code.print());
+ assertEquals("2.5", Eval2.eval(code, 0));
+ }
+
+ @Test public void testInt10() {
+ CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Type 2.5f is not of declared type i64",e.getMessage()); }
+ }
+
+
+ // ---------------------------------------------------------------
+ @Test public void testVar0() {
+ CodeGen code = new CodeGen("var d; return d;");
+ try { code.parse().opto(); fail(); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); }
+ }
+
+ @Test public void testVar1() {
+ CodeGen code = new CodeGen("val d; return d;");
+ try { code.parse().opto(); fail(); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); }
+ }
+
+ @Test public void testVar2() {
+ CodeGen code = new CodeGen("int x; x=3; x++; return x; // Ok, no initializer so x is mutable ");
+ code.parse().opto();
+ assertEquals("return 4;", code.print());
+ assertEquals("4", Eval2.eval(code, 0));
+ }
+
+ @Test public void testVar3() {
+ CodeGen code = new CodeGen("int x=3; x++; return x; // Ok, primitive so x is mutable despite initializer ");
+ code.parse().opto();
+ assertEquals("return 4;", code.print());
+ assertEquals("4", Eval2.eval(code, 0));
+ }
+
+ @Test public void testVar4() {
+ CodeGen code = new CodeGen("struct _S{int x;}; _S? s; s=new _S; s.x++; return s.x; // Ok, no initializer so x is mutable ");
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test public void testVar5() {
+ CodeGen code = new CodeGen("struct _S{int x; new _S={ int xx -> x=xx; };}; _S? s; s=new _S(3); s.x++; return s.x; // Ok, no initializer so x is mutable ");
+ code.parse().opto();
+ assertEquals("return 4;", code.print());
+ assertEquals("4", Eval2.eval(code, 0));
+ }
+
+ @Test public void testVar6() {
+ CodeGen code = new CodeGen("struct S{int x;}; val s = new S; s.x++; return s.x; // Error initializer so x is immutable ");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
+ }
+
+ @Test public void testVar7() {
+ CodeGen code = new CodeGen("struct S{int x; new S={ int xx -> x=xx; };}; val s = new S(3); s.x++; return s.x; // Error initializer so x is immutable ");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
+ }
+
+ @Test public void testVar8() {
+ CodeGen code = new CodeGen("struct _S{int x;}; _S !s=new _S; s.x++; return s.x; // Ok, has '!' so s.x is mutable ");
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test public void testVar9() {
+ CodeGen code = new CodeGen("struct _S{int x;}; var s=new _S; s.x++; return s.x; // Ok, has var so s.x is mutable ");
+ code.parse().opto();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test public void testVar10() {
+ CodeGen code = new CodeGen("struct S{int x;}; val s=new S; s.x++; return s.x; // Error, has val so x is immutable ");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
+ }
+
+ @Test public void testVar11() {
+ CodeGen code = new CodeGen("""
+struct Bar { int x; };
+Bar !bar = new Bar;
+bar.x = 3; // Ok, bar is mutable
+
+struct Foo { Bar? !bar; int y; new Foo = { Bar? b -> bar = b; }; };
+Foo !foo = new Foo(bar);
+foo.bar = bar; // Ok foo is mutable
+foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable
+
+val xfoo = foo; // Throw away mutability
+xfoo.bar.x++; // Error, cannot mutate through xfoo
+return xfoo.bar.x;
+ """);
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
+ }
+
+ @Test public void testVar12() {
+ CodeGen code = new CodeGen("""
+struct _Bar { int x; };
+_Bar !bar = new _Bar;
+bar.x = 3; // Ok, bar is mutable
+
+struct _Foo { _Bar? !bar; int y; };
+_Foo !foo = new _Foo;
+foo.bar = bar; // Ok bar is mutable
+foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable
+
+val xfoo = foo; // Throw away mutability
+int x4 = xfoo.bar.x; // Ok to read through xfoo, gets 4
+foo.bar.x++; // Bumps to 5
+int x5 = xfoo.bar.x; // Ok to read through xfoo, gets 5
+return x4*10+x5;
+""");
+ code.parse().opto();
+ assertEquals("return 45;", code.print());
+ assertEquals("45", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testVar13() {
+ CodeGen code = new CodeGen("""
+int i,i++;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Redefining name 'i'",e.getMessage()); }
+ }
+
+
+ @Test
+ public void testVar14() {
+ CodeGen code = new CodeGen("""
+struct B {};
+struct A { B b; new A = { ->
+ return b; // read before init
+ b = new B;
+}; };
+A x = new A;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Field 'b' might not be initialized",e.getMessage()); }
+ }
+
+ @Test
+ public void testVar15() {
+ CodeGen code = new CodeGen("""
+struct B {};
+struct A { B b; new A = { ->
+ if (arg) b = new B; // Constructor ends with partial init of b
+}; };
+return (new A).b;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); }
+ }
+
+ @Test
+ public void testVar16() {
+ CodeGen code = new CodeGen(
+"""
+struct S{ new S = { -> x = 2; }; };
+val x = 1;
+val s = new S;
+return x;
+"""
+);
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Undefined name 'x'",e.getMessage()); }
+ }
+
+ @Test
+ public void testVar17() {
+ CodeGen code = new CodeGen(
+"""
+struct Person {
+ bool focused;
+};
+val focus = { Person p ->
+ p.focused = true;
+};
+Person me; // Person !me = new Person;
+focus(me);
+return me;
+"""
+);
+ try { code.parse(); fail(); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); }
+ }
+
+ // ---------------------------------------------------------------
+ @Test
+ public void testTrinary0() {
+ CodeGen code = new CodeGen("""
+return arg ? 1 : 2;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,1,2);", code.print());
+ assertEquals("2", Eval2.eval(code, 0));
+ assertEquals("1", Eval2.eval(code, 1));
+ }
+
+ @Test
+ public void testTrinary1() {
+ CodeGen code = new CodeGen("""
+return arg ? 0 : arg;
+""");
+ code.parse().opto();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testTrinary2() {
+ CodeGen code = new CodeGen("""
+struct _Bar { int x; };
+var b = arg ? new _Bar : null;
+return b ? b.x++ + b.x++ : -1;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,((.x<<1)+1),-1);", code.print());
+ assertEquals("-1", Eval2.eval(code, 0));
+
+ assertEquals("1", Eval2.eval(code, 1));
+ }
+
+ @Test
+ public void testTrinary3() {
+ CodeGen code = new CodeGen("""
+struct _Bar { int x; };
+var b = arg ? new _Bar;
+return b ? b.x++ + b.x++ : -1;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,((.x<<1)+1),-1);", code.print());
+ assertEquals("-1", Eval2.eval(code, 0));
+ assertEquals("1", Eval2.eval(code, 1));
+ }
+
+ @Test
+ public void testTrinary4() {
+ // This test case will benefit from an unzipping transformation
+ CodeGen code = new CodeGen("""
+struct _Bar { _Bar? next; int x; new _Bar = { _Bar? n, int xx -> next=n; x=xx; }; };
+var b = arg ? new _Bar((arg==2) ? new _Bar(null,2), 1);
+return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0"
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,.x,0,.x);", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ assertEquals("1", Eval2.eval(code, 1));
+ assertEquals("2", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testTrinary5() {
+ CodeGen code = new CodeGen("""
+flt f=arg?1:1.2;
+return f; // missing widening
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Region,1.0f,1.2);", code.print());
+ assertEquals("1.2", Eval2.eval(code, 0));
+ }
+
+
+ @Test
+ public void testTrinary6() {
+ CodeGen code = new CodeGen("""
+struct S{};
+return arg ? 7 : new S;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("No common type amongst 7 and *Test.S {}",e.getMessage()); }
+ }
+
+ // ---------------------------------------------------------------
+ @Test
+ public void testFor0() {
+ CodeGen code = new CodeGen("""
+int sum=0;
+for( int i=0; i<arg; i++ )
+ sum += i;
+return sum;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print());
+ assertEquals("3", Eval2.eval(code, 3));
+ assertEquals("45", Eval2.eval(code, 10));
+ }
+ @Test
+ public void testFor1() {
+ CodeGen code = new CodeGen("""
+int sum=0, i=0;
+for( ; i<arg; i++ )
+ sum += i;
+return sum;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print());
+ assertEquals("3", Eval2.eval(code, 3));
+ assertEquals("45", Eval2.eval(code, 10));
+ }
+ @Test
+ public void testFor2() {
+ CodeGen code = new CodeGen("""
+int sum=0;
+for( int i=0; ; i++ ) {
+ if( i>=arg ) break;
+ sum += i;
+}
+return sum;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print());
+ assertEquals("3", Eval2.eval(code, 3));
+ assertEquals("45", Eval2.eval(code, 10));
+ }
+ @Test
+ public void testFor3() {
+ CodeGen code = new CodeGen("""
+int sum=0;
+for( int i=0; i<arg; )
+ sum += i++;
+return sum;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print());
+ assertEquals("3", Eval2.eval(code, 3));
+ assertEquals("45", Eval2.eval(code, 10));
+ }
+ @Test
+ public void testFor4() {
+ CodeGen code = new CodeGen("""
+int sum=0;
+for( int i=0; i<arg; i++ )
+ sum += i;
+return i;
+""");
+ try { code.parse().opto(); fail(); }
+ catch( Exception e ) { assertEquals("Undefined name 'i'",e.getMessage()); }
+ }
+ @Test
+ public void testFor5() {
+ CodeGen code = new CodeGen("""
+for(;;arg++;) {}
+""");
+ try { code.parse().opto(); fail(); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `Unexpected code after expression` but found `;`",e.getMessage()); }
+ }
+
+
+
+ // ---------------------------------------------------------------
+ @Test
+ public void testForward0() {
+ CodeGen code = new CodeGen("""
+struct _A {
+ B? f1;
+ B? f2;
+};
+return new _A;
+""");
+ code.parse().opto();
+ assertEquals("return Test._A;", code.print());
+ assertEquals("Test._A{f1=null,f2=null}", Eval2.eval(code,0));
+ }
+
+ @Test
+ public void testForward1() {
+ CodeGen code = new CodeGen("""
+struct _A {
+ B?[]? nil_array_of_b;
+ B?[] array_of_b;
+ new _A = { B?[] bs -> array_of_b = bs; };
+};
+return new _A(new B?[0]).array_of_b;
+""");
+ code.parse().opto();
+ assertEquals("return []*B?;", code.print());
+ assertEquals("*B {}?[]", Eval2.eval(code,0));
+ }
+
+ // ---------------------------------------------------------------
+ @Test
+ public void testLinkedList2() {
+ CodeGen code = new CodeGen("""
+struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int ii -> next=n; i=ii; }; };
+_LLI? !head = null;
+while( arg-- )
+ head = new _LLI(head,arg);
+int sum=0;
+var ptr = head; // A read-only ptr, to be assigned from read-only next fields
+for( ; ptr; ptr = ptr.next )
+ sum += ptr.i;
+return sum;
+""");
+ code.parse().opto();
+ assertEquals("return Phi(Loop,0,(Phi_sum+.i));", code.print());
+ assertEquals("45", Eval2.eval(code, 10));
+ }
+
+ @Test
+ public void sieveOfEratosthenes() {
+ CodeGen code = new CodeGen(
+"""
+var ary = new bool[arg], primes = new int[arg];
+var nprimes=0, p=0;
+// Find primes while p^2 < arg
+for( p=2; p*p < arg; p++ ) {
+ // skip marked non-primes
+ while( ary[p] ) p++;
+ // p is now a prime
+ primes[nprimes++] = p;
+ // Mark out the rest non-primes
+ for( int i = p + p; i < ary#; i += p )
+ ary[i] = true;
+}
+// Now just collect the remaining primes, no more marking
+for( ; p < arg; p++ )
+ if( !ary[p] )
+ primes[nprimes++] = p;
+// Copy/shrink the result array
+var !rez = new int[nprimes];
+for( int j=0; j<nprimes; j++ )
+ rez[j] = primes[j];
+return rez;
+""");
+ code.parse().opto().typeCheck().GCM();
+ assertEquals("return []i64;", code.print());
+ assertEquals("i64[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20));
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
new file mode 100644
index 00000000..de332d03
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -0,0 +1,412 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen.Phase;
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.junit.Assert.fail;
+
+public class Chapter18Test {
+
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen(
+"""
+return 0;
+""");
+ code.driver(Phase.LocalSched);
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testPhiParalleAssign() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+int b = 2;
+while(arg--) {
+ int t = a;
+ a = b;
+ b = t;
+}
+return a;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return Phi(Loop,1,Phi(Loop,2,Phi_a));", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ assertEquals("2", Eval2.eval(code, 1));
+ assertEquals("1", Eval2.eval(code, 2));
+ assertEquals("2", Eval2.eval(code, 3));
+ }
+
+
+ // ---------------------------------------------------------------
+ @Test
+ public void testType0() {
+ CodeGen code = new CodeGen(
+"""
+{int -> int}? x2 = null; // null function ptr
+return x2;
+""");
+ code.parse().opto();
+ assertEquals("return null;", code.print());
+ assertEquals("null", Eval2.eval(code, 0 ) );
+ }
+
+ @Test
+ public void testFcn0() {
+ CodeGen code = new CodeGen(
+"""
+{int -> int}? sq = { int x ->
+ x*x;
+};
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return { sq}; return (Parm_x(sq,i64)*x); ]", code.print());
+ assertEquals("{ ptr i64 -> i64 #[ 3]}", Eval2.eval(code, 3));
+ }
+
+ @Test
+ public void testFcn1() {
+ CodeGen code = new CodeGen(
+"""
+var _sq = { int x ->
+ x*x;
+};
+return _sq(arg)+_sq(3);
+""");
+ code.driver(Phase.LocalSched);
+ assertEquals("return ((arg*arg)+9);", code.print());
+ assertEquals("13", Eval2.eval(code, 2));
+ }
+
+ // Function scope test
+ @Test
+ public void testFcn2A() {
+ CodeGen code = new CodeGen(
+"""
+int cnt=1; // Global scope; exactly one of these
+return { -> cnt++; }; // Escape global-scope variable is OK
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return { ptr -> i64 #[ 3]}; return .cnt; ]", code.print());
+ assertEquals("{ ptr -> i64 #[ 3]}", Eval2.eval(code, 0));
+ }
+
+ // Function scope test
+ @Test
+ public void testFcn2B() {
+ CodeGen code = new CodeGen(
+"""
+val ctr = { ->
+ int cnt; // Function-local var
+ return { -> cnt=cnt+1; cnt; }; // Escape cnt out of scope
+};
+val A = ctr(); // Make a counter
+val B = ctr(); // Make a counter
+A(); // Increment counter A
+return A()*10 + B(); // Incr A and B; result: 2*10+1 == 21
+""");
+ try { code.parse().opto(); fail(); }
+ catch( Exception e ) { assertEquals("Variable 'cnt' is out of function scope and must be a final constant",e.getMessage()); }
+ }
+
+ // Function scope test
+ @Test
+ public void testFcn3() {
+ CodeGen code = new CodeGen(
+"""
+val cnt=2;
+return { -> cnt; }();
+""");
+ code.parse().opto();
+ assertEquals("return 2;", code.print());
+ assertEquals("2", Eval2.eval(code, 0));
+ }
+
+ // Function variables
+ @Test
+ public void testFcn4() {
+ CodeGen code = new CodeGen(
+"""
+var fcn = arg ? { int x -> x*x; } : { int x -> x+x; };
+return fcn(3);
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return #2; return (Parm_x($fun3,i64,3)*x); return (Parm_x($fun4,i64,3)<<1); ]", code.print());
+ assertEquals("6", Eval2.eval(code, 0));
+ assertEquals("9", Eval2.eval(code, 1));
+ }
+
+ // 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);");
+ code.parse().opto().typeCheck();
+ assertEquals("Stop[ return Phi(Region,1,(arg*#2)); return Phi(Region,1,(Parm_x(_fact,(arg-1),(x-1))*#2)); ]", code.print());
+ assertEquals( "1", Eval2.eval(code, 0));
+ assertEquals( "1", Eval2.eval(code, 1));
+ assertEquals( "2", Eval2.eval(code, 2));
+ assertEquals( "6", Eval2.eval(code, 3));
+ assertEquals("24", Eval2.eval(code, 4));
+ }
+
+ // The multiply initially sees one integer input and one unresolved
+ // The multiply initially sees one integer input and one unresolvedfcn8
+ // recursive result, but the floating base case requires a floating mode.
+ @Test
+ public void testFcn5Float() {
+ CodeGen code = new CodeGen("val _fact = { int x -> x <= 1 ? 1.0 : x*_fact(x-1); }; return _fact(arg);");
+ code.parse().opto().typeCheck();
+ assertEquals("1.0", Eval2.eval(code, 0));
+ assertEquals("1.0", Eval2.eval(code, 1));
+ assertEquals("2.0", Eval2.eval(code, 2));
+ assertEquals("6.0", Eval2.eval(code, 3));
+ assertEquals("24.0", Eval2.eval(code, 4));
+ }
+
+ @Test
+ public void testFcn5Minus() {
+ CodeGen code = new CodeGen("val _neg = { int x -> x ? -_neg(x-1) : 1; }; return _neg(arg);");
+ code.parse().opto().typeCheck();
+ assertEquals( "1", Eval2.eval(code, 0));
+ assertEquals("-1", Eval2.eval(code, 1));
+ assertEquals( "1", Eval2.eval(code, 2));
+ assertEquals("-1", Eval2.eval(code, 3));
+ }
+
+ @Test
+ public void testFcn6() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int i; new _S = { int x -> i=x; }; };
+val _newS = { int x -> return new _S(x); };
+return _newS(1).i;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ // Only forward refs to direct function calls allowed
+ @Test
+ public void testFcn7() {
+ CodeGen code = new CodeGen(
+"""
+val _f = {->1;};
+val _g = {->2;};
+if( arg ? _f : _g ) return 1;
+return 2;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testFcn8() {
+ CodeGen code = new CodeGen(
+"""
+{int -> int}? _i2i = null;
+var _id = {{int->int} f-> return f;};
+for(;;) {
+ if (_i2i) return _i2i(arg);
+ var _x = {int i-> return i;};
+ arg = _x(3);
+ _i2i = _id(_x);
+}
+""");
+ code.driver(Phase.LocalSched);
+ assertEquals("return Phi(Loop,arg,3);", code.print());
+ assertEquals("3", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testFcn9() {
+ CodeGen code = new CodeGen(
+"""
+{int -> int}? i2i = null;
+for(;;) {
+ if (i2i) return i2i(arg);
+ var x = {int i-> return i;};
+ arg = x(3);
+}
+""");
+ code.driver(Phase.LocalSched);
+ assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print());
+ assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, <clinit> never exits
+ }
+
+
+ @Test
+ public void testFcn10() {
+ CodeGen code = new CodeGen(
+"""
+struct _Person {
+ int age;
+};
+
+val _fcn = { _Person?[] ps, int x ->
+ val tmp = ps[x];
+ if( ps[x] )
+ ps[x].age++;
+};
+
+var ps = new _Person?[2];
+ps[0] = new _Person;
+ps[1] = new _Person;
+return _fcn(ps,1);
+""");
+ code.driver(Phase.LocalSched);
+ assertEquals("return .age;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ // Function break
+ @Test
+ public void testErr1() {
+ CodeGen code = new CodeGen(
+"""
+for(;;) {
+ val f = { ->
+ break;
+ };
+ f();
+ return 2;
+}
+return 1;
+""");
+ try { code.parse().opto(); fail(); }
+ catch( Exception e ) { assertEquals("No active loop for a break or continue",e.getMessage()); }
+ }
+
+ // Calling and inlining a null function
+ @Test
+ public void testErr2() {
+ CodeGen code = new CodeGen(
+"""
+{int -> int}? !i2i = { int i -> return i; };
+for(;;) {
+ if (i2i(2) == arg) break;
+ i2i = null;
+}
+return 0;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Might be null calling { ptr i64 -> i64 #[ 3]}?",e.getMessage()); }
+ }
+
+
+ @Test
+ public void testErr3() {
+ CodeGen code = new CodeGen(
+"""
+val f = { int i, int j -> return i+j; };
+return f();
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Expecting 3 arguments, but found 1",e.getMessage()); }
+ }
+
+
+ @Test
+ public void testErr4() {
+ CodeGen code = new CodeGen(
+"""
+struct S {
+ {int} f = { -> x(); return 0; }; // Do not let fref x be a field
+};
+val x = { -> return 1; };
+S? s = new S;
+return s.x;
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Accessing unknown field 'x' from '*Test.S'",e.getMessage()); }
+ }
+
+ @Test @Ignore
+ public void testErr5() {
+ CodeGen code = new CodeGen(
+"""
+val g = { ->
+ g();
+};
+return g();
+""");
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("No defined return type",e.getMessage()); }
+ }
+
+
+ // Mutual recursion. Fails without SCCP to lift the recursive return types.
+ @Ignore @Test
+ 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);
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("Stop[ return #2; return Phi(Region,0,1,#2); ]", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ assertEquals("0", Eval2.eval(code, 1));
+ assertEquals("1", Eval2.eval(code, 2));
+ assertEquals("0", Eval2.eval(code, 3));
+ }
+
+ // Forward ref to 'x' means that
+ @Ignore @Test
+ public void testForwardRef1() {
+ CodeGen code = new CodeGen(
+"""
+struct S {
+ { int } f = { -> return x(); };
+};
+val x = { -> return 1; };
+S? s = null;
+for(;;) {
+ if (s) return s.f;
+}
+""");
+ code.parse().opto().typeCheck().GCM();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
+ }
+
+ // Inline hidden called more than once
+ @Test
+ public void testInline() {
+ CodeGen code = new CodeGen(
+"""
+{int->int}?! _i2i_noInline = {int i->return i;};
+{{int->int}->{int->int}}! _f2f = {{int->int} f->return f;};
+val _o = _i2i_noInline;
+if (arg) _i2i_noInline = null;
+if (_i2i_noInline) return _i2i_noInline(arg);
+return _f2f(_o)(1);
+""");
+ code.driver(Phase.LocalSched);
+ assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(_i2i_noInline,i64); ]", code.print());
+ assertEquals("1", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testOperField() {
+ CodeGen code = new CodeGen(
+"""
+struct _Person {
+ int coffee_count;
+};
+_Person !p = new _Person;
+p.coffee_count += 1;
+return p.coffee_count;
+""");
+ code.driver(Phase.LocalSched);
+ assertEquals("return 1;", code.print());
+ assertEquals("1", Eval2.eval(code, 2));
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java
new file mode 100644
index 00000000..eaf76041
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -0,0 +1,435 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen.Phase;
+import com.seaofnodes.simple.codegen.CodeGen;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter19Test {
+
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen("""
+return 0;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 2));
+ }
+
+
+ @Test
+ public void testString() {
+ String src =
+"""
+struct String {
+ u8[~] cs;
+ int _hashCode;
+ new String = { u8[~] s -> cs=s; };
+};
+
+// Compare two Strings
+val equals = { String self, String s ->
+ if( self == s ) return true;
+ if( self.cs# != s.cs# ) return false;
+ for( int i=0; i< self.cs#; i++ )
+ if( self.cs[i] != s.cs[i] )
+ return false;
+ return true;
+};
+
+
+val _hashCodeString = { String self ->
+ int hash=0;
+ for( int i=0; i< self.cs#; i++ )
+ hash = hash*31 + self.cs[i];
+ if( !hash ) hash = 123456789;
+ return hash;
+};
+
+// Return the String hashCode (cached, and never 0)
+val hashCode = { String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
+
+hashCode(new String("Hello, World!"));
+""";
+ CodeGen code = new CodeGen(src).driver(Phase.LocalSched);
+ assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MEM[ 2:___ 3:___ 4:.cs=Parm_s(Test.String.String,*[]u8); 5:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:._hashCode=0;]; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(((Phi_hash<<5)-Phi_hash)+.[])))); ]", code.print());
+ assertEquals("4029215624828139541", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testTernaryStoreCall() {
+ String src =
+"""
+struct String {
+ u8[~] cs;
+ int _hashCode;
+ new String = { u8[~] s -> cs=s; };
+};
+
+val f = { String self -> 7; };
+
+val hashCode = { String self ->
+ self._hashCode
+ ? 3
+ : (self._hashCode = f(self));
+};
+
+hashCode(new String("Hello"));
+""";
+ CodeGen code = new CodeGen(src).driver(Phase.LocalSched);
+ assertFalse(code.print().contains("return Top"));
+ assertEquals("7", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testBasic0() {
+ CodeGen code = new CodeGen("return 0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testBasic1() {
+ CodeGen code = new CodeGen("return arg+1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (inc,arg);", code.print());
+ }
+
+ @Test
+ public void testBasic2() {
+ CodeGen code = new CodeGen("return -17;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return -17;", code.print());
+ }
+
+
+ @Test
+ public void testBasic3() {
+ CodeGen code = new CodeGen("return arg==1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (set==,(cmp,arg));", code.print());
+ }
+
+ @Test
+ public void testBasic4() {
+ CodeGen code = new CodeGen("return arg<<1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (shli,arg);", code.print());
+ }
+
+ @Test
+ public void testBasic5() {
+ CodeGen code = new CodeGen("return arg >> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (sari,arg);", code.print());
+ }
+
+ @Test
+ public void testBasic6() {
+ CodeGen code = new CodeGen("return arg >>> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (shri,arg);", code.print());
+ }
+
+ @Test
+ public void testBasic7() {
+ CodeGen code = new CodeGen("return arg / 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (div,arg,2);", code.print());
+ }
+
+ @Test
+ public void testBasic8() {
+ CodeGen code = new CodeGen("return arg * 6;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (muli,arg);", code.print());
+ }
+
+ @Test
+ public void testBasic9() {
+ CodeGen code = new CodeGen("return arg & 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (andi,arg);", code.print());
+ }
+
+ @Test
+ public void testBasic10() {
+ CodeGen code = new CodeGen("return arg | 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (ori,arg);", code.print());
+ }
+
+ @Test
+ public void testBasic11() {
+ CodeGen code = new CodeGen("return arg ^ 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (xori,arg);", code.print());
+ }
+
+ @Test
+ public void testBasic12() {
+ CodeGen code = new CodeGen("return arg + 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (addf,(cvtf,arg),2.0f);", code.print());
+ }
+
+ @Test
+ public void testBasic13() {
+ CodeGen code = new CodeGen("return arg - 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (subf,(cvtf,arg),2.0f);", code.print());
+ }
+
+ @Test
+ public void testBasic14() {
+ CodeGen code = new CodeGen("return arg * 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (mulf,(cvtf,arg),2.0f);", code.print());
+ }
+
+ @Test
+ public void testBasic15() {
+ CodeGen code = new CodeGen("return arg / 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (mulf,(cvtf,arg),0.5f);", code.print());
+ }
+
+ @Test
+ public void testBasic16() {
+ CodeGen code = new CodeGen(
+"""
+int arg1 = arg + 1;
+return arg1 / arg;""");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (div,(inc,arg),arg);", code.print());
+ }
+
+ @Test
+ public void testBasic17() {
+ CodeGen code = new CodeGen(
+"""
+int arg1 = arg + 1;
+return arg1 * arg;
+""");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (mul,(inc,arg),arg);", code.print());
+ }
+
+ @Test
+ public void testToFloat() {
+ CodeGen code = new CodeGen("""
+int a = arg;
+return a + 2.0;
+"""
+ ).driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (addf,(cvtf,arg),2.0f);", code.print());
+ }
+
+ @Test
+ public void testIfStmt() {
+ CodeGen code = new CodeGen(
+"""
+int a = 1;
+if (arg == 1)
+ a = arg+2;
+else {
+ a = arg-3;
+}
+return a;""");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return Phi(Region,(addi,arg),(addi,arg));", code.print());
+ }
+
+ @Test
+ public void testIfMerge2() {
+ CodeGen code = new CodeGen(
+"""
+int a=arg+1;
+int b=arg+2;
+if( arg==1 )
+ b=b+a;
+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());
+ }
+
+ @Test
+ public void testLoop() {
+ CodeGen code = new CodeGen(
+"""
+int sum=0;
+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());
+ }
+
+ @Test
+ public void testAlloc1() {
+ CodeGen code = new CodeGen(
+"""
+struct _S { int a; _S? c; };
+return new _S;""");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return Test._S;", code.print());
+ }
+
+ @Test
+ 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());
+ }
+
+ @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());
+ }
+
+ @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());
+ }
+
+ @Test
+ public void testAlloc2() {
+ CodeGen code = new CodeGen("int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return .[];", code.print());
+ }
+
+ @Test
+ 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());
+ }
+
+ @Test
+ public void testArray1() {
+ CodeGen code = new CodeGen(
+"""
+int[] !ary = new int[arg];
+// Fill [0,1,2,3,4,...]
+for( int i=0; i<ary#; i++ )
+ ary[i] = i;
+// Fill [0,1,3,6,10,...]
+for( int i=0; i<ary#-1; i++ )
+ ary[i+1] += ary[i];
+return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
+""");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return .[];", code.print());
+ }
+
+ @Test
+ public void testArray2() {
+ CodeGen code = new CodeGen(
+"""
+flt[] !A = new flt[arg], !B = new flt[arg];
+// Fill [0,1,2,3,4,...]
+for( int i=0; i<A#; i++ )
+ A[i] = i;
+for( int i=0; i<A#; i++ )
+ B[i] += A[i];
+return 0;
+""");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return 0;", code.print());
+ }
+
+ @Test
+ public void testArray3() {
+ CodeGen code = new CodeGen(
+"""
+byte[] !A = new byte[arg];
+for( int i=0; i<A#; i++ )
+ A[i]++;
+return A[1];
+""");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return .[];", code.print());
+ }
+
+ @Test
+ public void testNewton() throws IOException {
+ String src =
+"""
+val _test_sqrt = { flt x ->
+ flt epsilon = 1e-15;
+ flt guess = x;
+ while( 1 ) {
+ flt next = (x/guess + guess)/2;
+ if( guess-epsilon <= next & next <= guess+epsilon ) return guess;
+ //if( guess==next ) return guess;
+ guess = next;
+ }
+};
+flt farg = arg;
+return _test_sqrt(farg);
+""";
+ CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return Phi(Loop,(cvtf,arg),(mulf,(addf,Phi_guess,(divf,cvtf,Phi_guess)),0.5f));", code.print());
+ };
+
+
+ @Test
+ public void sieveOfEratosthenes() {
+ String src =
+"""
+val sieve = { int N ->
+ // The main Sieve array
+ bool[] !ary = new bool[N];
+ // The primes less than N
+ u32[] !primes = new u32[N>>1];
+ // Number of primes so far, searching at index p
+ int nprimes = 0, p=2;
+ // Find primes while p^2 < N
+ while( p*p < N ) {
+ // skip marked non-primes
+ while( ary[p] ) p++;
+ // p is now a prime
+ primes[nprimes++] = p;
+ // Mark out the rest non-primes
+ for( int i = p + p; i < ary#; i+= p )
+ ary[i] = true;
+ p++;
+ }
+
+ // Now just collect the remaining primes, no more marking
+ for( ; p < N; p++ )
+ if( !ary[p] )
+ primes[nprimes++] = p;
+
+ // Copy/shrink the result array
+ u32[] !rez = new u32[nprimes];
+ for( int j=0; j < nprimes; j++ )
+ rez[j] = primes[j];
+ return rez;
+};
+""";
+ CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("Stop[ return { sieve}; return []u32; ]", code.print());
+ //assertEquals("{ ptr i64 -> *[]u32 #[ 3]}",Eval2.eval(code, 20));
+ }
+
+
+ @Test
+ public void testFcn1() {
+ CodeGen code = new CodeGen(
+"""
+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());
+ }
+
+ @Test
+ public void testFcn2() {
+ CodeGen code = new CodeGen(
+"""
+val _sq = { int x -> x*x; };
+return _sq(arg) + _sq(3);
+""");
+ code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
+ assertEquals("return (addi,(mul,arg,arg));", code.print());
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
new file mode 100644
index 00000000..e4754213
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -0,0 +1,198 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter20Test {
+
+ @Test
+ public void testJig() {
+ CodeGen code = new CodeGen("return 0;");
+ code.parse().opto().typeCheck();
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 2));
+ }
+
+ static void testCPU( String src, String cpu, String os, int spills, String stop ) {
+ CodeGen code = new CodeGen(src);
+ code.driver(CodeGen.Phase.RegAlloc,cpu,os);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ if( spills != -1 )
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ if( stop != null )
+ assertEquals(stop, code.print());
+ }
+
+ private static void testAllCPUs( String src, int spills, String stop ) {
+ testCPU(src,"x86_64_v2", "SystemV",spills,stop);
+ testCPU(src,"riscv" , "SystemV",spills,stop);
+ testCPU(src,"arm" , "SystemV",spills,stop);
+ }
+
+ @Test public void testAlloc0() {
+ testCPU("return new u8[arg];","x86_64_v2", "SystemV",3,"return []u8;");
+ testCPU("return new u8[arg];","riscv" , "SystemV",5,"return []u8;");
+ testCPU("return new u8[arg];","arm" , "SystemV",5,"return []u8;");
+ }
+
+ @Test public void testBasic1() {
+ String src = "return arg | 2;";
+ testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));");
+ testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );");
+ testCPU(src,"arm" , "SystemV",0,"return (ori,arg);");
+ }
+
+ @Test
+ public void testNewtonInteger() {
+ String src =
+"""
+// Newtons approximation to the square root
+val _sqrt = { int x ->
+ int guess = x;
+ while( 1 ) {
+ int next = (x/guess + guess)>>1;
+ if( next == guess ) return guess;
+ guess = next;
+ }
+};
+return _sqrt(arg) + _sqrt(arg+2);
+""";
+ testCPU(src,"x86_64_v2", "win64" ,38,null);
+ testCPU(src,"riscv" , "SystemV",19,null);
+ testCPU(src,"arm" , "SystemV",19,null);
+ }
+
+ @Test
+ public void testNewtonFloat() {
+ String src =
+"""
+val _test_sqrt_noInline = { flt x ->
+ flt epsilon = 1e-15;
+ flt guess = x;
+ while( 1 ) {
+ flt next = (x/guess + guess)/2;
+ if( guess-epsilon <= next && next <= guess+epsilon )
+ return guess;
+ guess = next;
+ }
+};
+flt farg = arg; return _test_sqrt_noInline(farg) + _test_sqrt_noInline(farg+2.0);
+""";
+ testCPU(src,"x86_64_v2", "SystemV",65,null);
+ testCPU(src,"riscv" , "SystemV",35,null);
+ testCPU(src,"arm" , "SystemV",35,null);
+ }
+
+ @Test
+ public void testAlloc2() {
+ String src = "int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];";
+ testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
+ testCPU(src,"riscv" , "SystemV", 6,"return .[];");
+ testCPU(src,"arm" , "SystemV", 6,"return .[];");
+ }
+
+
+
+ @Test
+ public void testArray1() {
+ String src =
+"""
+int[] !ary = new int[arg];
+// Fill [0,1,2,3,4,...]
+for( int i=0; i<ary#; i++ )
+ ary[i] = i;
+// Fill [0,1,3,6,10,...]
+for( int i=0; i<ary#-1; i++ )
+ ary[i+1] += ary[i];
+return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
+""";
+ testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
+ testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));");
+ testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));");
+ }
+
+
+ @Test
+ public void testString() {
+ String src =
+"""
+struct String {
+ u8[] cs;
+ int _hashCode;
+};
+
+// Compare two Strings
+val equals = { String self, String s ->
+ if( self == s ) return true;
+ if( self.cs# != s.cs# ) return false;
+ for( int i=0; i< self.cs#; i++ )
+ if( self.cs[i] != s.cs[i] )
+ return false;
+ return true;
+};
+
+// Return the String hashCode (cached, and never 0)
+val hashCode = { String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
+
+val _hashCodeString = { String self ->
+ int hash=0;
+ for( int i=0; i< self.cs#; i++ )
+ hash = hash*31 + self.cs[i];
+ if( !hash ) hash = 123456789;
+ return hash;
+};
+""";
+ testCPU(src,"x86_64_v2", "SystemV",18,null);
+ testCPU(src,"riscv" , "SystemV", 5,null);
+ testCPU(src,"arm" , "SystemV", 4,null);
+ }
+
+ @Test
+ public void testCast() {
+ String src = "struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;";
+ testCPU(src,"x86_64_v2", "SystemV",3,null);
+ testCPU(src,"riscv" , "SystemV",3,null);
+ testCPU(src,"arm" , "SystemV",3,null);
+ }
+
+ @Test
+ public void testFltArg() {
+ String src = "return {int i, flt f, int j->return i+f+j;};";
+ testAllCPUs(src,0,null);
+ }
+
+ @Test
+ public void testFlags1() {
+ String src = """
+bool _b1 = arg == 1;
+bool b2 = arg == 2;
+if (b2) if (_b1) return 1;
+if (_b1) return 2;
+return 0;
+""";
+ testCPU(src,"x86_64_v2", "SystemV",0,"return Phi(Region,2,0,1);");
+ testCPU(src,"riscv" , "SystemV",0,"return Phi(Region,2,0,1);");
+ testCPU(src,"arm" , "SystemV",0,"return Phi(Region,2,0,1);");
+ }
+
+ @Test
+ public void testFlags2() {
+ String src = """
+bool b1 = arg == 1;
+while (arg > 0) {
+ arg--;
+ if (b1) arg--;
+}
+return arg;
+""";
+ testCPU(src,"x86_64_v2", "SystemV",3,null);
+ testCPU(src,"riscv" , "SystemV",2,null);
+ testCPU(src,"arm" , "SystemV",2,null);
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
new file mode 100644
index 00000000..a4621165
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -0,0 +1,464 @@
+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.util.SB;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter21Test {
+
+ @Test
+ public void testJig() throws IOException {
+ String src =
+"""
+struct _s0 {
+ bool v1;
+ i16 v2;
+ int v3;
+ i8 v4;
+ byte v5;
+};
+while(new _s0.v3)
+ while(new _s0.v5<<new _s0.v4) {}
+if(0) {
+ if(0) {
+ flt !P5ZUD4=new _s0.v2;
+ }
+ while(0) {}
+}
+return new _s0.v1;
+""";
+ testCPU(src,"x86_64_v2", "win64" ,-1,null);
+ testCPU(src,"riscv" , "SystemV",-1,null);
+ testCPU(src,"arm" , "SystemV",-1,null);
+ }
+
+ static void testCPU( String src, String cpu, String os, int spills, String stop ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ if( spills != -1 )
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ if( stop != null )
+ assertEquals(stop, code.print());
+ }
+
+
+ @Test public void testBasic1() {
+ String src = "return arg | 2;";
+ testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));");
+ testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );");
+ testCPU(src,"arm" , "SystemV",0,"return (ori,arg);");
+ }
+
+ @Test public void testInfinite() {
+ String src = "struct _S { int i; }; _S !s = new _S; while(1) s.i++; return s.i;";
+ testCPU(src,"x86_64_v2", "SystemV",0,"return Top;");
+ testCPU(src,"riscv" , "SystemV",2,"return Top;");
+ testCPU(src,"arm" , "SystemV",2,"return Top;");
+ }
+
+ @Test
+ public void testArray1() {
+ String src =
+"""
+int[] !ary = new int[arg];
+// Fill [0,1,2,3,4,...]
+for( int i=0; i<ary#; i++ )
+ ary[i] = i;
+// Fill [0,1,3,6,10,...]
+for( int i=0; i<ary#-1; i++ )
+ ary[i+1] += ary[i];
+return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
+""";
+ testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
+ testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));");
+ testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));");
+ }
+
+ @Test
+ public void testAntiDeps1() {
+ String src =
+"""
+struct _S { int f; };
+var v0 = new _S;
+_S? v1;
+if (arg) v1 = new _S;
+if (v1) {
+ v0.f = v1.f;
+} else {
+ v0.f = 2;
+}
+return v0;
+""";
+ testCPU(src,"x86_64_v2", "SystemV",10,"return mov(mov(Test._S));");
+ testCPU(src,"riscv" , "SystemV",10,"return mov(mov(Test._S));");
+ testCPU(src,"arm" , "SystemV",10,"return mov(mov(Test._S));");
+ }
+
+ @Test
+ public void testString() {
+ String src =
+"""
+struct String {
+ u8[] cs;
+ int _hashCode;
+};
+
+// Compare two Strings
+val equals = { String self, String s ->
+ if( self == s ) return true;
+ if( self.cs# != s.cs# ) return false;
+ for( int i=0; i< self.cs#; i++ )
+ if( self.cs[i] != s.cs[i] )
+ return false;
+ return true;
+};
+
+// Return the String hashCode (cached, and never 0)
+val hashCode = { String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
+
+val _hashCodeString = { String self ->
+ int hash=0;
+ for( int i=0; i< self.cs#; i++ )
+ hash = hash*31 + self.cs[i];
+ if( !hash ) hash = 123456789;
+ return hash;
+};
+""";
+ testCPU(src,"x86_64_v2", "SystemV",18,null);
+ testCPU(src,"riscv" , "SystemV", 5,null);
+ testCPU(src,"arm" , "SystemV", 4,null);
+ }
+
+ @Test public void testStringExport() throws IOException {
+ String src =
+"""
+struct String {
+ u8[] cs;
+ int _hashCode;
+};
+
+// Compare two Strings
+val equals = { String self, String s ->
+ if( self == s ) return true;
+ if( self.cs# != s.cs# ) return false;
+ for( int i=0; i< self.cs#; i++ )
+ if( self.cs[i] != s.cs[i] )
+ return false;
+ return true;
+};
+
+// Return the String hashCode (cached, and never 0)
+val hashCode = { String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
+
+val _hashCodeString = { String self ->
+ int hash=0;
+ for( int i=0; i< self.cs#; i++ )
+ hash = hash*31 + self.cs[i];
+ if( !hash ) hash = 123456789;
+ return hash;
+};
+""";
+ TestC.runC(src, "stringHash", "",18);
+ }
+
+ @Test public void testLoop2() {
+ String src =
+"""
+int i = 0;
+while(true) {
+ i += 1;
+ if( i==0 ) continue;
+ if( i==1 ) continue;
+ if( i==2 ) break;
+ continue;
+}
+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)));");
+ }
+
+ @Test public void testNewtonExport() throws IOException {
+ String result = """
+0 0.000000 (0)
+1 1.000000 (0)
+2 1.414214 (2.22045e-16)
+3 1.732051 (0)
+4 2.000000 (0)
+5 2.236068 (0)
+6 2.449490 (0)
+7 2.645751 (0)
+8 2.828427 (4.44089e-16)
+9 3.000000 (0)
+""";
+ String src =
+"""
+val test_sqrt = { flt x ->
+ flt epsilon = 1e-15;
+ flt guess = x;
+ while( 1 ) {
+ flt next = (x/guess + guess)/2;
+ if( guess-epsilon <= next & next <= guess+epsilon ) return guess;
+ //if( guess==next ) return guess;
+ guess = next;
+ }
+};
+""";
+ TestC.runC(src, "newtonFloat", result, 42);
+
+ EvalRisc5 R5 = TestRisc5.build( src, "test_sqrt", 0, 10, false);
+ R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 3.0;
+ int trap_r5 = R5.step(1000);
+ assertEquals(0,trap_r5);
+ // Return register A0 holds fib(8)==55
+ assertEquals(1.732051,R5.fregs[riscv.FA0 - riscv.F_OFFSET], 0.00001);
+
+ // arm
+ EvalArm64 A5 = TestArm64.build("test_sqrt", src,0, 10, false);
+ A5.fregs[arm.D0 - arm.D_OFFSET] = 3.0;
+ int trap_arm = A5.step(1000);
+ assertEquals(0,trap_arm);
+ assertEquals(1.732051, A5.fregs[arm.D0 - arm.D_OFFSET], 0.00001);
+ }
+
+
+ @Test public void testSieve() throws IOException {
+ String src =
+"""
+val sieve = { int N ->
+ // The main Sieve array
+ bool[] !ary = new bool[N];
+ // The primes less than N
+ u32[] !primes = new u32[N>>1];
+ // Number of primes so far, searching at index p
+ int nprimes = 0, p=2;
+ // Find primes while p^2 < N
+ while( p*p < N ) {
+ // skip marked non-primes
+ while( ary[p] ) p++;
+ // p is now a prime
+ primes[nprimes++] = p;
+ // Mark out the rest non-primes
+ for( int i = p + p; i < ary#; i+= p )
+ ary[i] = true;
+ p++;
+ }
+
+ // Now just collect the remaining primes, no more marking
+ for( ; p < N; p++ )
+ if( !ary[p] )
+ primes[nprimes++] = p;
+
+ // Copy/shrink the result array
+ u32[] !rez = new u32[nprimes];
+ for( int j=0; j < nprimes; j++ )
+ rez[j] = primes[j];
+ return rez;
+};
+""";
+ // The primes
+ int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, };
+ SB sb = new SB().p(primes.length).p("[");
+ for( int prime : primes )
+ sb.p(prime).p(", ");
+ String sprimes = sb.p("]").toString();
+
+ // Compile, link against native C; expect the above string of primes to be printed out by C
+ TestC.runC(src, "sieve", sprimes, 186);
+
+ // Evaluate on RISC5 emulator; expect return of an array of primes in
+ // the simulated heap.
+ EvalRisc5 R5 = TestRisc5.build( src, "sieve", 100, 90, false);
+ int trap = R5.step(10000);
+ assertEquals(0,trap);
+ // Return register A0 holds sieve(100)
+ int ary = (int)R5.regs[riscv.A0];
+ // Memory layout starting at ary(length,pad, prime1, primt2, prime3, prime4)
+ assertEquals(primes.length, R5.ld4s(ary));
+ for( int i=0; i<primes.length; i++ )
+ assertEquals(primes[i], R5.ld4s(ary + 4 + i*4));
+
+ // Evaluate on ARM5 emulator; expect return of an array of primes in
+ // the simulated heap.
+ EvalArm64 A5 = TestArm64.build("sieve", src, 100, 105, false);
+ int trap_arm = A5.step(10000);
+ assertEquals(0, trap_arm);
+ int ary_arm = (int)A5.regs[arm.X0];
+ // Memory layout starting at ary(length,pad, prime1, primt2, prime3, prime4)
+ assertEquals(primes.length, A5.ld4s(ary_arm));
+ for( int i = 0; i<primes.length; i++ )
+ assertEquals(primes[i], A5.ld4s(ary_arm + 4 + i * 4));
+ }
+
+ @Test public void testFibExport() throws IOException {
+ String src =
+"""
+val fib = { int n ->
+ int f1=1;
+ int f2=1;
+ while( n-- > 1 ){
+ int temp = f1+f2;
+ f1=f2;
+ f2=temp;
+ }
+ return f2;
+};
+""";
+ String fib = "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]";
+ TestC.runC(src, "fib", fib, 24);
+
+ EvalRisc5 R5 = TestRisc5.build( src, "fib", 9, 17, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ // Return register A0 holds fib(8)==55
+ assertEquals(55,R5.regs[riscv.A0]);
+
+ // arm
+ EvalArm64 A5 = TestArm64.build("fib", src, 9, 17, false);
+ int trap_arm = A5.step(100);
+ assertEquals(0,trap_arm);
+ // Return register X0 holds fib(8)==55
+ assertEquals(55, A5.regs[arm.X0]);
+ }
+
+ @Test public void testPerson() throws IOException {
+ String src =
+ """
+ struct Person {
+ i32 age;
+ };
+
+ val fcn = { Person?[] ps, int x ->
+ if( ps[x] )
+ ps[x].age++;
+ };
+ """;
+ String person = "6\n";
+ TestC.run(src, "person1", null, TestC.CALL_CONVENTION, "", TestC.C_DRIVERS_DIR+"person.c", person, 2);
+
+ // Memory layout starting at PS:
+ int ps = 1<<16; // Person array pointer starts at heap start
+ // Person[3] = { len,pad,P0,P1,P2 }; // sizeof = 4*8
+ // P0 = { age } // sizeof=8
+ int p0 = ps+4*8+0*8;
+ // P1 = { age } // sizeof=8
+ int p1 = ps+4*8+1*8;
+ // P2 = { age } // sizeof=8
+ int p2 = ps+4*8+2*8;
+ EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 0, false);
+ R5.regs[riscv.A1] = 1; // Index 1
+ R5.st8(ps,3); // Length
+ R5.st8(ps+1*8,p0);
+ R5.st8(ps+2*8,p1);
+ R5.st8(ps+3*8,p2);
+ R5.st8(p0, 5); // age= 5
+ R5.st8(p1,17); // age=17
+ R5.st8(p2,60); // age=60
+
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals( 5+0,R5.ld8(p0));
+ assertEquals(17+1,R5.ld8(p1));
+ assertEquals(60+0,R5.ld8(p2));
+
+ EvalArm64 A5 = TestArm64.build("fcn", src, ps, 0, false);
+ A5.regs[arm.X1] = 1; // Index 1
+ A5.st8(ps, 3);
+ A5.st8(ps+1*8,p0);
+ A5.st8(ps+2*8,p1);
+ A5.st8(ps+3*8,p2);
+ A5.st8(p0, 5); // age= 5
+ A5.st8(p1,17); // age=17
+ A5.st8(p2,60); // age=60
+
+ int trap_arm = A5.step(100);
+ assertEquals(0,trap_arm);
+ assertEquals( 5+0, A5.ld8(p0));
+ assertEquals(17+1, A5.ld8(p1));
+ assertEquals(60+0, A5.ld8(p2));
+ }
+
+ @Test public void testArgCount() throws IOException {
+ // Test passes more args than registers in Sys5, which is far, far more
+ // than what win64 allows - so win64 gets a lot more spills here.
+ String src =
+"""
+val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, int x8, flt f9, int i10, flt f11, int i12, flt f13, int i14, flt f15, int x16, flt f17 int x18, flt f19 ->
+ return
+ i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 +
+ i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ;
+};
+""";
+ String arg_count = "191.000000\n";
+
+ TestC.runC(src, "arg_count", arg_count, TestC.CALL_CONVENTION.equals("win64") ? 31 : 15);
+
+ EvalRisc5 R5 = TestRisc5.build( src, "addAll", 0, 4, false);
+
+ // Todo: handle stack(imaginary stack in emulator)
+ // pass in float arguments
+ R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA1 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA2 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA3 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA4 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA5 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA6 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA7 - riscv.F_OFFSET] = 1.1;
+
+ R5.regs[riscv.A0] = 2;
+ R5.regs[riscv.A1] = 2;
+ R5.regs[riscv.A2] = 2;
+ R5.regs[riscv.A3] = 2;
+ R5.regs[riscv.A4] = 2;
+ R5.regs[riscv.A5] = 2;
+ R5.regs[riscv.A6] = 2;
+
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+
+ double result = R5.fregs[riscv.FA0 - riscv.F_OFFSET];
+
+ assertEquals(22.8, result, 0.00001);
+
+ // arm
+ EvalArm64 A5 = TestArm64.build("addAll", src, 0, 4, false);
+
+ A5.fregs[arm.D0 - arm.D_OFFSET] = 1.1;
+ A5.fregs[arm.D1 - arm.D_OFFSET] = 1.1;
+ A5.fregs[arm.D2 - arm.D_OFFSET] = 1.1;
+ A5.fregs[arm.D3 - arm.D_OFFSET] = 1.1;
+ A5.fregs[arm.D4 - arm.D_OFFSET] = 1.1;
+ A5.fregs[arm.D5 - arm.D_OFFSET] = 1.1;
+ A5.fregs[arm.D6 - arm.D_OFFSET] = 1.1;
+ A5.fregs[arm.D7 - arm.D_OFFSET] = 1.1;
+
+ A5.regs[arm.X1] = 2;
+ A5.regs[arm.X2] = 2;
+ A5.regs[arm.X3] = 2;
+ A5.regs[arm.X4] = 2;
+ A5.regs[arm.X5] = 2;
+ A5.regs[arm.X6] = 2;
+ A5.regs[arm.X7] = 2;
+
+ int trap_arm = A5.step(100);
+ assertEquals(0,trap_arm);
+
+ double result1 = A5.fregs[arm.D0 - arm.D_OFFSET];
+ assertEquals(22.8, result1, 0.00001);
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
new file mode 100644
index 00000000..5a171659
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -0,0 +1,278 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.cpus.arm.arm;
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import java.io.IOException;
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter22Test {
+
+ @Test
+ public void testJig() throws IOException {
+ String src = """
+struct s0 {
+ bool v1;
+ i16 v2;
+ int v3;
+ i8 v4;
+ byte v5;
+};
+while(new s0.v3)
+ while(new s0.v5<<new s0.v4) {}
+if(0) {
+ if(0) {
+ flt !P5ZUD4=new s0.v2;
+ }
+ while(0) {}
+}
+return new s0.v1;
+""";
+ testCPU(src,"x86_64_v2", "win64" ,-1,null);
+ testCPU(src,"riscv" , "SystemV",-1,null);
+ testCPU(src,"arm" , "SystemV",-1,null);
+ }
+
+ static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ if( spills != -1 )
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ if( stop != null )
+ assertEquals(stop, code.print());
+ return code;
+ }
+
+
+ static int testCPUSize( String src, String cpu, String os, int spills ) {
+ return testCPU(src,cpu,os,spills, "return 0;" )._encoding._bits.size();
+ }
+
+ // Should not fold away
+ @Test public void testSextFail() throws IOException {
+ String src = """
+struct _Person { i32 age;};
+_Person !p = new _Person;
+p.age = (arg<<17)>>17;
+return 0;
+""";
+ assertEquals(83, testCPUSize(src, "x86_64_v2","win64", 3 ));
+ assertEquals(96, testCPUSize(src, "riscv", "SystemV", 4 ));
+ assertEquals(96, testCPUSize(src, "arm" , "SystemV", 4 ));
+
+ // do assertEquals here
+ EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away", 0, 4, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+
+ EvalArm64 A5 = TestArm64.build("sext_str_not_fold_away", src, 0, 4, false);
+ trap = A5.step(100);
+ assertEquals(0,trap);
+
+ }
+
+ // Should not fold away
+ @Test public void testSextFail2() throws IOException {
+ String src = """
+ struct _Person { i32 age;};
+ _Person !p = new _Person;
+ p.age = (arg<<48)>>48;
+ return 0;
+ """;
+
+ EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away_2", 0, 4, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+
+ EvalArm64 A5 = TestArm64.build("sext_str_not_fold_away_2", src, 0, 4, false);
+ int trap_arm = A5.step(100);
+ assertEquals(0,trap_arm);
+
+ assertEquals(83, testCPUSize(src, "x86_64_v2","win64",3 ));
+ assertEquals(96, testCPUSize(src, "riscv", "SystemV",4 ));
+ assertEquals(96, testCPUSize(src, "arm", "SystemV",4 ));
+
+ }
+
+ // Should fold away
+ @Test public void testSextSuccess() throws IOException {
+ String src = """
+// Should fold away sign extend
+struct _Person { i8 age;};
+_Person !p = new _Person;
+p.age = (arg<<48)>>48;
+return 0;
+""";
+
+ EvalRisc5 R5 = TestRisc5.build( src, "sext_str_fold_away", 0, 5, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+
+ EvalArm64 A5 = TestArm64.build("sext_str_fold_away", src, 0, 5, false);
+ int trap_arm = A5.step(100);
+ assertEquals(0,trap_arm);
+
+ assertEquals(71, testCPUSize(src, "x86_64_v2","win64",3 ));
+ assertEquals(92, testCPUSize(src, "riscv", "SystemV",5 ));
+ assertEquals(92, testCPUSize(src, "arm", "SystemV",5 ));
+ // do assertEquals here
+ }
+
+
+ // Int now is changed to 4 bytes.
+ @Test public void testPerson() throws IOException {
+ String src =
+"""
+struct Person { // Exports a field typed singleton+final *class:Person{age:i32}
+ i32 age;
+};
+
+val fcn = { Person?[] ps, int x -> // exports a field with a constant fcn ptr; exports the fcn also
+ if( ps[x] )
+ ps[x].age++;
+};
+""";
+ String person = "6\n";
+ TestC.run(src, "person2", null, TestC.CALL_CONVENTION, "", TestC.C_DRIVERS_DIR+"person.c", person, 2);
+
+ // Memory layout starting at PS:
+ int ps = 1<<16; // Person array pointer starts at heap start
+ // Person[3] = { len,pad,P0,P1,P2 }; // sizeof = 4*8
+ // P0 = { age } // sizeof=8
+ int p0 = ps+4*8+0*8;
+ // P1 = { age } // sizeof=8
+ int p1 = ps+4*8+1*8;
+ // P2 = { age } // sizeof=8
+ int p2 = ps+4*8+2*8;
+ EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 0, false);
+ R5.regs[riscv.A1] = 1; // Index 1
+ R5.st8(ps,3); // Length
+ R5.st8(ps+1*8,p0);
+ R5.st8(ps+2*8,p1);
+ R5.st8(ps+3*8,p2);
+ R5.st8(p0, 5); // age= 5
+ R5.st8(p1,17); // age=17
+ R5.st8(p2,60); // age=60
+
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals( 5+0,R5.ld8(p0));
+ assertEquals(17+1,R5.ld8(p1));
+ assertEquals(60+0,R5.ld8(p2));
+
+ EvalArm64 A5 = TestArm64.build("fcn", src, ps, 0, false);
+ A5.regs[arm.X1] = 1; // Index 1
+ A5.st8(ps, 3);
+ A5.st8(ps+1*8,p0);
+ A5.st8(ps+2*8,p1);
+ A5.st8(ps+3*8,p2);
+ A5.st8(p0, 5); // age= 5
+ A5.st8(p1,17); // age=17
+ A5.st8(p2,60); // age=60
+
+ int trap_arm = A5.step(100);
+ assertEquals(0,trap_arm);
+ assertEquals( 5+0, A5.ld8(p0));
+ assertEquals(17+1, A5.ld8(p1));
+ assertEquals(60+0, A5.ld8(p2));
+ }
+
+ @Test
+ public void testCoRecur() {
+ String src = """
+val x = 5; // aa.az; // Error to self-define forward ref
+struct _A { Test._B? b; Test._C? c; i64 ax; val az = x*2; new _A = { i64 x -> ax=x; }; };
+struct _B { Test._A? a; Test._C? c; f32 bx; val bz = x*3; new _B = { f32 x, Test._A? aa -> bx=x; a=aa; }; };
+struct _C { Test._A? a; Test._B? b; f64 cx; val cz = x*x; new _C = { f64 x, Test._A? aa, Test._B? bb -> cx=x; a=aa; b=bb; }; };
+_A !aa = new _A(17);
+_B !bb = new _B(3.14f, aa);
+_C !cc = new _C(2.73, aa, bb);
+aa.b = bb;
+aa.c = cc;
+bb.c = cc;
+return cc.cz;
+""";
+ CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ assertEquals("return 25;", code.print());
+ assertEquals("25", Eval2.eval(code, 0));
+ }
+
+ @Test
+ public void testHelloWorld() throws IOException {
+ String src = """
+// fd buf len -> len
+{ i32 i64 u32 -> u32 } write = "C";
+// Write byte array to stdout
+val p = { u8[~] str ->
+ i64 ptr = str; // cast array base to i64
+ return write(1,ptr,str#);
+};
+p("Hello, World!");
+return 0;
+""";
+ TestC.runSF(src,"helloWorld", "Hello, World!",2);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "helloWorld", 0, 5, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+ assertEquals("Hello, World!",R5._stdout.toString());
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("helloWorld", src,0, 5, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ assertEquals("Hello, World!",arm._stdout.toString());
+ }
+
+ @Test
+ public void testFinalArray() {
+ String src = """
+int N=4;
+i32[] !is = new i32[N];
+for( int i=0; i<N; i++ )
+ is[i] = i*i;
+val _sum = { i32[~] is -> // final array
+ int sum=0;
+ for( int i=0; i<is#; i++ )
+ sum += is[i];
+ return sum;
+};
+return _sum(is);
+""";
+ CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ assertEquals("return Phi(Loop,0,(Phi_sum+.[]));", code.print());
+ assertEquals("14", Eval2.eval(code, 0));
+ }
+
+
+ @Test @Ignore
+ public void testEcho() throws IOException {
+ String src =
+"""
+// Echo stdin to stdout.
+return sys.io.p( sys.io.stdin() );
+""";
+ TestC.runSF(src,"echo", "",0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "echo", 0, 2, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("echo", src, 0, 2, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ }
+
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java
new file mode 100644
index 00000000..7e64f226
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java
@@ -0,0 +1,288 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter23Test {
+
+ @Test @Ignore
+ public void testJig() throws IOException {
+ //String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp"));
+ String src = Files.readString(Path.of("docs/examples/BubbleSort.smp"));
+ testCPU(src,"x86_64_v2", "win64" ,-1,null);
+ testCPU(src,"riscv" , "SystemV",-1,null);
+ testCPU(src,"arm" , "SystemV",-1,null);
+ }
+
+
+ static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ if( spills != -1 )
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ if( stop != null )
+ assertEquals(stop, code.print());
+ return code;
+ }
+
+ @Test
+ public void testOr() throws IOException {
+ String src = """
+int a = 1;
+int b = 0;
+int rez = 2;
+
+if( a++ || b++ ) {
+ if( b == 0 && a == 2 )
+ rez = 0; // Expected answer
+} else {
+ rez = 1;
+}
+return rez;
+""";
+
+ TestC.runSF( src, "or1", "", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "or1", 0, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("or1", src,0, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ }
+
+ @Test
+ public void testAnd() throws IOException {
+ String src = """
+int a = 1;
+int b = 1;
+return a && b ? 0 : 1; // Expected answer 0
+""";
+ TestC.runSF( src, "and1", "", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "and1", 0, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("and1", src, 0, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ }
+
+ @Test
+ public void testAndPtr() throws IOException {
+ // Todo: have one src here
+ String src = """
+struct S { S? fld; new S = { S? f -> fld=f; }; };
+val ptr = arg == 1 ? null : new S(arg==1 ? null : new S(null));
+return ptr && ptr.fld ? "true" : "false";
+""";
+ CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ assertEquals("true", Eval2.eval(code, 0));
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "class:Test.<clinit>", 1, 8, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ int adr = (int)R5.regs[riscv.A0]; // Returns a Simple *u8[~] string
+ assertEquals(4,R5.ld4s(adr)); // String length
+ assertEquals(0x65757274,R5.ld4s(adr+4)); // "true"
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("class:Test.<clinit>", src, 1, 8, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ int adr2 = (int)arm.regs[0]; // Returns a Simple *u8[~] string
+ assertEquals(4,arm.ld4s(adr2)); // String length
+ assertEquals(0x65757274,arm.ld4s(adr2+4)); // "true"
+ }
+
+ // conditional side effect
+ @Test
+ public void testCondSideEffAnd() throws IOException {
+ String src = """
+int x=1;
+int y=1;
+int z=0;
+
+int g = x++ && y++ && z++;
+return x == 2 && y == 2 && z == 1 && g == 0
+ ? 0 // Effected, Expected
+ : 1; // Error, not effected
+""";
+
+ TestC.runSF( src, "and2", "", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "and2", 0, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("and2", src, 0, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ }
+
+ @Test
+ public void testCondSideEffOr() throws IOException {
+ String src = """
+int x = -1;
+int y = 1;
+int z = -1;
+int g = x++ || y++ || z++;
+int switch = (x == 4 || y == 4 || z == 4) ? -1 : 1;
+return (x == 0 && y == 1 && z==-1) && switch && g
+ ? 0 // Effected, expected
+ : 1; // Error
+""";
+ TestC.runSF( src, "or2", "", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "or2", 0, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("or2", src, 0, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ }
+
+ // test it with function calls
+ @Test
+ public void testFuncCall() throws IOException {
+ String src = """
+int a = 1;
+int b = 1;
+var sq_noInline = { int x ->
+ x*x;
+};
+return (a && sq_noInline(0))
+ ? 1 // Error
+ : 0; // Expected
+""";
+ TestC.runSF( src, "and3", "", 2);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "and3", 0, 5, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("and3", src, 0, 5, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ }
+
+
+ @Test
+ public void testFRefFields() {
+ String src = """
+// A hypothetical scanner class
+struct _Scan {
+ int !x;
+ u8[~] buf;
+ new _Scan = { u8[~] b -> buf=b; };
+ // Skip whitespace
+ val skip = { ->
+ while( buf[x] <= ' ' )
+ x++;
+ };
+ // Peek a character; if matched consume it, else false.
+ val peek = { u8 c ->
+ skip();
+ if( buf[x] != c ) return false;
+ x++;
+ return true;
+ };
+};
+_Scan !_s = new _Scan(" q");
+return _s.peek('q');
+""";
+
+ CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ assertEquals("return Phi(Region,0,1);", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ testCPU(src,"x86_64_v2", "win64",1,null);
+ };
+
+ @Test
+ public void testFRefFields2() {
+ String src ="""
+// A hypothetical scanner class
+struct _Scan {
+ int !x;
+ u8[~] buf;
+ new _Scan = { u8[~] b -> buf=b; };
+ // Skip whitespace
+ val skip = { ->
+ while( buf[x] <= ' ' )
+ x++;
+ return self;
+ };
+};
+val s = new _Scan(" q");
+return s.skip().x;
+""";
+
+ try { new CodeGen(src).parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) {
+ // Bad error message, but basically requires a mutable 'x' field.
+ assertEquals("Argument #0 isa *Test._Scan {i64 x; *[]u8 buf; { *Test._Scan -> *Test._Scan {i64 !x; *[]u8 buf; {[ 5]} skip; } #[ 5]} skip; }, but must be a *Test._Scan {i64 !x; *[]u8 buf; { *Test._Scan -> *Test._Scan #[ 5]} skip; }",e.getMessage());
+ }
+ };
+
+
+ @Test
+ public void testMethod() {
+ String src = """
+// A hypothetical scanner class
+struct _Scan {
+ int !x;
+ u8[~] buf;
+ new _Scan = { u8[~] b -> buf=b; };
+ // Skip whitespace
+ val skip = { ->
+ while( buf[x] <= ' ' )
+ x++;
+ };
+ val require = { u8 ch ->
+ skip();
+ buf[x++]==ch;
+ };
+};
+_Scan !_s = new _Scan(" [1,2]");
+return _s.require('[');
+""";
+
+ CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ assertEquals("return (.[]==91);", code.print());
+ assertEquals("1", Eval2.eval(code, 0));
+ testCPU(src,"x86_64_v2", "win64", 0, null);
+ };
+
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
new file mode 100644
index 00000000..0f2e1bed
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -0,0 +1,247 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter24Test {
+
+ @Test
+ public void testJig() throws IOException {
+ String src = "int v0=0; v0 = arg && 0== 0 !=0;";
+ testCPU(src,"x86_64_v2", "win64" ,-1,null);
+ testCPU(src,"riscv" , "SystemV",-1,null);
+ testCPU(src,"arm" , "SystemV",-1,null);
+ }
+
+ static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ if( spills != -1 )
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ if( stop != null )
+ assertEquals(stop, code.print());
+ return code;
+ }
+
+ // test it with function calls
+ @Test
+ public void testStack1() throws IOException {
+ String src ="""
+int something_specific = 11;
+return (10 < something_specific < 20)
+ ? 0 // Expected
+ : 1; // Error
+""";
+ TestC.runSF( src, "stacked_r_1", "", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_1", 0, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("stacked_r_1", src, 0, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ }
+
+ @Test
+ public void testStack3() throws IOException {
+ String src ="""
+var sq_noInline = { int x ->
+ x*x;
+};
+int score = 4;
+return (sq_noInline(2) <= score < sq_noInline(3))
+ ? 0 // Expected
+ : 1; // Error
+""";
+ TestC.runSF( src, "stacked_r_3", "", 4);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_3", 0, 5, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("stacked_r_3", src, 0, 5, false);
+ int trap_1 = arm.step(100);
+ assertEquals(0,trap_1);
+ assertEquals(0,arm.regs[0]);
+ }
+
+ @Test
+ public void testStack4() throws IOException {
+ String src ="""
+int something_specific = 9;
+return (10 < something_specific < 20)
+ ? 1 // Error
+ : 0; // Expected
+""";
+
+ TestC.runSF( src, "stacked_r_4", "", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_4", 0, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("stacked_r_4", src, 0, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ }
+
+ @Test
+ public void testStack6() {
+ try { new CodeGen("return 0 < arg > 1;" ).parse(); fail(); }
+ catch( RuntimeException e ) { assertEquals("Mixing relational directions in a chained relational test",e.getMessage()); }
+ }
+
+ @Test
+ public void testStack9() throws IOException {
+ String src = "var stack9 = { int x -> (x < 1) == 1; };";
+ String src2 = "return (0 < 1) == 1;";
+ TestC.runC(src, "stacked_r_9x", "1", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_9", 0, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("stacked_r_9", src2, 0, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,arm.regs[0]);
+ }
+
+ @Test
+ public void testStack10() throws IOException {
+ String src ="var stack10 = { int x -> (x < 1) == (1>x);};";
+ String src2 ="return (0 < 1) == (1>0);";
+ TestC.runC(src, "stacked_r_10x", "1",0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_10", 0, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("stacked_r_10", src2,0, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,arm.regs[0]);
+ }
+
+ @Test
+ public void testStack11() throws IOException {
+ String src = "var stack11 = { int x -> 0 < x < x+1 < 4;}; ";
+ String src2 = "return 0 < arg < arg+1 < 4;";
+ TestC.runC(src, "stacked_r_11x", "1", 2);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_11", 1, 0, false);
+ R5.regs[riscv.A1] = 1; // Arg in A1, Test.class in A0
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("stacked_r_11", src2, 1, 0, false);
+ arm.regs[1] = 1; // Arg in r1, Test.class in r0
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,arm.regs[0]);
+ }
+
+ // shows that it is parsed as
+ // ((0 != x) != 2)
+ @Test
+ public void testStack12() throws IOException {
+ String src = "var stack12 = { int x -> 0 != x != 2; };";
+ String src2 = "return 0 != arg != 2;";
+ TestC.runC(src, "stacked_r_12x", "1",0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_12", 1, 0, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("stacked_r_12", src2, 1, 0, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,arm.regs[0]);
+ }
+
+
+ // shows that it is parsed as
+ // ((0 == x) == 1)
+ @Test
+ public void testStack13() throws IOException {
+ String src = "var stack13 = { int x -> 0 == x == 1; };";
+ String src2 = "return 0 == arg == 1;";
+ TestC.runC(src, "stacked_r_13x", "1",0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_13", 1, 0, false);
+ R5.regs[riscv.A1] = 1; // Arg in A1, Test.class in A0
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,R5.regs[riscv.A0]);
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("stacked_r_13", src2, 1, 0, false);
+ arm.regs[1] = 1; // Arg in r1, Test.class in r0
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(1,arm.regs[0]);
+ }
+
+ @Test
+ public void testSCCP0() {
+ String src =
+"""
+int x = 1;
+int cnt = 5;
+while (cnt) {
+ cnt -= 1;
+ if (x == 0) {
+ x += 1;
+ }
+}
+return x;
+""";
+ CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ assertEquals("return 1;", code.print());
+ }
+
+ @Test
+ public void testSCCP1() {
+ String src =
+"""
+int x = 1;
+int cnt = 5;
+while( cnt-- )
+ if( x == 1 )
+ x = 2-x;
+return x;
+""";
+ CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ assertEquals("return 1;", code.print());
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
new file mode 100644
index 00000000..cf9d764e
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -0,0 +1,277 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.ElfReader;
+import com.seaofnodes.simple.codegen.ParseAll;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter25Test {
+
+ private static final String SYS_MODDIR = "src/main/smp";
+ private static final String SYS_BLDDIR = "build/objs/lib_"+TestC.CPU_ABI;
+ private static final String RELEASE_SYS_BLDDIR = TestC.RELEASE_SYS_DIR;
+
+ @Test
+ public void testPostfixFieldUpdate() {
+ String src = """
+ struct V {
+ u32 !len = 0;
+ i64[] !buf;
+ new V = { i64[] b -> buf=b; };
+ val grow = { i64 n -> self; };
+ val add = { i64 x -> grow(1).buf[len++] = x; self; };
+ };
+ return new V(new i64[1]).add(7).len;
+ """;
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("1",Eval2.eval(code,0));
+ }
+
+
+ @Test @Ignore
+ public void testModule0() throws IOException {
+ String MODDIR = "src/test/java/com/seaofnodes/simple/test0";
+ String BLDDIR = "build/objs/test0";
+ String a_obj = BLDDIR+"/A.o";
+ String ab_obj = BLDDIR+"/A/B.o";
+ // Remove any prior results so the test runs from scratch.
+ delELFiles(new File(BLDDIR));
+ writeB(MODDIR,5);
+
+ // Compile MODDIR/A.smp into MODDIR/A.o
+ // Since A refers to B also:
+ // Compile MODDIR/A/B.smp into MODDIR/A/B.o
+ CodeGen code1 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,TypeInteger.BOT);
+ code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ // Verify produces A.o, A/B.o
+ File a_file = new File( a_obj);
+ File ab_file = new File(ab_obj);
+ assertTrue( a_file.exists() );
+ assertTrue( ab_file.exists() );
+ long a_msec1 = a_file.lastModified();
+ long ab_msec1 = ab_file.lastModified();
+
+ // Link and execute: arg is true, so compute "5+1" as the exit code
+ String rez1 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez1);
+
+ // Compile again A, expecting both A.o and A/B.o to be up-to-date and not compiled
+ CodeGen code2 = new CodeGen(MODDIR, BLDDIR, null,
+ "A",null,123L,TypeInteger.BOT);
+ code2.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ assertTrue( a_file.exists() );
+ assertTrue( ab_file.exists() );
+ long a_msec2 = a_file.lastModified();
+ long ab_msec2 = ab_file.lastModified();
+
+ assertEquals( a_msec1, a_msec2 );
+ assertEquals( ab_msec1, ab_msec2 );
+
+ // Touch A.smp and recompile. A/B.o should not recompile.
+ new File(MODDIR+"/A.smp").setLastModified(System.currentTimeMillis());
+ CodeGen code3 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,TypeInteger.BOT);
+ code3.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec3 = a_file.lastModified();
+ long ab_msec3 = ab_file.lastModified();
+ assertTrue ( a_msec1 < a_msec3 );
+ assertEquals( ab_msec1, ab_msec3 );
+
+ // Link and execute: arg is true, so compute "5+1" as the exit code
+ String rez3 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez3);
+
+
+ // Modify B.smp and recompile A/B.o; it should recompile and A.o should not.
+ writeB(MODDIR,7);
+ CodeGen code4 = new CodeGen(MODDIR, BLDDIR,null,
+ "A/B",null,123L,TypeInteger.BOT);
+ code4.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec4 = a_file.lastModified();
+ long ab_msec4 = ab_file.lastModified();
+ assertEquals( a_msec3, a_msec4 );
+ assertTrue ( ab_msec3 < ab_msec4 );
+
+ // Link and execute: uses stale A.o, so remains '6' not '7+1' == 8
+ String rez4 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez4);
+
+ // Recompile A.o, it should recompile despite not being touched because
+ // it depends on A/B.o which recompiled in the prior step.
+ CodeGen code5 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,TypeInteger.BOT);
+ code5.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec5 = a_file.lastModified();
+ long ab_msec5 = ab_file.lastModified();
+ assertTrue ( a_msec4 < a_msec5 );
+ assertEquals( ab_msec4, ab_msec5 );
+
+ // Link and execute: updates A.o from B.o inlining, without compiling B.o
+ String rez5 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 8",rez5);
+ // Reset for next time
+ writeB(MODDIR,5);
+ }
+
+ private void writeB(String MODDIR, int x) throws IOException {
+ var bsmp = new FileWriter(MODDIR+"/A/B.smp");
+ bsmp.write("val x="+x+";\n");
+ bsmp.close();
+ }
+
+
+ // Recursive search (TODO: gzip, archives) and delete all .o files
+ private void delELFiles( File dir) {
+ if( dir.isDirectory() )
+ for( File f : dir.listFiles() )
+ delELFiles(f);
+ else if( dir.getName().endsWith(".o") )
+ dir.delete();
+ }
+
+
+ private File buildTestSys(boolean clean) {
+ if( clean )
+ delELFiles(new File(SYS_BLDDIR));
+ File sys_file = new File(SYS_BLDDIR+"/sys.o" );
+ if( sys_file.exists() )
+ return sys_file;
+
+ // Compile SYS_MODDIR/sys.smp into SYS_BLDDIR/sys.o. Chapter25
+ // compiler tests intentionally depend on this freshly built sys.o.
+ CodeGen code1 = new CodeGen(SYS_MODDIR, SYS_BLDDIR,null,
+ "sys",null,123L,TypeInteger.BOT);
+ code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false,0);
+ assertTrue( sys_file.exists() );
+ return sys_file;
+ }
+
+ @Test
+ public void testSys() {
+ delELFiles(new File(SYS_BLDDIR));
+
+ CodeGen code1 = new CodeGen(SYS_MODDIR, SYS_BLDDIR,null,
+ "sys",null,123L,TypeInteger.BOT);
+ code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false,0);
+
+ // Verify produces sys.o, sys/io.o
+ File sys_file = new File(SYS_BLDDIR+"/sys.o" );
+ assertTrue( sys_file.exists() );
+
+ // Can read the ELF files
+ ElfReader sys_elf = ElfReader.load( sys_file, null);
+ sys_elf.loadSimple(code1);
+
+ // Elf files are sane
+
+ // Sys depends on io, libc, aryi64, aryu8
+ assertEquals(6,sys_elf._deps.length);
+ assertSame("sys/aryu8" ,sys_elf._deps[0]);
+ assertSame("sys/io" ,sys_elf._deps[1]);
+ assertSame("sys/aryi64",sys_elf._deps[2]);
+ assertSame("sys/Ary" ,sys_elf._deps[3]);
+ assertSame("sys/Scan" ,sys_elf._deps[4]);
+ assertSame("sys/libc" ,sys_elf._deps[5]);
+ assertSame("class:sys" ,sys_elf._clz._name);
+ }
+
+ @Test
+ public void testHelloWorld() throws IOException {
+ File sys_file = buildTestSys(false);
+ assertTrue("Missing "+sys_file+"; testHelloWorld depends on testSys building it", sys_file.exists());
+
+ String expected = "Hello, World!";
+ String prog = "return sys.io.p(\""+expected+"\") - "+expected.length()+";";
+ TestC.run(prog,"helloWorld",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null, expected,0);
+ }
+
+ @Test
+ public void testHelloWorldDriver() throws Exception {
+ File sys_file = new File(RELEASE_SYS_BLDDIR+"/sys.o");
+ assertTrue("Missing "+sys_file+"; run make release or make tests_sys first", sys_file.exists());
+ Simple.main(new String[]{"-L",RELEASE_SYS_BLDDIR,"--norun","docs/examples/A_helloWorld.smp"});
+ }
+
+ @Test
+ public void testHelloWorldDriverLibFile() throws Exception {
+ File sys_file = new File(RELEASE_SYS_BLDDIR+"/sys.o");
+ assertTrue("Missing "+sys_file+"; run make release or make tests_sys first", sys_file.exists());
+ Simple.main(new String[]{"-L",sys_file.toString(),"docs/examples/A_helloWorld.smp"});
+ }
+
+ @Test
+ public void testHelloWorldNoInline() throws Exception {
+ File sys_file = buildTestSys(false);
+ assertTrue("Missing "+sys_file+"; testHelloWorldNoInline depends on testSys building it", sys_file.exists());
+
+ String base = "helloWorldNoInline";
+ String expected = "Hello, World!";
+ String prog = "return sys.io.p_noInline(\""+expected+"\") - "+expected.length()+";";
+ CodeGen code = new CodeGen(null,"build/objs",new Ary<>(new String[]{SYS_BLDDIR}),
+ base,prog,123L,TypeInteger.BOT);
+ code.driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,true);
+
+ String obj = "build/objs/"+base+".o";
+ String exe = "build/objs/"+base+(TestC.OS.startsWith("Windows") ? ".exe" : "");
+ String syms = run(new String[]{"nm",obj});
+ assertTrue(syms, syms.contains(" U sys.io.p_noInline"));
+
+ String out = run(new String[]{"gcc",obj,sys_file.toString(),"-lm","-g","-o",exe});
+ assertEquals("",out);
+ String rez = run(new String[]{exe});
+ assertEquals(expected,rez);
+ }
+
+ private static String run(String[] cmd) throws Exception {
+ Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
+ boolean normal = p.waitFor(5, TimeUnit.SECONDS);
+ String out = new String(p.getInputStream().readAllBytes());
+ assertTrue(out, normal);
+ assertEquals(out,0,p.exitValue());
+ return out;
+ }
+
+ @Test
+ public void testRedirectedRead() throws IOException {
+ File sys_file = buildTestSys(false);
+ assertTrue("Missing "+sys_file+"; testRedirectedRead depends on testSys building it", sys_file.exists());
+ String src = """
+u8[] buf = new u8[10];
+i64 ptr = buf;
+int rez = sys.libc.read(0,ptr,buf#);
+return rez < buf# ? 0 : sys.libc._exit(-2);
+""";
+ TestC.run(src,"redirectedRead",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null,
+ "abc", "", -1);
+ }
+
+ @Test
+ public void testBubbles() throws IOException {
+ File sys_file = buildTestSys(false);
+ assertTrue("Missing "+sys_file+"; testBubbles depends on testSys building it", sys_file.exists());
+ String src = Files.readString( Path.of("docs/examples/BubbleSort.smp"));
+ TestC.run(src,"BubbleSort",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null,
+ "[3, 2,-17, 999 ] ", "[-17, 2, 3, 999]", -1);
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java
new file mode 100644
index 00000000..e88e9caa
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java
@@ -0,0 +1,525 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.IRPrinter;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.*;
+
+public abstract class Eval2 {
+
+ // A mapping from Node ID to evaluation data Object.
+ // Data Objsts are a boxed Long or Double; or an Object[] of fields, which
+ // themselves are just more Objects.
+ private static class Frame {
+ private static int UID=1;
+
+ private final IdentityHashMap<Node,Object> _data;
+ private Frame _prior; // Prior frame in this sequence, means when making a new frame we can find old stuff
+ private int _refcnt; // TODO: Use this in a sentence (free/recycle unused stack frames)
+ final int _uid = UID++; // Debugging aid
+ Frame(Frame prior) {
+ _prior = prior;
+ if( prior!=null ) prior._refcnt++;
+ _data = new IdentityHashMap<>();
+ _refcnt = 1;
+ }
+
+ // Dec ref count
+ void dec() {
+ assert _refcnt>0;
+ if( --_refcnt == 0 ) {
+ Frame prior = _prior;
+ _prior = null;
+ _data.clear();
+ // TODO: FREE LIST?
+ if( prior!=null )
+ prior.dec();
+ }
+ }
+
+ // Put a mapping from Node->Object
+ Node put(Node n, Object d) {
+ assert d!=null;
+ return put0(n,d);
+ }
+ // Put a mapping from Node->Object?
+ Node put0(Node n, Object d) {
+ assert _refcnt>0;
+ _data.put(n,d);
+ return n;
+ }
+ // Get a mapped Node, perhaps using the prior stack frame crawl
+ Object get(Node n) {
+ assert _refcnt>0;
+ Object d = _data.get(n);
+ return d != null ? d
+ : _prior == null ? null
+ : _prior.get(n);
+ }
+
+ // Short debugging print
+ public String p() {
+ return "#"+_uid + (_prior==null ? "" : " >> " + _prior.p());
+ }
+ // Long debugging print
+ @Override public String toString() {
+ String s = "";
+ if( _prior != null ) s+= _prior+" << ";
+ s += "#"+_uid+"[ ";
+ for( Node n : _data.keySet() )
+ s += n + ":" + _data.get(n) + ",";
+ s += "]";
+
+ return s;
+ }
+ }
+
+ // Classic Closure; an environment (stack of frames) and an execution point
+ private static class Closure {
+ // Where to return too; either a CallEnd or a Function
+ final CFGNode _cc;
+
+ // Stack frame at the execution point. Includes Closures which
+ // recursively include other stack frames
+ private Frame _frame;
+
+ Closure(CFGNode cc, Frame frame) {
+ _cc = cc; // One of Fun, CallEnd or Stop (null)
+ _frame = frame;
+ assert _frame._refcnt >=0;
+ _frame._refcnt++;
+ }
+
+ FunNode fun() { return (FunNode)_cc; }
+ CallEndNode cend() { return (CallEndNode)_cc; }
+ boolean isStop() { return _cc instanceof StopNode; } // or Stop
+
+ //
+ void pushFrame() {
+ _frame = new Frame(_frame);
+ _frame._refcnt++;
+ }
+
+ @Override public String toString() {
+ return _cc.toString();
+ }
+ }
+
+ // CodeGen has the linker tables
+ public static CodeGen CODE;
+
+ // Current active Frame, to avoid having to pass it everywhere
+ public static Frame F;
+
+ public static String eval( CodeGen code, long arg ) { return eval(code,arg,1000); }
+ public static String eval( CodeGen code, long arg, int timeout ) {
+ if( code._start.uctrl()==null ) return ""; // The empty program
+ SB trace = null; // new SB(); // TRACE, set to null for off, new SB() for on
+ // Force local scheduling phase
+ code.driver(CodeGen.Phase.LocalSched);
+ // Set global, so don't have to pass everywhere
+ CODE = code;
+
+ // Timer for when we run too long. Ticks once per backedge or function call
+ int loopCnt = 0;
+
+ // Current value for each Node; boxed Long or Double; or an Object[] of
+ // fields, which themselves are just more Objects.
+ F = new Frame(null);
+
+ // Start at the START
+ CFGNode BB = code._start, prior = null;
+ // Return from main hits Stop
+ F.put(BB,new Closure(code._stop,F));
+ // For all <clinit> / Clazzes, install their uninitialized memory
+ for( FunNode fun : code._linker )
+ if( fun!=null && fun.isClz() ) {
+ TypeStruct clz = ((TypeMemPtr)fun.sig().arg(0))._obj;
+ // Map from the <clinit> self parameter to the
+ // singleton class structure.
+ F.put(fun.parm(2),alloc(clz));
+ }
+
+
+ // ------------
+ // Evaluate until exit or timeout. Each outer loop step computes
+ // all data nodes under some new Control.
+ while( true ) {
+ assert BB!=null;
+ traceCtrl(BB,trace);
+ if( trace!=null ) System.out.println(F.p());
+
+ // Parallel assign Phis
+ int i = BB instanceof RegionNode r
+ ? parallelAssignPhis(r,prior,arg,trace)
+ : 0;
+
+ // Compute the data nodes until BB control
+ for( ; i < BB.nOuts(); i++ )
+ if( !(BB.out(i) instanceof CFGNode) )
+ step(BB.out(i), trace);
+
+ // Find the next control target
+ prior = BB;
+ BB = BB.uctrl(); // Next unique control target
+ switch( prior ) {
+ case StartNode start: F = new Frame(F); break; // Frame for the call to main, as-if Called
+ case CallNode call : BB = call(call); break;
+ case IfNode iff : Object p = val(iff.pred()); BB = iff.cproj( p==null || (p instanceof Long x && x==0L) ? 1 : 0); break;
+ case ReturnNode ret : if( clj(ret.rpc()).isStop() ) return exit(ret); else BB = ret(ret); break;
+ case FunNode fun : if( loopCnt++ > timeout ) return null; break; // Timeout
+ case LoopNode loop : if( loopCnt++ > timeout ) return null; break; // Timeout
+ case CallEndNode cend: break;
+ case CProjNode cproj: break;
+ case RegionNode r : break;
+ default: throw Utils.TODO();
+ }
+ assert !BB.isDead();
+ }
+ }
+
+
+ // Phi cache; size is limited to number of Phis at a merge point
+ private static final Object[] PHICACHE = new Object[256];
+ static int parallelAssignPhis(RegionNode r, CFGNode prior, long arg, SB trace) {
+
+ // Compute input path on Phis for this Region
+ int path = r._inputs.find( prior );
+ // Parameters read from prior frame, Phis from local frame
+ Frame frame = (r instanceof FunNode ? F._prior : F);
+ boolean isClz = r instanceof FunNode fun && fun.isClz();
+
+ // Parallel assign Phis. First parallel read and cache
+ int i;
+ for( i = 0; i < r.nOuts(); i++ ) {
+ // Due to scheduling, all Phis are first in the outputs; first
+ // non-Phi is the end of all Phis.
+ if( !(r.out(i) instanceof PhiNode phi) ) break;
+
+ // Parms read from Call args -
+ // - except RPC is the actual prior frame
+ // - except <clinit> takes the singleton class object and external args
+ Object val;
+ if( isClz && phi instanceof ParmNode parm && parm._idx==3 )
+ PHICACHE[i] = arg; // Reading the initial arguments to <clinit>
+ else {
+ Node n = phi instanceof ParmNode parm
+ // RPC uses the prior Call directly;
+ // Parms may not be linked, so read call args directly
+ ? (parm._idx==0 ? prior : prior.in(parm._idx))
+ // Phis read from path input
+ : phi.in(path);
+ // Read the frame before overwriting
+ PHICACHE[i] = frame.get(n);
+ }
+ }
+ // Parallel assign; might assign before read, so read from cache
+ for( int j=0; j < i; j++ )
+ traceData(F.put0(r.out(j),PHICACHE[j]),trace);
+ // Return point in basic block past last Phi; the Phis have been
+ // evaluated here, and now we need to evaluate the rest of the block
+ return i;
+ }
+
+
+ // Make a call.
+ private static CFGNode call( CallNode call ) {
+ // Set up the Return PC and return frame (which is just the current frame).
+ // Really making a Continuation, but it's just a Closure
+ Closure cont = new Closure(call.cend(),F);
+ // Set the return continuation into the current frame RPC
+ F.put(call, cont );
+ // Next Frame to use
+ F = new Frame(F);
+ // Target function and environment
+ return CODE.link(tfp(call.fptr()));
+ }
+
+ private static CFGNode ret( ReturnNode ret ) {
+ // Fetch return expression value and continuation from current frame
+ Object rez = val(ret.expr());
+ Closure cont = clj(ret.rpc());
+ // Lower Frame reference count; might delete frame
+ F.dec(); // Lower ref count
+ // Install replacement Frame; set return result over the CallEnd
+ F = cont._frame;
+ F.put0(cont._cc,rez); // Set return value into CallEnd
+ return cont._cc; // Return CallEnd as the new control
+ }
+
+ private static String exit( ReturnNode ret ) {
+ return prettyPrint(ret.expr()._type,val(ret.expr()));
+ }
+
+ // Take a worklist step: one data op from the current Control is updated.
+ private static void step(Node n, SB trace) {
+ if( n instanceof PhiNode ) return;
+ // Compute new value
+ F.put0(n,compute(n));
+ traceData(n,trace);
+ // Also do any following projections, which are not in the local schedule otherwise
+ if( n instanceof MultiNode )
+ for( Node use : n._outputs )
+ step(use,trace);
+ }
+
+ // From here down shamelessly copied from Evaluator, written by @Xmilia
+ private static Object compute( Node n ) {
+ return switch( n ) {
+ case AddNode add -> add.mode()==1
+ ? (Object)(x(add.in(1)) + x(add.in(2)))
+ : (Object)(d(add.in(1)) + d(add.in(2)));
+ case AndNode and -> x(and.in(1)) & x(and.in(2));
+ case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; // Bool EQ supports pointers, nil, and integers
+ case BoolNode.LE le -> le.mode()==1
+ ? (x(le.in(1)) <= x(le.in(2)) ? 1L : 0L)
+ : (d(le.in(1)) <= d(le.in(2)) ? 1L : 0L);
+ case BoolNode.LT lt -> lt.mode()==1
+ ? (x(lt.in(1)) < x(lt.in(2)) ? 1L : 0L)
+ : (d(lt.in(1)) < d(lt.in(2)) ? 1L : 0L);
+ case CheckCastNode cast -> val(cast.in(1));
+ case GuardNode guard -> val(guard.in(1));
+ case ConstantNode con -> con(con._con);
+ case DivNode div -> div.mode()==1
+ ? (Object)(x(div.in(1)) / x(div.in(2)))
+ : (Object)((d(div.in(2))==0 ? 0D : d(div.in(1))) / d(div.in(2)));
+ case EscapeNode esc -> esc(esc);
+ case FunPtrNode fptr -> con(fptr._con);
+ case LoadNode ld -> load(ld);
+ case MemMergeNode merge-> "$mem";
+ case MinusNode sub -> sub.mode()==1 ? (Object)(- x(sub.in(1))) : (Object)(- d(sub.in(1)));
+ case MulNode mul -> mul.mode()==1
+ ? (Object)(x(mul.in(1)) * x(mul.in(2)))
+ : (Object)(d(mul.in(1)) * d(mul.in(2)));
+ case NewNode alloc-> alloc(alloc);
+ case NotNode not -> not(not);
+ case OrNode or -> x(or .in(1)) | x(or .in(2));
+ case ProjNode proj -> proj._type instanceof TypeMem ? "$mem" : val(proj.in(0));
+ case ReadOnlyNode read -> val(read.in(1));
+ case SarNode sar -> x(sar.in(1)) >> x(sar.in(2));
+ case ShlNode shl -> x(shl.in(1)) << x(shl.in(2));
+ case ShrNode shr -> x(shr.in(1)) >>>x(shr.in(2));
+ case StoreNode st -> store(st);
+ case SubNode sub -> sub.mode()==1
+ ? (Object)(x(sub.in(1)) - x(sub.in(2)))
+ : (Object)(d(sub.in(1)) - d(sub.in(2)));
+ case ToFloatNode toflt-> (double)x(toflt.in(1));
+ case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2));
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Fetch without unboxing, searching up-Frame
+ static Object val( Node n ) { return F.get(n); }
+ // Fetch and unbox as primitive long
+ static long x( Node n ) { Object d = F.get(n); return d==null ? 0 : (Long)d; }
+ // Fetch and unbox as primitive double
+ static double d( Node n ) { Object d = F.get(n); return d==null ? 0 : (Double)d; }
+ // Fetch and unbox a function constant
+ static TypeFunPtr tfp(Node n) {
+ return (TypeFunPtr)F.get(n);
+ }
+ // Fetch and unbox a closure
+ static Closure clj(Node n) { return (Closure)F.get(n); }
+
+ // Java box ints and floats. Other things can just be null or some informative string
+ private static Object con( Type t ) {
+ return switch ( t ) {
+ case TypeInteger i -> i.isConstant() ? (Long )i.value() : "INT";
+ case TypeFloat f -> f.isConstant() ? (Double)f.value() : "FLT";
+ case TypeFunPtr tfp -> tfp;
+ case TypeMem mem -> "MEM";
+ case TypeMemPtr tmp -> {
+ if( tmp._obj.isAry() ) { // Constant array ptr
+ Type elem = tmp._obj.field("[]")._t;
+ if( elem instanceof TypeConAry con ) {
+ Object[] xs = new Object[con.len()];
+ for( int i=0; i<con.len(); i++ )
+ xs[i] = con.at8(i);
+ yield xs;
+ } else {
+ // Generic constant array, used as a default input to a
+ // function; should never execute.
+ yield new Object[0];
+ }
+
+ } else {
+ // Constant object with all zeros
+ yield alloc(tmp._obj);
+ }
+ }
+ default -> null;
+ };
+ }
+
+ // Escapes of constant memory actually needs to set the memory
+ private static Object esc(EscapeNode esc) {
+ if( esc.priv() instanceof ConstantNode con ) {
+ TypeMem mem = (TypeMem)con._type;
+ Object val = con(mem._t);
+ TypeMemPtr tmp = (TypeMemPtr)esc.self()._type;
+ int idx = tmp._obj.findAlias(esc.fld()._alias);
+ Object[] fs = (Object[])val(esc.self());
+ if( tmp._obj.isAry() ) {
+ assert idx==0; // length field
+ assert (Long)val==fs.length;
+ } else {
+ fs[idx] = val;
+ }
+ }
+ return "$mem";
+ }
+
+
+ // Convert array size to array element count
+ private static int offToIdx( long off, TypeStruct t) {
+ off -= t.aryBase();
+ int scale = t.aryScale();
+ assert (off & ((1L << scale)-1)) == 0;
+ return (int)(off>>scale);
+ }
+
+ // Builds and returns a pointer Object
+ private static Object[] alloc(NewNode alloc) {
+ TypeStruct type = alloc._ts;
+ if( type.isAry() ) {
+ long sz = (Long)val(alloc.in(1));
+ long x = offToIdx(sz, type);
+ int n = (int)x;
+ if( n!=x || n<0 )
+ throw new NegativeArraySizeException(""+n);
+ Object[] ary = new Object[n]; // Array body
+ var elem = type._fields[1]._t;
+ if (elem instanceof TypeInteger) {
+ Arrays.fill( ary, 0L );
+ } else if (elem instanceof TypeFloat) {
+ Arrays.fill( ary, 0D );
+ } else {
+ assert elem instanceof TypeMemPtr || elem instanceof TypeFunPtr;
+ }
+ return ary;
+ }
+
+ return alloc(type);
+ }
+ private static Object[] alloc(TypeStruct type) {
+ assert !type.isAry();
+ int num = type._fields.length;
+ Object[] ptr = new Object[num];
+ for( int i=0; i<num; i++ )
+ ptr[i] = con(type._fields[i]._t.makeZero());
+ return ptr;
+ }
+
+ private static Object not( NotNode not ) {
+ Object n = val(not.in(1));
+ return ( n==null || (n instanceof Double D && D==0.0) || n instanceof Long L && L==0 )
+ ? 1L : 0L;
+ }
+
+ private static Object load( LoadNode ld ) {
+ Object f = val(ld.ptr());
+ // Check for dense constant array
+ if( f instanceof TypeMemPtr tmp ) {
+ throw Utils.TODO();
+ }
+ Object[] fs = (Object[])f;
+ if( ld._name.equals("#") )
+ return (long)fs.length;
+ TypeMemPtr tmp = (TypeMemPtr)ld.ptr()._type;
+ int idx = tmp._obj.isAry()
+ ? offToIdx(x(ld.off()),tmp._obj)
+ : tmp._obj.find(ld._name);
+ return fs[idx];
+ }
+
+ private static Object store( StoreNode st ) {
+ if( st._name.equals("#") )
+ return "$mem"; // Eval stores the length in the java object[], no need to set it now
+ TypeMemPtr tmp = (TypeMemPtr)st.ptr()._type;
+ Object[] fs = (Object[])val(st.ptr());
+ int idx = tmp._obj.isAry()
+ ? offToIdx(x(st.off()),tmp._obj)
+ : tmp._obj.find(st._name);
+ Object val = val(st.val());
+ fs[idx] = val;
+ return "$mem";
+ }
+
+
+ // ------------------------------------------------------------------------
+ private static final IdentityHashMap<Object,Object> VISIT = new IdentityHashMap<>();
+
+
+ static void traceCtrl( CFGNode BB, SB sb ) {
+ if( sb==null ) return;
+ IRPrinter.printLine(BB,sb.p("--- "));
+ System.out.print(sb);
+ sb.clear();
+ }
+ static void traceData( Node n, SB sb ) {
+ if( sb==null ) return;
+ IRPrinter.printLine(n,sb).unchar();
+ _print( n._type, F.get(n), sb.p('\t'), VISIT );
+ System.out.println(sb);
+ sb.clear();
+ VISIT.clear();
+ }
+
+ // Use type to print x as a string. Uses a new VISIT table so the debugger
+ // doesn't try to reuse VISIT if stopping mid-print. The trace code reuses
+ // VISIT endlessly for some minor speed hope
+ static String prettyPrint( Type t, Object x ) { return _print(t,x,new SB(), new IdentityHashMap<>()).toString(); }
+ static SB _print( Type t, Object x, SB sb, IdentityHashMap<Object,Object> visit ) {
+ if( x instanceof String s ) return sb.p(s);
+ if( x == null ) return sb.p("null");
+ return switch( t ) {
+ case TypeInteger i -> sb.p( (Long)x);
+ case TypeFloat f -> sb.p((Double)x);
+ case TypeMemPtr tmp -> {
+ if( visit.containsKey(x) ) yield sb.p("$cyclic");
+ visit.put(x,x);
+
+ Object[] xs = (Object[])x; // Array of fields
+ if( tmp._obj.isAry() ) {
+ Type elem = tmp._obj._fields[1]._t;
+ if( elem.isa(TypeInteger.U8) ) {
+ // Shortcut u8[] as a String
+ for( Object o : xs )
+ sb.p((char)(long)(Long)o);
+ } else {
+ // Array of elements
+ elem.print(sb).p("[ ");
+ if( elem instanceof TypeConAry ) elem = TypeInteger.ZERO;
+ for( Object o : xs )
+ _print( elem, o, sb, visit ).p( "," );
+ sb.unchar().p("]");
+ }
+ } else {
+ sb.p(tmp._obj._name).p("{");
+ Field[] flds = tmp._obj._fields;
+ for( int i=0; i<flds.length; i++ )
+ _print( flds[i]._t,xs[i], sb.p(flds[i]._fname).p("="), visit ).p(",");
+ sb.unchar().p("}");
+ }
+ yield sb;
+ }
+ case TypeFunPtr tfp -> sb.p(x.toString());
+ case TypeRPC rpc -> sb.p(x.toString());
+ case TypeMem mem -> sb.p("$mem");
+ case TypeTuple tt -> {
+ if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr )
+ // Assume a NewNode
+ yield _print( tt._types[1], x, sb, visit );
+ throw Utils.TODO();
+ }
+ case Type tt -> {
+ if( tt == Type.NIL ) yield sb.p("null");
+ throw Utils.TODO();
+ }
+ };
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java
new file mode 100644
index 00000000..32a5b152
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -0,0 +1,766 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.node.cpus.arm.arm;
+import com.seaofnodes.simple.util.Utils;
+import java.io.ByteArrayOutputStream;
+
+public class EvalArm64 {
+ // Memory image, always 0-based
+ public final byte[] _buf;
+
+ // GPRs
+ final long[] regs;
+ // FRs
+ final double[] fregs;
+
+ int _pc;
+
+ // Start of free memory for allocation
+ int _heap;
+
+ // Standard out and err streams
+ ByteArrayOutputStream _stdout, _stderr;
+
+ // Cycle counters
+ int _cycle;
+
+ // flags
+ boolean N = false;
+ boolean Z = false;
+ boolean C = false;
+ boolean V = false;
+
+ EvalArm64(byte[] buf, int stackSize) {
+ _buf = buf;
+ regs = new long[32];
+ fregs = new double[32];
+ // Stack grows down, heap grows up
+ regs[arm.RSP] = _heap = stackSize;
+ _pc = 0;
+ }
+
+ // Little endian
+ public int ld1s(int x) { return _buf[x ] ; }
+ public int ld1z(int x) { return _buf[x ]&0xFF; }
+ public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; }
+ public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; }
+ public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; }
+ public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; }
+
+
+ public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); }
+ public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); }
+
+ public void st1(int x, int val) { _buf[x] = (byte)val; }
+ public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
+ public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); }
+ public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+
+ public int step(int maxops) {
+ int trap = 0;
+ long rval = 0;
+ double frval = 0;
+ int pc = _pc;
+ int cycle = _cycle;
+ outer:
+ for(int icount = 0; icount < maxops; icount++) {
+ boolean is_f = false;
+ int ir = 0;
+ rval = 0;
+ frval = 0;
+ cycle++;
+ if( pc >= _buf.length ) {
+ trap = 1 + 1; // Handle access violation on instruction read.
+ break;
+ }
+ if( (pc & 3)!=0 ) {
+ trap = 1; // Handle PC-misaligned access
+ break;
+ }
+
+ // Load instruction from image buffer at PC
+ ir = ld4s( pc );
+
+ int rdid = ir & 0x1f;
+ // [31:24]
+ int opcode1 = (ir >> 24) & 0xFF;
+
+ // for the opcode we just match for bits[31:24];
+ switch( opcode1 ) {
+ case 0x14, 0x15, 0x16, 0x17: { //b
+ int imm26 = ir << 6 >> 6;
+ pc = pc + (imm26 * 4) - 4;
+ rdid = -1;
+ break;
+ }
+ case 0x1E: { // floats
+ is_f = true;
+ int rs1 = (ir >> 5) & 0x1F;
+ int rs2 = (ir >> 16) & 0x1F;
+
+ int op = (ir >> 10) & 0x3F;
+ switch(op) {
+ case 16: frval = fregs[rs1]; break; // fmov
+ case 10: frval = fregs[rs1] + fregs[rs2]; break; // fadd
+
+ case 6: if (fregs[rs2] == 0) frval = 0; else frval = fregs[rs1] / fregs[rs2]; break; // fdiv
+ case 8: {
+ // fcmp
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+
+ Z = (lhs == rhs);
+ N = (lhs < rhs);
+ C = (lhs > rhs);
+ V = Double.isNaN(lhs) || Double.isNaN(rhs);
+ rdid = -1;
+ break;
+ }
+ case 2: frval = fregs[rs1] * fregs[rs2]; break; // fmul
+ case 14: frval = fregs[rs1] - fregs[rs2]; break; // fsub
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0x38: {
+ // STRB/LDRB(register)
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch( ir >> 21 & 0x7 ) {
+ case 0x1 -> { // store
+ if( opcode1 == arm.OP_STORE_R_8 ) st1((int)regs[rn] + (int)regs[rm], (int)regs[rdid]);
+ else trap = (2+1);
+ rdid = -1;
+ }
+ case 0x3 -> { // load
+ if( opcode1 == arm.OP_LOAD_R_8 ) rval = ld1s((int)regs[rn] + (int)regs[rm]);
+ else trap = (2+1);
+ }
+ default -> trap = (2+1);
+ }
+ break;
+ }
+ case 0x39: {
+ // STRB/LDRB(immediate)
+ // str/ldr(immediate)
+ // rn
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == arm.OP_STORE_IMM_8) {
+ st1((int)rval, (int)regs[rdid]);
+ } else trap = (2+1);
+ rdid = -1;
+
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == arm.OP_LOAD_IMM_8) {
+ rval = ld1s((int)rval);
+ } else trap = (2+1);
+ rdid = -1;
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+ case 0x54: {
+ // b.cond
+ int imm19 = ir << 8 >> 13;
+ imm19 = pc + (imm19 * 4) - 4;
+ rdid = -1;
+
+ switch(ir & 0xF) {
+ case 0x0: if(Z) pc = imm19; break; // eq
+ case 0x1: if(!Z) pc = imm19; break; // ne
+ case 0x2: if(C) pc = imm19; break; // cs
+ case 0x3: if(!C) pc = imm19; break;
+ case 0x4: if(N) pc = imm19; break; // mi
+ case 0x5: if(!N) pc = imm19; break; // pl
+ case 0x6: if(V) pc = imm19; break; // vs
+ case 0x7: if(!V) pc = imm19; break; // vc
+ case 0x8: if(C && !Z) pc = imm19; break; // hi
+ case 0x9: if(!C || Z) pc = imm19; break; // ls
+ case 0xA: if(N == V) pc = imm19; break; // ge
+ case 0xB: if(N != V) pc = imm19; break; // lt
+ case 0xC: if(!Z && N == V) pc = imm19; break; // gt
+ case 0xD: if(Z || N != V) pc = imm19; break; // le
+ case 0xE: pc = imm19; break; // always executed(al)
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0x5c: {
+ // ldr
+ is_f = true;
+ // address = delta
+ int address = ir << 8 >> 13;
+ address *= 4;
+ if(address >= _buf.length-3) trap = (5 + 1);
+ frval = ld8f(pc + address);
+ break;
+ }
+
+ case 0x78: {
+ // STRH/LDRH(register)
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ // store
+ if(opcode1 == arm.OP_STORE_R_16) {
+ st1((int)regs[rn] + (int)regs[rm], (int)regs[rdid]);
+ }else {
+ trap = (2+1);
+ }
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if(opcode1 == arm.OP_LOAD_R_16) {
+ rval = ld1s((int)regs[rn] + (int)regs[rm]);
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0x79: {
+ // STRH/LDRH(immediate)
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+ imm *= 2;
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == arm.OP_STORE_IMM_16) {
+ st2((int)rval, (int)regs[rdid]);
+ } else trap = (2+1);
+ rdid = -1;
+
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == arm.OP_LOAD_IMM_16) {
+ rval = ld2s((int)rval);
+ } else trap = (2+1);
+ rdid = -1;
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0x8A: { // AND
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ rval = regs[rn] & regs[rm];
+ break;
+ }
+
+ case 0x8B: {
+ // add(shifted register)
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ long lhs = regs[rn];
+ long rhs = regs[rm];
+ rval = lhs + rhs;
+ break;
+ }
+
+ case 0x90, 0xB0, 0xD0, 0xF0: { // adrp
+ int immhi = ( ir << 8 ) >>> (8+5);
+ int immlo = (ir>>29)& 3;
+ int imm = ((immhi<<2) | immlo);
+ imm = (imm << 11) >> 11;
+ imm <<= 12;
+ rval = (pc & ~0xFFF) + imm;
+ break;
+ }
+
+ case 0x91: { // add(immediate)
+ int rn = (ir >> 5) & 0x1F;
+ int immediate = ir << 10 >> 20;
+ // only hit it twice
+ rval = regs[rn] + immediate;
+ break;
+ }
+ case 0x92: { // and(immediate)
+ int imm12 = (ir >> 10) & 0x1FFF;
+ long immediate = arm.decodeImm12(imm12);
+ int rn = (ir >> 5) & 0x1F;
+ rval = regs[rn] & immediate;
+ break;
+ }
+ case 0x93: {
+ // asr(immediate)
+ int imm = (ir >> 16) & 0x3F;
+ int rn = (ir >> 5) & 0x1F;
+ rval = regs[rn] >> (imm);
+ break;
+ }
+
+ case 0x94, 0x95, 0x96, 0x97: { // bl
+ rval = pc + 4;
+ regs[arm.X30] = rval;
+ int imm26 = (ir & 0x03FFFFFF);
+ int imm = (imm26 << 6) >> 6;
+ pc = pc + (imm << 2) ;
+ if( pc == Encoding.SENTINEL_CALLOC ) {
+ assert (_heap&7) == 0; // 8-byte aligned
+ long size = regs[arm.X0]*regs[arm.X1];
+ size = (size+7) & -8; // 8-byte aligned
+ regs[arm.X0] = _heap;
+ _heap += (int)size;
+ pc = (int)rval;
+ }
+ if( pc == Encoding.SENTINEL_WRITE ) {
+ ByteArrayOutputStream baos = switch((int)regs[0]) {
+ case 1 -> _stdout==null ? (_stdout = new ByteArrayOutputStream()) : _stdout;
+ case 2 -> _stderr==null ? (_stderr = new ByteArrayOutputStream()) : _stderr;
+ default -> throw new IllegalArgumentException();
+ };
+ baos.write(_buf,(int)regs[1],(int)regs[2]);
+ regs[0] = regs[2];
+ pc = (int)rval;
+ }
+ pc -= 4;
+ rdid = -1;
+ break;
+ }
+
+ case 0x9A: {
+ int encodedCond = (ir >> 12) & 0xF;
+ int decodedCond = encodedCond ^ 1;
+ // conditional select(csel)
+ boolean cond = switch (decodedCond) {
+ case 0x0 -> Z ; // eq
+ case 0x1 -> !Z ; // ne
+ case 0x2 -> C ; // cs
+ case 0x3 -> !C ; // cc
+ case 0x4 -> N ; // mi
+ case 0x5 -> !N ; // pl
+ case 0x6 -> V ; // vs
+ case 0x7 -> !V ; // vc
+ case 0x8 -> ( C && !Z ) ; // hi
+ case 0x9 -> (!C || Z ) ; // ls
+ case 0xA -> ( N == V) ; // ge
+ case 0xB -> ( N != V) ; // lt
+ case 0xC -> (!Z && N == V) ; // gt
+ case 0xD -> ( Z || N != V ); // le
+ case 0xE -> true ; // always
+ case 0xF -> false ; // never
+ default -> throw Utils.TODO();
+ };
+ if( cond ) rval = 1;
+ break;
+ }
+ case 0x9B: { // mul
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ rval = regs[rn] * regs[rm];
+ break;
+ }
+ case 0x9E: {
+ // scvtf
+ is_f = true;
+ int rs1 = (ir >> 5) & 0x1F;
+ frval = (double)regs[rs1];
+ break;
+ }
+
+ case 0xAA: {
+ // MOV(register)
+ int rm = (ir >> 16) & 0x1F;
+ rval = regs[rm];
+ break;
+ }
+
+ case 0xB8: {
+ // LDR/STR (register + index) 32 bits
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ if(opcode1 == arm.OP_STORE_R_32) {
+ st4((int)regs[rn] + (int)regs[rm], (int)regs[rdid]);
+ }else {
+ trap = (2+1);
+ }
+ // store(expected to store the length here)
+
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if( opcode1 == arm.OP_LOAD_R_32) {
+ rval = ld4s((int) (regs[rn] + regs[rm]));
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+
+ break;
+ }
+
+ case 0xB9: {
+ // str/ldr(immediate) 32 bits
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+ imm *= 4;
+
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+
+ opcode1 = (ir >> 22) & 0x3FF;
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == arm.OP_STORE_IMM_32) {
+ st4((int)rval, (int)regs[rdid]);
+ } else trap = (2+1);
+
+ rdid = -1;
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == arm.OP_LOAD_IMM_32) {
+ rval = ld4s((int) rval);
+ } else trap = (2+1);
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0xBC: {
+ // LDR/STR (register + index) float 32 bits
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ is_f = true;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ if(opcode1 == arm.OPF_STORE_R_32) {
+ st4((int)regs[rn] + (int)regs[rm], (int)regs[rdid]);
+ }else {
+ trap = (2+1);
+ }
+ // store(expected to store the length here)
+
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if(opcode1 == arm.OPF_LOAD_R_32) {
+ frval = ld4f((int) (regs[rn] + regs[rm]));
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+
+ break;
+ }
+
+ case 0xBD: {
+ // LDR/STR(immediate) 32 bits floats
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+
+ imm *= 4;
+ is_f = true;
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == arm.OPF_STORE_IMM_32) {
+ st4((int)rval, (int)regs[rdid]);
+ } else trap = (2+1);
+
+ rdid = -1;
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == arm.OPF_LOAD_IMM_32) {
+ frval = ld4s((int)rval);
+ } else trap = (2+1);
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0xD2: {
+ // movz
+ // get immediate
+ rval = (ir >> 5) & 0x7FFF;
+ break;
+ }
+
+ case 0xEB: {
+ // subs(shifted register)
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ long lhs = regs[rn];
+ long rhs = regs[rm];
+ rval = lhs - rhs;
+
+ // set flags as a side effect
+ Z = (rval == 0);
+ N = (rval < 0);
+ C = lhs >= rhs;
+
+ boolean sign_lhs = (lhs >>> 63) != 0;
+ boolean sign_rhs = (rhs >>> 63) != 0;
+ boolean sign_res = (rval >>> 63) != 0;
+ V = (sign_lhs != sign_rhs) && (sign_res != sign_lhs);
+ rdid = -1;
+ break;
+ }
+
+ case 0xD3: {
+ // lsl(immediate)
+ int imm = (ir >> 16) & 0x3F;
+ int rn = (ir >> 5) & 0x1F;
+ rval = regs[rn] << (64 - imm);
+ break;
+ }
+ case 0xD6: {
+ if( (ir & 0xFFFF) != 0 ) throw Utils.TODO();
+ if( ((ir>>16) & 0xFF) != 0x5F ) throw Utils.TODO(); // 0x3F is register call
+ rdid = -1;
+ int rn = ir << 22 >> 27;
+ assert rn == 0;
+ pc = (int)regs[30];
+ // Return to zero breaks simulation
+ if( pc==0 ) break outer;
+ pc -= 4; // Undo post-increment
+ break;
+ }
+
+ case 0xF1: {
+ // subs(immediate)
+ int rn = (ir >> 5) & 0x1F;
+ int immediate = ir << 10 >> 20;
+ long lhs = regs[rn];
+
+ rval = lhs - immediate;
+
+ // set flags as a side effect
+ Z = (rval == 0);
+ N = (rval < 0);
+ C = lhs >= immediate;
+
+ boolean sign_lhs = (lhs >>> 63) != 0;
+ boolean sign_rhs = (immediate >>> 63) != 0;
+ boolean sign_res = (rval >>> 63) != 0;
+ V = (sign_lhs != sign_rhs) && (sign_res != sign_lhs);
+ rdid = -1;
+ break;
+ }
+ case 0xF8: {
+ // LDR/STR (register + index) 64 bits
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ if(opcode1 == arm.OP_STORE_R_64) {
+ st8((int)regs[rn] + (int)regs[rm], regs[rdid]);
+ }else {
+ trap = (2+1);
+ }
+ // store(expected to store the length here)
+
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if(opcode1 == arm.OP_LOAD_R_64) {
+ rval = ld8((int) (regs[rn] + regs[rm]));
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+
+ break;
+ }
+ case 0xF9: {
+ // str/ldr(immediate) 64 bits
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+
+ imm *= 8;
+
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == arm.OP_STORE_IMM_64) {
+ st8((int)rval, regs[rdid]);
+ } else trap = (2+1);
+
+ rdid = -1;
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == arm.OP_LOAD_IMM_64) {
+ rval = ld8((int)rval);
+ } else trap = (2+1);
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0xFC: {
+ // LDR/STR (register + index) float 64 bits
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ is_f = true;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ if(opcode1 == arm.OPF_STORE_R_64) {
+ st8((int)regs[rn] + (int)regs[rm], (int)regs[rdid]);
+ }else {
+ trap = (2+1);
+ }
+ // store(expected to store the length here)
+
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if(opcode1 == arm.OPF_LOAD_R_64) {
+ frval = ld8f((int) (regs[rn] + regs[rm]));
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+
+ break;
+ }
+ case 0xFD: {
+ // LDR/STR(immediate) 64 bits floats
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+
+ imm *= 4;
+ is_f = true;
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == arm.OPF_STORE_IMM_64) {
+ st8((int)rval, regs[rdid]);
+ } else trap = (2+1);
+
+ rdid = -1;
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == arm.OPF_LOAD_IMM_64) {
+ frval = ld8((int)rval);
+ } else trap = (2+1);
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ default: trap = (2+1);
+ }
+
+ if(trap != 0) {
+ _pc = pc;
+ break;
+ }
+ if(rdid != -1) {
+ if(is_f) fregs[rdid] = frval;
+ else regs[rdid] = rval;
+ }
+
+ pc += 4;
+ }
+ // Handle traps and interrupts.
+ if(trap != 0)
+ return trap;
+ _cycle = cycle;
+ _pc = pc;
+ return 0;
+ }
+
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java
new file mode 100644
index 00000000..8c0a8792
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java
@@ -0,0 +1,368 @@
+ package com.seaofnodes.simple;
+
+/** Simple RISC5 CPU Emulator
+ *
+ * Original works credit Charles Lohr.
+ * This is a complete rewrite in Java.
+ *
+ * Load code into memory and go!
+ */
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import com.seaofnodes.simple.util.Utils;
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+
+public class EvalRisc5 {
+
+ // Memory image, always 0-based
+ public final byte[] _buf;
+
+ // GPRs
+ final long[] regs;
+ // FRs
+ final double[] fregs;
+ // PC
+ int _pc;
+
+ // Start of free memory for allocation
+ int _heap;
+
+ // Standard out and err streams
+ ByteArrayOutputStream _stdout, _stderr;
+
+ // Cycle counters
+ int _cycle;
+
+ EvalRisc5( byte[] buf, int stackSize ) {
+ _buf = buf;
+ regs = new long[32];
+ fregs = new double[32];
+ // Stack grows down, heap grows up
+ regs[riscv.RSP] = _heap = stackSize;
+ _pc = 0;
+ }
+
+ // Little endian
+ public int ld1s(int x) { return _buf[x ] ; }
+ public int ld1z(int x) { return _buf[x ]&0xFF; }
+ public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; }
+ public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; }
+ public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; }
+ public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; }
+
+ public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); }
+ public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); }
+
+
+ public void st1(int x, int val) { _buf[x] = (byte)val; }
+ public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
+ public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); }
+ public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+
+ // Note: only a few bits are used. (Machine = 3, User = 0)
+ // Bits 0..1 = privilege.
+ // Bit 2 = WFI (Wait for interrupt)
+ // Bit 3+ = Load/Store reservation LSBs.
+ int _extraflags;
+
+ public int step( int maxops ) {
+ int trap = 0;
+ long rval = 0;
+ double frval = 0;
+ int pc = _pc;
+ int cycle = _cycle;
+ boolean is_f = false;
+ outer:
+ for( int icount = 0; icount < maxops; icount++ ) {
+ int ir = 0;
+ rval = 0;
+ cycle++;
+
+ if( pc >= _buf.length ) {
+ trap = 1 + 1; // Handle access violation on instruction read.
+ break;
+ }
+ if( (pc & 3)!=0 ) {
+ trap = 1; // Handle PC-misaligned access
+ break;
+ }
+ // Load instruction from image buffer at PC
+ ir = ld4s( pc );
+ int rdid = (ir >> 7) & 0x1f;
+
+ switch( ir & 0x7f ) {
+ case 0x37: // LUI (0b0110111)
+ rval = ( ir & 0xfffff000 );
+ break;
+ case 0x17: // AUIPC (0b0010111)
+ rval = pc + ( ir & 0xfffff000 );
+ break;
+ case 0x6F: { // JAL (0b1101111)
+ int reladdy = ((ir & 0x80000000)>>11) | ((ir & 0x7fe00000)>>20) | ((ir & 0x00100000)>>9) | ((ir&0x000ff000));
+ if( (reladdy & 0x00100000)!=0 ) reladdy |= 0xffe00000; // Sign extension.
+ rval = pc + 4;
+ pc = pc + reladdy - 4;
+ if( pc+4 == Encoding.SENTINEL_WRITE ) {
+ ByteArrayOutputStream baos = switch((int)regs[10]) {
+ case 1 -> _stdout==null ? (_stdout = new ByteArrayOutputStream()) : _stdout;
+ case 2 -> _stderr==null ? (_stderr = new ByteArrayOutputStream()) : _stderr;
+ default -> throw new IllegalArgumentException();
+ };
+ baos.write(_buf,(int)regs[11],(int)regs[12]);
+ regs[10] = regs[12];
+ rdid = 0;
+ pc = (int)(rval - 4); // Unwind PC, as-if returned from write
+ }
+ break;
+ }
+ case 0x67: { // JALR (0b1100111)
+ int imm_se = ir >> 20; // Sign extend 12 bits
+ rval = pc + 4;
+ pc = ((int)regs[ (ir >> 15) & 0x1f ] + imm_se) & ~1;
+ // Return from top-level ; exit sim
+ if( pc==0 ) {
+ if( rdid!=0 ) regs[rdid] = rval; // Write PC into register before exit
+ break outer;
+ }
+ // Inline CALLOC effect
+ if( pc == Encoding.SENTINEL_CALLOC ) {
+ assert (_heap&7) == 0; // 8-byte aligned
+ int size = (int)(regs[10]*regs[11]);
+ size = (size+7) & -8; // 8-byte aligned
+ regs[10] = _heap; // Return next free address
+ _heap += size;
+ pc = (int)(rval - 4); // Unwind PC, as-if returned from calloc
+ } else
+ pc -= 4;
+ break;
+ }
+ case 0x63: { // Branch (0b1100011)
+ int immm4 = ((ir & 0xf00)>>7) | ((ir & 0x7e000000)>>20) | ((ir & 0x80) << 4) | ((ir >> 31)<<12);
+ if( (immm4 & 0x1000)!=0 ) immm4 |= 0xffffe000;
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ immm4 = pc + immm4 - 4;
+ rdid = 0;
+ if( switch( (ir >> 12) & 0x7 ) {
+ case 0 -> rs1 == rs2;
+ case 1 -> rs1 != rs2;
+ case 4 -> rs1 < rs2;
+ case 5 -> rs1 >= rs2;
+ case 6 -> Long.compareUnsigned(rs1,rs2) < 0; //BLTU
+ case 7 -> Long.compareUnsigned(rs1,rs2) >= 0; //BGEU
+ default -> { trap = (2+1); yield false; }
+ } ) pc = immm4;
+ break;
+ }
+ case 0x03: { // Load (0b0000011)
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ int imm = ir >> 20;
+ int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 );
+ int rsval = rs1 + imm_se;
+
+ if( rsval >= _buf.length-3 ) {
+ trap = (5+1);
+ rval = rsval;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) {
+ //LB, LH, LW, LBU, LHU
+ case 0: rval = ld1s( rsval ); break;
+ case 1: rval = ld2s( rsval ); break;
+ case 2: rval = ld4s( rsval ); break;
+ case 3: rval = ld8 ( rsval ); break;
+ case 4: rval = ld1z( rsval ); break;
+ case 5: rval = ld2z( rsval ); break;
+ case 6: rval = ld4s( rsval ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+ // floats
+ case 0x7: {
+ is_f = true;
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ int imm = ir >> 20;
+ int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 );
+ int rsval = rs1 + imm_se;
+
+ if( rsval >= _buf.length-3 ) {
+ trap = (5+1);
+ rval = rsval;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) {
+ case 2: frval = ld4f( rsval ); break;
+ case 3: frval = ld8f( rsval ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+
+ case 0x23: { // Store 0b0100011
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ int addy = ( ( ir >> 7 ) & 0x1f ) | ( ( ir & 0xfe000000 ) >> 20 );
+ if( (addy & 0x800)!=0 ) addy |= 0xfffff000;
+ addy += rs1;
+ rdid = 0;
+ if( addy >= _buf.length-3 ) {
+ trap = (7+1); // Store access fault.
+ rval = addy;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) { //SB, SH, SW
+ case 0: st1( addy, (int)rs2 ); break;
+ case 1: st2( addy, (int)rs2 ); break;
+ case 2: st4( addy, (int)rs2 ); break;
+ case 3: st8( addy, rs2 ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+ case 0x13: // Op-immediate 0b0010011
+ case 0x33: { // Op 0b0110011
+ int imm = ir >> 20;
+ imm = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0);
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ boolean is_reg = (ir & 0x20)!=0;
+ long rs2 = is_reg ? regs[imm & 0x1f] : imm;
+
+ // Insert the detection here
+ int rs1id = (ir >> 15) & 0x1f;
+ int funct3 = (ir >> 12) & 0x7;
+
+ if( is_reg && ( ir & 0x02000000 )!=0 ) {
+ switch( (ir>>12)&7 ) { //0x02000000 = RV32M
+ case 0: rval = rs1 * rs2; break; // MUL
+ case 1: rval = (int)((long)((int)rs1) * (long)((int)rs2)) >> 32; break; // MULH
+ case 2: rval = (int)((long)((int)rs1) * (long)rs2) >> 32; break; // MULHSU
+ case 3: rval = (int)((long)rs1 * (long)rs2) >> 32; break; // MULHU
+ case 4: if( rs2 == 0 ) rval = -1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? rs1 : ((int)rs1 / (int)rs2); break; // DIV
+ case 5: if( rs2 == 0 ) rval = 0xffffffff; else rval = rs1 / rs2; break; // DIVU
+ case 6: if( rs2 == 0 ) rval = rs1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? 0 : ((int)((int)rs1 % (int)rs2)); break; // REM
+ case 7: if( rs2 == 0 ) rval = rs1; else rval = rs1 % rs2; break; // REMU
+ }
+ } else {
+ rval = switch( (ir >> 12) & 7 ) { // These could be either op-immediate or op commands. Be careful.
+ case 0 -> (is_reg && (ir & 0x40000000) != 0) ? (rs1 - rs2) : (rs1 + rs2);
+ case 1 -> rs1 << (rs2 & 0x3F);
+ case 2 -> (rs1 < rs2) ? 1 : 0;
+ case 3 -> (rs1 < rs2) ? 1 : 0;
+ case 4 -> rs1 ^ rs2;
+ case 5 -> (ir & 0x40000000) != 0 ? (rs1 >>> (rs2 & 0x3F)) : (rs1 >> (rs2 & 0x3F));
+ case 6 -> rs1 | rs2;
+ case 7 -> rs1 & rs2;
+ default -> rval;
+ };
+ }
+ break;
+ }
+ case 0x0f: // 0b0001111
+ rdid = 0; // fencetype = (ir >> 12) & 0b111; We ignore fences in this impl.
+ break;
+ case 0x73: // Zifencei+Zicsr (0b1110011)
+ trap = (2+1); // Note micrrop 0b100 == undefined.
+ break;
+
+ case 0x2f: { // RV32A (0b00101111)
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ int irmid = ( ir>>27 ) & 0x1f;
+
+ // We don't implement load/store from UART or CLNT with RV32A here.
+ if( rs1 >= _buf.length-3 ) {
+ trap = (7+1); //Store/AMO access fault
+ rval = rs1;
+ } else {
+ rval = ld4s( (int)rs1 );
+
+ // Referenced a little bit of https://github.com/franzflasch/riscv_em/blob/master/src/core/core.c
+ boolean dowrite = true;
+ switch( irmid ) {
+ case 2: //LR.W (0b00010)
+ dowrite = false;
+ _extraflags = (_extraflags & 0x07) | (int)(rs1<<3);
+ break;
+ case 3: { //SC.W (0b00011) (Make sure we have a slot, and, it's valid)
+ boolean xflag = _extraflags >> 3 != ( rs1 & 0x1fffffff );
+ rval = xflag ? 1 : 0; // Validate that our reservation slot is OK.
+ dowrite = !xflag; // Only write if slot is valid.
+ break;
+ }
+ case 0: rs2 += rval; break; // AMOADD.W (0b00000)
+ case 1: break; // AMOSWAP.W (0b00001)
+ case 4: rs2 ^= rval; break; // AMOXOR.W (0b00100)
+ case 8: rs2 |= rval; break; // AMOOR.W (0b01000)
+ case 12: rs2 &= rval; break; // AMOAND.W (0b01100)
+ case 16: rs2 = Math.min( rs2, rval ); break; //AMOMIN.W (0b10000)
+ case 20: rs2 = Math.max( rs2, rval ); break; //AMOMAX.W (0b10100)
+ case 24: rs2 = Math.min( rs2, rval ); break; //AMOMINU.W (0b11000)
+ case 28: rs2 = Math.max( rs2, rval ); break; //AMOMAXU.W (0b11100)
+ default: trap = (2+1); dowrite = false; break; //Not supported.
+ }
+ if( dowrite ) st4( (int)rs1, (int)rs2 );
+ }
+ break;
+ }
+
+ case 0x53: {
+ is_f = true;
+ int rs1 = (ir >> 15) & 0x1F;
+ int rs2 = (ir >> 20) & 0x1F;
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+ int func5 = (ir >> 27) & 0x1F;
+ int func2 = (ir >> 25) & 0x3;
+ if( func2 != 1 ) throw Utils.TODO(); // 1==double
+ switch( func5 ) {
+ case 0: frval = lhs + rhs; break; // fadd.d
+ case 1: frval = lhs - rhs; break; // fsub.d
+ case 2: frval = lhs * rhs; break; // fmul.d
+ case 3: frval = lhs / rhs; break; // fdiv.d
+ case 5: frval = Math.min(lhs,rhs); break; // fmin.d, also used for move
+ case 20:
+ is_f = false; // Output into a GPR
+ rval = switch( (ir >> 12) & 7 ) {
+ case 0 -> lhs <= rhs ? 1 : 0;
+ case 1 -> lhs == rhs ? 1 : 0;
+ case 2 -> lhs < rhs ? 1 : 0;
+ default -> throw Utils.TODO();
+ };
+ break;
+ case 26: frval = (double)regs[rs1]; break; // fcvt.w.d
+ default:
+ trap = (2+1); // Fault: Invalid opcode.
+ throw Utils.TODO();
+ }
+ break;
+ }
+
+ default: trap = (2+1); // Fault: Invalid opcode.
+ }
+
+ // If there was a trap, do NOT allow register writeback.
+ if( trap!=0 ) {
+ _pc = pc;
+ break;
+ }
+
+ if( rdid!=0 || is_f) {
+ if(is_f) {fregs[rdid] = frval; is_f = false;} // Write back register.
+ else regs[rdid] = rval; // Write back register.
+ }
+
+ pc += 4;
+ }
+
+ // Handle traps and interrupts.
+ if( trap!=0 )
+ return trap;
+
+ _cycle = cycle;
+ _pc = pc;
+ return 0;
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
new file mode 100644
index 00000000..d67b2065
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
@@ -0,0 +1,43 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.fuzzer.Fuzzer;
+import java.util.Random;
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * To use the fuzzer run the <code>fuzzPeeps</code> methods.
+ * This can be done from IntelliJ Gui or remove the `@Ignore` annotation and start the test case from the command line
+ * via <code>mvn clean test -Dtest=com.seaofnodes.simple.Fuzzer08Test#fuzzPeeps</code>
+ */
+public class FuzzerWrap {
+
+ @Ignore
+ @Test
+ public void fuzzPeeps() {
+ var fuzzer = new Fuzzer();
+ for (int i=0; i<1000000; i++)
+ fuzzer.fuzzPeeps(i);
+ assertTrue(fuzzer.noExceptions());
+ }
+
+
+ @Test
+ public void fuzzPeepsSmall() {
+ Random R = new Random(System.currentTimeMillis());
+ var fuzzer = new Fuzzer();
+ for (int i=0; i<100; i++)
+ fuzzer.fuzzPeeps( R.nextLong());
+ assertTrue(fuzzer.noExceptions());
+ }
+
+ @Test
+ @Ignore
+ public void fuzzPeepTiming() {
+ var fuzzer = new Fuzzer();
+ int max_nid=0;
+ for (int i=0; i<1000000; i++)
+ max_nid = fuzzer.fuzzPeepTiming(i, max_nid);
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Main.java b/chapter25/src/test/java/com/seaofnodes/simple/Main.java
new file mode 100644
index 00000000..589c043b
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Main.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class Main {
+ // Compile and run a simple program
+ public static void main( String[] args ) throws Exception {
+ // First arg is file, 2nd+ args are program args
+ String src = Files.readString(Path.of(args[0]));
+ int arg = Integer.valueOf(args[1]);
+ CodeGen code = new CodeGen(src);
+ code.parse().opto().typeCheck().GCM().localSched();
+ System.out.println(code._stop);
+ System.out.println(Eval2.eval(code,arg,100000));
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/chapter25/src/test/java/com/seaofnodes/simple/MergeSortTest.java
new file mode 100644
index 00000000..ca33438a
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/MergeSortTest.java
@@ -0,0 +1,81 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import com.seaofnodes.simple.util.SB;
+import java.io.IOException;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class MergeSortTest {
+
+ @Test public void testMergeSort0() throws IOException {
+ // Test merge sort on a small array of primes
+ String src =
+"""
+val merge_sort = { int[] a ->
+ int[] !b = new int[a#];
+ _copy_array (a, 0, a#, b);
+ _split_merge(a, 0, a#, b);
+};
+
+val _split_merge = { int[] b, int begin, int end, int[] a ->
+ if (end - begin <= 1)
+ return 0;
+ int middle = (end + begin) / 2;
+ _split_merge(a, begin, middle, b);
+ _split_merge(a, middle, end, b);
+ _merge(b, begin, middle, end, a);
+ return 0;
+};
+
+val _merge = { int[] b, int begin, int middle, int end, int[] a ->
+ int i = begin, j = middle;
+
+ for (int k = begin; k < end; k++) {
+ // && and ||
+ bool cond = false;
+ if (i < middle) {
+ if (j >= end) cond = true;
+ else if (a[i] <= a[j]) cond = true;
+ }
+ if (cond) b[k] = a[i++];
+ else b[k] = a[j++];
+ }
+};
+
+val _copy_array = { int[] a, int begin, int end, int[] b ->
+ for (int k = begin; k < end; k++)
+ b[k] = a[k];
+};
+""";
+ int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, };
+ SB sb = new SB().p(primes.length).p("[");
+ for( int prime : primes )
+ sb.p(prime).p(", ");
+ String sprimes = sb.p("]").toString();
+ TestC.runC(src, "sort", sprimes, 34);
+
+
+ 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.st4(ps,primes.length);
+ for( int i=0; i<primes.length; i++ )
+ R5.st8(ps+(primes.length-1-i+1)*8,primes[i]);
+ // Pass array base when calling `merge_sort`
+ R5.regs[riscv.A0] = ps; // Array base to be sorted
+
+ int trap = R5.step(100000);
+ assertEquals(0,trap);
+
+ assertEquals(primes.length,R5.ld4s(ps));
+ for( int i=0; i<primes.length; i++ )
+ assertEquals(primes[i], R5.ld8(ps+(i+1)*8));
+
+ // TODO: replace with ARM Eval
+ Chapter21Test.testCPU(src,"arm", "SystemV",36,null);
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Simple.java b/chapter25/src/test/java/com/seaofnodes/simple/Simple.java
new file mode 100644
index 00000000..870792c0
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Simple.java
@@ -0,0 +1,251 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.CodeGen.Phase;
+import com.seaofnodes.simple.print.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+
+public class Simple {
+ static void usage() {
+ System.out.println(
+"""
+simple [options] input-file.smp ...program args...
+Options are applied in-order.
+Options:
+ --help - This text
+
+ --root root/ - Project root directory; this must end in a '/' and input
+ files are relative paths to this. If missing, the one input
+ file path is treated as its own root directory.
+
+ -o output - Project output directory; similar to --root, if 'output' ends in a
+ '/', then all result files use relative paths to 'output', using a input file
+ root-relative path. If --root is missing, the one result file is named 'output'
+
+ -L path - add an external Simple object search path
+ --eval - (slowly) evaluate the compiled code in emulator
+ --run - run the compiled code natively; this is the default
+ --cpu <cpu-name> - use specific CPU (x86_64_v2, riscv, arm)
+ --abi <abi-name> - use specific ABI variant (SystemV)
+ --target - print native CPU and ABI
+ --version
+ -S - dump generated assembler code
+ --dump-size - print the size of generated code
+ --dump-time - print compilation and execution times
+ --dump-after-parse - dump intermediate representation after parse
+ --dump-after-iter - dump intermediate representation after pessimistic iter pass
+ --dump-after-opto - dump intermediate representation after opto pass
+ --dump-after-type-check - dump intermediate representation after type check pass
+ --dump-after-select - dump intermediate representation after instruction selection pass
+ --dump-after-gcm - dump intermediate representation after GCM pass
+ --dump-after-local-sched - dump intermediate representation after local scheduling pass
+ --dump-after-reg-alloc - dump intermediate representation after register allocation pass
+ --dump-after-encode - dump intermediate representation after encoding pass
+ --dump-after-all - dump intermediate representation after all passes
+ --dump - dump final intermediate representation only
+ --dot - dump grapical representation of intermediate code into *.dot file(s)
+"""
+);
+ System.exit(0);
+ }
+
+ static void bad_usage() { throw bad("Invalid usage (use --help)"); }
+ static RuntimeException bad(String err) {
+ System.err.println("ERROR: "+err);
+ System.exit(1);
+ return new RuntimeException(err);
+ }
+
+ static void target() {
+ System.out.print(TestC.CPU_PORT);
+ System.out.print("-");
+ System.out.println(TestC.CALL_CONVENTION);
+ }
+
+ static void print_compilation_times(CodeGen code) {
+ long[] times = code._times;
+ for( int i=0; i<code._times.length; i++ )
+ System.out.printf("%20s %.3f sec%n", Phase.values()[i], times[i]);
+ }
+
+ static String getInput() {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ return reader.lines().collect(Collectors.joining("\n"));
+ }
+
+ public static void main(String[] args) throws Exception {
+ String root = null;
+ String input_filename = null;
+ boolean do_eval = false;
+ boolean do_run = true;
+ boolean do_codegen = false;
+ boolean dump_dot = false;
+ boolean print_time = false;
+ boolean print_size = false;
+ boolean print_asm = false;
+ int dump = 0;
+ int first_arg = 0;
+ String src = null;
+ String cpu = null;
+ String abi = null;
+ String out = null;
+ ArrayList<String> libPaths = new ArrayList<>();
+
+ // Parse command line
+ int i; for( i = 0; i < args.length; i++ ) {
+ String arg = args[i];
+ if( arg.charAt(0) != '-' ) {
+ input_filename = arg;
+ break;
+ }
+
+ switch (arg) {
+ case "-": input_filename=""; break;
+ case "--dump-after-parse": dump |= 1<<Phase.Parse .ordinal() ; break;
+ case "--dump-after-iter": dump |= 1<<Phase.Iter .ordinal() ; break;
+ case "--dump-after-opto": dump |= 1<<Phase.Opto .ordinal() ; break;
+ case "--dump-after-type-check": dump |= 1<<Phase.TypeCheck .ordinal() ; break;
+ case "--dump-after-loop-tree": dump |= 1<<Phase.LoopTree .ordinal() ; break;
+ case "--dump-after-select": dump |= 1<<Phase.Select .ordinal() ; break;
+ case "--dump-after-gcm": dump |= 1<<Phase.Schedule .ordinal() ; break;
+ case "--dump-after-local-sched": dump |= 1<<Phase.LocalSched.ordinal() ; break;
+ case "--dump-after-reg-alloc": dump |= 1<<Phase.RegAlloc .ordinal() ; break;
+ case "--dump-after-encode": dump |= 1<<Phase.Encoding .ordinal() ; break;
+ case "--dump-after-export": dump |= 1<<Phase.Export .ordinal() ; break;
+ case "--dump": dump |= 1<<Phase.LastPhase .ordinal() ; break;
+ case "--dump-after-all": dump |= (2<<Phase.LastPhase .ordinal())-1; break;
+ case "--dot": dump_dot = true; break;
+ case "-S": print_asm = true; break;
+ case "--eval": do_eval = true ; do_run = false; break;
+ case "--run": do_run = true ; break;
+ case "--norun": do_run = false; break;
+ case "--dump-size": print_size = true; break;
+ case "--dump-time": print_time = true; break;
+ case "-o": if (out != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
+ out = args[++i]; do_codegen=true;
+ break;
+ case "-L": if (i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
+ libPaths.add(args[++i]);
+ break;
+ case "--cpu": if (cpu != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
+ cpu = args[++i];
+ break;
+ case "--abi": if (abi != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
+ abi = args[++i];
+ break;
+ case "--target": target(); break;
+ case "--version": throw Utils.TODO();
+ case "--help": usage();
+ default: bad_usage();
+ }
+ }
+
+ // Break the paths into these parts:
+ // - A module path, given from "--root" or inferred from the one input file
+ // - Some sources [modpath/relpath/file.smp]*, all relative to the module path.
+ // - A build path, given from "-o" or inferred from the one input file
+ //
+ // All the sources are compiled, with outputs in [buildpath/relpath/file.o]*
+
+ if (input_filename == null) throw bad("no input file' (use --help)");
+ if( !input_filename.isEmpty() && !input_filename.endsWith(".smp") )
+ throw bad("File extension must be .smp");
+ Path inPath = Path.of(input_filename);
+
+ // Figure out the module path
+ Path modPath;
+ if( root == null ) {
+ modPath = inPath.getParent();
+ if( modPath == null )
+ modPath = Path.of(".");
+ } else {
+ throw Utils.TODO();
+ }
+
+ // Compute the module-relative paths for all sources
+ Path srcRelPath = modPath.relativize(inPath);
+ int n = srcRelPath.getNameCount();
+ Path relPath = n==1 ? null : srcRelPath.subpath(0,n-1);
+ // Compute source/class name
+ String srcNameExt = srcRelPath.getName(n-1).toString();
+ String srcName = srcNameExt.substring(0,srcNameExt.length()-4);
+
+ // Compute the build path.
+ Path outPath;
+ if( out == null ) {
+ // If no output given, the build dir is the modPath
+ outPath = modPath;
+ } else {
+ // If the output is a directory, that is our out path
+ outPath = Path.of(out);
+ if( !out.endsWith("/") ) {
+ // If the output is a file, the directory is our outPath
+ Path fullOut = outPath;
+ outPath = outPath.getParent();
+ if( outPath == null )
+ outPath = Path.of(".");
+ Path foo = outPath.relativize(fullOut);
+ if( !foo.toString().equals(srcName+".o") )
+ throw bad("Output path needs to be of the form "+srcName+".o");
+ }
+ }
+ String outBase = outPath.resolve(srcName).toString();
+
+ if( do_run || print_asm || print_size ) {
+ if (cpu == null) cpu = TestC.CPU_PORT;
+ if (abi == null) abi = TestC.CALL_CONVENTION;
+ do_codegen = true;
+ }
+ if( cpu == null ) cpu = TestC.CPU_PORT;
+ if( abi == null ) abi = TestC.CALL_CONVENTION;
+
+ String simpleHome = System.getenv("SIMPLE_HOME");
+ if( simpleHome != null && !simpleHome.isEmpty() )
+ libPaths.add(Path.of(simpleHome,cpu+"_"+abi).toString());
+
+ // Read input file
+ try {
+ src = input_filename.isEmpty() ? getInput() : Files.readString(Path.of(input_filename));
+ } catch( IOException e ) { throw bad("Cannot read input file: "+input_filename); }
+
+ // Compilation pipeline
+ 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, TypeInteger.BOT );
+ code.driver(Phase.LastPhase,cpu,abi, false, do_run || do_eval, dump);
+
+ if( do_codegen && print_asm )
+ System.out.println(code.asm());
+
+ if( print_size )
+ System.out.printf( "Code Size: %d%n", code._encoding._bits.size());
+
+ if( print_time )
+ print_compilation_times(code);
+
+ if( do_eval ) {
+ // TODO: Support for evaluation of functions with different argument numbers and types
+ long t = System.currentTimeMillis();
+ long arg = (i+1 < args.length) ? Integer.parseInt(args[i+1]) : 0;
+ System.out.println(Eval2.eval(code, arg, 100000));
+ if( print_time )
+ System.out.printf( "EXECUTION TIME: %.3f sec%n", (System.currentTimeMillis() - t) / 1e3);
+ }
+ if( do_run ) {
+ if ( !TestC.CPU_PORT.equals( cpu ) || !TestC.CALL_CONVENTION.equals( abi ) )
+ throw bad("cannot run code on not native target");
+ String exe = TestC.OS.startsWith("Windows") ? outBase+".exe" : outBase;
+ String result = TestC.gcc(outBase+".o", null, null, true, exe);
+ System.out.print(result);
+ }
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java b/chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java
new file mode 100644
index 00000000..7394b578
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.cpus.arm.arm;
+import com.seaofnodes.simple.util.BAOS;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+// Runs 32-bit ARM code in an emulator
+
+public class TestArm64 {
+
+ public static EvalArm64 build( String main, String src, int arg, int spills, boolean print ) throws IOException {
+ // Compile and export Simple
+ CodeGen code = new CodeGen(src).driver("arm", "SystemV",true,false);
+
+ // Allocation quality not degraded
+ int delta = spills>>3;
+ if( delta == 0 ) delta =1;
+ if( spills != -1 ) assertEquals("Expect spills:", spills, code._regAlloc._spillScaled, delta);
+
+ // Image
+ byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
+ EvalArm64 ARM = new EvalArm64(image, 1<<16);
+ int start = 0; // Code at offset 0
+ int cpool = fill(code._encoding._bits ,image,start);
+ int sdata = fill(code._encoding._cpool,image,cpool);
+ int end = fill(code._encoding._sdata,image,sdata);
+
+ // Initial incoming int arg
+ ARM.regs[arm.X0] = arg;
+ // malloc memory starts at 64K and goes up
+
+ // Look up starting PC or zero if not given
+ if( main!=null )
+ for( FunNode fun : code._linker )
+ if( fun != null && !fun.isDead() && fun._compunit == code.compunit() && main.equals(fun._name) )
+ ARM._pc = code._encoding.opStart(fun);
+
+ return ARM;
+ }
+
+ // Fill bits into image; return end point rounded up.
+ private static int fill( BAOS bits, byte[] image, int off) {
+ System.arraycopy(bits.buf(), 0, image, off, bits.size());
+ return (off+bits.size() + 15) & -16;
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
new file mode 100644
index 00000000..5c49d3a8
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
@@ -0,0 +1,209 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.concurrent.TimeUnit;
+import static org.junit.Assert.*;
+
+// Call a C driver program, which calls a Simple-generated .o, and self-checks.
+// Expects GCC from the default CLI, compiles C, compiles Simple, links and runs.
+public abstract class TestC {
+
+ public static final String OS = System.getProperty("os.name");
+ public static final String CPU = System.getProperty("os.arch");
+
+ public static final String CALL_CONVENTION = OS.startsWith("Windows") ? "win64" : "SystemV";
+ public static final String CPU_PORT = switch( CPU ) {
+ case "amd64" -> "x86_64_v2";
+ default -> throw Utils.TODO("Map Yer CPU Port Here");
+ };
+ public static final String CPU_ABI = CPU_PORT+"_"+CALL_CONVENTION;
+ public static final String RELEASE_SYS_DIR = "build/release/"+CPU_ABI;
+
+ public static final String C_DRIVERS_DIR = "src/test/java/com/seaofnodes/simple/progs/";
+
+ /**
+ * Compile, link and run
+ * - WITH a C driver
+ * - using the default OS/CPU calling convention.
+ * - no lib sys
+ */
+ public static void runC( String src, String base, String expected, int spills) throws IOException {
+ String cfile = C_DRIVERS_DIR+base+".c";
+ run(src, base, null, CALL_CONVENTION, "", cfile, expected, spills);
+ }
+
+ /**
+ * Compile, link and run
+ * - withOUT a C driver
+ * - using the default OS/CPU calling convention.
+ * - no lib sys
+ */
+ public static void runSF( String src, String base, String expected, int spills ) throws IOException {
+ run(src, base, null, CALL_CONVENTION, null,null, expected,spills);
+ }
+
+ /**
+ * Compile, link and run
+ * - withOUT a C driver
+ * - using the default OS/CPU calling convention.
+ * - WITH a test lib sys
+ */
+ public static void runSYS( String src, String base, String expected, int spills ) throws IOException {
+ Ary<String> externPaths = new Ary<>(new String[]{RELEASE_SYS_DIR});
+ run(src, base, externPaths, CALL_CONVENTION, null,null, expected,spills);
+ }
+
+
+ /**
+ * Compile, link and run - general case
+ *
+ * @param src Program source to compile
+ * @param base Compiled binary final base name
+ * @param externPaths External symbol search path
+ * @param simple_conv Argument convention when calling Simple code
+ * @param c_conv Argument convention when calling C code, or null if not
+ * linking against a C driver program
+ * @param cfile Associated C program which will drive the Simple program,
+ * or null if not linking against a C driver program
+ * @param expected Expected stdout string
+ * @param spills Expected limit on spill code, used to validate the
+ * register allocation is reasonable
+ */
+ public static void run( String src, String base, Ary<String> externPaths, String simple_conv, String c_conv, String cfile, String expected, int spills ) throws IOException {
+ run(src, base, externPaths, simple_conv, c_conv, cfile, null, expected, spills);
+ }
+
+ public static void run( String src, String base, Ary<String> externPaths, String simple_conv, String c_conv, String cfile, String stdin, String expected, int spills ) throws IOException {
+ // Simple file base-name example:
+ // foo.smp ->
+ // build/objs/foo.o - object file
+ // build/objs/foo.exe - linked executable Windows
+ // build/objs/foo - linked executable Linux
+ String pathBase = "build/objs/"+base;
+ String obj = pathBase+".o";
+ String exe = pathBase+(OS.startsWith("Windows") ? ".exe" : "");
+ // Compile simple, emit ELF
+ CodeGen code = new CodeGen(null,"build/objs",externPaths,base,src,126L,TypeInteger.BOT);
+ code.driver( CPU_PORT, simple_conv, false, cfile==null );
+
+ String result = gcc(obj, c_conv, cfile, stdin, linkObjs(externPaths), exe );
+ assertEquals(expected,result);
+
+ // Allocation quality not degraded
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ if( spills != -1 )
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ }
+
+ // Link with gcc, and execute the resulting binary, returning stdout as a
+ // String. Any errors *assert* instead of returning some error code; this
+ // utility is meant to execute inside a JUnit test which will catch the
+ // assert and flag the test as failed.
+ public static String gcc( String main, double ignore, String... objs ) throws IOException {
+ String BLDDIR = "build/objs/test0/";
+ String exe = BLDDIR+main+(OS.startsWith("Windows") ? ".exe" : "");
+ return gcc(objs[0],null,null,false,exe);
+ }
+
+ // Link with gcc, and execute the resulting binary, returning stdout as a
+ // String. Any errors *assert* instead of returning some error code; this
+ // utility is meant to execute inside a JUnit test which will catch the
+ // assert and flag the test as failed.
+ public static String gcc( String obj, String c_conv, String cfile, boolean stdin, String... args ) throws IOException {
+ return gcc(obj,c_conv,cfile,stdin ? "" : null,null,args);
+ }
+
+ public static String gcc( String obj, String c_conv, String cfile, String stdin, String... args ) throws IOException {
+ return gcc(obj,c_conv,cfile,stdin,null,args);
+ }
+
+ public static String gcc( String obj, String c_conv, String cfile, String stdin, Ary<String> linkObjs, String... args ) throws IOException {
+
+ // Compile the C program. Compiling code and constants in the low
+ // 2Gig. Pointers are 64b BUT since always in the low 2G all the
+ // high bits are zero - and Simple code can be emitted treating
+ // pointers as 4bytes.
+ var params = new Ary<>(String.class);
+ params.add("gcc");
+ if( cfile!=null ) params.add(cfile); // Associated C driver, usually has a `main`
+ params.add(obj);
+ if( linkObjs != null )
+ params.addAll(linkObjs.asAry());
+ params.addAll(new String[] {
+ "-lm", // Picks up 'sqrt' for newtonFloat tests to compare
+ "-g",
+ "-o",
+ args[0],
+ });
+ // Calling convention for C calls, if any
+ if( cfile!=null ) {
+ params.add("-D");
+ params.add("CALL_CONV="+c_conv);
+ }
+
+ // Run GCC to link (optionally compile C driver code)
+ Process gcc = new ProcessBuilder(params.asAry()).redirectErrorStream(true).start();
+ int exit;
+ try {
+ boolean normal = gcc.waitFor(2, TimeUnit.SECONDS);
+ exit = normal ? gcc.exitValue() : -1; // no exit???
+ } catch( InterruptedException e ) {
+ throw new IOException("interrupted");
+ }
+ String result = new String(gcc.getInputStream().readAllBytes());
+ if( exit!=0 ) {
+ System.err.println("gcc error code: "+exit);
+ System.err.println(result);
+ }
+ assertEquals( 0, exit );
+ //assertTrue(result.isEmpty()); // No data in error stream
+
+ // Execute results
+ ProcessBuilder smp = new ProcessBuilder(args);
+ File stdinFile = null;
+ File stdoutFile = File.createTempFile("simple-stdout",".txt");
+ smp.redirectOutput(stdoutFile);
+ if( stdin!=null && stdin.isEmpty() ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT);
+ else if( stdin!=null ) {
+ stdinFile = File.createTempFile("simple-stdin",".txt");
+ Files.writeString(stdinFile.toPath(),stdin);
+ smp.redirectInput(stdinFile);
+ }
+ Process p = smp.start();
+ if( stdin==null )
+ p.getOutputStream().close();
+ try { exit = (byte)p.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); }
+ result = Files.readString(stdoutFile.toPath());
+ if( stdinFile != null )
+ stdinFile.delete();
+ stdoutFile.delete();
+ if( exit!=0 )
+ return "exec exit code: "+exit;
+ return result;
+ }
+
+ private static Ary<String> linkObjs(Ary<String> externPaths) {
+ if( externPaths == null )
+ return null;
+ Ary<String> objs = new Ary<>(String.class);
+ for( String path : externPaths ) {
+ File file = new File(path);
+ if( file.isFile() && file.getName().endsWith(".o") )
+ objs.add(file.toString());
+ else if( file.isDirectory() ) {
+ File sys = new File(file,"sys.o");
+ if( sys.isFile() )
+ objs.add(sys.toString());
+ }
+ }
+ return objs.isEmpty() ? null : objs;
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java b/chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java
new file mode 100644
index 00000000..aff95c38
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java
@@ -0,0 +1,52 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import com.seaofnodes.simple.util.BAOS;
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+// Runs 32-bit R5 code in an emulator
+public abstract class TestRisc5 {
+
+ public static EvalRisc5 build( String src, String main, int arg, int spills, boolean print ) throws IOException {
+ // Compile and export Simple
+ CodeGen code = new CodeGen(src).driver("riscv", "SystemV",true,false);
+
+ // Allocation quality not degraded
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ if( spills != -1 )
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+
+ // Image
+ byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
+ EvalRisc5 R5 = new EvalRisc5(image, 1<<16);
+ int start = 0; // Code at offset 0
+ int cpool = fill(code._encoding._bits ,image,start);
+ int sdata = fill(code._encoding._cpool,image,cpool);
+ int end = fill(code._encoding._sdata,image,sdata);
+
+ // Initial incoming int arg
+ R5.regs[riscv.A0] = arg;
+ // malloc memory starts at 64K and goes up
+
+ // Look up starting PC or zero if not given
+ if( main!=null )
+ for( FunNode fun : code._linker )
+ if( fun != null && !fun.isDead() && fun._compunit == code.compunit() && main.equals(fun._name) )
+ R5._pc = code._encoding.opStart(fun);
+
+ return R5;
+ }
+
+ // Fill bits into image; return end point rounded up.
+ private static int fill( BAOS bits, byte[] image, int off) {
+ System.arraycopy(bits.buf(), 0, image, off, bits.size());
+ return (off+bits.size() + 15) & -16;
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter25/src/test/java/com/seaofnodes/simple/TypeTest.java
new file mode 100644
index 00000000..e881a2bf
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -0,0 +1,214 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.type.*;
+import org.junit.Assert;
+import org.junit.Test;
+import static org.junit.Assert.assertSame;
+
+public class TypeTest {
+
+ // Test basic properties and GLB
+ @Test
+ public void testTypeAdHoc() {
+ Assert.assertEquals( TypeScalar.BOT, TypeInteger.TRUE.meet(TypeNil.NIL) );
+ Assert.assertEquals( TypeScalar.BOT, TypeInteger.TOP .meet(TypeNil.NIL) );
+
+ TypeStruct s1 = TypeStruct.make("s1",false,
+ Field.make("a", TypeInteger.BOT,-1, false),
+ Field.make("b", TypeInteger.BOT,-2, false) );
+ TypeStruct s2 = TypeStruct.make("s2",false,
+ Field.make("a", TypeInteger.BOT,-3, false),
+ Field.make("b", TypeInteger.BOT,-4, false) );
+ TypeStruct x1ro = (TypeStruct)s1.makeRO();
+ TypeMemPtr p1 = TypeMemPtr.make(s1);
+ // GLB no longer forces RO
+ //Assert.assertEquals(x1ro, ((TypeMemPtr)p1.glb(false))._obj);
+ Assert.assertNotEquals(s1, s1.dual());
+ TypeStruct s1dglb = ((TypeMemPtr)p1.dual().glb(false))._obj;
+ Assert.assertTrue(x1ro.isa(s1dglb));
+
+ TypeMem m1 = TypeMem.make(2,TypeNil.NIL);
+ TypeMem m2 = TypeMem.make(3,TypeInteger.U16);
+ TypeMem m3 = TypeMem.make(4,TypeFloat.F64);
+ TypeMem m4 = TypeMem.make(5,TypeInteger.BOT);
+
+ Assert.assertNotEquals(m1, m2);
+ Assert.assertNotEquals(m2, m3);
+ Assert.assertNotEquals(m3, m4);
+
+ Assert.assertEquals(TypeStruct.BOT, s1.meet(s2));
+ TypeMem scalarMemBot = TypeMem.make(1,TypeScalar.BOT,false,false,false,XInt.FULL,XInt.FULL);
+ Assert.assertEquals(scalarMemBot, ((TypeMem)m1.meet(m2)).makeFrom( XInt.FULL, XInt.FULL));
+ Assert.assertEquals(TypeMem.make(1,TypeScalar.BOT), m1.meet(m3));
+ Assert.assertEquals(scalarMemBot, ((TypeMem)m3.meet(m4)).makeFrom( XInt.FULL, XInt.FULL));
+
+ Assert.assertEquals(TypeMem.make(2,Type.BOTTOM), m1.glb(false));
+ Assert.assertEquals(TypeMem.make(2,Type.XNIL,true,true,true, XInt.FULL, XInt.FULL), m1.dual());
+ Assert.assertEquals(m4.dual(), m4.glb(false).dual());
+
+ TypeMemPtr ptr1 = TypeMemPtr.make(s1);
+ Assert.assertEquals(s1, ptr1._obj);
+ TypeMemPtr ptr2 = TypeMemPtr.make(s2);
+ Assert.assertEquals(s2, ptr2._obj);
+
+ TypeMemPtr ptr1nil = TypeMemPtr.makeNullable(s1);
+ Assert.assertEquals(s1, ptr1nil._obj);
+ Assert.assertTrue( ptr1nil.nullable() );
+ Assert.assertNotNull( ptr1nil._obj );
+ TypeMemPtr ptr2nil = TypeMemPtr.makeNullable( s2);
+ Assert.assertEquals(s2, ptr2nil._obj);
+
+ Assert.assertNotEquals(ptr1, ptr2);
+ Type p1glb = ptr1.glb(false);
+ // GLB no longer forces RO
+ //Assert.assertNotEquals(ptr1, p1glb );
+ Type p1nro = ptr1nil.makeRO();
+ // GLB no longer forces RO
+ //Assert.assertEquals(p1nro, p1glb);
+
+ Assert.assertEquals(ptr1, ptr1.dual().dual());
+ Assert.assertTrue(p1glb.makeRO().isa( ptr1.dual().glb(false)));
+ Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT), ptr1.meet(ptr2nil));
+ // GLB no longer forces RO
+ //Assert.assertEquals(p1glb, ptr1.meet(TypeNil.NIL).makeRO());
+
+ TypeMemPtr TOP = TypeMemPtr.TOP;
+ TypeMemPtr BOT = TypeMemPtr.makeNullable(TypeStruct.BOT);
+ TypeMemPtr PTR = TypeMemPtr.make(TypeStruct.BOT);
+ Type NULL = TypeNil.NIL;
+ Type PTR_meet_NULL = NULL.meet(PTR);
+ Assert.assertEquals(BOT, PTR_meet_NULL);
+ Type ptr1_meet_ptr2 = ptr1.meet(ptr2);
+ Assert.assertEquals(PTR, ptr1_meet_ptr2);
+ Type NULL_join_ptr1 = NULL.join(ptr1);
+ Assert.assertEquals(TypePtr.XNPTR, NULL_join_ptr1);
+ Type NULL_join_PTR = PTR.join(NULL);
+ Assert.assertEquals(TypePtr.XNPTR, NULL_join_PTR);
+ Type ptr1_dual = ptr1.dual();
+ Type nullableptr1_dual = ptr1nil.dual();
+
+ // Cyclic check
+ TypeStruct S1 = ((TypeMemPtr)TypeStruct.SFLT1.field("s1")._t)._obj.close();
+ Assert.assertFalse(s1.isFinal());
+ Type s1ro = S1.makeRO();
+ Type s1ro2 = S1.makeRO();
+ Assert.assertSame(s1ro,s1ro2);
+ Assert.assertTrue(s1ro.isFinal());
+
+ Assert.assertFalse(S1.isConstant());
+ Type s1glb = S1.field("s2")._t.glb(false);
+ Type s1glb2 = S1.field("s2")._t.glb(false);
+ Assert.assertSame(s1glb,s1glb2);
+ }
+
+ // Test theoretical properties.
+ // This is a symmetric complete bounded (ranked) lattice.
+ // Also, the meet is commutative and associative.
+ // The lattice has a dual (symmetric), and join is ~(~x meet ~y).
+ // See https://en.wikipedia.org/wiki/Lattice_(order).
+ @Test
+ public void testLatticeTheory() {
+ Type[] ts = Type.gather();
+
+ // Confirm commutative & complete
+ for( Type t0 : ts )
+ for( Type t1 : ts ) {
+ check_commute (t0,t1);
+ check_symmetric(t0,t1);
+ }
+
+ // Confirm associative
+ for( Type t0 : ts )
+ for( Type t1 : ts )
+ for( Type t2 : ts )
+ assoc(t0,t1,t2);
+
+ // Confirm symmetry. If A isa B, then A.join(C) isa B.join(C)
+ for( Type t0 : ts )
+ for( Type t1 : ts )
+ if( t0.isa(t1) )
+ for( Type t2 : ts ) {
+ Type t02 = t0.join(t2);
+ Type t12 = t1.join(t2);
+ Type mt = t02.meet(t12);
+ assertSame(t0+" isa "+t1+" joined "+t2, mt,t12);
+ }
+ }
+
+ // By design in meet, args are already flipped to order _type, which forces
+ // symmetry for things with badly ordered _type fields. The question is
+ // still interesting for other orders.
+ private static void check_commute( Type t0, Type t1 ) {
+ if( t0==t1 ) return;
+ if( t0.is_simple() && !t1.is_simple() ) return; // By design, flipped the only allowed order
+ Type mta = t0.meet(t1);
+ Type mtb = t1.meet(t0); // Reverse args and try again
+ assertSame(mta,mtb);
+ }
+
+ // A & B = MT
+ // Expect: ~A & ~MT == ~A
+ // Expect: ~B & ~MT == ~B
+ private static void check_symmetric( Type t0, Type t1 ) {
+ if( t1==t0 ) return;
+ Type mt = t0.meet(t1);
+ Type dm = mt.dual();
+ Type d0 = t0.dual();
+ Type d1 = t1.dual();
+ Type ta = dm.meet(d1);
+ Type tb = dm.meet(d0);
+ assertSame(t0+" & "+t1+" mt="+mt, ta,d1);
+ assertSame(t0+" & "+t1+" mt="+mt, tb,d0);
+ }
+
+ private static void assoc( Type t0, Type t1, Type t2 ) {
+ Type t01 = t0 .meet(t1 );
+ Type t12 = t1 .meet(t2 );
+ Type t01_2 = t01.meet(t2 );
+ Type t0_12 = t0 .meet(t12);
+ assertSame(t0+" & "+t1+" & "+t2, t01_2,t0_12);
+ }
+
+ // Test cyclic types and meets
+ @Test
+ public void testCyclic0() {
+ Type d0 = TypeStruct.SFLT1.dual();
+ Type d1 = d0.dual();
+ assertSame(TypeStruct.SFLT1,d1);
+ }
+
+ @Test
+ public void testGLB() {
+ Type[] ts = Type.gather();
+ for( Type t0 : ts )
+ if( !(t0 instanceof Field || t0 instanceof TypeStruct || t0 instanceof TypeTuple || t0 instanceof TypeConAry ) )
+ Assert.assertTrue(t0.isa(t0.glb(false)));
+ }
+
+ @Test
+ public void testList() {
+ TypeStruct list = TypeStruct.open("List");
+ TypeMemPtr plist = TypeMemPtr.makeNullable(list);
+ list = list.add(Field.make("next", plist, 2, false) );
+ list = list.add(Field.make("x", TypeInteger.BOT, 3, false));
+ // Make a cyclic type
+ list = list.close();
+ // Fields are mutable
+ Assert.assertFalse(list.isFinal());
+ TypeStruct flist = (TypeStruct)list.makeRO();
+ Assert.assertTrue(flist.isFinal());
+ Assert.assertNotSame( list, flist );
+ }
+
+ @Test
+ public void testOpen() {
+ TypeStruct open = TypeStruct.make("List",false);
+ TypeMemPtr plist = TypeMemPtr.makeNullable(open);
+ TypeStruct list = open.add(Field.make("next", plist, 2, false) );
+ list = list.add(Field.make("x", TypeInteger.BOT, 3, false));
+ // Make a cyclic type
+ list = list.close();
+ Type mt = open.meet(list);
+ Assert.assertEquals(list,mt);
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
new file mode 100644
index 00000000..c1ffa6c1
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
@@ -0,0 +1,174 @@
+package com.seaofnodes.simple.fuzzer;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Eval2;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.Node;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.Random;
+import java.util.function.Consumer;
+
+/**
+ * Implementation of the fuzzer.
+ * Random testcases are generated using the ScriptGenerator.
+ * This will generate a script similar to how the parser parses it but instead of parsing it will
+ * randomly choose a variation the parser would parse and generate it.
+ * These scripts are the parsed by the parser and all exceptions are caught and filtered
+ * to only show one occurrence of one problem. This includes script which generate different
+ * results when the compiled graph with and without peeps is executed.
+ * To aid debugging scripts that cause errors are then reduced by applying rules and checking
+ * that the same issue persists.
+ */
+public class Fuzzer {
+
+ private static final int EVAL_TIMEOUT = 10000;
+
+ /**
+ * List of exceptions already encountered.
+ * This is used to filter new exceptions and discard them if they are already found.
+ */
+ private final ArrayList<Throwable> exceptions = new ArrayList<>();
+
+ /**
+ * Filter and record an exception in a script.
+ * @param e The exception caused by the script
+ * @param script The script causing the exception
+ * @param reproducer Reproducer which should be used to reproduce the test case.
+ */
+ private void recordException(Throwable e, String script, Consumer<String> reproducer, long seed) {
+ for (var ex : exceptions) {
+ if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return;
+ }
+ exceptions.add(e);
+ System.out.println("========== Stack ==========");
+ e.printStackTrace(System.out);
+ System.out.println("========== Seed ==========");
+ System.out.println(seed);
+ //System.out.println("========== Code ===========");
+ //System.out.println(script);
+ System.out.println("========= Reduced =========");
+ System.out.println(Reducer.reduce(script, e, reproducer));
+ System.out.println("===========================");
+ System.out.flush();
+ }
+
+ private static boolean neq(Object a, Object b) {
+ if (Objects.equals(a, b)) return false;
+ return true;
+ }
+
+ /**
+ * Check that two graphs result in the same output when supplied with a value
+ * @param e1 Evaluator for the first graph
+ * @param e2 Evaluator for the second graph
+ * @param in The input value to both graphs to test for an equal output
+ */
+ private static void checkGraphs(CodeGen code1, CodeGen code2, long in) {
+ var r1 = Eval2.eval(code1, in, EVAL_TIMEOUT);
+ var r2 = Eval2.eval(code2, in, EVAL_TIMEOUT);
+ if( r1 == null || r2 == null ) return; // Timeout test invalid
+ if( neq(r1, r2) )
+ throw new RuntimeException("Different calculations values " + r1 + " vs " + r2);
+ }
+
+ /**
+ * Run checks for script. Compile the script with peeps enabled and disabled.
+ * Check that exceptions raised in the parser by both methods are the same and only happens if the script may be invalid.
+ * If the script was successfully parsed check that both version behave the same.
+ * @param script The script to test
+ * @param valid If the script is definitely valid. If not some exceptions may be suppressed.
+ */
+ private static void runCheck(String script, boolean valid) {
+ CodeGen code1;
+ try {
+ code1 = FuzzerUtils.parse(script, 123);
+ } catch (RuntimeException e1) {
+ try {
+ FuzzerUtils.parse(script, 456);
+ } catch (RuntimeException e2) {
+ if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) {
+ if (!valid || e1.getClass() == Parser.ParseException.class) return;
+ } else {
+ e1.addSuppressed(e2);
+ }
+ }
+ throw e1;
+ }
+ CodeGen code2 = FuzzerUtils.parse(script, 456);
+ checkGraphs(code1, code2, 0);
+ checkGraphs(code1, code2, 1);
+ checkGraphs(code1, code2, 10);
+ }
+
+ /**
+ * Check that the script does not result in exceptions.
+ * @param script The script to test.
+ * @param valid If the script is valid or if it might contain syntax errors.
+ */
+ private void check(String script, boolean valid, long seed) {
+ try {
+ runCheck(script, valid);
+ } catch (Throwable e) {
+ recordException(e, script, s->runCheck(s, valid), seed);
+ }
+ }
+
+ /**
+ * Run one test with the given seed, and check for peephole counts.
+ * @param seed The seed to use for generating this test case
+ */
+ public int fuzzPeepTiming(long seed, int max_nids) {
+ var rand = new Random(seed);
+ var sb = new StringBuilder();
+ new ScriptGenerator(rand, sb, true).genProgram();
+ try {
+ var code = new CodeGen(sb.toString());
+
+ code.parse();
+ int nids = code.UID();
+ if( nids <= max_nids ) return max_nids; // Smaller, looking for trends as we grow
+ int parse_peeps= code._iter_cnt;
+ int parse_nops = code._iter_nop_cnt;
+ double parse_nop_ratio = (double)parse_nops/parse_peeps;
+ double parse_peeps_per_node = (double)parse_peeps/nids;
+
+ code.opto();
+ int iter_peeps= code._iter_cnt;
+ int iter_nops = code._iter_nop_cnt;
+ double iter_nop_ratio = (double)iter_nops/iter_peeps;
+ double iter_peeps_per_node = (double)iter_peeps/nids;
+
+ System.out.printf("%6d | Parsing: peeps: %5d, nops: %5d, ratio: %5.3f, last UID: %5d ps/node: %5.3f | Iter: peeps: %6d, nops: %6d, ratio: %5.3f, ps/node: %5.3f\n",
+ seed,
+ parse_peeps,parse_nops,parse_nop_ratio,nids,parse_peeps_per_node,
+ iter_peeps , iter_nops, iter_nop_ratio, iter_peeps_per_node);
+ return nids;
+ } catch (Throwable e) {
+ // ignore exceptions, let the normal fullPeeps catch and reduce,
+ // we're interested in valid program optimizations
+ }
+ return max_nids;
+ }
+
+
+ /**
+ * Run one test with the given seed.
+ * @param seed The seed to use for generating this test case
+ */
+ public void fuzzPeeps(long seed) {
+ var rand = new Random(seed);
+ var sb = new StringBuilder();
+ var valid = new ScriptGenerator(rand, sb, true).genProgram();
+ check(sb.toString(), valid, seed);
+ }
+
+
+ /**
+ * Check that no exceptions happened.
+ * @return true if no exception happened
+ */
+ public boolean noExceptions() {
+ return exceptions.isEmpty();
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java
new file mode 100644
index 00000000..9175e380
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java
@@ -0,0 +1,111 @@
+package com.seaofnodes.simple.fuzzer;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StopNode;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.util.BitSet;
+
+/**
+ * Some utilities for the fuzzer.
+ */
+class FuzzerUtils {
+
+ /**
+ * Get the val of a private field
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
+ return (T) (obj instanceof Class<?> clz ? getField(clz, name).get(null) : getField(obj.getClass(), name).get(obj));
+ }
+
+ /**
+ * Get access to a private field
+ */
+ public static Field getField(Class<?> clz, String name) throws NoSuchFieldException {
+ var field = clz.getDeclaredField(name);
+ try {
+ field.setAccessible(true);
+ } catch (Throwable e) {
+ // Intentionally left empty
+ }
+ return field;
+ }
+
+ /**
+ * Try to rethrow an exception or create a runtime exception and throw it.
+ */
+ public static RuntimeException rethrow(Throwable e) {
+ if (e instanceof RuntimeException re) return re;
+ if (e instanceof Error er) throw er;
+ return new RuntimeException(e);
+ }
+
+ /**
+ * Some problems might trigger debug output. Suppress this output with a null print stream during fuzzing.
+ */
+ public static final PrintStream NULL_PRINT_STREAM = new PrintStream(OutputStream.nullOutputStream());
+
+ /**
+ * Check that the messages are similar.
+ */
+ private static boolean similarMessages(String a, String b) {
+ if (a == null || b == null) return true;
+ a = a.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized");
+ b = b.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized");
+ return a.equals(b);
+ }
+
+ /**
+ * Check if both stack traces have the same first entry. Empty stack traces can happen with NPE optimization
+ * and allow the other to be any stack trace.
+ */
+ private static boolean sameStackTraceOrigin(StackTraceElement[] a, StackTraceElement[] b) {
+ if (a == null || a.length == 0 || b == null || b.length == 0) return true; // One or both stack traces are missing.
+ return a[0].equals(b[0]);
+ }
+
+ /**
+ * Checks if two exceptions are caused by the same problem.
+ */
+ public static boolean isExceptionFromSameCause(Throwable a, Throwable b) {
+ if (a == null || b == null) return a == b; // Handle the null case
+ if (a.getClass() != b.getClass()) return false; // Different classes
+ if (!similarMessages(a.getMessage(), b.getMessage())) return false; // Different messages. Null is allowed as wildcard
+ var s1 = a.getStackTrace();
+ var s2 = b.getStackTrace();
+ if (!sameStackTraceOrigin(s1, s2)) return false; // Different stack trace origins
+ if (!isExceptionFromSameCause(a.getCause(), b.getCause())) return false; // Different causes
+ var o1 = a.getSuppressed();
+ var o2 = b.getSuppressed();
+ if (o1 == null || o2 == null || o1.length != o2.length) return o1 == o2; // Different suppression
+ for (int i=0; i<o1.length; i++) {
+ if (!isExceptionFromSameCause(o1[i], o2[i])) return false; // Different suppression
+ }
+ return true;
+ }
+
+ /**
+ * Parse script with peepholes enabled or disabled
+ */
+ public static CodeGen parse(String script, long workListSeed) {
+ var err = System.err;
+ var out = System.out;
+ try {
+ System.setErr(NULL_PRINT_STREAM);
+ System.setOut(NULL_PRINT_STREAM);
+ var code = new CodeGen(script, workListSeed);
+ return code.driver(CodeGen.Phase.LocalSched);
+ } finally {
+ System.setErr(err);
+ System.setOut(out);
+ }
+ }
+
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java
new file mode 100644
index 00000000..f6e12e11
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java
@@ -0,0 +1,177 @@
+package com.seaofnodes.simple.fuzzer;
+
+import java.util.Arrays;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is used to reduce test cases created by the fuzzer.
+ * It applies rewrite rules to the test case and checks that the
+ * test still produces the same exception.
+ * This is done in a fixpoint iteration until every rewrite produces
+ * no or a different exception.
+ */
+public class Reducer {
+
+ private interface Replacer extends BiConsumer<StringBuilder, Matcher> {}
+
+ /**
+ * Holder for the rewrite rules
+ */
+ private record Replace(Pattern pattern, boolean iterate, Replacer... replacements) {
+ Replace(Pattern p, Object... replacements) {
+ this(p, replacements[0] == Boolean.TRUE, Arrays.stream(replacements).filter(o->!(o instanceof Boolean)).map(Replace::from).toArray(Replacer[]::new));
+ }
+ private static Replacer from(Object o) {
+ if (o instanceof Replacer r) return r;
+ if (o instanceof Integer i) return (s, m)->s.append(m.group(i));
+ if (o instanceof String r) return (s, m)->s.append(r);
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * List of rewrite rules.
+ */
+ private static final Replace[] REPLACEMENTS = {
+ new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "0", "1"),
+ new Replace(Pattern.compile("(?<=\\n|^)[^\\n]+(?=\\n|$)"), true, ""),
+ new Replace(Pattern.compile("(?<=[+\\-*/<>=(])--"), ""),
+ new Replace(Pattern.compile("\\b--"), "+"),
+ new Replace(Pattern.compile("[-!]"), ""),
+ new Replace(Pattern.compile("\\b\\+-"), "-"),
+ new Replace(Pattern.compile("(?<![+\\-*/<>=&|])(?:-|\\*|/|>=|<=|>>>|>>|>|<<|<|!=|==|&&|\\|\\||&|\\||^)(?![+*/<>=&|])"), "+"),
+ new Replace(Pattern.compile("\\?([^;]+):"), "+"),
+ new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)(?:\\n[ \\t]*)?}"), "", "{}", 1),
+ new Replace(Pattern.compile("(?<=\\n|^)([ \\t]*)\\{\\n(.*?)\\n\\1}", Pattern.DOTALL), 2),
+ new Replace(Pattern.compile("\\(([^()]+)\\)"), 1),
+ new Replace(Pattern.compile("\\d+ ?\\+ ?"), ""),
+ new Replace(Pattern.compile(" ?\\+ ?\\d+"), ""),
+ new Replace(Pattern.compile("\\n\\n+"), "\n"),
+ new Replace(Pattern.compile("^\\n+|\\n+$"), ""),
+ new Replace(Pattern.compile("else *"), ""),
+ new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""),
+ new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""),
+ new Replace(Pattern.compile("\\bstruct \\w+ \\{[^{}]*}"), "")
+ };
+
+ /**
+ * Predicate to test if the reduced script generates the same error.
+ */
+ private final Predicate<String> tester;
+
+ /**
+ * Create a reducer with a test function.
+ * @param tester Test function which returns true when the same issue is found in the script.
+ */
+ private Reducer(Predicate<String> tester) {
+ this.tester = tester;
+ }
+
+ /**
+ * Rewrites the script with one rewrite rule.
+ */
+ private String doReplacement(String script, Pattern pattern, Replacer[] replacements) {
+ var matcher = pattern.matcher(script);
+ var sb = new StringBuilder();
+ var last = 0;
+ var skip = 0;
+ outer:
+ while (matcher.find(last + skip)) {
+ sb.append(script, last, matcher.start());
+ last = matcher.end();
+ skip = last == matcher.start() ? 1 : 0;
+ var pos = sb.length();
+ for (var replacement : replacements) {
+ replacement.accept(sb, matcher);
+ var tail = sb.length();
+ sb.append(script, last, script.length());
+ if (tester.test(sb.toString())) {
+ sb.setLength(tail);
+ continue outer;
+ }
+ sb.setLength(pos);
+ }
+ last = matcher.start();
+ skip = 1;
+ }
+ sb.append(script, last, script.length());
+ return sb.toString();
+ }
+
+ /**
+ * Clean up variable names from the random ones generated by the fuzzer.
+ */
+ private String cleanVariables(String script) {
+ var matcher = Pattern.compile("\\b([0-9a-zA-Z_]+) !?([0-9a-zA-Z_]+)").matcher(script);
+ int[] num = {0, 0};
+ while (matcher.find()) {
+ var type = matcher.group(1);
+ var var = matcher.group(2);
+ if (type.equals("new")) continue;
+ String prefix = "v";
+ int t = 0;
+ if (type.equals("struct")) {
+ t = 1;
+ prefix = "s";
+ }
+ if (var.matches(prefix+"\\d+")) continue;
+ while (script.contains(prefix+num[t])) num[t]++;
+ var n = script.replaceAll("\\b"+var+"\\b", prefix + num[t]);
+ if (tester.test(n)) {
+ script = n;
+ }
+ }
+ return script;
+ }
+
+ /**
+ * Run all rewrite rules once.
+ */
+ private String doAllReplacements(String script) {
+ for (var replace: REPLACEMENTS) {
+ String old;
+ do {
+ old = script;
+ script = doReplacement(script, replace.pattern, replace.replacements);
+ } while (replace.iterate && !old.equals(script));
+ }
+ return script;
+ }
+
+ /**
+ * Reduce the script to a minimal version which produces the same problem with a test function.
+ * @param tester Test function which returns true when the same issue is found in the script.
+ */
+ public static String reduce(String script, Predicate<String> tester) {
+ var reducer = new Reducer(tester);
+ String old;
+ do {
+ old = script;
+ script = reducer.doAllReplacements(script);
+ script = reducer.cleanVariables(script);
+ } while (!script.equals(old));
+ return script;
+ }
+
+ /**
+ * Reduce the script to a minimal version which produces the same problem with a reproducer.
+ * @param ex The exception originally caused by the script
+ * @param reproducer Test function which throws an exception which will be checked against ex.
+ */
+ public static String reduce(String script, Throwable ex, Consumer<String> reproducer) {
+ return reduce(script, s->{
+ Throwable nex = null;
+ try {
+ reproducer.accept(s);
+ } catch (Throwable e) {
+ nex = e;
+ }
+ return FuzzerUtils.isExceptionFromSameCause(nex, ex);
+ });
+ }
+
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
new file mode 100644
index 00000000..dde5a1dc
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
@@ -0,0 +1,938 @@
+package com.seaofnodes.simple.fuzzer;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.ScopeNode;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Predicate;
+import static com.seaofnodes.simple.Parser.KEYWORDS;
+
+
+/**
+ * Generate a pseudo random script.
+ * This generator will generate the same script for two random number generators initialized with the same seed.
+ * To generate valid code it implements the parser but instead of parsing it emits the statements and expressions.
+ * It is guaranteed to terminate.
+ */
+public class ScriptGenerator {
+
+ private static final int MAX_STATEMENTS_PER_BLOCK = 5;
+ private static final int MAX_NAME_LENGTH = 10;
+ private static final int MAX_BLOCK_DEPTH = 4;
+ private static final int MAX_EXPRESSION_DEPTH = 10;
+
+ /**
+ * Number of spaces per indentation
+ */
+ private static final int INDENTATION = 4;
+ /**
+ * Valid characters for identifiers. The last 10 are not valid for the first character.
+ */
+ private static final String VAR_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
+
+ /**
+ * Flag for a statement that it does not pass on control flow to the next statement.
+ */
+ private static final int FLAG_STOP = 0x01;
+ /**
+ * Flag for a statement that it contains a final if statement without an else branch.
+ */
+ private static final int FLAG_IF_WITHOUT_ELSE = 0x02;
+
+ private static class Type {
+ final String name;
+ Type(String name) {this.name=name;}
+ boolean isa(Type other) { return this == other; }
+ }
+
+ private static class TypeStruct extends Type {
+ record Field (String name, Type type, TypeStruct struct) {}
+ Field[] fields;
+ final TypeNullable nullable = new TypeNullable(this);
+ TypeStruct(String name) {
+ super(name);
+ }
+ boolean isa(Type other) { return this == other || other == nullable; }
+ }
+
+ private static class TypeNullable extends Type {
+
+ final Type base;
+
+ TypeNullable(Type base) {
+ super(base.name+"?");
+ this.base = base;
+ }
+
+ }
+
+ private static final Type TYPE_FLT = new Type("flt");
+
+ private static class TypeInt extends Type {
+
+ TypeInt(String name) {
+ super(name);
+ }
+
+ @Override
+ boolean isa(Type other) {
+ return super.isa(other) || other == TYPE_FLT || other instanceof TypeInt;
+ }
+ }
+
+ private static final List<TypeInt> INTTYPES = new ArrayList<>();
+
+ static {
+ for( var e : Parser.TYPES.entrySet() ) {
+ if( e.getValue() instanceof TypeInteger ) {
+ INTTYPES.add(new TypeInt(e.getKey()));
+ }
+ }
+ }
+
+ private static final Type TYPE_INT = INTTYPES.stream().filter(t->t.name.equals("int")).findAny().get();
+ private static final Type TYPE_BOOL = INTTYPES.stream().filter(t->t.name.equals("bool")).findAny().get();
+
+ private static class Variable {
+ final String name;
+ final Type declared;
+ final Variable shadowing;
+ Type type;
+ boolean shadowed = false;
+
+ Variable(String name, Type type, Variable var) {
+ this.name = name;
+ this.declared = type;
+ this.type = type;
+ this.shadowing = var;
+ }
+ }
+
+ /**
+ * The random number generator used for random decisions.
+ */
+ private final Random random;
+ /**
+ * Indentation depth.
+ */
+ private int indentation = 0;
+ /**
+ * Output string builder
+ */
+ private final StringBuilder sb;
+ /**
+ * Number of nested loops
+ */
+ private int loopDepth = 0;
+ /**
+ * Current scope start in the variables list.
+ */
+ private int currScopeStart = 0;
+ /**
+ * The depth of blocks allowed.
+ */
+ private int depth = MAX_BLOCK_DEPTH;
+ /**
+ * Current variables in scope.
+ */
+ private final ArrayList<Variable> variables = new ArrayList<>();
+ /**
+ * All defined structs.
+ */
+ private final ArrayList<TypeStruct> structs = new ArrayList<>();
+ /**
+ * All forward declared structs
+ */
+ private final ArrayList<TypeStruct> forwardStructs = new ArrayList<>();
+ /**
+ * Allow to declare structs.
+ */
+ private boolean allowStructs = true;
+ /**
+ * If this needs to generate valid scripts or is also allowed to generate invalid ones.
+ */
+ private boolean generateValid;
+ /**
+ * If this script might be invalid.
+ */
+ private boolean maybeInvalid = false;
+
+
+ /**
+ *
+ * @param random the random number generator.
+ * @param sb the script will be written to this string builder.
+ * @param generateValid if the script should be guaranteed valid or if it is allowed to contain bugs.
+ */
+ public ScriptGenerator(Random random, StringBuilder sb, boolean generateValid) {
+ this.random = random;
+ this.sb = sb;
+ this.generateValid = generateValid;
+ }
+
+ /**
+ * Random number with log distribution
+ * @param max The maximum value
+ * @return A random number in the range [0, max) with higher numbers being more uncommon.
+ */
+ private int randLog(int max) {
+ max--;
+ int n = 1<<max;
+ var num = random.nextInt(n);
+ for (int i=max-1; i>=0; i--) {
+ if ((num & (1<<i))!=0) return max-i-1;
+ }
+ return max;
+ }
+
+ /**
+ * Helper function to print indentation.
+ * @return sb for chaining
+ */
+ private StringBuilder printIndentation() {
+ return sb.repeat(' ', indentation);
+ }
+
+ /**
+ * Generate a random name for variables. This might be invalid when it is a keyword.
+ * @return The random name generated
+ */
+ private StringBuilder getRandomName() {
+ int len = random.nextInt(MAX_NAME_LENGTH) + 1;
+ StringBuilder sb = new StringBuilder(len);
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()-10)));
+ for (int i=1; i<len; i++)
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length())));
+ return sb;
+ }
+
+ /**
+ * Should invalid code be generated?
+ * @return True if invalid code should be generated
+ */
+ private boolean generateInvalid() {
+ if (generateValid || random.nextInt(1000)>2) return false;
+ maybeInvalid = true;
+ return true;
+ }
+
+ /**
+ * Lookup a variable by name
+ * @param name name of the variable
+ * @return The index of the variable in variables or -1
+ */
+ private int lookup(String name) {
+ for (int i=variables.size()-1; i>=0; i--) {
+ if (variables.get(i).name.equals(name)) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Lookup a variable by name
+ * @param name name of the variable
+ * @return THe variable of null if not found
+ */
+ private Variable lookupVar(String name) {
+ int idx = lookup(name);
+ return idx == -1 ? null : variables.get(idx);
+ }
+
+ /**
+ * Add a variable to the current scope
+ * @param name The name of the variable
+ * @param type The declared type of the variable
+ */
+ private void addVariable(String name, Type type) {
+ var old = lookupVar(name);
+ variables.add(new Variable(name, type, old));
+ if (old != null) old.shadowed = true;
+ }
+
+ /**
+ * Pop variables to level to
+ * @param to Level until which variables should be popped
+ */
+ private void popVariables(int to) {
+ while (variables.size() > to) {
+ var v = variables.removeLast();
+ if (v.shadowing != null) v.shadowing.shadowed = false;
+ }
+ }
+
+ /**
+ * Get a random visible variable patching pred
+ * @param pred The predicate the variable needs to match
+ * @return A random visible variable matching pred or null if no variable mached pred.
+ */
+ private Variable findVisibleVariablesMatching(Predicate<Variable> pred) {
+ int num=0;
+ for (var v:variables) {
+ if (!v.shadowed && pred.test(v)) num++;
+ }
+ if (num == 0) return null;
+ num = random.nextInt(num);
+ for (var v:variables) {
+ if (!v.shadowed && pred.test(v) && num-- == 0) return v;
+ }
+ throw new AssertionError();
+ }
+
+ /**
+ * Get a structure by name
+ * @param name Name of the structure
+ * @return The structure of null
+ */
+ private TypeStruct getStruct(String name) {
+ for (var s:structs) {
+ if (s.name.equals(name)) return s;
+ }
+ for (var s:forwardStructs) if (s.name.equals(name)) return s;
+ return null;
+ }
+
+ /**
+ * Generate a variable name. This might be new random name or a name from an outer scope.
+ * If the program is allowed to be invalid this can return keywords and already used variable names in the current scope.
+ * @return The variable name.
+ */
+ private String getVarName() {
+ if (currScopeStart > 0 && random.nextInt(10) > 7) {
+ // Use a variable outside the current scope.
+ var v = variables.get(random.nextInt(currScopeStart));
+ if (lookup(v.name) < currScopeStart) return v.name;
+ if (!generateValid) {
+ maybeInvalid = true;
+ return v.name;
+ }
+ }
+ // Generate a new random variable name
+ StringBuilder sb = getRandomName();
+ var v = sb.toString();
+ if (!generateValid && (KEYWORDS.contains(v) || lookup(v) >= currScopeStart)) {
+ maybeInvalid = true;
+ return v;
+ }
+ while (KEYWORDS.contains(v) || lookup(v) >= currScopeStart) {
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length())));
+ v = sb.toString();
+ }
+ return v;
+ }
+
+ private <T> T randFromList(List<T> list) {
+ return list.get(random.nextInt(list.size()));
+ }
+
+ /**
+ * Get a random structure name
+ * @return A random structure name
+ */
+ private String getStructName() {
+ if (!structs.isEmpty() && generateInvalid()) return randFromList(structs).name;
+ if (!forwardStructs.isEmpty() && generateInvalid()) return randFromList(forwardStructs).name;
+
+ // Generate a new random struct name
+ StringBuilder sb = getRandomName();
+ var v = sb.toString();
+ if (!generateValid && (KEYWORDS.contains(v) || getStruct(v) != null)) {
+ maybeInvalid = true;
+ return v;
+ }
+ while (KEYWORDS.contains(v) || getStruct(v) != null) {
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length())));
+ v = sb.toString();
+ }
+ return v;
+ }
+
+ public TypeInt getIntType() {
+ return randFromList(INTTYPES);
+ }
+
+ /**
+ * Get a random type
+ * @return A random type
+ */
+ private Type getType(boolean allowForward) {
+ int t = random.nextInt(10);
+ if( t<5 || structs.isEmpty() ) return getIntType();
+ if( t<7 ) return TYPE_FLT;
+ if (allowForward && random.nextInt(10)==0) {
+ int i=random.nextInt(forwardStructs.size()+1);
+ if (i>0) return forwardStructs.get(i-1).nullable;
+ var name = getStructName();
+ var struct = new TypeStruct(name);
+ forwardStructs.add(struct);
+ return struct.nullable;
+ }
+ TypeStruct struct = randFromList(structs);
+ if (random.nextBoolean()) return struct.nullable;
+ return struct;
+ }
+
+ private Type getType() {
+ return getType(false);
+ }
+
+ /**
+ * Generates a program and writes it into the string builder supplied in the constructor.
+ * @return If the program is allowed to be invalid returns if something potentially invalid was generated.
+ */
+ public boolean genProgram() {
+ var oldGenerateValid = generateValid;
+ if (!generateValid && random.nextBoolean()) generateValid = true;
+ this.maybeInvalid = false;
+ addVariable(ScopeNode.ARG0, TYPE_INT);
+ currScopeStart = variables.size();
+ if ((genStatements() & FLAG_STOP) == 0) {
+ if (generateValid || random.nextInt(10)<7) {
+ genReturn();
+ } else {
+ this.maybeInvalid = true;
+ }
+ }
+ generateValid = oldGenerateValid;
+ return !this.maybeInvalid;
+ }
+
+ /**
+ * Generate a list of statements.
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the last statement generated
+ */
+ public int genStatements() {
+ var num = random.nextInt(MAX_STATEMENTS_PER_BLOCK * (depth+1));
+ for (int i=0; i<num; i++) {
+ printIndentation();
+ var stop = genStatement();
+ sb.append("\n");
+ if ((stop & FLAG_STOP) != 0) return stop;
+ }
+ return 0;
+ }
+
+ /**
+ * Generate a single statement
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement
+ */
+ public int genStatement() {
+ return switch (random.nextInt(12)) {
+ case 0 -> allowStructs || generateInvalid() ? genStruct() : genAssignment();
+ case 1 -> genBlock();
+ case 2 -> genIf();
+ case 3 -> genCountedLoop();
+ case 4 -> genWhile();
+ case 5, 6 -> genDecl();
+ case 7, 8, 9 -> genAssignment();
+ case 10 -> genNullCheck();
+ default -> genExit();
+ };
+ }
+
+ /**
+ * Generate a new structure
+ * @return 0
+ */
+ public int genStruct() {
+ int idx = random.nextInt(forwardStructs.size()+10);
+ TypeStruct struct;
+ if (idx < 10) {
+ var name = getStructName();
+ struct = new TypeStruct(name);
+ forwardStructs.add(struct);
+ } else {
+ struct = forwardStructs.get(idx-10);
+ }
+ var fields = new TypeStruct.Field[generateInvalid() ? 0 : random.nextInt(10)];
+ sb.append("struct ").append(struct.name).append(" {\n");
+ indentation += INDENTATION;
+ for (int i=0; i<fields.length; i++) {
+ var fieldName = getRandomName();
+ var type = getType(true);
+ printIndentation().append(type.name).append(" ").append(fieldName).append(";\n");
+ fields[i] = new TypeStruct.Field(fieldName.toString(), type, struct);
+ }
+ indentation -= INDENTATION;
+ printIndentation().append("};");
+ struct.fields = fields;
+ forwardStructs.remove(struct);
+ structs.add(struct);
+ return 0;
+ }
+
+ /**
+ * Generate a statement for if, else and while blocks.
+ * This is special since it disallows declarations and prefers to generate blocks.
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement
+ */
+ public int genStatementBlock() {
+ var oldAllowStructs = allowStructs;
+ allowStructs = false;
+ indentation += INDENTATION;
+ int res;
+ if (depth == 0) {
+ res = random.nextBoolean() ? genAssignment() : genExit();
+ } else {
+ depth--;
+ res = switch (random.nextInt(12)) {
+ case 1, 2, 3, 4, 5 -> {
+ depth++;
+ indentation -= INDENTATION;
+ var ret = genBlock();
+ indentation += INDENTATION;
+ depth--;
+ yield ret;
+ }
+ case 6 -> genIf();
+ case 7 -> genCountedLoop();
+ case 8 -> genWhile();
+ case 9 -> genAssignment();
+ case 10 -> genNullCheck();
+ default -> genExit();
+ };
+ depth++;
+ }
+ indentation -= INDENTATION;
+ allowStructs = oldAllowStructs;
+ return res;
+ }
+
+ /**
+ * Generate an exit. This can be return or break and continue when in a loop.
+ * @return FLAG_STOP
+ */
+ public int genExit() {
+ if (loopDepth == 0 && generateValid) return genReturn();
+ return switch (random.nextInt(7)) {
+ case 0,1,2 -> {
+ if (loopDepth == 0) maybeInvalid = true;
+ sb.append("continue;");
+ yield FLAG_STOP;
+ }
+ case 3,4,5 -> {
+ if (loopDepth == 0) maybeInvalid = true;
+ sb.append("break;");
+ yield FLAG_STOP;
+ }
+ default -> genReturn();
+ };
+ }
+
+ /**
+ * Generate a block statement. After a certain depth only empty blocks are generated to ensure termination of the generator.
+ * @return flag FLAG_STOP for the generated statement
+ */
+ public int genBlock() {
+ if (depth == 0) {
+ sb.append("{}");
+ return 0;
+ }
+ var oldAllowStructs = allowStructs;
+ var oldCSS = currScopeStart;
+ allowStructs = false;
+ currScopeStart = variables.size();
+ depth--;
+ sb.append("{\n");
+ indentation += INDENTATION;
+ var stop = genStatements();
+ indentation -= INDENTATION;
+ printIndentation().append("}");
+ popVariables(currScopeStart);
+ depth++;
+ currScopeStart = oldCSS;
+ allowStructs = oldAllowStructs;
+ return stop & ~FLAG_IF_WITHOUT_ELSE;
+ }
+
+ /**
+ * Generate an if statement with an optional else block.
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement
+ */
+ public int genIf() {
+ sb.append("if(");
+ genExpression(TYPE_BOOL, true, true);
+ sb.append(") ");
+ var stop = genStatementBlock();
+ if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) {
+ sb.append("\n");
+ printIndentation().append("else ");
+ stop &= genStatementBlock();
+ } else {
+ stop = FLAG_IF_WITHOUT_ELSE;
+ }
+ return stop;
+ }
+
+ /**
+ * Generate a block with a null check
+ * if (nullable) { handle_nullable_as_non_nullable; }
+ */
+ public int genNullCheck() {
+ var v = findVisibleVariablesMatching(va->va.declared instanceof TypeNullable);
+ if (v == null) return genIf();
+ var n = (TypeNullable)v.declared;
+ boolean negate = random.nextBoolean();
+ sb.append("if(");
+ if (negate) sb.append("!");
+ sb.append(v.name).append(")");
+ if (!negate) v.type = n.base;
+ var stop = genStatementBlock();
+ v.type = n;
+ if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) {
+ sb.append("\n");
+ printIndentation().append("else ");
+ if (negate) v.type = n.base;
+ stop &= genStatementBlock();
+ v.type = n;
+ } else {
+ stop = FLAG_IF_WITHOUT_ELSE;
+ }
+ return stop;
+ }
+
+ /**
+ * Generate a while loop.
+ * @return 0
+ */
+ public int genWhile() {
+ sb.append("while(");
+ genExpression(TYPE_BOOL, true, true);
+ sb.append(") ");
+ loopDepth++;
+ genStatementBlock();
+ loopDepth--;
+ return 0;
+ }
+
+ /**
+ * Generate a counted loop of the form
+ * <code>
+ * {
+ * int varname = start_expr;
+ * while (varname < end_expr) loop_body;
+ * }
+ * </code>
+ * @return 0
+ */
+ public int genCountedLoop() {
+ if (depth == 0) return genWhile();
+ var oldCSS = currScopeStart;
+ currScopeStart = variables.size();
+ sb.append("{\n");
+ indentation += INDENTATION;
+ String name = getVarName();
+ printIndentation().append("int ").append(name).append("=");
+ genExpression(TYPE_INT, true, false);
+ sb.append(";\n");
+ addVariable(name, TYPE_INT);
+ printIndentation().append("while(").append(name).append("<");
+ genShift(TYPE_INT, true, false, MAX_EXPRESSION_DEPTH);
+ sb.append(") {\n");
+ indentation += INDENTATION;
+ depth--;
+ loopDepth++;
+ printIndentation().append(name).append("=").append(name).append("+");
+ genExpression(TYPE_INT, true, false);
+ sb.append(";\n");
+ genStatements();
+ loopDepth--;
+ depth++;
+ indentation -= INDENTATION;
+ printIndentation().append("}\n");
+ indentation -= INDENTATION;
+ printIndentation().append("}");
+ popVariables(currScopeStart);
+ currScopeStart = oldCSS;
+ return 0;
+ }
+
+ /**
+ * Generate a declaration statement.
+ * @return 0
+ */
+ public int genDecl() {
+ var type = getType();
+ var name = getVarName();
+ // Always make them mutable
+ sb.append(generateInvalid() ? getRandomName() : type.name).append(" !").append(name);
+ if (!(type instanceof TypeNullable) || random.nextBoolean()) {
+ sb.append("=");
+ genExpression(type, true, type==TYPE_BOOL);
+ }
+ sb.append(";");
+ addVariable(name, type);
+ return 0;
+ }
+
+ /**
+ * Generate an assignment statement.
+ * @return 0
+ */
+ public int genAssignment() {
+ Type type;
+ Type declared;
+ if (generateInvalid()) {
+ sb.append(getRandomName());
+ type = getType();
+ declared = type;
+ } else {
+ var v = findVisibleVariablesMatching(va->true);
+ if (v == null) return genDecl();
+ sb.append(v.name);
+ type = v.type;
+ declared = v.declared;
+ }
+ if (generateInvalid()) {
+ var name = getRandomName();
+ sb.append(".").append(name);
+ declared = getType();
+ } else if (type instanceof TypeStruct s && s.fields.length > 0 && random.nextBoolean()) {
+ var field = s.fields[random.nextInt(s.fields.length)];
+ sb.append(".").append(field.name);
+ declared = field.type;
+ }
+ sb.append("=");
+ genExpression(declared, true, declared==TYPE_BOOL);
+ sb.append(";");
+ return 0;
+ }
+
+ /**
+ * Generate a return statement.
+ * @return FLAG_STOP
+ */
+ public int genReturn() {
+ var type = getType();
+ sb.append("return ");
+ genExpression(type, true, false);
+ sb.append(";");
+ return FLAG_STOP;
+ }
+
+ public void genExpression(Type type, boolean change, boolean preferLogical) {
+ genExpression(type, change, preferLogical, MAX_EXPRESSION_DEPTH);
+ }
+
+ public void genExpression(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ if (d > 0 && random.nextInt(10)==0) {
+ genLogical(TYPE_INT, true, true, d);
+ sb.append("?");
+ genExpression(type, true, preferLogical, d-1);
+ sb.append(":");
+ genExpression(type, false, preferLogical, d-1);
+ } else {
+ genLogical(type, false, preferLogical, d);
+ }
+ }
+
+ public void genLogical(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ genBitwise(type, false, preferLogical, d);
+ while (d-->0 && random.nextInt(preferLogical?5:50) == 0) {
+ sb.append(random.nextBoolean()?"&&":"||");
+ genBitwise(type, true, preferLogical, d);
+ }
+ }
+
+ public void genBitwise(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ genComparison(type, false, preferLogical, d);
+ while (type instanceof TypeInt && d-->0 && random.nextInt(preferLogical?50:5)==0) {
+ sb.append("&|^".charAt(random.nextInt(3)));
+ genComparison(type, true, preferLogical, d);
+ }
+ }
+
+ public void genComparison(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ if (d > 0 && type instanceof TypeInt && random.nextInt(preferLogical?5:50)==0) {
+ type = getType();
+ genComparison(type, true, preferLogical, d-1);
+ sb.append(random.nextBoolean()?"==":"!=");
+ }
+ genChaining(type, false, preferLogical, d);
+ }
+
+ public void genChaining(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ if (d>0 && type instanceof TypeInt && random.nextInt(preferLogical?5:50)==0) {
+ genShift(TYPE_INT, true, false, d);
+ char c = random.nextBoolean()?'>':'<';
+ sb.append(c);
+ if (random.nextInt(4)==0) sb.append('=');
+ genShift(TYPE_INT, true, false, --d);
+ while (d-->0 && random.nextInt(preferLogical?5:50)==0) {
+ if (generateInvalid()) c = c=='>' ? '<' : '>';
+ sb.append(c);
+ if (random.nextInt(4)==0) sb.append('=');
+ genShift(TYPE_INT, true, false, d);
+ }
+ } else {
+ genShift(type, false, preferLogical, d);
+ }
+ }
+
+ private static final String[] SHIFTS = {">>", ">>>", "<<"};
+
+ public void genShift(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ genAddition(type, false, preferLogical, d);
+ while (d-- > 0 && type instanceof TypeInt && random.nextInt(50)==0) {
+ sb.append(SHIFTS[random.nextInt(3)]);
+ genAddition(type, true, preferLogical, d);
+ }
+ }
+
+ public void genAddition(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ genMultiplication(type, false, preferLogical, d);
+ while (d-- > 0 && (type instanceof TypeInt || type==TYPE_FLT) && random.nextInt(50)==0) {
+ sb.append(random.nextBoolean()?" + ":" - ");
+ genMultiplication(type, true, preferLogical, d);
+ }
+ }
+
+ public void genMultiplication(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ genUnary(type, false, preferLogical, d);
+ while (d-- > 0 && (type instanceof TypeInt || type==TYPE_FLT) && random.nextInt(50)==0) {
+ sb.append(random.nextBoolean()?'*':'/');
+ genUnary(type, true, preferLogical, d);
+ }
+ }
+
+ /**
+ * Generate a unary expression.
+ */
+ public void genUnary(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ if (!(type instanceof TypeInt) && type != TYPE_FLT) {
+ genSuffix(type, false, preferLogical, d);
+ return;
+ }
+ while (d>0 && random.nextInt(5)==0) {
+ d--;
+ boolean what = type instanceof TypeInt && random.nextInt(preferLogical?2:20) == 0;
+ sb.append(what?'!':'-');
+ }
+ if (random.nextInt(10)==0) {
+ var t = type;
+ var v = findVisibleVariablesMatching(va->va.type.isa(t));
+ if (v != null) {
+ sb.append(random.nextBoolean()?"--":"++").append(v.name);
+ return;
+ }
+ }
+ genSuffix(type, false, preferLogical, d);
+ }
+
+ public TypeStruct.Field getField(Type type) {
+ int n=0;
+ for (var s: structs) {
+ for (var f: s.fields) {
+ if (f.type.isa(type)) n++;
+ }
+ }
+ if (n==0) return null;
+ n = random.nextInt(n);
+ for (var s: structs) {
+ for (var f: s.fields) {
+ if (f.type.isa(type)) if (n--==0) return f;
+ }
+ }
+ throw new AssertionError();
+ }
+
+ public void genSuffix(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ if (d > 0 && random.nextBoolean() && genField(type, d-1)) return;
+ if (d > 0 && random.nextInt(10) == 0) {
+ if (random.nextBoolean() && genField(type, d-1)) return;
+ var t = type;
+ var v = findVisibleVariablesMatching(va -> va.type.isa(t));
+ if (v != null) {
+ sb.append(v.name).append(random.nextBoolean() ? "--" : "++");
+ return;
+ }
+ }
+ genPrimary(type, false, preferLogical, d);
+ }
+
+ public boolean genField(Type type, int d) {
+ if (generateInvalid()) {
+ var field = getRandomName();
+ var t = getType();
+ genSuffix(t, true, false, d);
+ sb.append(".").append(field);
+ } else {
+ var field = getField(type);
+ if (field == null) return false;
+ genSuffix(field.struct, true, false, d);
+ sb.append(".").append(field.name);
+ }
+ return true;
+ }
+
+ /**
+ * Generate a primary expression.
+ */
+ public void genPrimary(Type type, boolean change, boolean preferLogical, int d) {
+ if (change && generateInvalid()) type = getType();
+ var rand = random.nextInt(10);
+ if (d > 0 && rand == 0) {
+ sb.append("(");
+ genExpression(type, false, preferLogical, d-1);
+ sb.append(")");
+ } else if (rand < 6) {
+ genVariable(type);
+ } else {
+ genConst(type);
+ }
+ }
+
+ /**
+ * Generate a constant.
+ */
+ public void genConst(Type type) {
+ if (generateInvalid()) type = getType();
+ if (type instanceof TypeInt || (type == TYPE_FLT && random.nextBoolean())) {
+ var rand = random.nextInt(10);
+ switch (rand) {
+ case 0 -> sb.append("true");
+ case 1 -> sb.append("false");
+ default -> sb.append(random.nextInt(1<<(rand-2)));
+ }
+ } else if (type == TYPE_FLT) {
+ sb.append(random.nextFloat());
+ } else if (type instanceof TypeNullable n) {
+ if ((n.base instanceof TypeStruct s && s.fields == null) || random.nextBoolean()) {
+ sb.append("null");
+ } else {
+ sb.append("new ").append(generateInvalid() ? getRandomName() : n.base.name);
+ }
+ } else {
+ sb.append("new ").append(generateInvalid() ? getRandomName() : type.name);
+ }
+ }
+
+ /**
+ * Generate a variable. If there are none generate a number instead.
+ * If the program is allowed to be invalid a random name can be generated in some cases.
+ */
+ public void genVariable(Type type) {
+ if (generateInvalid()) {
+ sb.append(getRandomName());
+ return;
+ }
+ if (generateInvalid()) type = getType();
+ var t = type;
+ var v = findVisibleVariablesMatching(va->va.type.isa(t));
+ if (v == null) genConst(type);
+ else sb.append(v.name);
+ }
+
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/arg_count.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/arg_count.c
new file mode 100644
index 00000000..e247e28a
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/arg_count.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+__attribute__ ((CALL_CONV))
+extern double addAll( long long i0, double f1, long long i2, double f3, long long i4, double f5, long long i6, double f7, long long i8, double f9, long long i10, double f11, long long i12, double f13, long long i14, double f15, long long i16, double f17, long long i18, double f19 );
+
+int main() {
+ double result = addAll( 0, 1.1, 2, 3.1, 4, 5.1, 6, 7.1, 8, 9.1, 10, 11.1, 12, 13.1, 14, 15.1, 16, 17.1, 18, 19.1 );
+ printf("%f\n", result);
+ return 0;
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/brain_fuck.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/brain_fuck.c
new file mode 100644
index 00000000..127cbe73
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/brain_fuck.c
@@ -0,0 +1,18 @@
+#include <stdio.h>
+#include <stdbool.h>
+
+typedef struct AryInt {
+ unsigned int len;
+ unsigned char data[];
+} AryInt;
+
+__attribute__ ((CALL_CONV))
+extern AryInt* brain_fuck();
+
+int main() {
+ AryInt* ps = brain_fuck();
+
+ printf("%.*s", ps->len, ps->data);
+
+ return 0;
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/fib.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/fib.c
new file mode 100644
index 00000000..263d2f13
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/fib.c
@@ -0,0 +1,16 @@
+#include <stdio.h>
+#include <stdbool.h>
+
+
+__attribute__ ((CALL_CONV))
+extern int fib(int x);
+
+int main() {
+ printf("[");
+ for (int i = 0; i < 10; i++) {
+ printf("%d", fib(i));
+ if (i < 9) printf(", ");
+ }
+ printf("]");
+ return 0;
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/loop2.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/loop2.c
new file mode 100644
index 00000000..934f2d89
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/loop2.c
@@ -0,0 +1,7 @@
+
+__attribute__ ((CALL_CONV))
+extern int loop2();
+
+int main() {
+ return loop2();
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c
new file mode 100644
index 00000000..7e7e796d
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/newtonFloat.c
@@ -0,0 +1,16 @@
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+
+extern double test_sqrt(double);
+
+int main( int argc, char** argv ) {
+ for( int i=0; i<10; i++ ) {
+ double d = test_sqrt((double)i), expect = sqrt((double)i);
+ double delta = fabs(d-expect);
+ printf("%d %f (%g)\n",i,d,delta);
+ if( delta > 1e-15 )
+ return 1;
+ }
+ return 0;
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/person.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/person.c
new file mode 100644
index 00000000..df95e1ca
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/person.c
@@ -0,0 +1,34 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+typedef struct Person {
+ int age;
+} Person;
+
+typedef struct PersonArray {
+ unsigned int length;
+ int pad;
+ Person* data[];
+} PersonArray;
+
+__attribute__ ((CALL_CONV))
+extern void fcn(PersonArray* people, int index);
+
+int main() {
+ Person base[5] = {{25},{1},{18},{45},{60}};
+ unsigned char buf[8+8*5];
+ PersonArray* people_array = (PersonArray*)buf;
+ people_array->length=5;
+ for (int i=0; i<people_array->length; i++) people_array->data[i] = &base[i];
+
+ fcn(people_array, 1);
+ fcn(people_array, 1);
+ fcn(people_array, 1);
+ fcn(people_array, 1);
+ fcn(people_array, 1);
+
+ printf("%d\n", people_array->data[1]->age); // expected: 6
+
+ return 0;
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/sieve.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/sieve.c
new file mode 100644
index 00000000..cac4cfa3
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/sieve.c
@@ -0,0 +1,19 @@
+#include <stdio.h>
+#include <stdbool.h>
+
+typedef struct AryInt {
+ unsigned int len;
+ unsigned int data[];
+} AryInt;
+
+__attribute__ ((CALL_CONV))
+extern AryInt *sieve(int N);
+
+int main() {
+ AryInt* ps = sieve(100);
+ printf("%d[",ps->len);
+ for( int i=0; i<ps->len; i++ )
+ printf("%d, ",ps->data[i]);
+ printf("]");
+ return 0;
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/sort.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/sort.c
new file mode 100644
index 00000000..ad76cc7f
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/sort.c
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+// Test wrapper to call `sort.smp`
+
+typedef struct AryInt {
+ unsigned int len;
+ long long 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 main() {
+ AryInt *as = (AryInt*)primes;
+
+ merge_sort(as);
+
+ printf("%d[",as->len);
+ for( int i=0; i<as->len; i++ )
+ printf("%d, ",as->data[i]);
+ printf("]");
+
+ return 0;
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_10x.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_10x.c
new file mode 100644
index 00000000..d44d5387
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_10x.c
@@ -0,0 +1,13 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+__attribute__ ((CALL_CONV))
+extern int stack10(int x);
+
+int main() {
+int result = stack10(0);
+
+printf("%d", result);
+return 0;
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_11x.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_11x.c
new file mode 100644
index 00000000..d71979d1
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_11x.c
@@ -0,0 +1,13 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+__attribute__ ((CALL_CONV))
+extern int stack11(int x);
+
+int main() {
+int result = stack11(1);
+
+printf("%d", result);
+return 0;
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_12x.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_12x.c
new file mode 100644
index 00000000..a4e425cb
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_12x.c
@@ -0,0 +1,13 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+__attribute__ ((CALL_CONV))
+extern int stack12(int x);
+
+int main() {
+int result = stack12(0);
+
+printf("%d", result);
+return 0;
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_13x.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_13x.c
new file mode 100644
index 00000000..a3a51d0e
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_13x.c
@@ -0,0 +1,13 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+__attribute__ ((CALL_CONV))
+extern int stack13(int x);
+
+int main() {
+int result = stack13(0);
+
+printf("%d", result);
+return 0;
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_9x.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_9x.c
new file mode 100644
index 00000000..4f93ba24
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/stacked_r_9x.c
@@ -0,0 +1,13 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+__attribute__ ((CALL_CONV))
+extern int stack9(int x);
+
+int main() {
+int result = stack9(0);
+
+printf("%d", result);
+return 0;
+} \ No newline at end of file
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/stringHash.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/stringHash.c
new file mode 100644
index 00000000..2c6d0fa9
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/stringHash.c
@@ -0,0 +1,30 @@
+#include <stdio.h>
+#include <stdbool.h>
+
+typedef struct ArrayU8 {
+ unsigned int length;
+ unsigned char data[];
+} ArrayU8;
+typedef struct String {
+ ArrayU8 * cs;
+ long long _hashCode;
+} String;
+
+
+__attribute__ ((CALL_CONV))
+extern bool equals(String*st1, String*st2);
+
+int main() {
+ String test1 = {.cs=(ArrayU8*)"\x04\x00\x00\x00test"};
+ String test2 = {.cs=(ArrayU8*)"\x05\x00\x00\x00test1"};
+
+ if( equals(&test1, &test2) ) {
+ printf("Expected test1 and test2 unequal");
+ return 1;
+ }
+ if( !equals(&test2, &test2) ) {
+ printf("Expected test2 and test2 equal");
+ return 1;
+ }
+ return 0;
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/test0/A.smp b/chapter25/src/test/java/com/seaofnodes/simple/test0/A.smp
new file mode 100644
index 00000000..b94fa147
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/test0/A.smp
@@ -0,0 +1,6 @@
+// -*- mode: java; -*-
+
+val _fcn = { -> arg ? { int x -> x+1; } : { int x -> x-1; }; };
+
+// Refer to child B is OK because last field of A has been initialized
+_fcn()(B.x);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/test0/A/B.smp b/chapter25/src/test/java/com/seaofnodes/simple/test0/A/B.smp
new file mode 100644
index 00000000..82844f74
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/test0/A/B.smp
@@ -0,0 +1 @@
+val x=5;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/test5/par.smp b/chapter25/src/test/java/com/seaofnodes/simple/test5/par.smp
new file mode 100644
index 00000000..f19bd57b
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/test5/par.smp
@@ -0,0 +1,20 @@
+// -*- mode: java; -*-
+
+// Parent module
+val par0 = 3; // Some final field
+
+// Nested class in parent.
+// Makes a field: "val child0 = ..."
+struct child0 {
+ val kid0 = 5;
+};
+
+// Nested class in parent, comes from disk.
+// These are defined in-order
+val child1 = child1;
+
+// Nested class in parent, comes from disk
+val child2 = child2;
+
+// Nested class in parent, comes from disk
+val child3 = child3;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/test5/par/child1.smp b/chapter25/src/test/java/com/seaofnodes/simple/test5/par/child1.smp
new file mode 100644
index 00000000..7bca0cf7
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/test5/par/child1.smp
@@ -0,0 +1,12 @@
+// -*- mode: java; -*-
+
+// Child module
+
+// Same field name as child2
+val kid1 = 7;
+
+// Can read parent directly
+val kid2 = par0;
+
+// Error to reference a later sibling
+// val kid3 = par.child2.kid1;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/test5/par/child2.smp b/chapter25/src/test/java/com/seaofnodes/simple/test5/par/child2.smp
new file mode 100644
index 00000000..ab5e2ac1
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/test5/par/child2.smp
@@ -0,0 +1,13 @@
+// -*- mode: java; -*-
+
+// Child module
+
+// Same field name as child2
+val kid1 = 11;
+
+// Can read parent directly.
+// Can read defined-in-parent fields.
+val kid2 = child0.kid0;
+
+// OK to reference a past sibling
+val kid3 = child1.kid1;