summaryrefslogtreecommitdiff
path: root/chapter21/src/test/java/com/seaofnodes
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-09-20 14:49:32 -0700
committerCliff Click <cliffc@acm.org>2026-09-20 14:49:32 -0700
commit0487ef57a2b7f80fd6f9ed3f6aeea0b0ec88e89f (patch)
treece84334450dbe54ca9ea3c7f39419730b4f86678 /chapter21/src/test/java/com/seaofnodes
parent4f3d4859e9096ab8f15c3e9eb02d154c268b108d (diff)
downloadSimple-0487ef57a2b7f80fd6f9ed3f6aeea0b0ec88e89f.tar.gz
Simple-0487ef57a2b7f80fd6f9ed3f6aeea0b0ec88e89f.zip
Add back copy coaleascing in ch21
Minor arm encoding fix.
Diffstat (limited to 'chapter21/src/test/java/com/seaofnodes')
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java6
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java306
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java55
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java30
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java6
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/MergeSortTest.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/SpillStats.java63
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java6
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/TestC.java13
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java7
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java198
11 files changed, 609 insertions, 83 deletions
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
index 1e1a1dc3..7912929b 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
@@ -16,7 +16,7 @@ public class BrainFuckTest {
String brain_fuck = "Hello World!\n";
TestC.run("brain_fuck", brain_fuck, 40);
- EvalRisc5 R5 = TestRisc5.build("brain_fuck", 0, 28, false);
+ EvalRisc5 R5 = TestRisc5.build("brain_fuck", 0, 211, false);
int trap = R5.step(100000);
assertEquals(0,trap);
int ptr = (int)R5.regs[riscv.A0];
@@ -24,7 +24,7 @@ public class BrainFuckTest {
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", 0, 28, false);
+ EvalArm64 A5 = TestArm64.build("brain_fuck", 0, 34, false);
int trap_arm = A5.step(100000);
assertEquals(0,trap_arm);
assertEquals(0,trap);
@@ -32,7 +32,7 @@ public class BrainFuckTest {
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));
+ assertEquals(brain_fuck.charAt(i), A5.ld1z(ptr_arm+4+i));
}
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index d6bf1f4f..4685e4d5 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -3,15 +3,38 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.print.ASMPrinter;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter20Test {
+ @Test public void testInlinedReturnValue() {
+ String src = """
+struct S { int x; };
+val f = { S s -> s.x = g(); };
+val g = { -> 123; };
+S !s = new S;
+return f(s);
+""";
+ // The entry of g can disappear before its Return is folded into f.
+ // Exercise both worklist orders; deleting the entry does not kill 123.
+ for( int seed=0; seed<64; seed++ ) {
+ CodeGen code = new CodeGen(src,com.seaofnodes.simple.type.TypeInteger.BOT,seed).parse().opto().typeCheck();
+ assertEquals("seed "+seed, "123", Eval2.eval(code,0));
+ }
+ }
+
+
+ @org.junit.Rule public final org.junit.rules.ErrorCollector _errors = new org.junit.rules.ErrorCollector();
+ @Test public void testAllocatorMasks() { com.seaofnodes.simple.codegen.RegAllocTestSupport.masks(); }
+ @Test public void testAllocatorUnion() { com.seaofnodes.simple.codegen.RegAllocTestSupport.union(); }
+ @Test public void testAllocatorCopyClobber() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyClobber(); }
+ @Test public void testAllocatorCommutativePhi() { com.seaofnodes.simple.codegen.RegAllocTestSupport.commutativePhi(); }
+ @Test public void testAllocatorKills() { com.seaofnodes.simple.codegen.RegAllocTestSupport.killWithoutResult(); }
+ @Test public void testAllocatorDependencies() { com.seaofnodes.simple.codegen.RegAllocTestSupport.nullUseMask(); }
+ @Test public void testAllocatorCloneClass() { com.seaofnodes.simple.codegen.RegAllocTestSupport.cloneRegisterClass(); }
+
@Test public void testPrintingRegisters() throws Exception {
com.seaofnodes.simple.codegen.PrintRegTestSupport.check();
}
@@ -25,34 +48,37 @@ public class Chapter20Test {
assertEquals("0", Eval2.eval(code, 2));
}
- static void testCPU( String src, String cpu, String os, int spills, String stop ) {
+ // Collect differences so every target runs; JUnit still fails the test.
+ private void testTarget(String src, String cpu, String os, int spills, String stop) {
+ _errors.checkSucceeds(() -> { testCPU(src,cpu,os,spills,stop); return null; });
+ }
+
+ 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._stop.toString());
+ com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code);
+ SpillStats.record(code,"Chapter20",cpu,os);
+ SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ if( stop!=null ) assertEquals(stop,code._stop.toString());
}
- 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);
+ private void testAllCPUs( String src, int spills, String stop ) {
+ testTarget(src,"x86_64_v2", "SystemV",spills,stop);
+ testTarget(src,"riscv" , "SystemV",spills,stop);
+ testTarget(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];");
+ testTarget("return new u8[arg];","x86_64_v2","SystemV",4,"return [u8];");
+ testTarget("return new u8[arg];","riscv","SystemV",5,"return [u8];");
+ testTarget("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);");
+ testTarget(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));");
+ testTarget(src,"riscv" , "SystemV",0,"return ( arg | #2 );");
+ testTarget(src,"arm" , "SystemV",0,"return (ori,arg);");
}
@Test
@@ -70,53 +96,107 @@ val sqrt = { int x ->
};
return sqrt(arg) + sqrt(arg+2);
""";
- testCPU(src,"x86_64_v2", "Win64" ,23,null);
- testCPU(src,"riscv" , "SystemV",19,null);
- testCPU(src,"arm" , "SystemV",19,null);
+ testTarget(src,"x86_64_v2", "SystemV",23,null);
+ testTarget(src,"riscv" , "SystemV",17,null);
+ testTarget(src,"arm" , "SystemV",18,null);
}
@Test
- public void testNewtonFloat() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp"))
- + "flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);";
- testCPU(src,"x86_64_v2", "SystemV",39,null);
- testCPU(src,"riscv" , "SystemV",17,null);
- testCPU(src,"arm" , "SystemV",18,null);
+ public void testNewtonFloat() {
+ String src =
+"""
+// Newtons approximation to the square root
+val sqrt = { flt x ->
+ flt guess = x;
+ while( 1 ) {
+ flt next = (x/guess + guess)/2;
+ if( next == guess ) return guess;
+ guess = next;
+ }
+};
+flt farg = arg;
+return sqrt(farg) + sqrt(farg+2.0);
+""";
+ testTarget(src,"x86_64_v2", "SystemV",23,null);
+ testTarget(src,"riscv" , "SystemV",18,null);
+ testTarget(src,"arm" , "SystemV",18,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 .[];");
+ testTarget(src,"x86_64_v2","SystemV",3,"return .[];");
+ testTarget(src,"riscv","SystemV",8,"return .[];");
+ testTarget(src,"arm","SystemV",10,"return .[];");
}
-
-
@Test
- public void testArray1() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/array1.smp"));
- testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
- testCPU(src,"riscv" , "SystemV", 8,"return (add,.[],(mul,.[],1000));");
- testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));");
+ 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
+""";
+ testTarget(src,"x86_64_v2", "SystemV",9,"return .[];");
+ testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));");
+ testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));");
}
-
@Test
- public void testString() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp"));
- testCPU(src,"x86_64_v2", "SystemV", 9,null);
- testCPU(src,"riscv" , "SystemV", 3,null);
- testCPU(src,"arm" , "SystemV", 3,null);
+ public void testString() {
+ String src = """
+struct String {
+ u8[] cs;
+ int _hashCode;
+};
+
+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 hashCode = { String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
+
+val _hashCodeString = { String self ->
+ int hash=0;
+ if( self.cs ) {
+ for( int i=0; i< self.cs#; i++ )
+ hash = hash*31 + self.cs[i];
+ }
+ if( !hash ) hash = 123456789;
+ return hash;
+};
+
+String !s = new String { cs = new u8[17]; };
+s.cs[0] = 67; // C
+s.cs[1] = 108; // l
+hashCode(s);
+""";
+ testTarget(src,"x86_64_v2", "SystemV",14,null);
+ testTarget(src,"riscv" , "SystemV", 14,null);
+ testTarget(src,"arm" , "SystemV", 13,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",2,null);
- testCPU(src,"riscv" , "SystemV",2,null);
- testCPU(src,"arm" , "SystemV",2,null);
+ testTarget(src,"x86_64_v2","SystemV",1,null);
+ testTarget(src,"riscv","SystemV",2,null);
+ testTarget(src,"arm","SystemV",2,null);
}
@Test
@@ -134,9 +214,9 @@ if (b2) if (b1) return 1;
if (b1) return 2;
return 0;
""";
- testCPU(src,"x86_64_v2", "SystemV",0,"return Phi(Region,1,2,0);");
- testCPU(src,"riscv" , "SystemV",0,"return Phi(Region,1,2,0);");
- testCPU(src,"arm" , "SystemV",0,"return Phi(Region,1,2,0);");
+ testTarget(src,"x86_64_v2", "SystemV",0,"return Phi(Region,1,2,0);");
+ testTarget(src,"riscv" , "SystemV",0,"return Phi(Region,1,2,0);");
+ testTarget(src,"arm" , "SystemV",0,"return Phi(Region,1,2,0);");
}
@Test
@@ -149,8 +229,126 @@ while (arg > 0) {
}
return arg;
""";
- testCPU(src,"x86_64_v2", "SystemV",3,null);
- testCPU(src,"riscv" , "SystemV",2,null);
- testCPU(src,"arm" , "SystemV",2,null);
+ testTarget(src,"x86_64_v2", "SystemV",3,null);
+ testTarget(src,"riscv" , "SystemV",2,null);
+ testTarget(src,"arm" , "SystemV",2,null);
+ }
+ // Original Chapter 20 allocation workload, kept fixed for cohort comparisons.
+ @Test
+ public void testBrainfuck() {
+ var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(java.nio.charset.StandardCharsets.UTF_8);
+ var encoded = new StringBuilder("u8[] !program = new u8[").append(program.length).append("];");
+ for (int i = 0; i < program.length; i++) {
+ int value = program[i] & 0xFF;
+ encoded.append("program[").append(i).append("] = ").append(value).append(";");
+ }
+
+ String src = encoded + """
+
+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 == 62) {
+ d++;
+ } else if (command == 60) {
+ d--;
+ } else if (command == 43) {
+ data[d]++;
+ } else if (command == 45) {
+ data[d]--;
+ } else if (command == 46) {
+ // 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 == 44) {
+ data[d] = 42;
+ } else if (command == 91) {
+ if (data[d] == 0) {
+ for( int d = 1; d > 0; ) {
+ command = program[++pc];
+ if (command == 91) d++;
+ if (command == 93) d--;
+ }
+ }
+ } else if (command == 93) {
+ if (data[d]) {
+ for( int d = 1; d > 0; ) {
+ command = program[--pc];
+ if (command == 93) d++;
+ if (command == 91) d--;
+ }
+ }
+ }
+}
+return output;
+""";
+ testTarget(src,"x86_64_v2", "SystemV",40,null);
+ testTarget(src,"riscv" , "SystemV",211,null);
+ testTarget(src,"arm" , "SystemV",34,null);
+ //assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000));
}
+ // Original Chapter 20 allocation workload, kept fixed for cohort comparisons.
+ @Test
+ public void testMergeSort() {
+ String src =
+"""
+// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort
+
+val merge_sort = { int[] a, int[] b, int n ->
+ copy_array(a, 0, n, b);
+ split_merge(a, 0, n, 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[] !a = new int[arg];
+int[] !b = new int[a#];
+
+for (int i = 0; i < a#; i++)
+ a[i] = a# - i;
+
+merge_sort(a, b, a#);
+
+return a;
+""";
+ testTarget(src,"x86_64_v2", "SystemV",52,null);
+ testTarget(src,"riscv" , "SystemV",44,null);
+ testTarget(src,"arm" , "SystemV",44,null);
+//assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11));
+ }
+
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java
new file mode 100644
index 00000000..39b410d5
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple;
+
+import java.io.IOException;
+import java.nio.file.*;
+import org.junit.Test;
+
+// Revised Chapter 20 examples introduced with native encoding.
+public class Chapter21AllocTest {
+ @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)/2;
+ if( next == guess ) return guess;
+ guess = next;
+ }
+};
+return sqrt(arg) + sqrt(arg+2);
+""";
+ Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,23,null);
+ Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null);
+ Chapter21Test.testCPU(src,"arm" , "SystemV",19,null);
+ }
+
+ @Test
+ public void testNewtonFloat() throws IOException {
+ String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp"))
+ + "flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);";
+ Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",39,null);
+ Chapter21Test.testCPU(src,"riscv" , "SystemV",17,null);
+ Chapter21Test.testCPU(src,"arm" , "SystemV",18,null);
+ }
+
+ @Test
+ public void testArray1() throws IOException {
+ String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/array1.smp"));
+ Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
+ Chapter21Test.testCPU(src,"riscv" , "SystemV", 8,"return (add,.[],(mul,.[],1000));");
+ Chapter21Test.testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));");
+ }
+
+
+ @Test
+ public void testString() throws IOException {
+ String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp"));
+ Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null);
+ Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null);
+ Chapter21Test.testCPU(src,"arm" , "SystemV", 3,null);
+ }
+
+}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index fd91770a..20d94f92 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -11,6 +11,21 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+ @Test public void testArmSubtractRegisters() {
+ EvalArm64 cpu = new EvalArm64(new byte[16],16);
+ cpu.st4(0,0xCB020020); // SUB X0,X1,X2, no shift or flag update.
+ for( long[] pair : new long[][]{{7,3},{3,7},{Long.MIN_VALUE,1},{1L<<40,3}} ) {
+ cpu._pc = 0;
+ cpu.regs[1] = pair[0]; cpu.regs[2] = pair[1];
+ cpu.N=true; cpu.Z=false; cpu.C=true; cpu.V=true;
+ assertEquals(0,cpu.step(1));
+ assertEquals(pair[0]-pair[1],cpu.regs[0]);
+ assertTrue(cpu.N); assertFalse(cpu.Z); assertTrue(cpu.C); assertTrue(cpu.V);
+ }
+ }
+
+
+ @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); }
@Test public void testRisc64BitStore() {
byte[] mem = new byte[24];
EvalRisc5 cpu = new EvalRisc5(mem,mem.length);
@@ -50,10 +65,9 @@ public class Chapter21Test {
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);
+ com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code);
+ SpillStats.record(code,"Chapter21",cpu,os);
+ SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
if( stop != null )
assertEquals(stop, code._stop.toString());
}
@@ -148,11 +162,11 @@ public class Chapter21Test {
String sprimes = sb.p("]").toString();
// Compile, link against native C; expect the above string of primes to be printed out by C
- TestC.run("sieve",sprimes, 257);
+ TestC.run("sieve",sprimes, 178);
// Evaluate on RISC5 emulator; expect return of an array of primes in
// the simulated heap.
- EvalRisc5 R5 = TestRisc5.build("sieve", 100, 160, false);
+ EvalRisc5 R5 = TestRisc5.build("sieve", 100, 89, false);
int trap = R5.step(10000);
assertEquals(0,trap);
// Return register A0 holds sieve(100)
@@ -164,7 +178,7 @@ public class Chapter21Test {
// Evaluate on ARM5 emulator; expect return of an array of primes in
// the simulated heap.
- EvalArm64 A5 = TestArm64.build("sieve", 100, 160, false);
+ EvalArm64 A5 = TestArm64.build("sieve", 100, 93, false);
int trap_arm = A5.step(10000);
assertEquals(0, trap_arm);
int ary_arm = (int)A5.regs[arm.X0];
@@ -243,7 +257,7 @@ public class Chapter21Test {
// than what Win64 allows - so Win64 gets a lot more spills here.
String arg_count = "191.000000\n";
TestC.run("arg_count", arg_count,
- TestC.CALL_CONVENTION.equals("Win64") ? 42 : 15);
+ TestC.CALL_CONVENTION.equals("Win64") ? 32 : 15);
EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", 0, 0, false);
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
index c10eccde..99807345 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -628,13 +628,13 @@ public class EvalArm64 {
rdid = -1;
break;
}
- case 0x8B: {
- // add(shifted register)
+ case 0x8B, 0xCB: {
+ // add/sub(register), unshifted form emitted by Simple
int rn = (ir >> 5) & 0x1F;
int rm = (ir >> 16) & 0x1F;
long lhs = regs[rn];
long rhs = regs[rm];
- rval = lhs + rhs;
+ rval = opcode1==0x8B ? lhs + rhs : lhs - rhs;
break;
}
case 0x91: {
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/chapter21/src/test/java/com/seaofnodes/simple/MergeSortTest.java
index a1004fb5..82b1e58e 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/MergeSortTest.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/MergeSortTest.java
@@ -17,7 +17,7 @@ public class MergeSortTest {
for( int prime : primes )
sb.p(prime).p(", ");
String sprimes = sb.p("]").toString();
- TestC.run("sort", sprimes, 40);
+ TestC.run("sort", sprimes, 34);
EvalRisc5 R5 = TestRisc5.build("src/test/java/com/seaofnodes/simple/progs", "sort", "merge_sort", 0, 36, false);
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/SpillStats.java b/chapter21/src/test/java/com/seaofnodes/simple/SpillStats.java
new file mode 100644
index 00000000..318a6e33
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/SpillStats.java
@@ -0,0 +1,63 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.runner.*;
+import org.junit.runner.notification.*;
+
+// Run existing tests and sum actual allocations, including ones without goldens.
+// Helpers call record after allocation; ordinary JUnit runs collect nothing.
+public class SpillStats extends RunListener {
+ private static SpillStats ACTIVE;
+ private Description _test;
+ private int _qualityFailures;
+ private final Map<String,long[]> _totals = new TreeMap<>();
+
+ @Override public void testStarted(Description test) { _test = test; }
+ public static void record(CodeGen code, String cohort, String cpu, String abi) {
+ if( ACTIVE==null ) return;
+ String test = ACTIVE._test.getTestClass().getSimpleName()+"."+ACTIVE._test.getMethodName();
+ for( String key : new String[]{cohort+","+cpu+","+abi,cohort+",ALL,ALL"} ) {
+ long[] sum = ACTIVE._totals.computeIfAbsent(key,k -> new long[3]);
+ sum[0]++; sum[1] += code._regAlloc._spills; sum[2] += code._regAlloc._spillScaled;
+ }
+ System.out.println("allocation,"+cohort+","+test+","+cpu+","+abi+","+
+ code._regAlloc._spills+","+code._regAlloc._spillScaled);
+ }
+
+ // Keep running every CPU and native result check when measuring quality.
+ // A changed golden still makes spill-stats fail at the end.
+ public static void checkSpills(int expected, int actual) {
+ if( expected==-1 ) return;
+ try { org.junit.Assert.assertEquals("Expect spills:",expected,actual,Math.max(1,expected>>3)); }
+ catch( AssertionError error ) {
+ if( ACTIVE==null ) throw error;
+ ACTIVE._qualityFailures++;
+ System.err.println(ACTIVE._test+": "+error.getMessage());
+ }
+ }
+
+ public static void main(String[] args) throws ClassNotFoundException {
+ if( args.length==0 ) args = new String[]{"Chapter20Test","Chapter21Test","Chapter21AllocTest","BrainFuckTest","MergeSortTest"};
+ Class<?>[] tests = new Class<?>[args.length];
+ for( int i=0; i<args.length; i++ )
+ tests[i] = Class.forName("com.seaofnodes.simple."+args[i]);
+ ACTIVE = new SpillStats();
+ JUnitCore junit = new JUnitCore();
+ junit.addListener(ACTIVE);
+ Result result = junit.run(tests);
+ boolean passed = result.wasSuccessful() && ACTIVE._qualityFailures==0;
+ if( !passed ) System.out.println("FAILED test run: totals are not a complete comparison.");
+ System.out.println("cohort,cpu,abi,allocations,splits,scaled");
+ for( var row : ACTIVE._totals.entrySet() ) {
+ long[] sum = row.getValue();
+ System.out.println(row.getKey()+","+sum[0]+","+sum[1]+","+sum[2]);
+ }
+ for( Failure failure : result.getFailures() ) System.err.println(failure.getTrace());
+ System.out.println("Tests: "+result.getRunCount()+", failures: "+result.getFailureCount());
+ System.out.println("Spill golden failures: "+ACTIVE._qualityFailures);
+ ACTIVE = null;
+ if( !passed ) System.exit(1);
+ }
+}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java b/chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java
index 5d7d1361..39676d3a 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java
@@ -22,12 +22,12 @@ public class TestArm64 {
// Compile and export Simple
String src = Files.readString(Path.of(dir+"/"+file+".smp"));
CodeGen code = new CodeGen(src).driver("arm", "SystemV",null);
+ com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code);
+ SpillStats.record(code,"Chapter21","arm","SystemV");
if( print ) { code.print_as_hex(); System.out.print(code.asm()); }
// Allocation quality not degraded
- int delta = spills>>3;
- if( delta == 0 ) delta =1;
- if( spills != -1 ) assertEquals("Expect spills:", spills, code._regAlloc._spillScaled, delta);
+ SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
// Image
byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/TestC.java b/chapter21/src/test/java/com/seaofnodes/simple/TestC.java
index 5609758b..b3e21ea7 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/TestC.java
@@ -43,6 +43,8 @@ public abstract class TestC {
String obj = bin+".o";
// Compile simple, emit ELF
CodeGen code = new CodeGen(src).driver( CPU_PORT, simple_conv, obj);
+ com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code);
+ SpillStats.record(code,"Chapter21",CPU_PORT,simple_conv);
// Compile the C program
var params = new String[] {
@@ -58,8 +60,8 @@ public abstract class TestC {
"CALL_CONV="+c_conv,
};
Process gcc = new ProcessBuilder(params).redirectErrorStream(true).start();
- byte error;
- try { error = (byte)gcc.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); }
+ int error;
+ try { error = gcc.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); }
String result = new String(gcc.getInputStream().readAllBytes());
if( error!=0 ) {
System.err.println("gcc error code: "+error);
@@ -70,7 +72,7 @@ public abstract class TestC {
// Execute results
Process smp = new ProcessBuilder(bin).redirectErrorStream(true).start();
- try { error = (byte)smp.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); }
+ try { error = smp.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); }
result = new String(smp.getInputStream().readAllBytes());
if( error!=0 ) {
System.err.println("exec error code: "+error);
@@ -80,10 +82,7 @@ public abstract class TestC {
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);
+ SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
}
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java b/chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java
index 041ed833..36c5fb50 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/TestRisc5.java
@@ -21,13 +21,12 @@ public abstract class TestRisc5 {
// Compile and export Simple
String src = Files.readString(Path.of(dir+"/"+file+".smp"));
CodeGen code = new CodeGen(src).driver("riscv", "SystemV",null);
+ com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code);
+ SpillStats.record(code,"Chapter21","riscv","SystemV");
if( print ) { code.print_as_hex(); System.out.print(code.asm()); }
// Allocation quality not degraded
- int delta = spills>>3;
- if( delta==0 ) delta = 1;
- if( spills != -1 )
- assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
// Image
byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter21/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
new file mode 100644
index 00000000..8c8f0f98
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
@@ -0,0 +1,198 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2;
+import com.seaofnodes.simple.type.TypeInteger;
+import static org.junit.Assert.*;
+
+// Small scheduled machine graphs isolate register constraints from peepholes.
+public class RegAllocTestSupport {
+ private static final RegMask A = new RegMask(0), B = new RegMask(1);
+ private static class Op extends MachConcreteNode {
+ final RegMask _def, _use, _kill;
+ final boolean _clone;
+ Op(RegMask def, RegMask use, RegMask kill, boolean clone, Node... ins) {
+ super(ins); _def=def; _use=use; _kill=kill; _clone=clone; _type=TypeInteger.BOT;
+ }
+ @Override public String op() { return "testOp"; }
+ @Override public RegMask outregmap() { return _def; }
+ @Override public RegMask regmap(int i) { return _use; }
+ @Override public RegMask killmap() { return _kill; }
+ @Override public boolean isClone() { return _clone; }
+ @Override public Node copy() { return new Op(_def,_use,_kill,_clone,(Node)null); }
+ @Override public void encoding(Encoding enc) { throw new AssertionError(); }
+ }
+ private static CodeGen graph() {
+ CodeGen code = new CodeGen("");
+ code._mach = new x86_64_v2(code);
+ code._cfg.add(code._start);
+ return code;
+ }
+
+ public static void masks() {
+ for( int reg : new int[]{0,63,64,65,127} ) {
+ RegMask mask = new RegMask(reg);
+ assertEquals(reg,mask.firstReg());
+ assertEquals(1,mask.size());
+ }
+ assertEquals(-1,new RegMask(0L).firstReg());
+ assertFalse(new RegMask(0L).size1());
+ RegMask boundary = new RegMask(Long.MIN_VALUE,Long.MIN_VALUE|1);
+ assertEquals(64,boundary.nextReg((short)63));
+ assertEquals(127,boundary.nextReg((short)64));
+ assertEquals(-1,boundary.nextReg((short)127));
+ }
+
+ public static void union() {
+ CodeGen code = graph();
+ Op use = new Op(null,B,null,false,code._start);
+ LRG a = new LRG((short)1), b = new LRG((short)2);
+ a._mask = new RegMask(3L);
+ b._mask = new RegMask(6L);
+ b.machUse(use,(short)2,true);
+ LRG both = a.union(b);
+ assertNotNull(both._mask);
+ assertEquals(1,both._mask.firstReg());
+ assertEquals(1,both._mask.size());
+ assertSame(use,both._machUse);
+ assertEquals(2,both._uidx);
+ }
+
+ public static void killWithoutResult() {
+ CodeGen code = graph();
+ Op def = new Op(A,null,null,false,code._start);
+ new Op(null,null,A,false,code._start); // Clobbers A, produces no value.
+ new Op(null,A,null,false,code._start,def);
+ RegAlloc alloc = new RegAlloc(code);
+ assertTrue(BuildLRG.run(0,alloc));
+ assertFalse("A live value cannot survive a register kill",IFG.build(0,alloc));
+ }
+
+ public static void nullUseMask() {
+ CodeGen code = graph();
+ Op def = new Op(A,null,null,false,code._start);
+ new Op(null,null,null,false,code._start,def); // Scheduling dependency only.
+ RegAlloc alloc = new RegAlloc(code);
+ assertTrue(BuildLRG.run(0,alloc));
+ assertTrue(IFG.build(0,alloc));
+ }
+
+ public static void cloneRegisterClass() {
+ for( RegMask required : new RegMask[]{B,new RegMask(6L)} ) {
+ CodeGen code = graph();
+ Op def = new Op(A,null,null,true,code._start);
+ Op use = new Op(null,required,null,false,code._start,def);
+ Op use2 = new Op(null,required,null,false,code._start,def);
+ RegAlloc alloc = new RegAlloc(code);
+ alloc.regAlloc(); // Re-cloning an A-only value cannot satisfy B/C uses.
+ assertEquals(0,alloc.regnum(def));
+ assertTrue(required.test(alloc.regnum(use.in(1))));
+ assertTrue(required.test(alloc.regnum(use2.in(1))));
+ assertTrue(use.in(1) instanceof SplitNode);
+ }
+ }
+
+ public static void commutativePhi() {
+ CodeGen code = graph();
+ Op a = new Op(A,null,null,false,code._start);
+ Op b = new Op(A,null,null,false,code._start);
+ Op add = new Op(A,A,null,false,code._start,a,b) {
+ @Override public boolean commutes() { return true; }
+ };
+ RegionNode region = new RegionNode(null,code._start,code._start);
+ new PhiNode("sum",TypeInteger.BOT,region,add,a);
+ code._cfg.add(region);
+ assertTrue(BuildLRG.run(0,new RegAlloc(code)));
+ }
+
+ public static void checkRegisters(CodeGen code) {
+ assertTrue(code._regAlloc.verifyFunctionLocalEdges());
+ for( CFGNode bb : code._cfg )
+ for( Node n : bb._outputs ) {
+ if( n instanceof MachNode mach ) {
+ if( code._regAlloc.regnum(n)>=0 && mach.outregmap()!=null )
+ assertTrue(n.getClass().getSimpleName()+"#"+n._nid,code._regAlloc.regnum(n)>=0 && mach.outregmap().test(code._regAlloc.regnum(n)));
+ for( int i=n instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ )
+ if( n.in(i)!=null && code._regAlloc.regnum(n.in(i))>=0 && mach.regmap(i)!=null )
+ assertTrue(n.getClass().getSimpleName()+"#"+n._nid+" input "+i,code._regAlloc.regnum(n.in(i))>=0 && mach.regmap(i).test(code._regAlloc.regnum(n.in(i))));
+ if( mach.twoAddress()!=0 )
+ assertEquals(code._regAlloc.regnum(n),code._regAlloc.regnum(n.in(mach.twoAddress())));
+ }
+ if( n instanceof PhiNode && code._regAlloc.regnum(n)>=0 )
+ for( int i=n instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ )
+ assertEquals(code._regAlloc.regnum(n),code._regAlloc.regnum(n.in(i)));
+ }
+ }
+
+ public static void copyClobber() throws Exception {
+ for( boolean killed : new boolean[]{false,true} ) {
+ CodeGen code = graph();
+ RegAlloc alloc = new RegAlloc(code);
+ Op def = new Op(A,null,null,false,code._start);
+ alloc.newLRG(def)._reg=0;
+ SplitNode hi = code._mach.split("test",(byte)0,null);
+ hi.setDef(0,code._start); hi.setDef(1,def);
+ alloc.newLRG(hi)._reg=1;
+ new Op(null,null,killed ? A : null,false,code._start);
+ SplitNode lo = code._mach.split("test",(byte)0,null);
+ lo.setDef(0,code._start); lo.setDef(1,hi);
+ alloc.newLRG(lo)._reg=0;
+ Op use = new Op(null,A,null,false,code._start,lo);
+ var post = RegAlloc.class.getDeclaredMethod("postColor");
+ post.setAccessible(true); post.invoke(alloc);
+ assertSame(killed ? lo : def,use.in(1));
+ }
+ CodeGen pre = graph();
+ RegAlloc uncolored = new RegAlloc(pre);
+ Op def0 = new Op(A,null,null,false,pre._start);
+ uncolored.newLRG(def0)._mask=A;
+ Op fixed = new Op(A,null,null,false,pre._start);
+ uncolored.newLRG(fixed)._mask=A;
+ SplitNode copy = pre._mach.split("test",(byte)0,null);
+ copy.setDef(0,pre._start); copy.setDef(1,def0);
+ assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy));
+ }
+
+ public static void coalescing() {
+ // Merge, incompatible masks, interference, capacity, and adjacency remapping.
+ for( int kind=0; kind<5; kind++ ) {
+ CodeGen code = graph();
+ RegAlloc alloc = new RegAlloc(code);
+ Op def = new Op(new RegMask(3L),null,null,false,code._start);
+ SplitNode copy = code._mach.split("test",(byte)0,null);
+ copy.setDef(0,code._start); copy.setDef(1,def);
+ Op use = new Op(null,new RegMask(3L),null,false,code._start,copy);
+ LRG from = alloc.newLRG(def), to = alloc.newLRG(copy);
+ from._mask = kind==1 ? A : new RegMask(3L);
+ to._mask = kind==1 ? B : new RegMask(3L);
+ from.machDef(def,false); from.machUse(copy,(short)1,false);
+ to.machDef(copy,false); to.machUse(use,(short)1,false);
+ LRG left = new LRG((short)100), right = new LRG((short)101);
+ if( kind==2 ) { from.addNeighbor(to); to.addNeighbor(from); }
+ if( kind>=3 ) {
+ from.addNeighbor(left); left.addNeighbor(from);
+ if( kind==3 ) { to.addNeighbor(right); right.addNeighbor(to); }
+ else { to.addNeighbor(left); left.addNeighbor(to); }
+ }
+ int fcnt=from.nadj(), tcnt=to.nadj();
+ assertTrue(Coalesce.coalesce(0,alloc));
+ if( kind==0 || kind==4 ) {
+ assertSame(def,use.in(1));
+ assertSame(alloc.lrg(def),alloc.lrg(copy));
+ assertSame(use,alloc.lrg(def)._machUse);
+ assertEquals(1,alloc.lrg(def)._uidx);
+ if( kind==4 ) {
+ assertEquals(1,left.nadj());
+ assertSame(alloc.lrg(def),left._adj.at(0));
+ assertSame(left,alloc.lrg(def)._adj.at(0));
+ }
+ } else {
+ assertSame(copy,use.in(1));
+ assertNotSame(alloc.lrg(def),alloc.lrg(copy));
+ assertEquals(fcnt,from.nadj());
+ assertEquals(tcnt,to.nadj());
+ }
+ }
+ }
+
+}