summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java28
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java28
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/EvalRisc5.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java28
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/EvalRisc5.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java28
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/EvalRisc5.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java28
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java2
-rw-r--r--docs/chapter-backports.md33
16 files changed, 182 insertions, 11 deletions
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index d8a5cebc..a5d86fa5 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -11,6 +11,34 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+ @Test public void testRisc64BitStore() {
+ byte[] mem = new byte[24];
+ EvalRisc5 cpu = new EvalRisc5(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ @Test public void testArm64BitStore() {
+ byte[] mem = new byte[24];
+ EvalArm64 cpu = new EvalArm64(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ // Check bytes independently of ld8, including overwrite and adjacent memory.
+ private static void check64BitStore(byte[] mem,
+ java.util.function.BiConsumer<Integer,Long> store,
+ java.util.function.IntToLongFunction load) {
+ java.util.Arrays.fill(mem,(byte)0xA5);
+ for( long value : new long[]{0x0123456789ABCDEFL,0xFEDCBA9876543210L,
+ -1L,0L,Long.MIN_VALUE,Long.MAX_VALUE} ) {
+ store.accept(8,value);
+ for( int i=0; i<mem.length; i++ ) {
+ int expected = i>=8 && i<16 ? (int)(value >>> (8*(i-8)))&0xFF : 0xA5;
+ assertEquals("byte "+i,expected,mem[i]&0xFF);
+ }
+ assertEquals(value,load.applyAsLong(8));
+ }
+ }
+
@Test @Ignore
public void testJig() throws IOException {
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
index 3ea9bea0..c10eccde 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -50,7 +50,7 @@ public class EvalArm64 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
public int step(int maxops) {
int trap = 0;
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java
index fd953342..aa9702f1 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java
@@ -55,7 +55,7 @@ import java.util.Arrays;
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
// Note: only a few bits are used. (Machine = 3, User = 0)
// Bits 0..1 = privilege.
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 77d1e337..5382a2bc 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -11,6 +11,34 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+ @Test public void testRisc64BitStore() {
+ byte[] mem = new byte[24];
+ EvalRisc5 cpu = new EvalRisc5(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ @Test public void testArm64BitStore() {
+ byte[] mem = new byte[24];
+ EvalArm64 cpu = new EvalArm64(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ // Check bytes independently of ld8, including overwrite and adjacent memory.
+ private static void check64BitStore(byte[] mem,
+ java.util.function.BiConsumer<Integer,Long> store,
+ java.util.function.IntToLongFunction load) {
+ java.util.Arrays.fill(mem,(byte)0xA5);
+ for( long value : new long[]{0x0123456789ABCDEFL,0xFEDCBA9876543210L,
+ -1L,0L,Long.MIN_VALUE,Long.MAX_VALUE} ) {
+ store.accept(8,value);
+ for( int i=0; i<mem.length; i++ ) {
+ int expected = i>=8 && i<16 ? (int)(value >>> (8*(i-8)))&0xFF : 0xA5;
+ assertEquals("byte "+i,expected,mem[i]&0xFF);
+ }
+ assertEquals(value,load.applyAsLong(8));
+ }
+ }
+
@Test @Ignore
public void testJig() throws IOException {
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java
index 981799bb..6f596799 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -54,7 +54,7 @@ public class EvalArm64 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
public int step(int maxops) {
int trap = 0;
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter22/src/test/java/com/seaofnodes/simple/EvalRisc5.java
index 0a1c034f..7b918615 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/EvalRisc5.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/EvalRisc5.java
@@ -58,7 +58,7 @@ public class EvalRisc5 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
// Note: only a few bits are used. (Machine = 3, User = 0)
// Bits 0..1 = privilege.
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 7f4898e0..d4913698 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -12,6 +12,34 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+ @Test public void testRisc64BitStore() {
+ byte[] mem = new byte[24];
+ EvalRisc5 cpu = new EvalRisc5(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ @Test public void testArm64BitStore() {
+ byte[] mem = new byte[24];
+ EvalArm64 cpu = new EvalArm64(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ // Check bytes independently of ld8, including overwrite and adjacent memory.
+ private static void check64BitStore(byte[] mem,
+ java.util.function.BiConsumer<Integer,Long> store,
+ java.util.function.IntToLongFunction load) {
+ java.util.Arrays.fill(mem,(byte)0xA5);
+ for( long value : new long[]{0x0123456789ABCDEFL,0xFEDCBA9876543210L,
+ -1L,0L,Long.MIN_VALUE,Long.MAX_VALUE} ) {
+ store.accept(8,value);
+ for( int i=0; i<mem.length; i++ ) {
+ int expected = i>=8 && i<16 ? (int)(value >>> (8*(i-8)))&0xFF : 0xA5;
+ assertEquals("byte "+i,expected,mem[i]&0xFF);
+ }
+ assertEquals(value,load.applyAsLong(8));
+ }
+ }
+
@Test
public void testJig() throws IOException {
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java
index c719b8e8..e7e4ada3 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -55,7 +55,7 @@ public class EvalArm64 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
public int step(int maxops) {
int trap = 0;
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter23/src/test/java/com/seaofnodes/simple/EvalRisc5.java
index 8c0a8792..a4cc7044 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/EvalRisc5.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/EvalRisc5.java
@@ -59,7 +59,7 @@ public class EvalRisc5 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
// Note: only a few bits are used. (Machine = 3, User = 0)
// Bits 0..1 = privilege.
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index ec55735e..7277a4d2 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -12,6 +12,34 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+ @Test public void testRisc64BitStore() {
+ byte[] mem = new byte[24];
+ EvalRisc5 cpu = new EvalRisc5(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ @Test public void testArm64BitStore() {
+ byte[] mem = new byte[24];
+ EvalArm64 cpu = new EvalArm64(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ // Check bytes independently of ld8, including overwrite and adjacent memory.
+ private static void check64BitStore(byte[] mem,
+ java.util.function.BiConsumer<Integer,Long> store,
+ java.util.function.IntToLongFunction load) {
+ java.util.Arrays.fill(mem,(byte)0xA5);
+ for( long value : new long[]{0x0123456789ABCDEFL,0xFEDCBA9876543210L,
+ -1L,0L,Long.MIN_VALUE,Long.MAX_VALUE} ) {
+ store.accept(8,value);
+ for( int i=0; i<mem.length; i++ ) {
+ int expected = i>=8 && i<16 ? (int)(value >>> (8*(i-8)))&0xFF : 0xA5;
+ assertEquals("byte "+i,expected,mem[i]&0xFF);
+ }
+ assertEquals(value,load.applyAsLong(8));
+ }
+ }
+
@Test
public void testJig() throws IOException {
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java
index 9575d9fe..04fee337 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -55,7 +55,7 @@ public class EvalArm64 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
public int step(int maxops) {
int trap = 0;
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter24/src/test/java/com/seaofnodes/simple/EvalRisc5.java
index 8c0a8792..a4cc7044 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/EvalRisc5.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/EvalRisc5.java
@@ -59,7 +59,7 @@ public class EvalRisc5 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
// Note: only a few bits are used. (Machine = 3, User = 0)
// Bits 0..1 = privilege.
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 6eb9a698..6d64f2d2 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -9,6 +9,34 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+ @Test public void testRisc64BitStore() {
+ byte[] mem = new byte[24];
+ EvalRisc5 cpu = new EvalRisc5(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ @Test public void testArm64BitStore() {
+ byte[] mem = new byte[24];
+ EvalArm64 cpu = new EvalArm64(mem,mem.length);
+ check64BitStore(mem,cpu::st8,cpu::ld8);
+ }
+
+ // Check bytes independently of ld8, including overwrite and adjacent memory.
+ private static void check64BitStore(byte[] mem,
+ java.util.function.BiConsumer<Integer,Long> store,
+ java.util.function.IntToLongFunction load) {
+ java.util.Arrays.fill(mem,(byte)0xA5);
+ for( long value : new long[]{0x0123456789ABCDEFL,0xFEDCBA9876543210L,
+ -1L,0L,Long.MIN_VALUE,Long.MAX_VALUE} ) {
+ store.accept(8,value);
+ for( int i=0; i<mem.length; i++ ) {
+ int expected = i>=8 && i<16 ? (int)(value >>> (8*(i-8)))&0xFF : 0xA5;
+ assertEquals("byte "+i,expected,mem[i]&0xFF);
+ }
+ assertEquals(value,load.applyAsLong(8));
+ }
+ }
+
@Test
public void testJig() throws IOException {
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java
index 32a5b152..d511f18f 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -55,7 +55,7 @@ public class EvalArm64 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
public int step(int maxops) {
int trap = 0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java
index 8c0a8792..a4cc7044 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java
@@ -59,7 +59,7 @@ public class EvalRisc5 {
public void st1(int x, int val) { _buf[x] = (byte)val; }
public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); }
- public void st8(int x, long val) { st4(x,(int)val); st2(x+4,(int)(val>>32)); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
// Note: only a few bits are used. (Machine = 3, User = 0)
// Bits 0..1 = privilege.
diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md
index 7dc4cbc3..7151dc4d 100644
--- a/docs/chapter-backports.md
+++ b/docs/chapter-backports.md
@@ -19,7 +19,6 @@ the correction into the chapter's representation, not the entire modern file.
| ID | Change | Proposed destination | Scope and acceptance evidence |
|---|---|---|---|
-| B10 | Preserve widening state in integer nonzero refinement | **Audit** first chapter with both widening and `nonZero`; no later than 24 | Chapter 25 `TypeInteger.nonZero` preserves `_widen`. Check lattice laws and loop refinement. Exclude TypeScalar, storage-type, and serialization changes. |
| B11 | Diagnose return types using the optimized return expression | 18; 19 already has the correction | `ReturnNode.err()` uses `expr()._type` instead of the parse-time `mt` aggregate. Chapter 18 rejects `struct S { u8 x; }; return new S; return 0;` with a mixed integer/reference error; Chapter 19 accepts it. Also test genuinely reachable incompatible returns. This is a candidate, not yet an applied or isolated-patch-verified fix. |
| B12 | Encode the actual destination register for two-address immediate multiply | 21 | `MulIX86` inherits `ImmX86.encoding`, whose ModRM.reg is fixed to zero and whose REX.R is clear. With source/destination both `rcx`, multiplying by 11 emits `48 6b c1 0b` (destination `rax`) instead of `48 6b c9 0b`. With both `r9`, it emits `49 6b c1 0b` instead of `4d 6b c9 0b`. Give multiply its proper register fields while preserving Chapter 21's two-address allocation contract; do not change the opcode-extension fields used by other `ImmX86` subclasses. Later chapters use a separate multiply encoder. |
@@ -132,6 +131,38 @@ Historical results below are dated evidence, not a substitute for a fresh
baseline. Logs live in ignored build directories and may no longer exist.
Reusable implementation lessons are in `skills/chapter25-codex-notes.md`.
+### Issue #247: emulator 64-bit stores corrected locally, 2026-09-19
+
+[Issue #247](https://github.com/SeaOfNodes/Simple/issues/247) identifies `st8`
+writing the upper half with `st2`. The identical bug exists in EvalRisc5 and
+EvalArm64 in every snapshot 21-25, starting with their introduction. Both now
+use `st4` for the upper half, writing all eight bytes rather than leaving the
+upper two unchanged.
+
+`Chapter21Test.testRisc64BitStore` and `testArm64BitStore` are carried through
+21-25. They check individual little-endian bytes independently of the load
+helper, round trips, positive/negative/extreme values, overwriting with zero,
+and untouched neighboring memory. All ten cases failed before the correction
+at the first unwritten byte (0xA5 instead of 0x23).
+
+Full `make tests` passed with assertions enabled in each snapshot 21-25,
+including 445 tests in Chapter 25. Log:
+`chapter25/build/issue247-review/fixed.log`.
+
+### B10 audit: still pending in Chapter 24, 2026-09-19
+
+The fix is present in Chapter 25 (introduced with commit `51f1f8f4`), but has
+not reached Chapter 24. Integer `nonZero` appears in 15; `_widen` first appears
+in 24, so earlier snapshots do not need this backport.
+
+An assertions-enabled probe of the current compiled snapshots tested `[0,255]`
+and `[-255,0]` at widening levels 0-3. Chapter 24 resets all six nonzero widening
+levels to zero; Chapter 25 preserves all of them. Both pass refinement (`isa`),
+idempotence, double-dual, singleton normalization, and zero-input checks. This
+establishes the missing type-level backport; it is not a loop-failure reproducer.
+Probe: `chapter25/build/b10-review/B10Probe.java`. No compiler correction was
+made during this audit; B10 remains in the pending queue above.
+
### B09: complete locally, 2026-09-19
Audited `Node.p(depth)`, recursive printing, labels, scope/graph viewers, and