diff options
| author | Cliff Click <cliffc@acm.org> | 2026-09-17 14:46:50 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-09-17 14:46:50 -0700 |
| commit | c433f2cc5748f05e3f30f968db958f33414469dc (patch) | |
| tree | abdbcf03c93591bba779ee714e22f47e9871ea93 | |
| parent | 0c99d74c85b937e5d566a7f2899d75025d9c54ea (diff) | |
| download | Simple-c433f2cc5748f05e3f30f968db958f33414469dc.tar.gz Simple-c433f2cc5748f05e3f30f968db958f33414469dc.zip | |
Back point cmpmem encoding
10 files changed, 144 insertions, 14 deletions
@@ -13,4 +13,4 @@ tag: tags $(foreach action,$(ACTIONS),$(eval $(action): $(addsuffix /$(action),$(CHAPTERS)))) $(CHAPTER_TARGETS): - +$(MAKE) -C $(dir $@) $(notdir $@) + +$(MAKE) --no-print-directory -C $(dir $@) $(notdir $@) diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index 489853eb..9f2cd5f9 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -38,7 +38,7 @@ public class CmpMemX86 extends MemOpX86 { case 0: enc.add1(_imm); break; case 1: enc.add2(_imm); break; case 2: enc.add4(_imm); break; - case 3: enc.add8(_imm); break; + case 3: enc.add4(_imm); break; } } else { encVal(enc, _declaredType, ptr, idx, src, _off, _scale, _swap); diff --git a/chapter21/src/test/java/com/seaofnodes/simple/X86EncodingTest.java b/chapter21/src/test/java/com/seaofnodes/simple/X86EncodingTest.java new file mode 100644 index 00000000..54fa96a8 --- /dev/null +++ b/chapter21/src/test/java/com/seaofnodes/simple/X86EncodingTest.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.CmpMemX86; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86EncodingTest { + @Test + public void testMemoryCompare64Immediate32() { + for (int immediate : new int[]{123456789, -123456789}) { + String src = "int[] !a = new int[2]; a[0] = arg; return a[arg & 1] == " + immediate + ";"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding, "x86_64_v2", "SystemV"); + var cmp = code._stop.walk(n -> n instanceof CmpMemX86 ? n : null); + assertNotNull("Must exercise a memory comparison", cmp); + var enc = code._encoding; + int start = enc._opStart[cmp._nid]; + + // REX.W, 81 /7, SIB with scale 8, disp8, and a sign-extended imm32. + assertEquals(0x48, enc.read1(start) & 0xF8); + assertEquals(0x81, enc.read1(start + 1)); + assertEquals(0x7C, enc.read1(start + 2)); + assertEquals(0xC0, enc.read1(start + 3) & 0xC0); + assertEquals(8, enc.read1(start + 4)); + assertEquals(immediate, enc.read4(start + 5)); + assertEquals("CMP r/m64 uses four immediate bytes", 9, enc._opLen[cmp._nid]); + + // SETE must immediately follow the CPU's nine-byte CMP instruction. + assertEquals(0x0F, enc.read1(start + 9)); + assertEquals(0x94, enc.read1(start + 10)); + } + } +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index a1845363..8c558ff2 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -38,7 +38,7 @@ public class CmpMemX86 extends MemOpX86 { case 0: enc.add1(_imm); break; case 1: enc.add2(_imm); break; case 2: enc.add4(_imm); break; - case 3: enc.add8(_imm); break; + case 3: enc.add4(_imm); break; } } else { encVal(enc, _declaredType, ptr, idx, src, _off, _scale, _swap); diff --git a/chapter22/src/test/java/com/seaofnodes/simple/X86EncodingTest.java b/chapter22/src/test/java/com/seaofnodes/simple/X86EncodingTest.java new file mode 100644 index 00000000..54fa96a8 --- /dev/null +++ b/chapter22/src/test/java/com/seaofnodes/simple/X86EncodingTest.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.CmpMemX86; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86EncodingTest { + @Test + public void testMemoryCompare64Immediate32() { + for (int immediate : new int[]{123456789, -123456789}) { + String src = "int[] !a = new int[2]; a[0] = arg; return a[arg & 1] == " + immediate + ";"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding, "x86_64_v2", "SystemV"); + var cmp = code._stop.walk(n -> n instanceof CmpMemX86 ? n : null); + assertNotNull("Must exercise a memory comparison", cmp); + var enc = code._encoding; + int start = enc._opStart[cmp._nid]; + + // REX.W, 81 /7, SIB with scale 8, disp8, and a sign-extended imm32. + assertEquals(0x48, enc.read1(start) & 0xF8); + assertEquals(0x81, enc.read1(start + 1)); + assertEquals(0x7C, enc.read1(start + 2)); + assertEquals(0xC0, enc.read1(start + 3) & 0xC0); + assertEquals(8, enc.read1(start + 4)); + assertEquals(immediate, enc.read4(start + 5)); + assertEquals("CMP r/m64 uses four immediate bytes", 9, enc._opLen[cmp._nid]); + + // SETE must immediately follow the CPU's nine-byte CMP instruction. + assertEquals(0x0F, enc.read1(start + 9)); + assertEquals(0x94, enc.read1(start + 10)); + } + } +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index 11f900ed..278bf0e6 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -38,7 +38,7 @@ public class CmpMemX86 extends MemOpX86 { case 0: enc.add1(_imm); break; case 1: enc.add2(_imm); break; case 2: enc.add4(_imm); break; - case 3: enc.add8(_imm); break; + case 3: enc.add4(_imm); break; } } else { encVal(enc, _declaredType, ptr, idx, src, _off, _scale, _swap); diff --git a/chapter23/src/test/java/com/seaofnodes/simple/X86EncodingTest.java b/chapter23/src/test/java/com/seaofnodes/simple/X86EncodingTest.java new file mode 100644 index 00000000..54fa96a8 --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/X86EncodingTest.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.CmpMemX86; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86EncodingTest { + @Test + public void testMemoryCompare64Immediate32() { + for (int immediate : new int[]{123456789, -123456789}) { + String src = "int[] !a = new int[2]; a[0] = arg; return a[arg & 1] == " + immediate + ";"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding, "x86_64_v2", "SystemV"); + var cmp = code._stop.walk(n -> n instanceof CmpMemX86 ? n : null); + assertNotNull("Must exercise a memory comparison", cmp); + var enc = code._encoding; + int start = enc._opStart[cmp._nid]; + + // REX.W, 81 /7, SIB with scale 8, disp8, and a sign-extended imm32. + assertEquals(0x48, enc.read1(start) & 0xF8); + assertEquals(0x81, enc.read1(start + 1)); + assertEquals(0x7C, enc.read1(start + 2)); + assertEquals(0xC0, enc.read1(start + 3) & 0xC0); + assertEquals(8, enc.read1(start + 4)); + assertEquals(immediate, enc.read4(start + 5)); + assertEquals("CMP r/m64 uses four immediate bytes", 9, enc._opLen[cmp._nid]); + + // SETE must immediately follow the CPU's nine-byte CMP instruction. + assertEquals(0x0F, enc.read1(start + 9)); + assertEquals(0x94, enc.read1(start + 10)); + } + } +} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index 11f900ed..278bf0e6 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -38,7 +38,7 @@ public class CmpMemX86 extends MemOpX86 { case 0: enc.add1(_imm); break; case 1: enc.add2(_imm); break; case 2: enc.add4(_imm); break; - case 3: enc.add8(_imm); break; + case 3: enc.add4(_imm); break; } } else { encVal(enc, _declaredType, ptr, idx, src, _off, _scale, _swap); diff --git a/chapter24/src/test/java/com/seaofnodes/simple/X86EncodingTest.java b/chapter24/src/test/java/com/seaofnodes/simple/X86EncodingTest.java new file mode 100644 index 00000000..54fa96a8 --- /dev/null +++ b/chapter24/src/test/java/com/seaofnodes/simple/X86EncodingTest.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.CmpMemX86; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86EncodingTest { + @Test + public void testMemoryCompare64Immediate32() { + for (int immediate : new int[]{123456789, -123456789}) { + String src = "int[] !a = new int[2]; a[0] = arg; return a[arg & 1] == " + immediate + ";"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding, "x86_64_v2", "SystemV"); + var cmp = code._stop.walk(n -> n instanceof CmpMemX86 ? n : null); + assertNotNull("Must exercise a memory comparison", cmp); + var enc = code._encoding; + int start = enc._opStart[cmp._nid]; + + // REX.W, 81 /7, SIB with scale 8, disp8, and a sign-extended imm32. + assertEquals(0x48, enc.read1(start) & 0xF8); + assertEquals(0x81, enc.read1(start + 1)); + assertEquals(0x7C, enc.read1(start + 2)); + assertEquals(0xC0, enc.read1(start + 3) & 0xC0); + assertEquals(8, enc.read1(start + 4)); + assertEquals(immediate, enc.read4(start + 5)); + assertEquals("CMP r/m64 uses four immediate bytes", 9, enc._opLen[cmp._nid]); + + // SETE must immediately follow the CPU's nine-byte CMP instruction. + assertEquals(0x0F, enc.read1(start + 9)); + assertEquals(0x94, enc.read1(start + 10)); + } + } +} diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md index 4e7e2a69..bd7e08e5 100644 --- a/docs/chapter-backports.md +++ b/docs/chapter-backports.md @@ -1,6 +1,6 @@ # Chapter backport review -This is a work queue, not a record of completed compiler backports. Independent +This is a queue of pending work. Remove completed corrections from the queue. Independent corrections should start in the earliest affected chapter and propagate through every later affected snapshot. A regression in Chapter 25's `Chapter21Test` does not test the compiler in the `chapter21` directory. @@ -9,17 +9,14 @@ For now, keep building SSA with incomplete types in Chapter 25. Moving that architecture earlier, or splitting Chapter 25, is deferred while small changes establish the review workflow. No renumbering is committed. -## First two review candidates +## Next review candidate | ID | Small change | Destination | Evidence and regression | |---|---|---|---| -| B01 | Encode a 64-bit memory comparison with a 32-bit immediate | 21, then 22-24; 25 already fixed | `CmpMemX86.encoding`: `case 3: enc.add8(_imm)` becomes `add4`. Keep each chapter's type accessor. Check instruction length/bytes and a comparison followed by another instruction. | | B02 | Correct immediate-multiply REX register order | 22, then 23-24; audit 21 separately | `MulIX86.encoding`: `rex(src,dst,0)` becomes `rex(dst,src,0)`. Exercise source and destination on opposite sides of register 8. Chapter 21 uses `ImmX86`, not this one-line implementation. | -The initial proposal was B01 followed by B02. The Chapter 18 investigation below -now supplies another small first-review candidate, B11. Review one correction's -reduced failure, smallest patch, and test results before starting another. -No compiler fixes have been applied with this build and documentation change. +Review B02 before starting another candidate. Keep each correction's reduced +failure, smallest patch, and test results together for review. ## Subsequent small changes @@ -68,7 +65,8 @@ and regressions. Keep one logical correction per commit across affected chapters changes invalidate their IR/native code. Force a full Java rebuild when shared APIs or constants change: older Makefiles do not track all Java dependencies. Do not weaken checks or drop tests. -6. Record chapters, commands, and results here. Keep unrelated dirty changes; +6. Report chapters, commands, and results for review; remove completed items from + this queue. Keep unrelated dirty changes; do not push without explicit approval. The top-level runner supports all 25 chapters: @@ -89,7 +87,7 @@ starts, so run `make lib` separately before `make tests`. ## Validation record -All compiler backports B01-B11 are pending. +### Initial setup baseline On 2026-09-17, using Windows/Cygwin and OpenJDK 22.0.2: |
