summaryrefslogtreecommitdiff
path: root/chapter-interprocedural-sccp/src/test/java/com/seaofnodes/simple/Chapter23Test.java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-19 14:56:14 -0700
committerCliff Click <cliffc@acm.org>2026-10-19 14:56:14 -0700
commit7edca9823ec8ef0aed10c88fa07ef969792aab77 (patch)
treed95e308cac7a9c5039a59be636d881982a9b4878 /chapter-interprocedural-sccp/src/test/java/com/seaofnodes/simple/Chapter23Test.java
parent3617495d8efbcce23bbc9f1754c7d27d34d30a52 (diff)
downloadSimple-main.tar.gz
Simple-main.zip
Split from ch25, SCCP and interprocedural SCCPmain
Diffstat (limited to 'chapter-interprocedural-sccp/src/test/java/com/seaofnodes/simple/Chapter23Test.java')
-rw-r--r--chapter-interprocedural-sccp/src/test/java/com/seaofnodes/simple/Chapter23Test.java422
1 files changed, 422 insertions, 0 deletions
diff --git a/chapter-interprocedural-sccp/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter-interprocedural-sccp/src/test/java/com/seaofnodes/simple/Chapter23Test.java
new file mode 100644
index 00000000..22be3af1
--- /dev/null
+++ b/chapter-interprocedural-sccp/src/test/java/com/seaofnodes/simple/Chapter23Test.java
@@ -0,0 +1,422 @@
+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 com.seaofnodes.simple.type.TypeInteger;
+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 = new ConstantNode(tfp);
+ var call = new CallNode(null,con,con,con);
+ var field = Type.class.getDeclaredField("VISIT");
+ field.setAccessible(true);
+ var visit = (java.util.Map<Object,Type>)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
+ public void testOrRhsLoopLocal() throws IOException {
+ checkOrRhsLoop("""
+ struct S { int f; };
+ int x = 0;
+ int result = (arg & 1) || (new S {
+ while (x < 3) x++;
+ f = ++x;
+ }).f;
+ return x * 10 + result;
+ """);
+ }
+
+ @Test
+ public void testOrRhsLoopMemory() throws IOException {
+ checkOrRhsLoop("""
+ struct S { int f; };
+ !S !s = new S;
+ int result = (arg & 1) || (new S {
+ while (s.f < 3) s.f++;
+ s.f++;
+ f = s.f;
+ }).f;
+ return s.f * 10 + result;
+ """);
+ }
+
+ private static void checkOrRhsLoop(String src) throws IOException {
+ CodeGen code = new CheckedCodeGen(src).driver("riscv", "SystemV", null);
+ SpillStats.record(code,"Chapter23","riscv","SystemV");
+ for (int arg : new int[]{0, 1, 2, 3}) {
+ byte[] image = new byte[1 << 20];
+ System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding._bits.size());
+ EvalRisc5 r5 = new EvalRisc5(image, 1 << 16);
+ r5.regs[riscv.A0] = arg;
+ assertEquals("Execution must finish", 0, r5.step(1000));
+ // False LHS runs the loop and final increment, returning 4.
+ // True LHS skips all RHS effects and returns the LHS value, 1.
+ assertEquals("OR result and side effects for arg=" + arg,
+ (arg & 1) == 0 ? 44 : 1, r5.regs[riscv.A0]);
+ }
+ }
+
+ @Ignore
+ @Test
+ public void testJig() throws IOException {
+ String src =
+"""
+var apply = { i64 x, { i64 -> u32 } fcn ->
+ return fcn(x);
+}
+""";
+ }
+
+
+ static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) {
+ CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
+ SpillStats.record(code,"Chapter23",cpu,os);
+ SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ if( stop != null )
+ assertEquals(stop, code._stop.toString());
+ return code;
+ }
+
+ @Test
+ public void testOr() throws IOException {
+ String src =
+ """
+ int a = 1;
+ int b = 0;
+
+ if(a++ || b++ ) {
+ if(b == 0 && a == 2) {
+ sys.io.p("Or");
+ }
+ } else{
+ sys.io.p("And");
+ }
+ return 0;
+ """;
+
+ TestC.runSF("or1", src, null, "Or", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build("or1", src, 0, 2, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+ assertEquals("Or",R5._stdout.toString());
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("or1", src,0, 2, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ assertEquals("Or",arm._stdout.toString());
+ }
+
+ @Test
+ public void testAnd() throws IOException {
+ String src =
+ """
+ int a = 1;
+ int b = 1;
+
+ if(a && b) {
+ sys.io.p("And");
+ } else {
+ sys.io.p("Or");
+ }
+ return 0;
+ """;
+ TestC.runSF("and1", src, null, "And", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build("and1", src, 0, 2, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+ assertEquals("And",R5._stdout.toString());
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("and1", src, 0, 2, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ assertEquals("And",arm._stdout.toString());
+ }
+
+ @Test
+ public void testAndPtr() throws IOException {
+ // Todo: have one src here
+ String src =
+ """
+ struct S { !S? !fld; };
+ val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};};
+ return ptr && ptr.fld ? "true" : "false";
+ """;
+ String src2 =
+ """
+ struct S { !S? !fld; };
+
+ val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};};
+ if( ptr && ptr.fld ) {
+ sys.io.p("true");
+ } else {
+ sys.io.p("false");
+ }
+
+ return 0;
+ """;
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("[97-117][ 116,114,117,101]", Eval2.eval(code, 0));
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build("ptrand",src2, 1, 8, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+ assertEquals("false",R5._stdout.toString());
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("ptrand", src2, 1, 8, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ assertEquals("false",arm._stdout.toString());
+ }
+
+ // conditional side effect
+ @Test
+ public void testCondSideEffAnd() throws IOException {
+ String src =
+ """
+ int a = 1;
+ int b = 1;
+
+ int x=1;
+ int y=1;
+ int z=0;
+
+ int g = x++ && y++ && z++;
+
+ if(x == 2 && y == 2 && z == 1 && g == 0) {
+ sys.io.p("Effected");
+ } else {
+ sys.io.p("Not effected");
+ }
+ return 0;
+ """;
+
+ TestC.runSF("and2", src, null, "Effected", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build("and2", src, 0, 2, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+ assertEquals("Effected",R5._stdout.toString());
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("and2", src, 0, 2, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ assertEquals("Effected",arm._stdout.toString());
+ }
+
+ @Test
+ public void testCondSideEffOr() throws IOException {
+ String src =
+ """
+ int a = 1;
+ int b = 1;
+
+ int x = -1;
+ int y=1;
+ int z= -1;
+
+ int g = x++ || y++ || z++;
+
+ int switch = 0;
+ if(x == 4 || y == 4 || z == 4) {
+ switch = -1;
+ } else {
+ switch = 1;
+ }
+ int cd = 1;
+ if((x == 0 && y == 1 && z==-1) && switch && g) {
+ sys.io.p("Effected");
+ } else {
+ sys.io.p("Not effected");
+ }
+ return 0;
+ """;
+ TestC.runSF("or2", src, null, "Effected", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build("or2", src, 0, 2, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+ assertEquals("Effected",R5._stdout.toString());
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("or2", src, 0, 2, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ assertEquals("Effected",arm._stdout.toString());
+ }
+
+ // test it with function calls
+ @Test
+ public void testFuncCall() throws IOException {
+ String src =
+ """
+ // -*- mode: java; -*-
+ int a = 1;
+ int b = 1;
+
+ var sq = { int x ->
+ x*x;
+ };
+
+ if(a && sq(0)) {
+ sys.io.p("And");
+ } else {
+ sys.io.p("Or");
+ }
+ return 0;
+ """;
+ TestC.runSF("and3", src, null, "Or", 0);
+
+ // Evaluate on RISC5 emulator
+ EvalRisc5 R5 = TestRisc5.build("and3", src, 0, 2, false);
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,R5.regs[riscv.A0]);
+ assertEquals("Or",R5._stdout.toString());
+
+ // Evaluate on ARM emulator
+ EvalArm64 arm = TestArm64.build("and3", src, 0, 2, false);
+ trap = arm.step(100);
+ assertEquals(0,trap);
+ assertEquals(0,arm.regs[0]);
+ assertEquals("Or",arm._stdout.toString());
+ }
+
+
+ @Test
+ public void testFRefFields() {
+ String src =
+"""
+// A hypothetical scanner class
+struct Scan {
+ int !x;
+ u8[~] !buf;
+ // Skip whitespace
+ val skip = { !Scan s ->
+ while( s.buf[s.x] <= ' ' )
+ s.x++;
+ };
+ // Peek a character; if matched consume it, else false.
+ val peek = { !Scan !s, u8 c ->
+ skip(s);
+ if( s.buf[s.x] != c ) return false;
+ s.x++;
+ return true;
+ };
+};
+!Scan !s = new Scan{ buf = " q"; };
+return Scan.peek(s,'q');
+ """;
+
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("return Phi(Region,0,1);", code._stop.toString());
+ assertEquals("1", Eval2.eval(code, 0));
+ testCPU(src,"x86_64_v2", "Win64" ,23,null);
+ };
+
+ @Test
+ public void testFRefFields2() {
+ String src =
+"""
+// A hypothetical scanner class
+struct Scan {
+ int !x;
+ u8[~] !buf;
+ // Skip whitespace
+ val skip = { ->
+ while( buf[x] <= ' ' )
+ x++;
+ return self;
+ };
+};
+~Scan s = new Scan{ buf = " q"; };
+return s.skip().x;
+ """;
+
+ try { new CodeGen(src).driver(CodeGen.Phase.TypeCheck); fail(); }
+ catch( Exception e ) { org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa")); }
+ };
+
+
+ @Test
+ public void testMethod() {
+ String src =
+"""
+// A hypothetical scanner class
+struct Scan {
+ int !x;
+ u8[~] !buf;
+ // Skip whitespace
+ val skip = { ->
+ while( buf[x] <= ' ' )
+ x++;
+ };
+ val require = { u8 ch ->
+ skip();
+ buf[x++]==ch;
+ };
+};
+!Scan !s = new Scan{ buf = " [1,2]"; };
+return s.require('[');
+ """;
+
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("return (.[]==91);", code._stop.toString());
+ assertEquals("1", Eval2.eval(code, 0));
+ testCPU(src,"x86_64_v2", "Win64" ,4,null);
+ };
+
+
+}