From f4bd723d929e53475f7990374dca7b7dfcbbdc42 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sun, 1 Nov 2026 10:33:55 -0800 Subject: Add Chapter 29 for tech debt cleanup with the parser split Keep Chapter 28b intact and carry the grammar-oriented parser package, Var move, IterPeeps move, and parser regression tests into Chapter 29. Defer privacy and exclude its experimental tests and fixture changes. Register the chapter in the indexes, IntelliJ modules and run configurations, Maven reactor, roadmap, and linear-history publisher. Windows and Linux each pass 573 tests, release builds, 199 allocation replays, and 29 native checks. --- .../java/com/seaofnodes/simple/Chapter23Test.java | 341 +++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 chapter29/src/test/java/com/seaofnodes/simple/Chapter23Test.java (limited to 'chapter29/src/test/java/com/seaofnodes/simple/Chapter23Test.java') diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter23Test.java new file mode 100644 index 00000000..df02d3b4 --- /dev/null +++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter23Test.java @@ -0,0 +1,341 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; + + +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 public void testPopularUses() { com.seaofnodes.simple.codegen.RegAllocTestSupport.popularUses(); } + + @Test public void testDiagnosticTypeAccessors() throws Exception { + new CodeGen("return 0;").parse(); + var tfp = TypeFunPtr.TEST; + var con = ConstantNode.raw(tfp); + var call = new CallNode(null,con,con,con); + var field = Type.class.getDeclaredField("VISIT"); + field.setAccessible(true); + var visit = (java.util.Map)field.get(null); + var marker = new Object(); + visit.put(marker,Type.BOTTOM); + try { + assertEquals(0,TypeInteger.ZERO.alignment()); + assertEquals(0,TypeInteger.ZERO.value()); + org.junit.Assert.assertTrue(tfp._isConstant()); + con.toString(); + call.name(); + con.p(1); + org.junit.Assert.assertSame(Type.BOTTOM,visit.get(marker)); + assertEquals(1,visit.size()); + } finally { visit.clear(); } + } + + + @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 CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); + int delta = spills>>3; + if( delta==0 ) delta = 1; + if( spills != -1 && !CodeGen.iterSeedOverridden() ) + assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); + if( stop != null ) + assertEquals(stop, code.print()); + 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 testLogicalValueSemantics() { + assertEquals("42", Eval2.eval(new CodeGen("return 7 && 42;").driver(CodeGen.Phase.FinalizeIR), 0)); + assertEquals("0", Eval2.eval(new CodeGen("return 0 && 42;").driver(CodeGen.Phase.FinalizeIR), 0)); + assertEquals("7", Eval2.eval(new CodeGen("return 7 || 42;").driver(CodeGen.Phase.FinalizeIR), 0)); + assertEquals("42", Eval2.eval(new CodeGen("return 0 || 42;").driver(CodeGen.Phase.FinalizeIR), 0)); + } + + @Test + public void testMixedScalarNotPhiPredicate() { + CodeGen code = new CodeGen(""" +struct S {}; +S? s = arg ? new S; +return !(!s || !arg) ? 1 : 0; +"""); + code.driver(CodeGen.Phase.TypeCheck); + assertEquals("0", Eval2.eval(code, 0)); + assertEquals("1", Eval2.eval(code, 1)); + } + + @Test + public void testAndPtr() throws IOException { + // Todo: have one src here + String src = """ +struct S { !S? !fld; 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).driver(CodeGen.Phase.TypeCheck); + assertEquals("true", Eval2.eval(code, 0)); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build( src, "class:Test.", 1, 12, 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.", src, 1, 12, 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, 2, 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, 2, 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).driver(CodeGen.Phase.TypeCheck); + assertEquals("return Phi(Region,0,1);", code.print()); + assertEquals("1", Eval2.eval(code, 0)); + testCPU(src,"x86_64_v2", "win64",23,null); + }; + + @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; + }; +}; +~_Scan s = new _Scan(" q"); +return s.skip().x; +"""; + + try { new CodeGen(src).driver(CodeGen.Phase.TypeCheck); fail(); } + catch( Exception e ) { + // Bad error message, but basically requires a mutable 'x' field. + org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa")); + } + }; + + + @Test + public void testMethod() { + String src = """ +// 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).driver(CodeGen.Phase.TypeCheck); + assertEquals("return (.[]==91);", code.print()); + assertEquals("1", Eval2.eval(code, 0)); + testCPU(src,"x86_64_v2", "win64",4, null); + }; + + +} -- cgit v1.2.3