summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-07 21:31:18 -0700
committerCliff Click <cliffc@acm.org>2026-10-07 21:31:18 -0700
commit87a75c83b75480e65147a4d425fddd14a05df4d9 (patch)
treef486050aa3a13e218ad17ab57efaccbe35336483
parent2eba63966bdc3d9cfdf41ee085f35cbb6d529bbc (diff)
downloadSimple-87a75c83b75480e65147a4d425fddd14a05df4d9.tar.gz
Simple-87a75c83b75480e65147a4d425fddd14a05df4d9.zip
More test case vs RA cleanup
Missing a few risc/arm encodings. Make private many tests, so they don't escape and the escaped fcn inflate x86 spill numbers
-rw-r--r--build-support/AllocMetrics.java.in176
-rw-r--r--build-support/capture-alloc-sources.py28
-rw-r--r--build-support/measure-alloc.py41
-rw-r--r--build-support/spill-matrix.py75
-rw-r--r--chapter20/README.md16
-rw-r--r--chapter21/README.md20
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java4
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java4
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java3
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java6
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter22/README.md20
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java4
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java4
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java3
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java6
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter23/README.md22
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java4
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java4
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java3
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java6
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter24/README.md24
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java4
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java3
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java6
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java22
-rw-r--r--chapter25/README.md26
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java24
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp13
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp8
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp3
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp13
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp6
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp6
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp6
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp8
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp40
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/README.md13
-rw-r--r--docs/chapter-backports.md68
-rw-r--r--docs/regalloc-spills.md226
-rw-r--r--isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java16
-rw-r--r--isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java17
-rw-r--r--isa/src/test/java/com/seaofnodes/isa/RiscTest.java41
-rw-r--r--skills/chapter25-codex-notes.md18
62 files changed, 829 insertions, 284 deletions
diff --git a/build-support/AllocMetrics.java.in b/build-support/AllocMetrics.java.in
new file mode 100644
index 00000000..bcf97f0d
--- /dev/null
+++ b/build-support/AllocMetrics.java.in
@@ -0,0 +1,176 @@
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.isa.eval.*;
+import java.nio.file.*;
+import java.util.*;
+
+// Compiled separately against each chapter. No compiler or emulator mutation.
+public class Measure {
+ static final int CH=CHAPTER;
+ static int start(Encoding e,Node n) { return OPSTART; }
+ static int length(Encoding e,Node n) { return OPLEN & 255; }
+ static final Map<String,Type> returns=new HashMap<>();
+ static CodeGen compile(String src,String cpu) {
+ CodeGen c=CONSTRUCTOR.driver(CodeGen.Phase.TypeCheck);
+ EXTERNDATA
+ returns.clear();
+ c._stop.walk(n->{if(n instanceof FunNode f && f.ret()!=null)returns.put(f._name,f.ret().expr()._type);return null;});
+ RESUME
+ }
+ static byte[] image(CodeGen c) throws Exception {
+ c.encode();
+ var externalField=Encoding.class.getDeclaredField("_externals");externalField.setAccessible(true);
+ @SuppressWarnings("unchecked") var externals=(Map<Node,String>)externalField.get(c._encoding);
+ var extra=new LinkedHashMap<Node,String>();
+ for(var e:externals.entrySet())if(!e.getValue().equals("calloc")&&!e.getValue().equals("write")){extra.put(e.getKey(),e.getValue());e.setValue("write");}
+ EXPORT;
+ for(var e:extra.entrySet()){
+ Node n=e.getKey();String name=e.getValue();externals.put(n,name);
+ int target=name.equals("read")?-12:name.equals("errno")?0x3808:-16;
+ ((RIPRelSize)n).patch(c._encoding,start(c._encoding,n),(byte)length(c._encoding,n),target-start(c._encoding,n));
+ }
+ byte[] image=new byte[16<<20];
+ var e=c._encoding;
+ System.arraycopy(e.bits(),0,image,0,e._bits.size());
+ COPYPOOLS
+ return image;
+ }
+ static class CPU {
+ EvalRisc5 r; EvalArm64 a; long[] regs; double[] fregs; byte[] mem;
+ long ops,ra; BitSet added; int inputPos;
+ final byte[] input="[4, 3, 2, 1]".getBytes(java.nio.charset.StandardCharsets.UTF_8);
+ CPU(String cpu,byte[] img,BitSet additions) {
+ mem=img; added=additions;
+ if(cpu.equals("riscv")) { r=new EvalRisc5(img,1<<20); regs=r.regs; fregs=r.fregs; }
+ else { a=new EvalArm64(img,1<<20); regs=a.regs; fregs=a.fregs; }
+ }
+ int base(){return r!=null?10:0;}
+ int pc(){return r!=null?r._pc:a._pc;}
+ void pc(int p){if(r!=null)r._pc=p;else a._pc=p;}
+ long load(int p,int sz){long x=0;for(int i=0;i<sz;i++)x|=(mem[p+i]&255L)<<(8*i);return x;}
+ void store(int p,long x,int sz){for(int i=0;i<sz;i++)mem[p+i]=(byte)(x>>(8*i));}
+ String stdout(){var s=r!=null?r._stdout:a._stdout;return s==null?"":s.toString(java.nio.charset.StandardCharsets.UTF_8);}
+ void run(int entry) {
+ pc(entry);regs[r!=null?1:30]=0;
+ for(int i=0;i<2_000_000;i++) {
+ int p=pc(),ir=(int)load(p,4);
+ int target=Integer.MAX_VALUE;
+ if(r!=null) {
+ if((ir&127)==0x67)target=(int)((regs[(ir>>>15)&31]+(ir>>20))&~1L);
+ else if((ir&127)==0x6f){int d=((ir>>31)<<20)|((ir>>>21&1023)<<1)|((ir>>>20&1)<<11)|(ir&0xff000);target=p+d;}
+ }else if((ir&0xfc000000)==0x94000000)target=p+((ir<<6)>>4);
+ else if((ir&0xfffffc1f)==0xd63f0000)target=(int)regs[(ir>>>5)&31];
+ if(target==-12) {
+ int len=Math.min((int)regs[base()+2],input.length-inputPos);
+ System.arraycopy(input,inputPos,mem,(int)regs[base()+1],len);inputPos+=len;
+ regs[base()]=len;regs[r!=null?1:30]=p+4;pc(p+4);
+ ops++;if(added.get(p))ra++;continue;
+ }
+ boolean ret=r!=null
+ ? (ir&127)==0x67 && ((regs[(ir>>>15)&31]+(ir>>20))&~1L)==0
+ : (ir&0xfffffc1f)==0xd65f0000 && regs[(ir>>>5)&31]==0;
+ int trap=r!=null?r.step(1):a.step(1);
+ if(trap!=0)throw new IllegalStateException("trap="+trap+" pc="+p+" ir="+Integer.toHexString(ir));
+ if((pc()&3)!=0)throw new IllegalStateException("unaligned next pc="+pc()+" from="+p+" ir="+Integer.toHexString(ir)+" rpc="+regs[r!=null?1:30]);
+ ops++;if(added.get(p))ra++;
+ if(ret)return;
+ }
+ throw new IllegalStateException("instruction budget exhausted");
+ }
+ }
+ static String result(CPU cpu,Type t,long x,int depth) {
+ if(depth>4)return "...";
+ if(t instanceof TypeInteger ti) return Long.toString(x);
+ if(t instanceof TypeFloat) return Double.toString(Double.longBitsToDouble(x));
+ if(t instanceof TypeMemPtr p) {
+ if(x==0)return "null";
+ TypeStruct s=p._obj;int addr=(int)x;
+ if(s.isAry()) {
+ int len=(int)cpu.load(addr,4);
+ if(len<0||len>10000)throw new AssertionError("array length "+len);
+ int scale=s.aryScale(),base=s.aryBase();
+ var out=new StringBuilder("[");
+ for(int i=0;i<len;i++)out.append(cpu.load(addr+base+(i<<scale),1<<scale)).append(',');
+ return out.append(']').toString();
+ }
+ var out=new StringBuilder("{");
+ for(int i=0;i<s._fields.length;i++) {
+ Type ft=s._fields[i]._t;
+ out.append(s._fields[i]._fname).append('=');
+ out.append(result(cpu,ft,cpu.load(addr+s.offset(i),1<<ft.log_size()),depth+1)).append(',');
+ }
+ return out.append('}').toString();
+ }
+ return Long.toString(x);
+ }
+ public static void main(String[] args)throws Exception {
+ Path index=Path.of(args[0]);
+ int attempts=0, failures=0;
+ for(String line:Files.readAllLines(index)) {
+ if(line.startsWith("#")||line.isBlank())continue;
+ String[] row=line.split("\t");
+ String cohort=row[0],test=row[1],cpu=row[2],file=row[4];
+ if(!cpu.equals("arm")&&!cpu.equals("riscv"))continue;
+ String shortTest=test.substring(test.indexOf('.')+1);
+ String filter=System.getenv("DYNAMIC_CASE");
+ if(filter!=null&&!test.equals(filter))continue;
+ // Inputs are fixed per program and shared across compiler chapters.
+ String entry=switch(shortTest){case "testFibExport"->"fib";case "testNewtonExport"->"test_sqrt";case "testSieve"->"sieve";case "testBubbleSort"->"main";default->"";};
+ boolean skip=switch(shortTest){case "testString","testStringExport","testFltArg","testInfinite","testInfiniteReturn","testPerson","testArgCount","testStackArguments","testMergeSort0","testBrainFuck"->true;default->false;};
+ if(skip){System.out.println("skip,"+cohort+","+test+","+cpu+",needs driver or does not terminate");continue;}
+ long[] inputs=switch(shortTest){case "testAlloc2"->new long[]{0,1,2};case "testFibExport"->new long[]{9};case "testSieve"->new long[]{100};case "testNewtonExport","testNewtonFloat","testNewtonInteger","testArray1","testMergeSort","testAlloc0"->new long[]{16};case "testStack13","testFlags1","testFlags2","testCast","testAndPtr","testOrRhsLoopLocal","testOrRhsLoopMemory"->new long[]{0,1,2,3};default->new long[]{0};};
+ attempts++;
+ try {
+ CodeGen c=compile(Files.readString(index.getParent().resolve(file)),cpu);
+ Set<Node> before=Collections.newSetFromMap(new IdentityHashMap<>());
+ for(var bb:c._cfg){before.add(bb);for(Node n:bb._outputs)before.add(n);}
+ c.regAlloc();
+ Set<Node> added=Collections.newSetFromMap(new IdentityHashMap<>());
+ for(var bb:c._cfg)for(Node n:bb._outputs)
+ if(n instanceof SplitNode || (!before.contains(n)&&n instanceof MachNode m&&m.isClone()))added.add(n);
+ byte[] img=image(c);
+ if(filter!=null)System.out.println(c.asm());
+ BitSet pcs=new BitSet();
+ for(Node n:added){int s=start(c._encoding,n),len=length(c._encoding,n);if(s>=0&&len>0)pcs.set(s,s+len);}
+ FunNode init=null,fun=null;
+ for(var bb:c._cfg)if(bb instanceof FunNode f){
+ if(f._name.equals(CH==25?"class:Test.<clinit>":"main"))init=f;
+ if(f._name.equals(entry))fun=f;
+ }
+ if(entry.isEmpty())fun=init;
+ if(fun==null)throw new IllegalStateException("No entry "+entry);
+ Type ret=returns.get(fun._name);
+ long ops=0,ra=0;var results=new ArrayList<String>();
+ for(long arg:inputs){
+ CPU machine=new CPU(cpu,img.clone(),pcs);
+ if(shortTest.equals("testExternDataEncoding"))machine.store(0x3800,-7,4);
+ if(!entry.isEmpty()&&init!=null&&init!=fun)machine.run(start(c._encoding,init));
+ for(Node n:fun._outputs)if(n instanceof ParmNode parm && n instanceof MachNode m && !parm.label().equals("$rpc")) {
+ if(parm._type instanceof TypeInteger)machine.regs[m.outregmap().firstReg()]=arg;
+ if(parm._type instanceof TypeFloat)machine.fregs[m.outregmap().firstReg()-32]=arg;
+ }
+ machine.run(start(c._encoding,fun));
+ long value=ret instanceof TypeFloat?Double.doubleToRawLongBits(machine.fregs[cpu.equals("riscv")?10:0]):machine.regs[machine.base()];
+ switch(shortTest) {
+ case "testNewtonInteger": if(value!=8)throw new AssertionError("Newton integer result");break;
+ case "testNewtonFloat": if(!(Math.abs(Double.longBitsToDouble(value)-(4+Math.sqrt(18)))<=1e-12))throw new AssertionError("Newton float result");break;
+ case "testNewtonExport": if(!(Math.abs(Double.longBitsToDouble(value)-4)<=1e-12))throw new AssertionError("Newton export result");break;
+ case "testStack13": if(value!=(arg==0?1:0))throw new AssertionError("Nested equality result");break;
+ case "testFlags1": if(value!=(arg==1?2:0))throw new AssertionError("Equality result for "+arg+": "+value);break;
+ case "testArray1": if(value!=1006)throw new AssertionError("Array result");break;
+ case "testFibExport": if(value!=55)throw new AssertionError("Fibonacci result");break;
+ case "testOrRhsLoopLocal", "testOrRhsLoopMemory": if(value!=((arg&1)==0?44:1))throw new AssertionError("Short-circuit result");break;
+ case "testBubbleSort": if(value!=0||!machine.stdout().equals("[1, 2, 3, 4]"))throw new AssertionError("Bubble Sort result: "+value+" stdout="+machine.stdout());break;
+ case "testExternDataEncoding": if(value!=-4||machine.load(0x3800,4)!=0xfffffffcL)throw new AssertionError("C data result");break;
+ }
+ results.add(result(machine,ret,value,0)+"/"+machine.stdout().replace("\n","\\n").replace(",",";"));
+ ops+=machine.ops;ra+=machine.ra;
+ }
+ System.out.println("dynamic,"+cohort+","+test+","+cpu+","+inputs.length+","+ops+","+ra+","+String.join("|",results).replace(",",";")+","+Arrays.toString(inputs).replace(",",";"));
+ }catch(Throwable e){failures++;System.out.println("FAILED,"+cohort+","+test+","+cpu+","+e); e.printStackTrace(System.err);}
+ }
+ System.out.println("Dynamic cases: "+attempts+"; failures: "+failures);
+ if(failures!=0)System.exit(1);
+ }
+}
diff --git a/build-support/capture-alloc-sources.py b/build-support/capture-alloc-sources.py
new file mode 100644
index 00000000..f474ac55
--- /dev/null
+++ b/build-support/capture-alloc-sources.py
@@ -0,0 +1,28 @@
+"""Capture historical spill sources with a temporary instrumented test listener."""
+from pathlib import Path
+import subprocess,sys,os
+root=Path(__file__).resolve().parents[1]
+ch=int(sys.argv[1]); chapter=root/f'chapter{ch}'
+if ch not in range(21,25): raise SystemExit("Capture supports chapters 21-24")
+out=root/'build/dynamic-stats'/str(ch); out.mkdir(parents=True,exist_ok=True)
+src=(chapter/'src/test/java/com/seaofnodes/simple/SpillStats.java').read_text()
+needle='String test = ACTIVE._test.getTestClass().getSimpleName()+"."+ACTIVE._test.getMethodName();'
+extra='''
+ try {
+ var dir=java.nio.file.Path.of("../build/dynamic-stats/CH/captured");
+ java.nio.file.Files.createDirectories(dir);
+ String name=test+"-"+cpu+"-"+abi+"-"+(_capture++)+".smp";
+ java.nio.file.Files.writeString(dir.resolve(name),code._src);
+ java.nio.file.Files.writeString(dir.resolve("index.tsv"),cohort+"\\t"+test+"\\t"+cpu+"\\t"+abi+"\\t"+name+"\\n",java.nio.file.StandardOpenOption.CREATE,java.nio.file.StandardOpenOption.APPEND);
+ } catch(java.io.IOException e) { throw new RuntimeException(e); }
+'''.replace('CH',str(ch))
+assert needle in src
+src=src.replace(needle,needle+extra).replace('private static SpillStats ACTIVE;','private static SpillStats ACTIVE;\n private static int _capture;')
+(out/'SpillStats.java').write_text(src,newline='\n')
+cp=os.pathsep.join([str(chapter/'build/classes/main'),str(chapter/'build/classes/test'),str(chapter/'lib/*')])
+subprocess.run(['javac','-cp',cp,'-d',str(out),str(out/'SpillStats.java')],check=True)
+idx=out/'captured/index.tsv'
+if idx.exists(): idx.write_text('')
+with (out/'capture.log').open('w') as log:
+ subprocess.run(['java','-ea','-cp',str(out)+os.pathsep+cp,'com.seaofnodes.simple.SpillStats'],cwd=chapter,stdout=log,stderr=subprocess.STDOUT,check=True)
+print(ch,'captured',len(idx.read_text().splitlines()))
diff --git a/build-support/measure-alloc.py b/build-support/measure-alloc.py
new file mode 100644
index 00000000..61ae31e2
--- /dev/null
+++ b/build-support/measure-alloc.py
@@ -0,0 +1,41 @@
+"""Compile the shared measurement harness against one chapter's existing classes."""
+from pathlib import Path
+import json,os,re,subprocess,sys
+root=Path(__file__).resolve().parents[1]; ch=int(sys.argv[1]); chapter=root/f'chapter{ch}'
+if ch not in range(21,26): raise SystemExit("Dynamic measurement supports chapters 21-25")
+out=root/'build/dynamic-stats'/str(ch);out.mkdir(parents=True,exist_ok=True)
+src=(root/'build-support/AllocMetrics.java.in').read_text()
+subs={'CHAPTER':str(ch),'OPSTART':'e.opStart(n)' if ch==25 else 'e._opStart[n._nid]',
+ 'OPLEN':'e.opLen(n)' if ch==25 else 'e._opLen[n._nid]',
+ 'CONSTRUCTOR':'new CodeGen(src,123L,true)' if ch==25 else 'new CodeGen(src,TypeInteger.BOT,123L,true)',
+ 'EXPORT':'c.exportELF(true,false)' if ch==25 else 'c.exportELF(null)',
+ 'EXTERNDATA':'c._externDataAddresses.put("counter",0x3800);c._externDataAddresses.put("errno",0x3808);' if ch==25 else '',
+ 'RESUME':'return c.loopTree().instSelect(cpu,"SystemV").GCM().localSched();' if ch==21 else 'return c.driver(CodeGen.Phase.LocalSched,cpu,"SystemV");',
+ 'COPYPOOLS':'''int cp=(e._bits.size()+15)&-16,sd=(cp+e._cpool.size()+15)&-16;
+ System.arraycopy(e._cpool.buf(),0,image,cp,e._cpool.size());
+ System.arraycopy(e._sdata.buf(),0,image,sd,e._sdata.size());''' if ch==25 else ''}
+for a,b in subs.items():src=src.replace(a,b)
+if ch<=22:
+ src=src.replace('TypeInteger.BOT,123L,true','TypeInteger.BOT,123L').replace('s._fields[i]._t','s._fields[i]._type')
+(out/'Measure.java').write_text(src,newline='\n')
+cp=os.pathsep.join([str(chapter/'build/classes/main'),str(chapter/'build/classes/test'),str(chapter/'lib/*')])
+subprocess.run(['javac','-cp',cp,'-d',str(out),str(out/'Measure.java')],check=True)
+subprocess.run(['javac','-cp',cp,'-d',str(out),*[str(p) for p in (root/'isa/src/test-support/java/com/seaofnodes/isa/eval').glob('*.java')]],check=True)
+if ch==25:
+ p=root/'chapter25/src/test/java/com/seaofnodes/simple/spill'
+ index=out/'index.tsv'
+ rows=[]
+ for line in (p/'cohorts.tsv').read_text().splitlines():
+ if line.startswith('#') or not line:continue
+ r=line.split('\t');rows.append('\t'.join(r[:4]+[str(p/r[5])]))
+ test=(chapter/'src/test/java/com/seaofnodes/simple/Chapter25Test.java').read_text(encoding='utf-8')
+ body=test.split('void testExternDataEncoding()',1)[1].split('@Test',1)[0]
+ literal=re.search(r'new CheckedCodeGen\(("(?:\\.|[^"\\])*")\)',body)
+ if literal is None: raise SystemExit('Cannot capture testExternDataEncoding source')
+ extra=out/'extern-data.smp';extra.write_text(json.loads(literal[1])+'\n',encoding='utf-8',newline='\n')
+ for cpu in ['arm','riscv']:rows.append('\t'.join(['Chapter25','Chapter25Test.testExternDataEncoding',cpu,'SystemV',str(extra)]))
+ index.write_text('\n'.join(rows)+'\n')
+else:index=out/'captured/index.tsv'
+with (out/'dynamic.log').open('w') as log:
+ subprocess.run(['java','-ea','-cp',str(out)+os.pathsep+cp,'Measure',str(index)],cwd=chapter,stdout=log,stderr=subprocess.STDOUT,check=True)
+print(ch, 'done')
diff --git a/build-support/spill-matrix.py b/build-support/spill-matrix.py
index 77c617a1..9ee8f7eb 100644
--- a/build-support/spill-matrix.py
+++ b/build-support/spill-matrix.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""Measure allocator chapters, check cohort membership, and render scaled spills."""
+"""Measure fixed cohorts: dynamic ARM/RISC-V ops, allocator ops, and x86 estimates."""
import argparse
from collections import Counter, defaultdict
from pathlib import Path
@@ -60,13 +60,69 @@ def read_results(log_dir):
return results
-def table(results, last=25):
+def read_dynamic(results):
+ dynamic = {}
+ values = {}
+ records = []
+ for chapter in range(21, 26):
+ path = ROOT / f"build/dynamic-stats/{chapter}/dynamic.log"
+ text = read_log(path)
+ if "FAILED," in text:
+ raise ValueError(f"Dynamic measurement failed; see {path}")
+ completion = re.search(r"Dynamic cases: (\d+); failures: 0", text)
+ if not completion:
+ raise ValueError(f"Incomplete dynamic measurement; see {path}")
+ cohorts = defaultdict(list)
+ for line in text.splitlines():
+ if not line.startswith("dynamic,"):
+ continue
+ _, cohort, test, cpu, count, ops, ra, value, inputs = line.split(",")
+ cohort = int(cohort.removeprefix("Chapter"))
+ test = test.removeprefix("Chapter25Test.") if cohort == 25 else test
+ row = (test, cpu, inputs, int(count), int(ops), int(ra))
+ cohorts[cohort].append(row)
+ identity = (cohort, test, inputs)
+ if identity in values and values[identity] != value:
+ raise ValueError(f"Dynamic results disagree: {identity}: {values[identity]} vs {value}")
+ values[identity] = value
+ records.append(f"{chapter},{cohort},{test},{cpu},{inputs},{count},{ops},{ra},{value}")
+ if set(cohorts) != set(range(20, chapter+1)):
+ raise ValueError(f"Missing dynamic cohort in Chapter {chapter}")
+ if sum(map(len, cohorts.values())) != int(completion[1]):
+ raise ValueError(f"Dynamic record count differs from completion record in {path}")
+ for cohort, rows in cohorts.items():
+ available = Counter((r[0], r[1]) for r in results[chapter][cohort])
+ measured = Counter(r[:2] for r in rows)
+ if measured - available:
+ raise ValueError(f"Dynamic cases absent from static cohort {chapter}/{cohort}")
+ dynamic[chapter] = cohorts
+ for cohort in CHAPTERS:
+ first = max(21, cohort)
+ baseline = Counter(r[:4] for r in dynamic[first][cohort])
+ for chapter in range(first+1, 26):
+ if Counter(r[:4] for r in dynamic[chapter][cohort]) != baseline:
+ raise ValueError(f"Dynamic membership/inputs changed: {chapter}/{cohort}")
+ header = "compiler,cohort,test,cpu,inputs,runs,instructions,allocator_instructions,result\n"
+ (ROOT / "build/spill-stats/dynamic-counts.csv").write_text(header+"\n".join(records)+"\n", encoding="utf-8", newline="\n")
+ return dynamic
+
+
+def table(results, dynamic, last=25):
chapters = range(20, last + 1)
- lines = ["| Test cohort | # tests | " + " | ".join(f"Ch {c}" for c in chapters) + " |",
+ lines = ["| Test cohort | Cases D / X | " + " | ".join(f"Ch {c}: Ops / RA / X" for c in chapters) + " |",
"|---|---:|" + "---:|" * len(chapters)]
for cohort in chapters:
- cells = [f"Ch {cohort}", str(len(results[cohort][cohort]))]
- cells += ["" if c < cohort else f"{sum(r[4] for r in results[c][cohort]):,}" for c in chapters]
+ count = len(dynamic[max(21,cohort)][cohort])
+ nx = sum(r[1]=="x86_64_v2" for r in results[cohort][cohort])
+ cells = [f"Ch {cohort}", f"{count} / {nx}"]
+ for c in chapters:
+ if c < cohort:
+ cells.append("")
+ continue
+ x = sum(r[4] for r in results[c][cohort] if r[1]=="x86_64_v2")
+ ops = f"{sum(r[4] for r in dynamic[c][cohort]):,}" if c>=21 else "—"
+ ra = f"{sum(r[5] for r in dynamic[c][cohort]):,}" if c>=21 else "—"
+ cells.append(f"{ops} / {ra} / {x:,}")
lines.append("| " + " | ".join(cells) + " |")
return "\n".join(lines)
@@ -97,12 +153,17 @@ def main():
stdout=log, stderr=subprocess.STDOUT)
if run.returncode:
raise ValueError(f"Chapter {chapter} failed (exit {run.returncode}); see {path}")
+ for chapter in range(21, 26):
+ if chapter < 25:
+ subprocess.run([sys.executable, str(ROOT / "build-support/capture-alloc-sources.py"), str(chapter)], check=True)
+ subprocess.run([sys.executable, str(ROOT / "build-support/measure-alloc.py"), str(chapter)], check=True)
results = read_results(args.log_dir)
- matrix = table(results)
+ dynamic = read_dynamic(results)
+ matrix = table(results, dynamic)
(args.log_dir / "matrix.md").write_text(matrix + "\n", encoding="utf-8", newline="\n")
if args.update_docs:
paths = [(ROOT / "docs/regalloc-spills.md", matrix)]
- paths += [(ROOT / f"chapter{c}/README.md", table(results, c)) for c in CHAPTERS]
+ paths += [(ROOT / f"chapter{c}/README.md", table(results, dynamic, c)) for c in CHAPTERS]
# Validate all destinations before changing any document.
for path, _ in paths:
text = path.read_text(encoding="utf-8")
diff --git a/chapter20/README.md b/chapter20/README.md
index 9f7cb1c5..96803e51 100644
--- a/chapter20/README.md
+++ b/chapter20/README.md
@@ -503,16 +503,18 @@ two-address operands, and Phi register agreement; tiny machine graphs exercise
hard conflicts independently of optimizer choices. Native execution comes with
encoding in Chapter 21.
-Rows are fixed test cohorts; columns are the compiler/allocator chapters.
-Each cell is the sum of `_spillScaled`: retained split moves, including register
-copies, weighted by `8^loopDepth`. Lower is better. **# tests counts compilations**
-(program/CPU/ABI cases), including zero-spill cases, rather than JUnit methods.
-The count and membership of each row stay fixed across columns.
+Rows are fixed test cohorts; columns are compiler chapters. Each cell gives
+**Ops / RA / X**: executed ARM+RISC-V instructions, the subset emitted for
+allocator copies/rematerializations, and x86's loop-weighted split-move estimate.
+**Cases D / X** counts measured dynamic program/target cases and x86 compilations;
+membership and inputs stay fixed across columns. Native-library work is excluded.
+Chapter 20 has no encoder, hence no dynamic counts. Lower is better within each
+metric; see the [measurement details](../docs/regalloc-spills.md) for coverage.
<!-- spill-matrix:start -->
-| Test cohort | # tests | Ch 20 |
+| Test cohort | Cases D / X | Ch 20: Ops / RA / X |
|---|---:|---:|
-| Ch 20 | 39 | 360 |
+| Ch 20 | 22 / 13 | — / — / 161 |
<!-- spill-matrix:end -->
These Windows measurements use optimizer seed 123 for cohorts 20-24 and include
diff --git a/chapter21/README.md b/chapter21/README.md
index 63b02ae9..80304c1a 100644
--- a/chapter21/README.md
+++ b/chapter21/README.md
@@ -147,22 +147,24 @@ its two-address operand. For example, `mov rpc=s8; st1 [rpc+4],s1` becomes
Phi and control-flow users are excluded. This local cleanup leaves spill
selection unchanged; it complements coalescing without another CFG analysis.
-For looking at allocator progress, we will keep a table of weighted spill costs
+For looking at allocator progress, we will keep a table of execution and allocation costs
a chapters' tests - and use the later chapter compiler and register allocator
on them. The later chapters also modify the graph (sometimes adding boiler
plate graph) so its not really a 100% fair comparison of how well this
*heuristic* does, but it does give us some idea that we're not losing ground.
-Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
-cell is the sum of `_spillScaled`: retained split moves, including register
-copies, weighted by loopDepth. Lower is better. **# tests counts
-compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
-JUnit methods. The count and membership of each row stay fixed across columns.
+Rows are fixed test cohorts; columns are compiler chapters. Each cell gives
+**Ops / RA / X**: executed ARM+RISC-V instructions, the subset emitted for
+allocator copies/rematerializations, and x86's loop-weighted split-move estimate.
+**Cases D / X** counts measured dynamic program/target cases and x86 compilations;
+membership and inputs stay fixed across columns. Native-library work is excluded.
+Chapter 20 has no encoder, hence no dynamic counts. Lower is better within each
+metric; see the [measurement details](../docs/regalloc-spills.md) for coverage.
<!-- spill-matrix:start -->
-| Test cohort | # tests | Ch 20 | Ch 21 |
+| Test cohort | Cases D / X | Ch 20: Ops / RA / X | Ch 21: Ops / RA / X |
|---|---:|---:|---:|
-| Ch 20 | 39 | 360 | 576 |
-| Ch 21 | 52 | | 1,072 |
+| Ch 20 | 22 / 13 | — / — / 161 | 55,173 / 5,407 / 174 |
+| Ch 21 | 20 / 18 | | 6,207 / 261 / 461 |
<!-- spill-matrix:end -->
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
index 5fa68e19..6fd4274a 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -17,7 +17,7 @@ public class LoadARM extends MemOpARM {
super(ld, base, idx, off, 0);
}
@Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.RMASK; }
+ @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; }
private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, };
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
index 5c388bab..ec017032 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -19,9 +19,9 @@ public class NotARM extends MachConcreteNode implements MachNode{
// subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
short self = enc.reg(this );
short reg1 = enc.reg(in(1));
- int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, self);
+ int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31);
enc.add4(subs);
- int cset = Arm64.cond_set(Arm64.OP_CSET, 31, Arm64.COND.EQ, 63, reg1);
+ int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self);
enc.add4(cset);
}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 4006565e..66a59fde 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -215,7 +215,7 @@ public class arm extends Machine {
public static void f_cmp(Encoding enc, Node n) {
short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
- int body = Arm64.f_cmp(0b00011110, 3, reg1, reg2);
+ int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1);
enc.add4(body);
}
@@ -325,7 +325,7 @@ public class arm extends Machine {
private Node cmp(BoolNode bool){
Node cmp = _cmp(bool);
- return new SetARM(cmp, IfNode.negate(bool.op()));
+ return new SetARM(cmp, bool.op());
}
private Node _cmp(BoolNode bool) {
if( bool.isFloat() )
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
index 469221f3..6aefc54b 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -15,7 +15,8 @@ public class NotRISC extends MachConcreteNode implements MachNode {
@Override public void encoding( Encoding enc ) {
short dst = enc.reg(this );
short src = enc.reg(in(1));
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 2, src, 1));
+ // SEQZ is unsigned: negative nonzero inputs must also produce false.
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1));
}
@Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
index f270bf12..8b6512e1 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -7,7 +7,11 @@ import com.seaofnodes.simple.node.Node;
public class SetIRISC extends ImmRISC {
final boolean _unsigned; // slti vs sltiu
public SetIRISC( Node src, int imm12, boolean unsigned ) {
- super(src,imm12);
+ this(src,imm12,unsigned,true);
+ }
+ public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) {
+ super(src,imm12,pop);
+ if( !pop ) _inputs.setLen(2);
_unsigned = unsigned;
}
@Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 7e840509..335347e9 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -304,7 +304,7 @@ public class riscv extends Machine {
// x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1);
// x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
- case "==" -> new SetIRISC(new SubRISC(bool),1,true);
+ case "==" -> new SetIRISC(new SubRISC(bool),1,true,false);
default -> throw Utils.TODO();
};
}
diff --git a/chapter22/README.md b/chapter22/README.md
index 1f839de5..00ae6183 100644
--- a/chapter22/README.md
+++ b/chapter22/README.md
@@ -203,16 +203,18 @@ are small preferences, not a general cost model. Grouping popular values by
register class is left for Chapter 23, cold loop splits for 24, and area/cost
ranking for 25.
-Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
-cell is the sum of `_spillScaled`: retained split moves, including register
-copies, weighted by loopDepth. Lower is better. **# tests counts
-compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
-JUnit methods. The count and membership of each row stay fixed across columns.
+Rows are fixed test cohorts; columns are compiler chapters. Each cell gives
+**Ops / RA / X**: executed ARM+RISC-V instructions, the subset emitted for
+allocator copies/rematerializations, and x86's loop-weighted split-move estimate.
+**Cases D / X** counts measured dynamic program/target cases and x86 compilations;
+membership and inputs stay fixed across columns. Native-library work is excluded.
+Chapter 20 has no encoder, hence no dynamic counts. Lower is better within each
+metric; see the [measurement details](../docs/regalloc-spills.md) for coverage.
<!-- spill-matrix:start -->
-| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 |
+| Test cohort | Cases D / X | Ch 20: Ops / RA / X | Ch 21: Ops / RA / X | Ch 22: Ops / RA / X |
|---|---:|---:|---:|---:|
-| Ch 20 | 39 | 360 | 576 | 430 |
-| Ch 21 | 52 | | 1,072 | 960 |
-| Ch 22 | 26 | | | 63 |
+| Ch 20 | 22 / 13 | — / — / 161 | 55,173 / 5,407 / 174 | 58,751 / 9,099 / 175 |
+| Ch 21 | 20 / 18 | | 6,207 / 261 / 461 | 6,324 / 379 / 475 |
+| Ch 22 | 14 / 8 | | | 198 / 60 / 7 |
<!-- spill-matrix:end -->
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
index 5fa68e19..6fd4274a 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -17,7 +17,7 @@ public class LoadARM extends MemOpARM {
super(ld, base, idx, off, 0);
}
@Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.RMASK; }
+ @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; }
private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, };
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
index 5c388bab..ec017032 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -19,9 +19,9 @@ public class NotARM extends MachConcreteNode implements MachNode{
// subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
short self = enc.reg(this );
short reg1 = enc.reg(in(1));
- int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, self);
+ int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31);
enc.add4(subs);
- int cset = Arm64.cond_set(Arm64.OP_CSET, 31, Arm64.COND.EQ, 63, reg1);
+ int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self);
enc.add4(cset);
}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index da5e6d4b..ddd9797b 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -215,7 +215,7 @@ public class arm extends Machine {
public static void f_cmp(Encoding enc, Node n) {
short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
- int body = Arm64.f_cmp(0b00011110, 3, reg1, reg2);
+ int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1);
enc.add4(body);
}
@@ -327,7 +327,7 @@ public class arm extends Machine {
private Node cmp(BoolNode bool){
Node cmp = _cmp(bool);
- return new SetARM(cmp, IfNode.negate(bool.op()));
+ return new SetARM(cmp, bool.op());
}
private Node _cmp(BoolNode bool) {
if( bool.isFloat() )
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
index 469221f3..6aefc54b 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -15,7 +15,8 @@ public class NotRISC extends MachConcreteNode implements MachNode {
@Override public void encoding( Encoding enc ) {
short dst = enc.reg(this );
short src = enc.reg(in(1));
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 2, src, 1));
+ // SEQZ is unsigned: negative nonzero inputs must also produce false.
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1));
}
@Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
index f270bf12..8b6512e1 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -7,7 +7,11 @@ import com.seaofnodes.simple.node.Node;
public class SetIRISC extends ImmRISC {
final boolean _unsigned; // slti vs sltiu
public SetIRISC( Node src, int imm12, boolean unsigned ) {
- super(src,imm12);
+ this(src,imm12,unsigned,true);
+ }
+ public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) {
+ super(src,imm12,pop);
+ if( !pop ) _inputs.setLen(2);
_unsigned = unsigned;
}
@Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index a1e3b767..f2d1e0b9 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -305,7 +305,7 @@ public class riscv extends Machine {
// x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1,false);
// x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
- case "==" -> new SetIRISC(new SubRISC(bool),1,true);
+ case "==" -> new SetIRISC(new SubRISC(bool),1,true,false);
default -> throw Utils.TODO();
};
}
diff --git a/chapter23/README.md b/chapter23/README.md
index 7dbe385b..85a5b201 100644
--- a/chapter23/README.md
+++ b/chapter23/README.md
@@ -445,17 +445,19 @@ the fallback, including splitting an existing copy when all uses have the same
loop depth. Cold-first loop splitting is left for Chapter 24; area/cost spill
ranking remains for Chapter 25.
-Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
-cell is the sum of `_spillScaled`: retained split moves, including register
-copies, weighted by loopDepth. Lower is better. **# tests counts
-compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
-JUnit methods. The count and membership of each row stay fixed across columns.
+Rows are fixed test cohorts; columns are compiler chapters. Each cell gives
+**Ops / RA / X**: executed ARM+RISC-V instructions, the subset emitted for
+allocator copies/rematerializations, and x86's loop-weighted split-move estimate.
+**Cases D / X** counts measured dynamic program/target cases and x86 compilations;
+membership and inputs stay fixed across columns. Native-library work is excluded.
+Chapter 20 has no encoder, hence no dynamic counts. Lower is better within each
+metric; see the [measurement details](../docs/regalloc-spills.md) for coverage.
<!-- spill-matrix:start -->
-| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | Ch 23 |
+| Test cohort | Cases D / X | Ch 20: Ops / RA / X | Ch 21: Ops / RA / X | Ch 22: Ops / RA / X | Ch 23: Ops / RA / X |
|---|---:|---:|---:|---:|---:|
-| Ch 20 | 39 | 360 | 576 | 430 | 442 |
-| Ch 21 | 52 | | 1,072 | 960 | 961 |
-| Ch 22 | 26 | | | 63 | 63 |
-| Ch 23 | 24 | | | | 103 |
+| Ch 20 | 22 / 13 | — / — / 161 | 55,173 / 5,407 / 174 | 58,751 / 9,099 / 175 | 58,736 / 9,084 / 184 |
+| Ch 21 | 20 / 18 | | 6,207 / 261 / 461 | 6,324 / 379 / 475 | 6,326 / 381 / 475 |
+| Ch 22 | 14 / 8 | | | 198 / 60 / 7 | 194 / 56 / 7 |
+| Ch 23 | 14 / 8 | | | | 630 / 160 / 36 |
<!-- spill-matrix:end -->
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
index f3bbbbeb..8fc22a67 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -17,7 +17,7 @@ public class LoadARM extends MemOpARM {
super(ld, base, idx, off, 0);
}
@Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.RMASK; }
+ @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; }
private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, };
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
index 8cf72aaf..f8e450f6 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -18,9 +18,9 @@ public class NotARM extends MachConcreteNode implements MachNode{
// subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
short self = enc.reg(this );
short reg1 = enc.reg(in(1));
- int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, self);
+ int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31);
enc.add4(subs);
- int cset = Arm64.cond_set(Arm64.OP_CSET, 31, Arm64.COND.EQ, 63, reg1);
+ int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self);
enc.add4(cset);
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 8bdcfe10..9faf1d40 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -215,7 +215,7 @@ public class arm extends Machine {
public static void f_cmp(Encoding enc, Node n) {
short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
- int body = Arm64.f_cmp(0b00011110, 3, reg1, reg2);
+ int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1);
enc.add4(body);
}
@@ -327,7 +327,7 @@ public class arm extends Machine {
private Node cmp(BoolNode bool){
Node cmp = _cmp(bool);
- return new SetARM(cmp, IfNode.negate(bool.op()));
+ return new SetARM(cmp, bool.op());
}
private Node _cmp(BoolNode bool) {
if( bool.isFloat() )
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
index dbe9ceb0..6f499db1 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -15,7 +15,8 @@ public class NotRISC extends MachConcreteNode implements MachNode {
@Override public void encoding( Encoding enc ) {
short dst = enc.reg(this );
short src = enc.reg(in(1));
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 2, src, 1));
+ // SEQZ is unsigned: negative nonzero inputs must also produce false.
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1));
}
@Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
index 690e2102..9bc86565 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -7,7 +7,11 @@ import com.seaofnodes.simple.node.Node;
public class SetIRISC extends ImmRISC {
final boolean _unsigned; // slti vs sltiu
public SetIRISC( Node src, int imm12, boolean unsigned ) {
- super(src,imm12);
+ this(src,imm12,unsigned,true);
+ }
+ public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) {
+ super(src,imm12,pop);
+ if( !pop ) _inputs.setLen(2);
_unsigned = unsigned;
}
@Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 78d21f3b..7c994624 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -305,7 +305,7 @@ public class riscv extends Machine {
// x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1,false);
// x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
- case "==" -> new SetIRISC(new SubRISC(bool),1,true);
+ case "==" -> new SetIRISC(new SubRISC(bool),1,true,false);
default -> throw Utils.TODO();
};
}
diff --git a/chapter24/README.md b/chapter24/README.md
index 07c63ee3..ceb7e03e 100644
--- a/chapter24/README.md
+++ b/chapter24/README.md
@@ -305,19 +305,21 @@ also covers a backedge-only conflict where the first attempt inserts no copy:
that attempt must still consume the one permitted delay. A regression checks
both the cheaper first attempt and the mandatory fallback.
-Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
-cell is the sum of `_spillScaled`: retained split moves, including register
-copies, weighted by loopDepth. Lower is better. **# tests counts
-compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
-JUnit methods. The count and membership of each row stay fixed across columns.
+Rows are fixed test cohorts; columns are compiler chapters. Each cell gives
+**Ops / RA / X**: executed ARM+RISC-V instructions, the subset emitted for
+allocator copies/rematerializations, and x86's loop-weighted split-move estimate.
+**Cases D / X** counts measured dynamic program/target cases and x86 compilations;
+membership and inputs stay fixed across columns. Native-library work is excluded.
+Chapter 20 has no encoder, hence no dynamic counts. Lower is better within each
+metric; see the [measurement details](../docs/regalloc-spills.md) for coverage.
<!-- spill-matrix:start -->
-| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | Ch 23 | Ch 24 |
+| Test cohort | Cases D / X | Ch 20: Ops / RA / X | Ch 21: Ops / RA / X | Ch 22: Ops / RA / X | Ch 23: Ops / RA / X | Ch 24: Ops / RA / X |
|---|---:|---:|---:|---:|---:|---:|
-| Ch 20 | 39 | 360 | 576 | 430 | 442 | 441 |
-| Ch 21 | 52 | | 1,072 | 960 | 961 | 948 |
-| Ch 22 | 26 | | | 63 | 63 | 63 |
-| Ch 23 | 24 | | | | 103 | 103 |
-| Ch 24 | 58 | | | | | 1,387 |
+| Ch 20 | 22 / 13 | — / — / 161 | 55,173 / 5,407 / 174 | 58,751 / 9,099 / 175 | 58,736 / 9,084 / 184 | 58,720 / 9,068 / 183 |
+| Ch 21 | 20 / 18 | | 6,207 / 261 / 461 | 6,324 / 379 / 475 | 6,326 / 381 / 475 | 6,325 / 380 / 470 |
+| Ch 22 | 14 / 8 | | | 198 / 60 / 7 | 194 / 56 / 7 | 194 / 56 / 7 |
+| Ch 23 | 14 / 8 | | | | 630 / 160 / 36 | 630 / 160 / 36 |
+| Ch 24 | 36 / 20 | | | | | 4,923 / 1,341 / 790 |
<!-- spill-matrix:end -->
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
index f3bbbbeb..8fc22a67 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -17,7 +17,7 @@ public class LoadARM extends MemOpARM {
super(ld, base, idx, off, 0);
}
@Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.RMASK; }
+ @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; }
private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, };
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
index ac21c706..f8e450f6 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -18,9 +18,9 @@ public class NotARM extends MachConcreteNode implements MachNode{
// subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
short self = enc.reg(this );
short reg1 = enc.reg(in(1));
- int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, self);
+ int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31);
enc.add4(subs);
- int cset = Arm64.cond_set(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, reg1);
+ int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self);
enc.add4(cset);
}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index defec5ef..32c1e2fc 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -215,7 +215,7 @@ public class arm extends Machine {
public static void f_cmp(Encoding enc, Node n) {
short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
- int body = Arm64.f_cmp(0b00011110, 3, reg1, reg2);
+ int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1);
enc.add4(body);
}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
index dbe9ceb0..6f499db1 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -15,7 +15,8 @@ public class NotRISC extends MachConcreteNode implements MachNode {
@Override public void encoding( Encoding enc ) {
short dst = enc.reg(this );
short src = enc.reg(in(1));
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 2, src, 1));
+ // SEQZ is unsigned: negative nonzero inputs must also produce false.
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1));
}
@Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
index 690e2102..9bc86565 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -7,7 +7,11 @@ import com.seaofnodes.simple.node.Node;
public class SetIRISC extends ImmRISC {
final boolean _unsigned; // slti vs sltiu
public SetIRISC( Node src, int imm12, boolean unsigned ) {
- super(src,imm12);
+ this(src,imm12,unsigned,true);
+ }
+ public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) {
+ super(src,imm12,pop);
+ if( !pop ) _inputs.setLen(2);
_unsigned = unsigned;
}
@Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 78d21f3b..7c994624 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -305,7 +305,7 @@ public class riscv extends Machine {
// x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1,false);
// x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
- case "==" -> new SetIRISC(new SubRISC(bool),1,true);
+ case "==" -> new SetIRISC(new SubRISC(bool),1,true,false);
default -> throw Utils.TODO();
};
}
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index bb89aa16..9a657855 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -585,17 +585,21 @@ return 0 < arg < arg+1 < 4;
""";
TestC.run(src, "stacked_r_13x", null, stack12,0);
- // Evaluate on RISC5 emulator
+ // Compile once per target, then check false, true and negative inputs.
EvalRisc5 R5 = TestRisc5.build("stacked_r_13", src2, 1, 0, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(1,R5.regs[riscv.A0]);
-
- // Evaluate on ARM emulator
+ for( int arg : new int[]{-1,0,1} ) {
+ R5._pc = 0;
+ R5.regs[riscv.A0] = arg;
+ assertEquals(0,R5.step(100));
+ assertEquals(arg==0 ? 1 : 0,R5.regs[riscv.A0]);
+ }
EvalArm64 arm = TestArm64.build("stacked_r_13", src2, 1, 0, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(1,arm.regs[0]);
+ for( int arg : new int[]{-1,0,1} ) {
+ arm._pc = 0;
+ arm.regs[0] = arg;
+ assertEquals(0,arm.step(100));
+ assertEquals(arg==0 ? 1 : 0,arm.regs[0]);
+ }
}
@Test
diff --git a/chapter25/README.md b/chapter25/README.md
index ec8a8ea8..6d2a376b 100644
--- a/chapter25/README.md
+++ b/chapter25/README.md
@@ -183,21 +183,23 @@ calculation next to the use adds no instruction and avoids preserving a
callee-save register merely to hold the constant. Adjacent uses and multi-use
addresses retain the area/cost ranking.
-Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
-cell is the sum of `_spillScaled`: retained split moves, including register
-copies, weighted by loopDepth. Lower is better. **# tests counts
-compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
-JUnit methods. The count and membership of each row stay fixed across columns.
+Rows are fixed test cohorts; columns are compiler chapters. Each cell gives
+**Ops / RA / X**: executed ARM+RISC-V instructions, the subset emitted for
+allocator copies/rematerializations, and x86's loop-weighted split-move estimate.
+**Cases D / X** counts measured dynamic program/target cases and x86 compilations;
+membership and inputs stay fixed across columns. Native-library work is excluded.
+Chapter 20 has no encoder, hence no dynamic counts. Lower is better within each
+metric; see the [measurement details](../docs/regalloc-spills.md) for coverage.
<!-- spill-matrix:start -->
-| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | Ch 23 | Ch 24 | Ch 25 |
+| Test cohort | Cases D / X | Ch 20: Ops / RA / X | Ch 21: Ops / RA / X | Ch 22: Ops / RA / X | Ch 23: Ops / RA / X | Ch 24: Ops / RA / X | Ch 25: Ops / RA / X |
|---|---:|---:|---:|---:|---:|---:|---:|
-| Ch 20 | 39 | 360 | 576 | 430 | 442 | 441 | 491 |
-| Ch 21 | 52 | | 1,072 | 960 | 961 | 948 | 1,077 |
-| Ch 22 | 26 | | | 63 | 63 | 63 | 71 |
-| Ch 23 | 24 | | | | 103 | 103 | 152 |
-| Ch 24 | 58 | | | | | 1,387 | 1,503 |
-| Ch 25 | 14 | | | | | | 1,862 |
+| Ch 20 | 22 / 13 | — / — / 161 | 55,173 / 5,407 / 174 | 58,751 / 9,099 / 175 | 58,736 / 9,084 / 184 | 58,720 / 9,068 / 183 | 59,043 / 9,094 / 209 |
+| Ch 21 | 20 / 18 | | 6,207 / 261 / 461 | 6,324 / 379 / 475 | 6,326 / 381 / 475 | 6,325 / 380 / 470 | 6,233 / 219 / 533 |
+| Ch 22 | 14 / 8 | | | 198 / 60 / 7 | 194 / 56 / 7 | 194 / 56 / 7 | 332 / 136 / 11 |
+| Ch 23 | 14 / 8 | | | | 630 / 160 / 36 | 630 / 160 / 36 | 1,106 / 442 / 36 |
+| Ch 24 | 36 / 20 | | | | | 4,923 / 1,341 / 790 | 5,044 / 1,470 / 609 |
+| Ch 25 | 2 / 12 | | | | | | 20 / 0 / 1,862 |
<!-- spill-matrix:end -->
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
index 4fbd6840..53494092 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -17,7 +17,7 @@ public class LoadARM extends MemOpARM {
super(ld, base, idx, off, 0);
}
@Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.RMASK; }
+ @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; }
private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, };
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
index ac21c706..f8e450f6 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -18,9 +18,9 @@ public class NotARM extends MachConcreteNode implements MachNode{
// subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
short self = enc.reg(this );
short reg1 = enc.reg(in(1));
- int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, self);
+ int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31);
enc.add4(subs);
- int cset = Arm64.cond_set(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, reg1);
+ int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self);
enc.add4(cset);
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
index dbe9ceb0..6f499db1 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -15,7 +15,8 @@ public class NotRISC extends MachConcreteNode implements MachNode {
@Override public void encoding( Encoding enc ) {
short dst = enc.reg(this );
short src = enc.reg(in(1));
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 2, src, 1));
+ // SEQZ is unsigned: negative nonzero inputs must also produce false.
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1));
}
@Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
index 690e2102..9bc86565 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -7,7 +7,11 @@ import com.seaofnodes.simple.node.Node;
public class SetIRISC extends ImmRISC {
final boolean _unsigned; // slti vs sltiu
public SetIRISC( Node src, int imm12, boolean unsigned ) {
- super(src,imm12);
+ this(src,imm12,unsigned,true);
+ }
+ public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) {
+ super(src,imm12,pop);
+ if( !pop ) _inputs.setLen(2);
_unsigned = unsigned;
}
@Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index ae69b439..19b91866 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -317,7 +317,7 @@ public class riscv extends Machine {
// x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1,false);
// x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
- case "==" -> new SetIRISC(new SubRISC(bool),1,true);
+ case "==" -> new SetIRISC(new SubRISC(bool),1,true,false);
default -> throw Utils.TODO();
};
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index ef65c6d9..3e21c73e 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -226,19 +226,21 @@ return (10 < something_specific < 20)
String src2 = "return 0 == arg == 1;";
TestC.runC(src, "stacked_r_13x", "1",0);
- // Evaluate on RISC5 emulator
+ // Compile once per target, then check false, true and negative inputs.
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
+ for( int arg : new int[]{-1,0,1} ) {
+ R5._pc = 0;
+ R5.regs[riscv.A1] = arg;
+ assertEquals(0,R5.step(100));
+ assertEquals(arg==0 ? 1 : 0,R5.regs[riscv.A0]);
+ }
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]);
+ for( int arg : new int[]{-1,0,1} ) {
+ arm._pc = 0;
+ arm.regs[1] = arg;
+ assertEquals(0,arm.step(100));
+ assertEquals(arg==0 ? 1 : 0,arm.regs[0]);
+ }
}
@Test
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp
index 586ccb85..a18a5b88 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp
@@ -1,12 +1,12 @@
// -*- mode: java; -*-
-struct String {
+struct _String {
u8[] cs;
int _hashCode;
- new String = { u8[] data -> cs=data; };
+ new _String = { u8[] data -> cs=data; };
};
// Compare two Strings
-val equals = { !String !self, !String s ->
+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++ )
@@ -16,13 +16,13 @@ val equals = { !String !self, !String s ->
};
// Return the String hashCode (cached, and never 0)
-val hashCode = { !String self ->
+val hashCode = { !_String self ->
self._hashCode
? self._hashCode
: (self._hashCode = _hashCodeString(self));
};
-val _hashCodeString = { !String self ->
+val _hashCodeString = { !_String self ->
int hash=0;
if( self.cs )
for( int i=0; i< self.cs#; i++ )
@@ -30,3 +30,6 @@ val _hashCodeString = { !String self ->
if( !hash ) hash = 123456789;
return hash;
};
+
+// This fixture exports its API, not the last declaration as a result.
+return 0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp
index 950e282d..315cc5b6 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp
@@ -1,7 +1,7 @@
-struct S { int f; };
-var v0 = new S;
-!S? !v1;
-if (arg) v1 = new S;
+struct _S { int f; };
+var v0 = new _S;
+!_S? !v1;
+if (arg) v1 = new _S;
if (v1) {
v0.f = v1.f;
} else {
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp
index 5e517b61..1804ad59 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp
@@ -1 +1 @@
-struct S { int i; }; !S !s = new S; while(1) s.i++;
+struct _S { int i; }; !_S !s = new _S; while(1) s.i++;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp
index f3d34b0a..ee5a0560 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp
@@ -28,3 +28,6 @@ val _hashCodeString = { !String self ->
if( !hash ) hash = 123456789;
return hash;
};
+
+// This fixture exports its API, not the last declaration as a result.
+return 0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp
index 4ef375e9..49c80a99 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp
@@ -1,12 +1,12 @@
// -*- mode: java; -*-
-struct String {
+struct _String {
u8[] cs;
int _hashCode;
- new String = { u8[] data -> cs=data; };
+ new _String = { u8[] data -> cs=data; };
};
// Compare two Strings
-val equals = { !String !self, !String s ->
+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++ )
@@ -16,16 +16,19 @@ val equals = { !String !self, !String s ->
};
// Return the String hashCode (cached, and never 0)
-val hashCode = { !String self ->
+val hashCode = { !_String self ->
self._hashCode
? self._hashCode
: (self._hashCode = _hashCodeString(self));
};
-val _hashCodeString = { !String 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;
};
+
+// This fixture exports its API, not the last declaration as a result.
+return 0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp
index 556a1375..a1e4e9ab 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp
@@ -1,8 +1,8 @@
{i32 i64 u32 -> u32} write="C";
val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
-struct S { !S? !fld; new S={ !S? f -> fld=f; }; };
+struct _S { !_S? !fld; new _S={ !_S? f -> fld=f; }; };
-val ptr = arg == 1 ? null : new S(arg==1 ? null : new S(null));
+val ptr = arg == 1 ? null : new _S(arg==1 ? null : new _S(null));
if( ptr && ptr.fld ) {
_p("true");
} else {
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp
index 00fcb589..eac0a542 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp
@@ -1,8 +1,8 @@
// A hypothetical scanner class
-struct Scan {
+struct _Scan {
int !x;
u8[~] buf;
- new Scan={ u8[~] b -> buf=b; };
+ new _Scan={ u8[~] b -> buf=b; };
// Skip whitespace
val skip = { ->
while( buf[x] <= ' ' )
@@ -16,5 +16,5 @@ struct Scan {
return true;
};
};
-!Scan !s = new Scan(" q");
+!_Scan !s = new _Scan(" q");
return s.peek('q');
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp
index b522a811..8a7b90d9 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp
@@ -4,11 +4,11 @@ val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
int a = 1;
int b = 1;
-var sq = { int x ->
+var _sq = { int x ->
x*x;
};
-if(a && sq(0)) {
+if(a && _sq(0)) {
_p("And");
} else {
_p("Or");
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp
index 27a19988..ced649e3 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp
@@ -1,8 +1,8 @@
// A hypothetical scanner class
-struct Scan {
+struct _Scan {
int !x;
u8[~] buf;
- new Scan={ u8[~] b -> buf=b; };
+ new _Scan={ u8[~] b -> buf=b; };
// Skip whitespace
val skip = { ->
while( buf[x] <= ' ' )
@@ -13,5 +13,5 @@ struct Scan {
buf[x++]==ch;
};
};
-!Scan !s = new Scan(" [1,2]");
+!_Scan !s = new _Scan(" [1,2]");
return s.require('[');
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp
index cf6d93ab..c4e4f9b5 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp
@@ -1,10 +1,10 @@
-struct S { int f; };
+struct _S { int f; };
int x=0;
int result=arg&1;
if(!result) {
- !S !s=new S;
+ !_S !s=new _S;
while(x<3) x++;
s.f=++x;
- result=!!s.f;
+ result=s.f;
}
return x*10+result;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp
index 370bfb8d..79478408 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp
@@ -1,11 +1,11 @@
-struct S { int f; };
-!S !s=new S;
+struct _S { int f; };
+!_S !s=new _S;
int result=arg&1;
if(!result) {
- !S !t=new S;
+ !_S !t=new _S;
while(s.f<3) s.f++;
s.f++;
t.f=s.f;
- result=!!t.f;
+ result=t.f;
}
return s.f*10+result;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp
index 7aeeaa35..f292254e 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp
@@ -16,22 +16,22 @@
i32 MAP_PRIVATE = 2;
i32 MAP_ANON = 32;
- val copy = { u8[] dst, int doff, u8[~] src, int soff, int len ->
+ val _copy = { u8[] dst, int doff, u8[~] src, int soff, int len ->
for( int i=0; i<len; i++ )
dst[i+doff] = src[i+soff];
return dst;
};
- struct aryu8 {
+ struct _aryu8 {
u32 !len = 0;
u8[] !buf;
- new aryu8={ u8[] b -> buf=b; };
+ new _aryu8={ u8[] b -> buf=b; };
// Grow as needed
val _grow = { int len2 ->
if( len+len2 < buf# ) return self;
int newlen = buf# * 2;
while( len+len2 >= newlen ) newlen *= 2;
u8[] !buf2 = new u8[newlen];
- copy(buf2,0,buf,0,buf#);
+ _copy(buf2,0,buf,0,buf#);
buf = buf2;
self;
};
@@ -45,7 +45,7 @@
// Add a plain array to the aryu8
val addAry = { u8[~] ary ->
_grow(ary#);
- copy(buf,len,ary,0,ary#);
+ _copy(buf,len,ary,0,ary#);
len += ary#;
self;
};
@@ -75,10 +75,10 @@
// Write to fd, returns the number of bytes written (len).
val write = { i32 fd -> i64 ptr = buf; write(fd,ptr,len); };
};
-struct Scan {
+struct _Scan {
int !x;
u8[~] buf;
- new Scan={ u8[~] b -> buf=b; };
+ new _Scan={ u8[~] b -> buf=b; };
// Skip whitespace
val skip = { ->
@@ -105,10 +105,10 @@ struct Scan {
return neg ? -n : n;
};
};
-struct aryi64 {
+struct _aryi64 {
u32 !len = 0;
i64[] !buf;
- new aryi64={ i64[] b -> buf=b; };
+ new _aryi64={ i64[] b -> buf=b; };
// Grow as needed
val _grow = { int len2 ->
if( len+len2 < buf# ) return self;
@@ -130,17 +130,17 @@ struct aryi64 {
// Write to aryu8
val str = { ->
// TODO: clunky default constructor syntax
- var str = new aryu8(new u8[1]).add('[');
+ var str = new _aryu8(new u8[1]).add('[');
for( int i=0; i<len; i++ )
str.num(buf[i]).add(',').add(' ');
str.undo(2).add(']');
};
};
- val parseAryI64 = { u8[~] str ->
- !Scan !s = new Scan(str);
+ val _parseAryI64 = { u8[~] str ->
+ !_Scan !s = new _Scan(str);
if( !s.match('[') ) return null;
// Array of parsed results
- var nums = new aryi64(new i64[1]);
+ var nums = new _aryi64(new i64[1]);
// Parse a list of numbers
if( !s.match(']') ) { // Handle the empty list
// Parse "123, " until a something else
@@ -156,13 +156,13 @@ struct aryi64 {
// Print string to stdout
- val p = { u8[~] str ->
+ val _p = { u8[~] str ->
i64 ptr = str; // cast array base to i64
return write(1,ptr,str#);
};
// Return stdin as a u8[]
- val stdin = { ->
+ val _stdin = { ->
var buf = new u8[8*1024];
i64 ptr = buf;
int len = 0;
@@ -176,7 +176,7 @@ struct aryi64 {
}
// Compress
var dst = new u8[len];
- return copy(dst,0,buf,0,len);
+ return _copy(dst,0,buf,0,len);
};
// -*- mode: java; -*-
@@ -185,7 +185,7 @@ struct aryi64 {
//
// Bubble sort, with reading (parsing) and writing (formatting) the data.
-val sort = { !aryi64 nums ->
+val _sort = { !_aryi64 nums ->
var done = false;
while( !done ) {
done = true;
@@ -202,14 +202,14 @@ val sort = { !aryi64 nums ->
// Read from stdin a line of the form "[ 4, 3, 2, 1]" and print it sorted.
val main = { ->
// Parse array of numbers from stdin
- var nums = parseAryI64( stdin() );
+ var nums = _parseAryI64( _stdin() );
if( !nums ) {
- p("stdin must look like [4, 3, 2, 1]");
+ _p("stdin must look like [4, 3, 2, 1]");
return 1;
}
// Bubble sort array of integers
- sort(nums);
+ _sort(nums);
// Print a list of numbers to stdout
nums.str().write(1);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp
index 6a3db406..4700f8ce 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp
@@ -1,11 +1,11 @@
{i32 i64 u32 -> u32} write="C";
val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
-var sq = { int x ->
+var _sq = { int x ->
x*x;
};
int score = 5;
-val str = (sq(2) <= score < sq(3)) ? "In Range" : "Out of range";
+val str = (_sq(2) <= score < _sq(3)) ? "In Range" : "Out of range";
_p(str);
return 0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp
index e4660752..3c2c4208 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp
@@ -1,12 +1,12 @@
{i32 i64 u32 -> u32} write="C";
val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
-var sq = { int x ->
+var _sq = { int x ->
x*x;
};
int score = 5;
-if (sq(2) <= score < sq(3)) {
+if (_sq(2) <= score < _sq(3)) {
_p("In Range");
} else {
_p("Out of range");
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md b/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md
index 08371ae9..b5ab9d80 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md
@@ -35,7 +35,10 @@ Chapter 25 cannot parse all earlier sources unchanged. Adaptations are explicit:
sources are a new comparison baseline; their totals cannot be compared as
identical inputs with the earlier constructor-free fixtures.
- The two short-circuit RHS initializer loops become equivalent guarded blocks,
- preserving allocation, side effects, and the final result.
+ preserving allocation, side effects, and the final result. Their RHS assigns
+ the field value directly: Simple's `||` returns an operand, not a normalized
+ boolean. The 2026-10-07 cleanup removes an erroneous `!!` in those adaptations;
+ both fixtures return 44 for even arguments and 1 for odd arguments.
- Simple I/O cases include the old `write` binding and print helper directly.
Bubble Sort includes the old library bodies, with namespace prefixes flattened,
explicit constructors, and mutable replacement buffers. This retains the old
@@ -50,6 +53,14 @@ Chapter 25 cannot parse all earlier sources unchanged. Adaptations are explicit:
StringExport, Person and the C-return wrappers, retain their public names.
These visibility adaptations change the generated workload and are recorded
separately from the allocator improvement in the cross-chapter comparison.
+- The remaining visibility cleanup makes local classes/functions private in
+ AntiDeps1, Infinite, the Chapter 21/23 allocation String cases, the Chapter 23
+ scanner/pointer/short-circuit/function cases, and Stack3/Stack5. Bubble Sort's
+ copied library implementation is private; its `main` stays public. The three
+ adapted String API fixtures end in `return 0;`, avoiding an accidental return
+ of the last declared function. Their equality/hash APIs stay public, and the
+ named StringExport fixture also keeps its public String factory. No cohort
+ entries, workload operations, or intentional export APIs are removed.
- Other source texts are retained.
All entries replay **through RegAlloc**, checking scheduled register constraints.
diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md
index fdc4eff4..49288607 100644
--- a/docs/chapter-backports.md
+++ b/docs/chapter-backports.md
@@ -465,6 +465,19 @@ lines beyond the original extraction.
## Pending corrections
+- **Residual allocation comparisons (investigated 2026-10-07; no compiler fix).**
+ After fixture cleanup, cohort 23 is 103 in Chapter 24 versus 104 in 25;
+ cohort 24 improves 1,387 to 1,315. The small actionable case is Win64
+ NewtonExport: six moves in both chapters, but Chapter 25 puts five in the loop
+ rather than four (34 to 41 weighted). Inspect destructive-add operand choice,
+ coalescing and Phi/return-register bias. Bubble Sort
+ now executes fewer instructions on both ARM and RISC-V; its higher static
+ weight does not represent more executed allocator instructions.
+ Ordinary Newton's larger static score mostly counts duplicated inlined loops:
+ a no-inline diagnostic nearly matches Chapter 24. The
+ [remaining-differences report](regalloc-spills.md#remaining-differences-after-fixture-cleanup)
+ separates these findings from the deferred inliner work below.
+
- **Chapter 25 deterministic inlining (deferred until after the Chapter 25 split).**
As of 2026-10-07, ordinary functions reject `_approxUIDs >= 100` before checking
the optimized body. `_approxUIDs` counts node IDs consumed during parsing and
@@ -609,9 +622,9 @@ which checks matching test/CPU/ABI membership with assertions enabled.
| 20 | Basic coloring, rematerialization and loop splitting, with shared legality/progress fixes | 39 |
| 21 | Conservative copy coalescing and adjacent-copy forwarding | 39, 52 |
| 22 | Stronger copy-chain/backedge bias and cheap-spill ordering | 39, 52, 26 |
-| 23 | Group popular single-def uses by compatible register classes | 39, 52, 26, 30 |
-| 24 | One cold-only attempt for loop-Phi self-conflicts, then mandatory fallback | 39, 52, 26, 30, 67 |
-| 25 | Existing area/cost ranking and later conflict strategies | 39, 52, 26, 30, 67, 14 |
+| 23 | Group popular single-def uses by compatible register classes | 39, 52, 26, 24 |
+| 24 | One cold-only attempt for loop-Phi self-conflicts, then mandatory fallback | 39, 52, 26, 24, 58 |
+| 25 | Existing area/cost ranking and later conflict strategies | 39, 52, 26, 24, 58, 14 |
Shared corrections include RegMask/LRG bookkeeping, null use masks, kills without
an output LRG, self-conflict splitting, compatible rematerialization, clobber-aware
@@ -635,7 +648,8 @@ registers removes that conflict. Encoding must then use the allocated register
bank, with five-bit register numbers, for both immediate and indexed loads/stores.
The emulator must preserve floating bits and scale double offsets by eight.
-Chapter 25 freezes the 214 earlier compilation entries in
+Chapter 25 freezes 199 earlier compilation entries, after excluding Jig scratch
+tests, in
[documented source fixtures](../chapter25/src/test/java/com/seaofnodes/simple/spill/README.md).
Constructor/library adaptations change IR, so this is a program-cohort comparison,
not identical machine graphs. Those rows replay allocation and legality checks;
@@ -677,6 +691,52 @@ The top-level runner accepts explicit chapter lists, e.g.
## Validation record
+- **Equality and ARM execution corrections, 2026-10-07.** Chapters 21-25
+ retain RISC-V's subtraction when expanding equality and use unsigned SEQZ
+ for logical-not (including negative inputs), exclude SP/XZR from
+ narrow ARM load destinations, and encode ARM logical-not with CMP-to-XZR
+ followed by CSET-to-result. ARM condition inversion is corrected in 21-23;
+ FCMP operand order in 21-24. Chapter 20 has different selection/stub encoders
+ and does not contain these faulty expansions. Shared evaluators now use
+ unsigned SLT/SLTIU comparisons and discard ARM XZR destination writes.
+ The existing Chapter 24 nested-equality test in 24/25 expected the wrong
+ result for argument 1; it now checks -1, 0 and 1. Independent ISA checks and full
+ Make suites pass in 21-25 (including native/system tests). All previously
+ excluded measurement cases execute successfully; the reporter also checks
+ nested equality at 0-3. Logs: `build/dynamic-stats/fix-tests{21..25}.log`
+ and `fix-isa.log`. The expanded matrices and a measured breakdown of the
+ Chapter 24-to-25 increase are in [the allocation report](regalloc-spills.md).
+ Cohort 23's +476 Ops is chiefly class-field initialization; its +282 RA
+ instructions comprise 280 rematerializations and only two extra splits.
+
+- **Dynamic allocation metrics, 2026-10-07.** Chapter 20-25 README matrices now
+ report executed ARM/RISC-V instructions, the allocator-created subset (copies,
+ spills/reloads and rematerializations), and x86-only weighted split estimates.
+ Dynamic membership and input vectors are fixed per cohort, with results
+ checked across compilers/targets. Native-library bodies are excluded. Chapter
+ 20 has no encoder. The dynamic rows cover 22/20/14/14/36/2 program-target
+ cases; compilation-only/API exclusions are explicit.
+ The original full allocation cohorts are unchanged. Expanded shared evaluator
+ support fixes ARM SDIV, ORR-immediate and false CSET values, and RISC-V floating
+ stores and FEQ/FLT decoding. Independent instruction-word checks pass in
+ `make -C isa tests`; full Make suites pass in 21-25, including Chapter 25's
+ native/system groups. Logs: `build/dynamic-stats/tests-{21..25}.log` and
+ `isa-tests.log`. The reporter validates cohort identities, inputs and results;
+ generated per-case records are in `build/spill-stats/dynamic-counts.csv`.
+
+- **Remaining replay fixture cleanup, 2026-10-07.** Fourteen Chapter 25 replay
+ sources make implementation helpers/classes private while retaining export
+ APIs, explicitly return zero from three String API fixtures, and correct two
+ guarded short-circuit adaptations to return their RHS value (not `!!value`).
+ Disposable Eval2 checks at arguments 0-3 return 44, 1, 44, 1 for both loops.
+ `make -C chapter25 spill-stats CTAGS=` passes all 199 historical allocations,
+ fresh library encoding, 23 native tests and spill expectations. Fixed cohort
+ membership remains unchanged. Cohort 21 changes 1,077 to 1,036; 23 changes
+ 152 to 104; 24 changes 1,503 to 1,315. Other rows are unchanged. The matrix
+ reporter validates membership against saved Chapter 20-24 reports and updates
+ the documentation. No compiler changes or permanent tests were added.
+ Logs and disposable allocation/assembly probes: `build/fixture-cleanup/`.
+
- **Ignore debugging Jigs (2026-10-07).** All `testJig*` methods are scratch
placeholders, not regression tests. Added missing `@Ignore` annotations in
Chapters 14-25, preserving their bodies, and removed all 15 Jig entries and
diff --git a/docs/regalloc-spills.md b/docs/regalloc-spills.md
index 9b96a30b..4f738fe5 100644
--- a/docs/regalloc-spills.md
+++ b/docs/regalloc-spills.md
@@ -1,108 +1,142 @@
# Register allocation across chapters
-Read a row from left to right to follow a fixed test cohort through successive
-compilers. Columns identify the compiler/allocator chapter; each cell is its
-sum of `_spillScaled`. Blank cells precede the cohort's introduction.
-There is no total across different cohorts.
+Read a row left to right to follow a fixed cohort through successive compilers.
+Each cell gives **Ops / RA / X**:
+
+- **Ops:** executed ARM + RISC-V machine instructions, with fixed inputs.
+- **RA:** executed instructions belonging to allocator-created copies,
+ spills/reloads and rematerializations; a subset of Ops. A replacement constant
+ materialization counts even when it adds no instruction relative to the
+ original program. This measures allocator-generated code, not a net overhead.
+- **X:** x86-only `_spillScaled`, counting retained split moves (including
+ register copies) with weight `8^loopDepth`. It is a static estimate and does
+ not count rematerializations.
+
+**Cases D / X** counts dynamic program/target cases and x86 compilations.
+Repeated historical compilation entries remain repeated. These are independent
+coverage counts, not a sum; dynamic cases may use several inputs. Membership,
+inputs and returned values are checked across compiler columns. Chapter 20 has
+no encoder, so its dynamic cells are unavailable. There is no cross-cohort total.
<!-- spill-matrix:start -->
-| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | Ch 23 | Ch 24 | Ch 25 |
+| Test cohort | Cases D / X | Ch 20: Ops / RA / X | Ch 21: Ops / RA / X | Ch 22: Ops / RA / X | Ch 23: Ops / RA / X | Ch 24: Ops / RA / X | Ch 25: Ops / RA / X |
|---|---:|---:|---:|---:|---:|---:|---:|
-| Ch 20 | 39 | 360 | 576 | 430 | 442 | 441 | 491 |
-| Ch 21 | 52 | | 1,072 | 960 | 961 | 948 | 1,077 |
-| Ch 22 | 26 | | | 63 | 63 | 63 | 71 |
-| Ch 23 | 24 | | | | 103 | 103 | 152 |
-| Ch 24 | 58 | | | | | 1,387 | 1,503 |
-| Ch 25 | 14 | | | | | | 1,862 |
+| Ch 20 | 22 / 13 | — / — / 161 | 55,173 / 5,407 / 174 | 58,751 / 9,099 / 175 | 58,736 / 9,084 / 184 | 58,720 / 9,068 / 183 | 59,043 / 9,094 / 209 |
+| Ch 21 | 20 / 18 | | 6,207 / 261 / 461 | 6,324 / 379 / 475 | 6,326 / 381 / 475 | 6,325 / 380 / 470 | 6,233 / 219 / 533 |
+| Ch 22 | 14 / 8 | | | 198 / 60 / 7 | 194 / 56 / 7 | 194 / 56 / 7 | 332 / 136 / 11 |
+| Ch 23 | 14 / 8 | | | | 630 / 160 / 36 | 630 / 160 / 36 | 1,106 / 442 / 36 |
+| Ch 24 | 36 / 20 | | | | | 4,923 / 1,341 / 790 | 5,044 / 1,470 / 609 |
+| Ch 25 | 2 / 12 | | | | | | 20 / 0 / 1,862 |
<!-- spill-matrix:end -->
-Here **# tests counts compilations**, including the same program on different
-CPU/ABI combinations and separately recorded compilation phases. It is not the
-number of JUnit methods. Every filled cell in a row uses the same number of
-compilations and the same test/CPU/ABI membership, including zero-spill cases.
-The reporter checks the multiset of those identities before emitting the table.
-Jig methods are temporary debugging placeholders and are always ignored. Their
-15 replay entries are excluded consistently: six from cohort 23 and nine from
-cohort 24. The two cohort-23 Jigs duplicated one Fibonacci scratch program;
-Chapters 23-24 discarded its default main, while 25 compiled it and an exported
-function. Its removal eliminates 228 weighted moves from the Chapter 25 column
-without changing the compiler.
-Chapter 20's BrainFuck and MergeSort tests were moved into Chapter20Test in 21;
-the reporter recognizes that rename.
-
-`_spillScaled` counts retained SplitNodes, including register-to-register moves,
-weighted by `8^loopDepth`. Lower is better; these are estimated move costs,
-not measured memory traffic or execution time.
-
-Measured on Windows x86-64, 2026-10-07. The earlier cohorts combine x86-64
-SystemV/Win64 with ARM and RISC-V SystemV, at optimizer seed 123. Cohort 25
-contains 13 recorded native-test allocations at their existing seeds and a
-fresh system-library compilation at seed 456.
-
-The rows compare whole chapter compilers. Optimizations, scheduling, ABI
-lowering, and library representation also change, so a row need not decrease
-at every step. In particular, Chapter 25 replays earlier programs with the
-[documented syntax, constructor and visibility adaptations](../chapter25/src/test/java/com/seaofnodes/simple/spill/README.md).
-The two Chapter 22 C-return ABI cases are included there as well, keeping that
-row at 26 compilations. They cost no moves in 22-25 after Chapter 25's targeted
-function-address rematerialization preference.
-Use a controlled comparison within one compiler to isolate an allocator heuristic.
-
-The Chapter 25 comparison below separates that allocator change from making
-local helpers/classes private in eleven replay fixtures. The ranking trial uses
-the same sources, seeds, targets and ABIs as its baseline. Private names remove
-unneeded exported bodies and allocation factories; their effect is a change to
-the lowered program, not an allocator gain. Export/API fixtures stay public.
-
-| Test cohort | Before | Ranking only | + Private helpers | + Tiny-body check |
-|---|---:|---:|---:|---:|
-| Ch 20 | 608 | 608 | 491 | 491 |
-| Ch 21 | 1,170 | 1,170 | 1,077 | 1,077 |
-| Ch 22 | 127 | 99 | 71 | 71 |
-| Ch 23 | 152 | 152 | 152 | 152 |
-| Ch 24 | 1,558 | 1,545 | 1,506 | 1,503 |
-| Ch 25 | 1,851 | 1,829 | 1,829 | 1,862 |
-
-These four successful Chapter 25 reports have been recomputed from their saved
-per-allocation records with the Jig entries excluded, leaving 199 replay
-allocations. They also passed fresh library encoding and 23 native/system tests.
-The final compiler also passes its complete
-Make suite and a fresh SystemV library encoding on Windows. The earlier compiler
-columns are unchanged.
-
-The tiny-body check bypasses an oversized parse-time estimate only for recognized
-straight-line boilerplate containing at most 15 nodes. Parsing an initializer
-also builds its nested methods, so that estimate can greatly exceed the surviving
-body. Dependencies on Return inputs and the bounded inspected prefix allow retries
-after folding, without walking a large body on every change. The walk follows uses
-from entry to Return, with bounded fanout, avoiding sparse memory input arrays.
-This removes six constructor calls in the replay corpus, all in Bubble Sort:
-estimates 276, 352 and 479 represented bodies of only 11, 11 and 14 nodes. Public
-constructor bodies remain exported. The optimized-body limits remain 100 for
-ordinary functions and 200 for `<new>`/instance `<init>`; selection order is unchanged.
-It reproduces all 213 retained allocation records from the preceding 1,000-node constructor
-parse-allowance trial, replacing that allowance with a bounded structural check.
-
-This is a boilerplate reduction, not a uniform spill improvement. Bubble Sort's
-weighted moves change by -10 on x86, +3 on ARM and +4 on RISC-V. The fresh Win64
-library improves 842 to 809 and Dijkstra 584 to 582, while FileIO rises 293 to 361.
-The separately checked SystemV library improves 803 to 777. The broader inliner
-redesign is deferred until after the Chapter 25 split.
-
-Chapters 20-24 also recognize these tiny bodies, copying only straight-line
-stores and memory merges after ordinary peepholes settle. They already expand
-constructor defaults directly, so there are no analogous constructor calls to
-remove. Rerunning their full suites and allocation reports after this backport
-changed no raw or scaled allocation record; the matrix above remains unchanged.
-
-Run `make spill-stats` from the repository root to rerun all six chapters and
-write logs plus `matrix.md` under `build/spill-stats/`. A failed test or changed
-cohort membership makes the command fail; partial results are not published.
-To refresh these tables after a successful run:
+Measured on Windows, 2026-10-07, using optimizer seed 123 for the historical
+cohorts. The x86 Chapter 25 cohort retains its native-test seeds and the fresh
+system-library compilation at seed 456. Dynamic counts include generated class
+initializers and calls, but exclude the bodies of native `calloc`, `read` and
+`write`. Their deterministic emulator stubs provide allocation and fixed I/O.
+No clock timings, native-library instruction counts or code-size metric are mixed in.
+
+## Inputs and coverage
+
+| Cohort | Dynamic cases | Executions per compiler | x86 compilations |
+|---|---:|---:|---:|
+| Ch 20 | 22 | 44 | 13 |
+| Ch 21 | 20 | 20 | 18 |
+| Ch 22 | 14 | 14 | 8 |
+| Ch 23 | 14 | 26 | 8 |
+| Ch 24 | 36 | 48 | 20 |
+| Ch 25 | 2 | 2 | 12 |
+
+Newton, array-fill and Merge Sort use argument 16; allocation-index tests use
+0, 1 and 2. Branch/short-circuit and nested-equality cases use 0-3, Fibonacci uses 9, and Sieve uses
+100. Bubble Sort reads `[4, 3, 2, 1]`; BrainFuck runs its embedded Hello World
+program. Other measured cases use argument 0. The Chapter 25 dynamic row is only
+the two existing external-data emulator cases, with `counter` initialized to -7;
+it does not represent execution of the whole system library or native suite.
+
+String/Person APIs, the returned mixed-argument function, exported Merge Sort
+and BrainFuck, and mixed/stack-argument API tests still need explicit emulator
+drivers. Deliberately infinite programs remain compilation-only. The original
+allocation cohorts are preserved; these cases are not silently measured as zero.
+The previously excluded equality, ARM Newton and ARM Bubble Sort cases now run
+in every applicable compiler column. The measurement harness checks their
+answers, including nested equality at arguments 0-3, finite Newton results and
+Bubble Sort's complete output. The shared ISA tests and full Chapter 21-25
+Make suites pass after the backend/evaluator corrections.
+
+## Remaining differences after fixture cleanup
+
+The Chapter 24-to-25 comparison, using the same inputs and cases in each row:
+
+| Cohort | Ch 24 Ops | Ch 25 Ops | Change |
+|---|---:|---:|---:|
+| Ch 20 | 58,720 | 59,043 | +0.6% |
+| Ch 21 | 6,325 | 6,233 | -1.5% |
+| Ch 22 | 194 | 332 | +71.1% |
+| Ch 23 | 630 | 1,106 | +75.6% |
+| Ch 24 | 4,923 | 5,044 | +2.5% |
+
+Chapter 25 retains top-level bindings in the compilation unit's class object.
+Initializing its fields generates stores, function-pointer constants and class
+addresses, even when helper calls have already inlined and folded. Chapters
+through 24 can eliminate these bindings as locals. Small programs make this
+fixed initialization cost particularly visible.
+
+For example, `Chapter23Test.testAnd` takes 12 instructions per target in 24 and
+32 in 25. The extra 20 are four field stores, four instructions materializing
+two function pointers, ten materializing the class address at five uses
+(including the hidden call argument), and two integer constants. The actual
+short-circuit expression has already folded in both chapters.
+
+Across the entire Chapter 23 cohort, executed-node attribution accounts for
+the **476** extra instructions:
+
+| Generated work | Instruction change |
+|---|---:|
+| Field stores | +126 |
+| Function-pointer materialization | +108 |
+| Pointer-address materialization | +240 |
+| Integer constants | +50 |
+| Allocator copies/spills/reloads | +2 |
+| Unconditional jumps | +2 |
+| Calls, prologues/returns, extensions and loads removed | -52 |
+
+Its RA subset rises **160 to 442**, comprising **224** extra pointer-address
+rematerialization instructions, **56** extra integer rematerializations and
+only **2** extra copies/spills/reloads. RA labels allocator-created code; it
+does not mean all 282 extra instructions are stack traffic. `testAndPtr` also
+loses a helper call per execution, so inlining has improved despite the larger
+whole-program count. Moving constant initialization to static data would
+remove much of this work; reusing class addresses could reduce the repeated
+materialization. Neither optimization is included in this measurement update.
+
+Both Bubble Sort targets improve: RISC-V **2,062 / 591 to 1,877 / 493**, and
+ARM **2,040 / 579 to 1,868 / 478** (Ops / RA). Together they save 357 instructions;
+the other Chapter 24 cases add 478, leaving a net increase of 121. Newton
+integer improves **136 / 36 to 116 / 17** across both targets. In the Chapter 20
+BrainFuck case, ARM adds 95 instructions: 88 net extra integer materializations
+plus seven initialization instructions; its executed split count stays at 33.
+These tables compare whole chapter compilers, including frontend and inlining
+changes, rather than isolating allocator rankings.
+
+## Reproduce
+
+Run `make spill-stats` at the repository root to compile/run the static cohorts,
+capture their historical sources, execute the dynamic cases and validate the
+comparison. Capture uses temporary instrumented copies of the existing
+SpillStats listeners under `build/dynamic-stats`; it does not edit chapter code.
+The measured machine graph is stopped before allocation to identify subsequently
+inserted copies/cloned constants. After encoding, their instruction ranges are
+matched against the emulator's executed PCs, including multi-instruction expansions.
+
+After a successful run, update the marked tables with:
```sh
python build-support/spill-matrix.py --reuse --update-docs
```
-Each Chapter 20-25 README contains the matrix up to that chapter. The per-chapter
-`make spill-stats` commands still provide individual allocations and CPU/ABI sums.
+Per-program inputs, results and dynamic counts are written to
+`build/spill-stats/dynamic-counts.csv`; emulator logs and explicit skips live
+under `build/dynamic-stats/{21..25}/dynamic.log`. The x86 records remain in
+`build/spill-stats/spill-matrix-{20..25}.log`. The source adaptations for Chapter
+25 are documented in the [replay README](../chapter25/src/test/java/com/seaofnodes/simple/spill/README.md).
diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java
index 2fa4aec1..1ff77f59 100644
--- a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java
+++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java
@@ -329,6 +329,11 @@ public class EvalArm64 {
rval = regs[rn] & immediate;
break;
}
+ case 0xB2: { // ORR (immediate)
+ int rn = (ir >>> 5) & 31;
+ rval = (rn==31 ? 0 : regs[rn]) | Arm64.decodeImm12((ir >>> 10) & 0x1FFF);
+ break;
+ }
case 0x93, 0xD3: { // SBFM/UBFM: shifts and signed/unsigned extracts
int immr = (ir >>> 16) & 63;
int imms = (ir >>> 10) & 63;
@@ -372,6 +377,12 @@ public class EvalArm64 {
}
case 0x9A: {
+ if( (ir & 0xFFE0FC00)==0x9AC00C00 ) { // SDIV Xd,Xn,Xm
+ int rn=(ir >>> 5)&31, rm=(ir >>> 16)&31;
+ long lhs=rn==31 ? 0 : regs[rn], rhs=rm==31 ? 0 : regs[rm];
+ rval = rhs==0 ? 0 : lhs/rhs;
+ break;
+ }
int encodedCond = (ir >> 12) & 0xF;
int decodedCond = encodedCond ^ 1;
// conditional select(csel)
@@ -394,7 +405,7 @@ public class EvalArm64 {
case 0xF -> false ; // never
default -> throw new UnsupportedOperationException();
};
- if( cond ) rval = 1;
+ rval = cond ? 1 : 0;
break;
}
case 0x9B: { // mul
@@ -761,7 +772,8 @@ public class EvalArm64 {
}
if(rdid != -1) {
if(is_f) fregs[rdid] = frval;
- else regs[rdid] = rval;
+ // Register 31 is XZR except for ADD/SUB immediate, which writes SP.
+ else if( rdid!=31 || opcode1==0x91 || opcode1==0xD1 ) regs[rdid] = rval;
}
pc += 4;
diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java
index 46baf447..e6860ccd 100644
--- a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java
+++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java
@@ -203,6 +203,17 @@ public class EvalRisc5 {
break;
}
+ case 0x27: { // FSW/FSD
+ int rs1=(int)regs[(ir >>> 15)&31], rs2=(ir >>> 20)&31;
+ int addr=rs1+((ir >> 25)<<5)+((ir >>> 7)&31);
+ switch((ir >>> 12)&7) {
+ case 2: st4(addr,Float.floatToRawIntBits((float)fregs[rs2])); break;
+ case 3: st8(addr,Double.doubleToRawLongBits(fregs[rs2])); break;
+ default: trap=3;
+ }
+ rdid=0;
+ break;
+ }
case 0x23: { // Store 0b0100011
int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
long rs2 = regs[(ir >> 20) & 0x1f];
@@ -252,7 +263,7 @@ public class EvalRisc5 {
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 3 -> Long.compareUnsigned(rs1,rs2)<0 ? 1 : 0;
case 4 -> rs1 ^ rs2;
case 5 -> (ir & 0x40000000) != 0 ? (rs1 >> (rs2 & 0x3F)) : (rs1 >>> (rs2 & 0x3F));
case 6 -> rs1 | rs2;
@@ -329,8 +340,8 @@ public class EvalRisc5 {
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;
+ case 1 -> lhs < rhs ? 1 : 0;
+ case 2 -> lhs == rhs ? 1 : 0;
default -> throw new UnsupportedOperationException();
};
break;
diff --git a/isa/src/test/java/com/seaofnodes/isa/RiscTest.java b/isa/src/test/java/com/seaofnodes/isa/RiscTest.java
index 0cef8531..d10f97a4 100644
--- a/isa/src/test/java/com/seaofnodes/isa/RiscTest.java
+++ b/isa/src/test/java/com/seaofnodes/isa/RiscTest.java
@@ -9,6 +9,47 @@ public class RiscTest {
assert expected==actual : String.format("Expected %08x, got %08x",expected,actual);
}
public static void main(String[] args) {
+ // Instructions encountered by dynamic allocation measurements.
+ for( long[] pair : new long[][]{{17,3},{-17,3},{17,0},{Long.MIN_VALUE,-1}} ) {
+ var a=new EvalArm64(new byte[1024],512);
+ a.st4(4,0x9AC20C20); // sdiv x0,x1,x2
+ a._pc=4;a.regs[1]=pair[0];a.regs[2]=pair[1];
+ assert a.step(1)==0;
+ assert a.regs[0]==(pair[1]==0 ? 0 : pair[0]/pair[1]);
+ }
+ var discard=new EvalArm64(new byte[1024],512);
+ discard.st4(4,0x3862683F); // ldrb wzr,[x1,x2]; discard without changing SP
+ discard._pc=4;discard.regs[1]=100;discard.regs[2]=4;discard.st1(104,91);
+ assert discard.step(1)==0 && discard.regs[31]==512;
+ for(long value:new long[]{-1,0,1,Long.MIN_VALUE}) {
+ var unsigned=new EvalRisc5(new byte[1024],512);
+ unsigned.st4(4,0x00113193); // sltiu x3,x2,1 (seqz)
+ unsigned._pc=4;unsigned.regs[2]=value;
+ assert unsigned.step(1)==0;
+ assert unsigned.regs[3]==(value==0 ? 1 : 0);
+ }
+ var logical=new EvalArm64(new byte[1024],512);
+ logical.st4(4,0xB27F0020); // orr x0,x1,#2
+ logical.st4(8,0x9A9F17E2); // cset x2,eq; false must not retain prior result
+ logical._pc=4;logical.regs[1]=8;
+ assert logical.step(2)==0;
+ assert logical.regs[0]==10 && logical.regs[2]==0;
+ for(double rhs:new double[]{1,2,3,Double.NaN}) {
+ var r=new EvalRisc5(new byte[1024],512);
+ r.st4(4,0xA220A1D3); // feq.d x3,f1,f2
+ r.st4(8,0xA2209253); // flt.d x4,f1,f2
+ r._pc=4;r.fregs[1]=2;r.fregs[2]=rhs;
+ assert r.step(2)==0;
+ assert r.regs[3]==(2==rhs ? 1 : 0);
+ assert r.regs[4]==(2<rhs ? 1 : 0);
+ }
+ var store=new EvalRisc5(new byte[1024],512);
+ store.st4(4,0x00113827); // fsd f1,16(sp)
+ store.st4(8,0xFE112E27); // fsw f1,-4(sp)
+ store._pc=4;store.fregs[1]=-3.25;
+ assert store.step(2)==0;
+ assert store.ld8(528)==Double.doubleToRawLongBits(-3.25);
+ assert store.ld4s(508)==Float.floatToRawIntBits(-3.25f);
word(0x9E780020,Arm64.floatToInteger(1,0)); // FCVTZS x0,d1
for( double value : new double[]{0.0,-0.0,6.92,-6.92,0.99,-0.99,
0x1p63,Math.nextDown(0x1p63),-0x1p63,Math.nextDown(-0x1p63),
diff --git a/skills/chapter25-codex-notes.md b/skills/chapter25-codex-notes.md
index 2fd52938..36c5ee56 100644
--- a/skills/chapter25-codex-notes.md
+++ b/skills/chapter25-codex-notes.md
@@ -37,6 +37,12 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
They must not become benchmarks or regression tests just because their current
contents compile. Cohorts 23/24 now contain 24/58 allocations (199 total replay
entries), after removing all 15 frozen Jig entries.
+- Historical replay adaptations must preserve operand-valued short-circuit
+ expressions: replacing a `||` RHS with `!!rhs` changes its result. Compare
+ actual results as well as allocation legality when rewriting these fixtures.
+ Static `_spillScaled` also counts each inlined loop copy separately, without
+ credit for eliminated calls; inspect the optimized call graph before calling
+ a higher cross-chapter score an allocator regression.
- Prefer concrete ArrayList to List, common base classes to unnecessary
interfaces, and short names (`pred`, `proj`, `def`, `use`). Avoid redundant
Objects.requireNonNull. Prefer int[]/AryInt to List<Integer>; avoid large boxed
@@ -877,3 +883,15 @@ Cliff removed the frozen Chapter 18 fixture, replay tests, related reductions,
and seed-list entry on 2026-09-27 as unnecessary test overhead. Retain the
evaluator fixes; do not recreate that test suite. Do not describe timeout-skipped
comparisons as equal runtime results.
+
+- Dynamic ARM/RISC counts must validate results, not just termination. RISC
+ equality expands to SUB then unsigned SLTIU; the second node must consume
+ the SUB, not copy its inputs. ARM register 31 is XZR for load destinations,
+ so narrow loads use WMASK, never RMASK. Logical-not uses CMP to XZR followed
+ by CSET to its result register. Shared emulator fixes and Chapter 21-25
+ backports are recorded in `docs/chapter-backports.md`.
+- Cohort 23's dynamic increase from compiler 24 to 25 is primarily class-field
+ initialization: Ops 630->1106. RA 160->442 includes 224 extra pointer-address
+ and 56 integer rematerialization instructions, but only two extra splits.
+ Do not call that 282 additional spills. ARM and RISC Bubble Sort both improve
+ when executed. The measured breakdown lives in `docs/regalloc-spills.md`.