diff options
| author | Cliff Click <cliffc@acm.org> | 2026-10-28 13:39:05 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-28 13:39:05 -0700 |
| commit | 86ce5bc5d5018dcb62054d214b23f7a59b9c0d7d (patch) | |
| tree | 7b564b142c32924db354edfd8553d34cb66fff3e | |
| parent | e481e3d215882faa82b19f29cfde5746d4e51863 (diff) | |
| download | Simple-86ce5bc5d5018dcb62054d214b23f7a59b9c0d7d.tar.gz Simple-86ce5bc5d5018dcb62054d214b23f7a59b9c0d7d.zip | |
Fix bugs x86 storing only 32bits of a large constant
Missed field narrowing in a few cases
38 files changed, 1071 insertions, 67 deletions
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index e7e0b874..1b14bcf1 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -430,7 +430,7 @@ public class x86_64_v2 extends Machine { } private int imm( Node xval ) { assert val==null && imm==0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && (int)ti.value()==ti.value() ) { val = null; imm = (int)ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter19/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter19/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c32a69c3 --- /dev/null +++ b/chapter19/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.InstructionSelection,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 06e3e578..2b813379 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -488,7 +488,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val==null && imm==0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && (int)ti.value()==ti.value() ) { val = null; imm = (int)ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter20/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter20/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c853082f --- /dev/null +++ b/chapter20/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.InstSelect,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 1dc47188..253a8135 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -544,7 +544,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter21/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java b/chapter21/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java new file mode 100644 index 00000000..f0c9d37f --- /dev/null +++ b/chapter21/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; + +public class X86StoreNativeTest { + @Test public void testConstantStoresAndAdds() throws IOException { + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("x86ConstantStore.smp"), """ + struct Narrow { u32 x; u32 guard; }; + struct Wide { int x; int guard; }; + val storeNarrow = { !Narrow s -> s.x=4294967295; return 0; }; + val storePositive = { !Wide s -> s.x=81985529216486895; return 0; }; + val storeNegative = { !Wide s -> s.x=-2147483649; return 0; }; + val addPositive = { !Wide s -> s.x+=4294967295; return 0; }; + val addNegative = { !Wide s -> s.x+=-2147483649; return 0; }; + """); + // Read from C so load forwarding cannot hide an incorrectly encoded Store. + Files.writeString(dir.resolve("x86ConstantStore.c"), """ + #include <stdint.h> + struct Narrow { uint32_t x, guard; }; + struct Wide { int64_t x, guard; }; + extern long long storeNarrow(struct Narrow *); + extern long long storePositive(struct Wide *); + extern long long storeNegative(struct Wide *); + extern long long addPositive(struct Wide *); + extern long long addNegative(struct Wide *); + int main(void) { + struct Narrow n = {0, 0x12345678}; + struct Wide w = {0, 0x123456789abcdefLL}; + storeNarrow(&n); + if (n.x != UINT32_MAX || n.guard != 0x12345678) return 1; + storePositive(&w); + if (w.x != 81985529216486895LL || w.guard != 0x123456789abcdefLL) return 2; + storeNegative(&w); + if (w.x != -2147483649LL || w.guard != 0x123456789abcdefLL) return 3; + w.x = 7; + addPositive(&w); + if (w.x != 4294967302LL || w.guard != 0x123456789abcdefLL) return 4; + addNegative(&w); + if (w.x != 2147483653LL || w.guard != 0x123456789abcdefLL) return 5; + return 0; + } + """); + TestC.run("build/objs","x86ConstantStore","",-1); + } +} diff --git a/chapter21/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter21/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c853082f --- /dev/null +++ b/chapter21/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.InstSelect,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index dc3f813e..8a578c51 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -550,7 +550,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter22/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java b/chapter22/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java new file mode 100644 index 00000000..f0c9d37f --- /dev/null +++ b/chapter22/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; + +public class X86StoreNativeTest { + @Test public void testConstantStoresAndAdds() throws IOException { + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("x86ConstantStore.smp"), """ + struct Narrow { u32 x; u32 guard; }; + struct Wide { int x; int guard; }; + val storeNarrow = { !Narrow s -> s.x=4294967295; return 0; }; + val storePositive = { !Wide s -> s.x=81985529216486895; return 0; }; + val storeNegative = { !Wide s -> s.x=-2147483649; return 0; }; + val addPositive = { !Wide s -> s.x+=4294967295; return 0; }; + val addNegative = { !Wide s -> s.x+=-2147483649; return 0; }; + """); + // Read from C so load forwarding cannot hide an incorrectly encoded Store. + Files.writeString(dir.resolve("x86ConstantStore.c"), """ + #include <stdint.h> + struct Narrow { uint32_t x, guard; }; + struct Wide { int64_t x, guard; }; + extern long long storeNarrow(struct Narrow *); + extern long long storePositive(struct Wide *); + extern long long storeNegative(struct Wide *); + extern long long addPositive(struct Wide *); + extern long long addNegative(struct Wide *); + int main(void) { + struct Narrow n = {0, 0x12345678}; + struct Wide w = {0, 0x123456789abcdefLL}; + storeNarrow(&n); + if (n.x != UINT32_MAX || n.guard != 0x12345678) return 1; + storePositive(&w); + if (w.x != 81985529216486895LL || w.guard != 0x123456789abcdefLL) return 2; + storeNegative(&w); + if (w.x != -2147483649LL || w.guard != 0x123456789abcdefLL) return 3; + w.x = 7; + addPositive(&w); + if (w.x != 4294967302LL || w.guard != 0x123456789abcdefLL) return 4; + addNegative(&w); + if (w.x != 2147483653LL || w.guard != 0x123456789abcdefLL) return 5; + return 0; + } + """); + TestC.run("build/objs","x86ConstantStore","",-1); + } +} diff --git a/chapter22/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter22/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c07ac904 --- /dev/null +++ b/chapter22/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Select,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index fcae17f8..1c452403 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -551,7 +551,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter23/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java b/chapter23/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java new file mode 100644 index 00000000..f0c9d37f --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; + +public class X86StoreNativeTest { + @Test public void testConstantStoresAndAdds() throws IOException { + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("x86ConstantStore.smp"), """ + struct Narrow { u32 x; u32 guard; }; + struct Wide { int x; int guard; }; + val storeNarrow = { !Narrow s -> s.x=4294967295; return 0; }; + val storePositive = { !Wide s -> s.x=81985529216486895; return 0; }; + val storeNegative = { !Wide s -> s.x=-2147483649; return 0; }; + val addPositive = { !Wide s -> s.x+=4294967295; return 0; }; + val addNegative = { !Wide s -> s.x+=-2147483649; return 0; }; + """); + // Read from C so load forwarding cannot hide an incorrectly encoded Store. + Files.writeString(dir.resolve("x86ConstantStore.c"), """ + #include <stdint.h> + struct Narrow { uint32_t x, guard; }; + struct Wide { int64_t x, guard; }; + extern long long storeNarrow(struct Narrow *); + extern long long storePositive(struct Wide *); + extern long long storeNegative(struct Wide *); + extern long long addPositive(struct Wide *); + extern long long addNegative(struct Wide *); + int main(void) { + struct Narrow n = {0, 0x12345678}; + struct Wide w = {0, 0x123456789abcdefLL}; + storeNarrow(&n); + if (n.x != UINT32_MAX || n.guard != 0x12345678) return 1; + storePositive(&w); + if (w.x != 81985529216486895LL || w.guard != 0x123456789abcdefLL) return 2; + storeNegative(&w); + if (w.x != -2147483649LL || w.guard != 0x123456789abcdefLL) return 3; + w.x = 7; + addPositive(&w); + if (w.x != 4294967302LL || w.guard != 0x123456789abcdefLL) return 4; + addNegative(&w); + if (w.x != 2147483653LL || w.guard != 0x123456789abcdefLL) return 5; + return 0; + } + """); + TestC.run("build/objs","x86ConstantStore","",-1); + } +} diff --git a/chapter23/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter23/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c07ac904 --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Select,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter24a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter24a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index fcae17f8..1c452403 100644 --- a/chapter24a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter24a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -551,7 +551,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter24a/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java b/chapter24a/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java new file mode 100644 index 00000000..f0c9d37f --- /dev/null +++ b/chapter24a/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; + +public class X86StoreNativeTest { + @Test public void testConstantStoresAndAdds() throws IOException { + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("x86ConstantStore.smp"), """ + struct Narrow { u32 x; u32 guard; }; + struct Wide { int x; int guard; }; + val storeNarrow = { !Narrow s -> s.x=4294967295; return 0; }; + val storePositive = { !Wide s -> s.x=81985529216486895; return 0; }; + val storeNegative = { !Wide s -> s.x=-2147483649; return 0; }; + val addPositive = { !Wide s -> s.x+=4294967295; return 0; }; + val addNegative = { !Wide s -> s.x+=-2147483649; return 0; }; + """); + // Read from C so load forwarding cannot hide an incorrectly encoded Store. + Files.writeString(dir.resolve("x86ConstantStore.c"), """ + #include <stdint.h> + struct Narrow { uint32_t x, guard; }; + struct Wide { int64_t x, guard; }; + extern long long storeNarrow(struct Narrow *); + extern long long storePositive(struct Wide *); + extern long long storeNegative(struct Wide *); + extern long long addPositive(struct Wide *); + extern long long addNegative(struct Wide *); + int main(void) { + struct Narrow n = {0, 0x12345678}; + struct Wide w = {0, 0x123456789abcdefLL}; + storeNarrow(&n); + if (n.x != UINT32_MAX || n.guard != 0x12345678) return 1; + storePositive(&w); + if (w.x != 81985529216486895LL || w.guard != 0x123456789abcdefLL) return 2; + storeNegative(&w); + if (w.x != -2147483649LL || w.guard != 0x123456789abcdefLL) return 3; + w.x = 7; + addPositive(&w); + if (w.x != 4294967302LL || w.guard != 0x123456789abcdefLL) return 4; + addNegative(&w); + if (w.x != 2147483653LL || w.guard != 0x123456789abcdefLL) return 5; + return 0; + } + """); + TestC.run("build/objs","x86ConstantStore","",-1); + } +} diff --git a/chapter24a/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter24a/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c07ac904 --- /dev/null +++ b/chapter24a/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Select,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter24b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter24b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index fcae17f8..1c452403 100644 --- a/chapter24b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter24b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -551,7 +551,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter24b/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java b/chapter24b/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java new file mode 100644 index 00000000..f0c9d37f --- /dev/null +++ b/chapter24b/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; + +public class X86StoreNativeTest { + @Test public void testConstantStoresAndAdds() throws IOException { + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("x86ConstantStore.smp"), """ + struct Narrow { u32 x; u32 guard; }; + struct Wide { int x; int guard; }; + val storeNarrow = { !Narrow s -> s.x=4294967295; return 0; }; + val storePositive = { !Wide s -> s.x=81985529216486895; return 0; }; + val storeNegative = { !Wide s -> s.x=-2147483649; return 0; }; + val addPositive = { !Wide s -> s.x+=4294967295; return 0; }; + val addNegative = { !Wide s -> s.x+=-2147483649; return 0; }; + """); + // Read from C so load forwarding cannot hide an incorrectly encoded Store. + Files.writeString(dir.resolve("x86ConstantStore.c"), """ + #include <stdint.h> + struct Narrow { uint32_t x, guard; }; + struct Wide { int64_t x, guard; }; + extern long long storeNarrow(struct Narrow *); + extern long long storePositive(struct Wide *); + extern long long storeNegative(struct Wide *); + extern long long addPositive(struct Wide *); + extern long long addNegative(struct Wide *); + int main(void) { + struct Narrow n = {0, 0x12345678}; + struct Wide w = {0, 0x123456789abcdefLL}; + storeNarrow(&n); + if (n.x != UINT32_MAX || n.guard != 0x12345678) return 1; + storePositive(&w); + if (w.x != 81985529216486895LL || w.guard != 0x123456789abcdefLL) return 2; + storeNegative(&w); + if (w.x != -2147483649LL || w.guard != 0x123456789abcdefLL) return 3; + w.x = 7; + addPositive(&w); + if (w.x != 4294967302LL || w.guard != 0x123456789abcdefLL) return 4; + addNegative(&w); + if (w.x != 2147483653LL || w.guard != 0x123456789abcdefLL) return 5; + return 0; + } + """); + TestC.run("build/objs","x86ConstantStore","",-1); + } +} diff --git a/chapter24b/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter24b/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c07ac904 --- /dev/null +++ b/chapter24b/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Select,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java index ff2d1138..0848e8e7 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -211,7 +211,7 @@ public class LoadNode extends MemOpNode { } private Node folded(Node win) { - return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + return win instanceof StoreNode st ? extend(castRO(st.val()),st._declaredType) : zero((NewNode)win); } // Materialize only after the profitability search succeeds. The value Phi @@ -267,8 +267,10 @@ public class LoadNode extends MemOpNode { // When a load bypasses a store, the store might truncate bits - and the // load will need to zero/sign-extend. - private Node extend(Node val) { - if( !(_declaredType instanceof TypeInteger ti) ) return val; + // Use the matching Store's width: while a class is open, this Load may + // already know its alias but not yet have recorded its storage type. + private Node extend(Node val, Type storage) { + if( !(storage instanceof TypeInteger ti) ) return val; if( ti._min==0 ) // Unsigned return new AndNode(null,val,con(ti._max)); // Signed extension diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index f0598422..ab298f2e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -551,7 +551,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter25/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java b/chapter25/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java new file mode 100644 index 00000000..f6cc2e4b --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java @@ -0,0 +1,89 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.isa.eval.EvalRisc5; +import org.junit.Test; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import static org.junit.Assert.*; + +public class NarrowFieldTest { + private static final String[] TYPES = {"u8", "i8", "u16", "i16", "u32", "i32"}; + private static final long[] MINS = {0, -128, 0, -32768, 0, Integer.MIN_VALUE}; + private static final long[] MAXS = {255, 127, 65535, 32767, 0xffffffffL, Integer.MAX_VALUE}; + + // Both the method result and a later read must see the stored, narrowed value. + private static String source(int i, boolean increment, boolean argument) { + String name = "C"+i+(increment ? "Inc" : "Dec"); + long initial = argument ? 0 : (increment ? MAXS[i] : MINS[i]); + return "struct "+name+" { "+TYPES[i]+" x="+initial+"; " + +"val next_noInline={ -> x"+(increment ? "++" : "--")+"; return x; }; }; " + +"var c=new "+name+"; "+(argument ? "c.x=arg; " : "") + +"val a=c.next_noInline(); val b=c.next_noInline(); " + +"return a+10*b+100*c.x;"; + } + + private static long expected(int i, boolean increment) { + long first = increment ? MINS[i] : MAXS[i]; + long second = first+(increment ? 1 : -1); + return first+110*second; + } + + @Test public void testMethodResults() { + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) + for( int seed : new int[]{2,13,123} ) { + String src = source(i,increment,false); + CodeGen code = new CodeGen(src,TypeInteger.BOT,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals(src,Long.toString(expected(i,increment)),Eval2.eval(code,0)); + } + } + + @Test public void testNativeMethodResults() throws IOException { + StringBuilder src = new StringBuilder(); + StringBuilder declarations = new StringBuilder(); + StringBuilder checks = new StringBuilder(); + // Supply the initial value through the native argument to also exercise + // narrowing an unknown full-width value before the method calls. + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) { + String name = "narrow"+i+(increment ? "Inc" : "Dec"); + src.append("val ").append(name).append("={int arg -> ") + .append(source(i,increment,true)).append(" }; "); + declarations.append("__attribute__((CALL_CONV)) extern long long ").append(name).append("(long long);\n"); + checks.append("if (").append(name).append('(').append(increment ? MAXS[i] : MINS[i]) + .append("LL)!=").append(expected(i,increment)).append("LL) return 1;\n"); + } + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Path driver = dir.resolve("narrowField.c"); + Files.writeString(driver,declarations+"int main(void) {\n"+checks+"return 0; }\n"); + String abi = TestC.OS.startsWith("Windows") ? "ms_abi" : "sysv_abi"; + TestC.run(src.toString(),TestC.CALL_CONVENTION,null,abi,driver.toString(),"build/objs/narrowField","S","",-1); + } + + @Test public void testRiscvMethodResults() throws IOException { + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) { + String src = source(i,increment,false); + CodeGen code = new CheckedCodeGen(src).driver("riscv","SystemV",null); + int entry = -1; + for( var node : code._start._outputs ) + if( node instanceof FunNode fun && "main".equals(fun._name) ) + entry = code._encoding._opStart[fun._nid]; + assertTrue(entry>=0); + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu._pc = entry; + assertEquals(src,0,cpu.step(100000)); + assertEquals(src,expected(i,increment),cpu.regs[riscv.A0]); + } + } +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java b/chapter25/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java new file mode 100644 index 00000000..f0c9d37f --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; + +public class X86StoreNativeTest { + @Test public void testConstantStoresAndAdds() throws IOException { + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("x86ConstantStore.smp"), """ + struct Narrow { u32 x; u32 guard; }; + struct Wide { int x; int guard; }; + val storeNarrow = { !Narrow s -> s.x=4294967295; return 0; }; + val storePositive = { !Wide s -> s.x=81985529216486895; return 0; }; + val storeNegative = { !Wide s -> s.x=-2147483649; return 0; }; + val addPositive = { !Wide s -> s.x+=4294967295; return 0; }; + val addNegative = { !Wide s -> s.x+=-2147483649; return 0; }; + """); + // Read from C so load forwarding cannot hide an incorrectly encoded Store. + Files.writeString(dir.resolve("x86ConstantStore.c"), """ + #include <stdint.h> + struct Narrow { uint32_t x, guard; }; + struct Wide { int64_t x, guard; }; + extern long long storeNarrow(struct Narrow *); + extern long long storePositive(struct Wide *); + extern long long storeNegative(struct Wide *); + extern long long addPositive(struct Wide *); + extern long long addNegative(struct Wide *); + int main(void) { + struct Narrow n = {0, 0x12345678}; + struct Wide w = {0, 0x123456789abcdefLL}; + storeNarrow(&n); + if (n.x != UINT32_MAX || n.guard != 0x12345678) return 1; + storePositive(&w); + if (w.x != 81985529216486895LL || w.guard != 0x123456789abcdefLL) return 2; + storeNegative(&w); + if (w.x != -2147483649LL || w.guard != 0x123456789abcdefLL) return 3; + w.x = 7; + addPositive(&w); + if (w.x != 4294967302LL || w.guard != 0x123456789abcdefLL) return 4; + addNegative(&w); + if (w.x != 2147483653LL || w.guard != 0x123456789abcdefLL) return 5; + return 0; + } + """); + TestC.run("build/objs","x86ConstantStore","",-1); + } +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter25/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c07ac904 --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Select,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter26/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter26/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 736d91a2..aed0e703 100644 --- a/chapter26/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter26/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -253,7 +253,7 @@ public class LoadNode extends MemOpNode { } private Node folded(Node win) { - return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + return win instanceof StoreNode st ? extend(castRO(st.val()),st._declaredType) : zero((NewNode)win); } // Materialize only after the profitability search succeeds. The value Phi @@ -311,8 +311,10 @@ public class LoadNode extends MemOpNode { // When a load bypasses a store, the store might truncate bits - and the // load will need to zero/sign-extend. - private Node extend(Node val) { - if( !(_declaredType instanceof TypeInteger ti) ) return val; + // Use the matching Store's width: while a class is open, this Load may + // already know its alias but not yet have recorded its storage type. + private Node extend(Node val, Type storage) { + if( !(storage instanceof TypeInteger ti) ) return val; if( ti._min==0 ) // Unsigned return new AndNode(null,val,con(ti._max)); // Signed extension diff --git a/chapter26/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter26/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index c854dd9d..a92b43d5 100644 --- a/chapter26/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter26/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -552,7 +552,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter26/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java b/chapter26/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java new file mode 100644 index 00000000..f6cc2e4b --- /dev/null +++ b/chapter26/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java @@ -0,0 +1,89 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.isa.eval.EvalRisc5; +import org.junit.Test; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import static org.junit.Assert.*; + +public class NarrowFieldTest { + private static final String[] TYPES = {"u8", "i8", "u16", "i16", "u32", "i32"}; + private static final long[] MINS = {0, -128, 0, -32768, 0, Integer.MIN_VALUE}; + private static final long[] MAXS = {255, 127, 65535, 32767, 0xffffffffL, Integer.MAX_VALUE}; + + // Both the method result and a later read must see the stored, narrowed value. + private static String source(int i, boolean increment, boolean argument) { + String name = "C"+i+(increment ? "Inc" : "Dec"); + long initial = argument ? 0 : (increment ? MAXS[i] : MINS[i]); + return "struct "+name+" { "+TYPES[i]+" x="+initial+"; " + +"val next_noInline={ -> x"+(increment ? "++" : "--")+"; return x; }; }; " + +"var c=new "+name+"; "+(argument ? "c.x=arg; " : "") + +"val a=c.next_noInline(); val b=c.next_noInline(); " + +"return a+10*b+100*c.x;"; + } + + private static long expected(int i, boolean increment) { + long first = increment ? MINS[i] : MAXS[i]; + long second = first+(increment ? 1 : -1); + return first+110*second; + } + + @Test public void testMethodResults() { + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) + for( int seed : new int[]{2,13,123} ) { + String src = source(i,increment,false); + CodeGen code = new CodeGen(src,TypeInteger.BOT,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals(src,Long.toString(expected(i,increment)),Eval2.eval(code,0)); + } + } + + @Test public void testNativeMethodResults() throws IOException { + StringBuilder src = new StringBuilder(); + StringBuilder declarations = new StringBuilder(); + StringBuilder checks = new StringBuilder(); + // Supply the initial value through the native argument to also exercise + // narrowing an unknown full-width value before the method calls. + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) { + String name = "narrow"+i+(increment ? "Inc" : "Dec"); + src.append("val ").append(name).append("={int arg -> ") + .append(source(i,increment,true)).append(" }; "); + declarations.append("__attribute__((CALL_CONV)) extern long long ").append(name).append("(long long);\n"); + checks.append("if (").append(name).append('(').append(increment ? MAXS[i] : MINS[i]) + .append("LL)!=").append(expected(i,increment)).append("LL) return 1;\n"); + } + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Path driver = dir.resolve("narrowField.c"); + Files.writeString(driver,declarations+"int main(void) {\n"+checks+"return 0; }\n"); + String abi = TestC.OS.startsWith("Windows") ? "ms_abi" : "sysv_abi"; + TestC.run(src.toString(),TestC.CALL_CONVENTION,null,abi,driver.toString(),"build/objs/narrowField","S","",-1); + } + + @Test public void testRiscvMethodResults() throws IOException { + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) { + String src = source(i,increment,false); + CodeGen code = new CheckedCodeGen(src).driver("riscv","SystemV",null); + int entry = -1; + for( var node : code._start._outputs ) + if( node instanceof FunNode fun && "main".equals(fun._name) ) + entry = code._encoding._opStart[fun._nid]; + assertTrue(entry>=0); + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu._pc = entry; + assertEquals(src,0,cpu.step(100000)); + assertEquals(src,expected(i,increment),cpu.regs[riscv.A0]); + } + } +} diff --git a/chapter26/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java b/chapter26/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java new file mode 100644 index 00000000..f0c9d37f --- /dev/null +++ b/chapter26/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; + +public class X86StoreNativeTest { + @Test public void testConstantStoresAndAdds() throws IOException { + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("x86ConstantStore.smp"), """ + struct Narrow { u32 x; u32 guard; }; + struct Wide { int x; int guard; }; + val storeNarrow = { !Narrow s -> s.x=4294967295; return 0; }; + val storePositive = { !Wide s -> s.x=81985529216486895; return 0; }; + val storeNegative = { !Wide s -> s.x=-2147483649; return 0; }; + val addPositive = { !Wide s -> s.x+=4294967295; return 0; }; + val addNegative = { !Wide s -> s.x+=-2147483649; return 0; }; + """); + // Read from C so load forwarding cannot hide an incorrectly encoded Store. + Files.writeString(dir.resolve("x86ConstantStore.c"), """ + #include <stdint.h> + struct Narrow { uint32_t x, guard; }; + struct Wide { int64_t x, guard; }; + extern long long storeNarrow(struct Narrow *); + extern long long storePositive(struct Wide *); + extern long long storeNegative(struct Wide *); + extern long long addPositive(struct Wide *); + extern long long addNegative(struct Wide *); + int main(void) { + struct Narrow n = {0, 0x12345678}; + struct Wide w = {0, 0x123456789abcdefLL}; + storeNarrow(&n); + if (n.x != UINT32_MAX || n.guard != 0x12345678) return 1; + storePositive(&w); + if (w.x != 81985529216486895LL || w.guard != 0x123456789abcdefLL) return 2; + storeNegative(&w); + if (w.x != -2147483649LL || w.guard != 0x123456789abcdefLL) return 3; + w.x = 7; + addPositive(&w); + if (w.x != 4294967302LL || w.guard != 0x123456789abcdefLL) return 4; + addNegative(&w); + if (w.x != 2147483653LL || w.guard != 0x123456789abcdefLL) return 5; + return 0; + } + """); + TestC.run("build/objs","x86ConstantStore","",-1); + } +} diff --git a/chapter26/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter26/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c07ac904 --- /dev/null +++ b/chapter26/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Select,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/chapter27/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter27/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 736d91a2..aed0e703 100644 --- a/chapter27/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter27/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -253,7 +253,7 @@ public class LoadNode extends MemOpNode { } private Node folded(Node win) { - return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + return win instanceof StoreNode st ? extend(castRO(st.val()),st._declaredType) : zero((NewNode)win); } // Materialize only after the profitability search succeeds. The value Phi @@ -311,8 +311,10 @@ public class LoadNode extends MemOpNode { // When a load bypasses a store, the store might truncate bits - and the // load will need to zero/sign-extend. - private Node extend(Node val) { - if( !(_declaredType instanceof TypeInteger ti) ) return val; + // Use the matching Store's width: while a class is open, this Load may + // already know its alias but not yet have recorded its storage type. + private Node extend(Node val, Type storage) { + if( !(storage instanceof TypeInteger ti) ) return val; if( ti._min==0 ) // Unsigned return new AndNode(null,val,con(ti._max)); // Signed extension diff --git a/chapter27/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter27/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index c854dd9d..a92b43d5 100644 --- a/chapter27/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter27/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -552,7 +552,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range diff --git a/chapter27/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java b/chapter27/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java new file mode 100644 index 00000000..f6cc2e4b --- /dev/null +++ b/chapter27/src/test/java/com/seaofnodes/simple/NarrowFieldTest.java @@ -0,0 +1,89 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.isa.eval.EvalRisc5; +import org.junit.Test; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import static org.junit.Assert.*; + +public class NarrowFieldTest { + private static final String[] TYPES = {"u8", "i8", "u16", "i16", "u32", "i32"}; + private static final long[] MINS = {0, -128, 0, -32768, 0, Integer.MIN_VALUE}; + private static final long[] MAXS = {255, 127, 65535, 32767, 0xffffffffL, Integer.MAX_VALUE}; + + // Both the method result and a later read must see the stored, narrowed value. + private static String source(int i, boolean increment, boolean argument) { + String name = "C"+i+(increment ? "Inc" : "Dec"); + long initial = argument ? 0 : (increment ? MAXS[i] : MINS[i]); + return "struct "+name+" { "+TYPES[i]+" x="+initial+"; " + +"val next_noInline={ -> x"+(increment ? "++" : "--")+"; return x; }; }; " + +"var c=new "+name+"; "+(argument ? "c.x=arg; " : "") + +"val a=c.next_noInline(); val b=c.next_noInline(); " + +"return a+10*b+100*c.x;"; + } + + private static long expected(int i, boolean increment) { + long first = increment ? MINS[i] : MAXS[i]; + long second = first+(increment ? 1 : -1); + return first+110*second; + } + + @Test public void testMethodResults() { + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) + for( int seed : new int[]{2,13,123} ) { + String src = source(i,increment,false); + CodeGen code = new CodeGen(src,TypeInteger.BOT,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals(src,Long.toString(expected(i,increment)),Eval2.eval(code,0)); + } + } + + @Test public void testNativeMethodResults() throws IOException { + StringBuilder src = new StringBuilder(); + StringBuilder declarations = new StringBuilder(); + StringBuilder checks = new StringBuilder(); + // Supply the initial value through the native argument to also exercise + // narrowing an unknown full-width value before the method calls. + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) { + String name = "narrow"+i+(increment ? "Inc" : "Dec"); + src.append("val ").append(name).append("={int arg -> ") + .append(source(i,increment,true)).append(" }; "); + declarations.append("__attribute__((CALL_CONV)) extern long long ").append(name).append("(long long);\n"); + checks.append("if (").append(name).append('(').append(increment ? MAXS[i] : MINS[i]) + .append("LL)!=").append(expected(i,increment)).append("LL) return 1;\n"); + } + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Path driver = dir.resolve("narrowField.c"); + Files.writeString(driver,declarations+"int main(void) {\n"+checks+"return 0; }\n"); + String abi = TestC.OS.startsWith("Windows") ? "ms_abi" : "sysv_abi"; + TestC.run(src.toString(),TestC.CALL_CONVENTION,null,abi,driver.toString(),"build/objs/narrowField","S","",-1); + } + + @Test public void testRiscvMethodResults() throws IOException { + for( int i = 0; i<TYPES.length; i++ ) + for( boolean increment : new boolean[]{true,false} ) { + String src = source(i,increment,false); + CodeGen code = new CheckedCodeGen(src).driver("riscv","SystemV",null); + int entry = -1; + for( var node : code._start._outputs ) + if( node instanceof FunNode fun && "main".equals(fun._name) ) + entry = code._encoding._opStart[fun._nid]; + assertTrue(entry>=0); + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu._pc = entry; + assertEquals(src,0,cpu.step(100000)); + assertEquals(src,expected(i,increment),cpu.regs[riscv.A0]); + } + } +} diff --git a/chapter27/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java b/chapter27/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java new file mode 100644 index 00000000..f0c9d37f --- /dev/null +++ b/chapter27/src/test/java/com/seaofnodes/simple/X86StoreNativeTest.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; + +public class X86StoreNativeTest { + @Test public void testConstantStoresAndAdds() throws IOException { + Path dir = Path.of("build/objs"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("x86ConstantStore.smp"), """ + struct Narrow { u32 x; u32 guard; }; + struct Wide { int x; int guard; }; + val storeNarrow = { !Narrow s -> s.x=4294967295; return 0; }; + val storePositive = { !Wide s -> s.x=81985529216486895; return 0; }; + val storeNegative = { !Wide s -> s.x=-2147483649; return 0; }; + val addPositive = { !Wide s -> s.x+=4294967295; return 0; }; + val addNegative = { !Wide s -> s.x+=-2147483649; return 0; }; + """); + // Read from C so load forwarding cannot hide an incorrectly encoded Store. + Files.writeString(dir.resolve("x86ConstantStore.c"), """ + #include <stdint.h> + struct Narrow { uint32_t x, guard; }; + struct Wide { int64_t x, guard; }; + extern long long storeNarrow(struct Narrow *); + extern long long storePositive(struct Wide *); + extern long long storeNegative(struct Wide *); + extern long long addPositive(struct Wide *); + extern long long addNegative(struct Wide *); + int main(void) { + struct Narrow n = {0, 0x12345678}; + struct Wide w = {0, 0x123456789abcdefLL}; + storeNarrow(&n); + if (n.x != UINT32_MAX || n.guard != 0x12345678) return 1; + storePositive(&w); + if (w.x != 81985529216486895LL || w.guard != 0x123456789abcdefLL) return 2; + storeNegative(&w); + if (w.x != -2147483649LL || w.guard != 0x123456789abcdefLL) return 3; + w.x = 7; + addPositive(&w); + if (w.x != 4294967302LL || w.guard != 0x123456789abcdefLL) return 4; + addNegative(&w); + if (w.x != 2147483653LL || w.guard != 0x123456789abcdefLL) return 5; + return 0; + } + """); + TestC.run("build/objs","x86ConstantStore","",-1); + } +} diff --git a/chapter27/src/test/java/com/seaofnodes/simple/X86StoreTest.java b/chapter27/src/test/java/com/seaofnodes/simple/X86StoreTest.java new file mode 100644 index 00000000..c07ac904 --- /dev/null +++ b/chapter27/src/test/java/com/seaofnodes/simple/X86StoreTest.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class X86StoreTest { + @Test public void testConstantStores() { + check("u8",255,false); + check("u16",65535,false); + check("u32",Integer.MAX_VALUE,false); + check("u32",2147483648L,true); + check("u32",4294967295L,true); + check("int",Integer.MIN_VALUE,false); + check("int",Integer.MAX_VALUE,false); + for( long value : new long[]{2147483648L,4294967295L,-2147483649L, + 0x123456789abcdefL,Long.MIN_VALUE,Long.MAX_VALUE} ) + check("int",value,true); + } + + private static void check(String type, long value, boolean register) { + // Returning the object keeps the Store; returning its field could fold. + String literal = value==Long.MIN_VALUE ? "(-9223372036854775807-1)" : Long.toString(value); + String src = "struct S { "+type+" x="+literal+"; }; return new S;"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Select,"x86_64_v2","SystemV"); + StoreX86 store = code._stop.walk(n -> n instanceof StoreX86 st && st._name.equals("x") ? st : null); + assertNotNull(src,store); + if( register ) { + assertNotNull(src,store.in(4)); + assertEquals(src,value,((TypeInteger)store.in(4)._type).value()); + } else assertNull(src,store.in(4)); + } +} diff --git a/docs/roadmap.ja.md b/docs/roadmap.ja.md index e4a46bfc..057e4440 100644 --- a/docs/roadmap.ja.md +++ b/docs/roadmap.ja.md @@ -7,8 +7,7 @@ ## 正確性とコンパイル -- IR 読み込み時の全体再解析は、新しいコードと影響を受ける既存コードの更新に置き換えた。残る作業は Opto 後のワークリスト、デシリアライズ中のフェーズ変更、Eval2 の入力条件と局所的なスケジューリングの整理。 -- 第25〜27章で、メソッドが狭いフィールドを更新した後に切り詰め前の値を返す問題を修正する。 +- `ArithNode` と `ConvertNode` の型計算をフェーズに依存しない単調な規則にする。Eval2 の追加ロード時のスケジューリング用領域、`StopCUNode` の全呼び出し・関数要約の反復走査、Export 内に残るエンコード処理も整理する。 - 依存関係全体を調べ、変更を伝播してから保存 IR を再利用する、正しい増分ビルドを実装する。 - 型の読み込みからネイティブリンクまでのライブラリ変更を検出する。SHA-256 を含むシンボルの照合は将来案であり、Eval2 は整合したビルドを前提とする。 - コンパイル単位ごとの選択的ロードを、オブジェクト内の関数単位へ細分化する。 diff --git a/docs/roadmap.md b/docs/roadmap.md index 14cf563f..0878bbc7 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -3,58 +3,31 @@ English | [日本語](roadmap.ja.md) Open compiler work and language proposals, reviewed against the current -snapshots on 2026-10-25. These are future changes, not promises about the +snapshots on 2026-10-28. These are future changes, not promises about the language implemented by the tutorial. Language changes still need their own design decisions and chapter boundaries. -## Correctness - -### Narrow field results after method updates - -Chapters 25-27 can return an untruncated increment when a method updates a -narrow field and then reads it. With evaluator stores corrected, this example -returns 366 instead of 110. Chapter 25's generated RISC-V code also returns -366: the method's optimized Return uses the raw Add with range `[1-256]`, -although the Store writes a byte. Chapters 23, 24a/24b, and 28a/28b return 110 -with the corrected evaluator. - -```simple -struct C { u8 x=255; val next_noInline={ -> x++; return x; }; }; -var c=new C; -val a=c.next_noInline(); -val b=c.next_noInline(); -return a+10*b+100*c.x; -``` - -Find where the field read loses its narrowing during parsing or optimization, -and preserve the converted result independently of the Store. Check both -signed and unsigned fields and compare ideal evaluation with generated code. - ## Compilation and optimization ### Phase ordering and incremental analysis -Import loading now repairs the new graph's types and propagates changed bounds -through affected callers before resuming peepholes. It no longer restarts Iter -and Opto over the combined graph. Unknown calls retain conservative effects -and escaping callbacks. Entry initializers seed reachability; live calls, -including CLInit and the callees recorded in summaries, discover other targets. - -Smaller follow-ups: +Remaining work: -- Try queuing only nodes whose types changed during Opto, plus affected - neighbors and nodes whose edges or numeric modes changed, for IterPostOpto. -- Remove the deserializer's temporary `Parse` phase. Node construction should - express its needs without changing the driver's progress marker. -- Have Eval2 require checked input in a supported phase. Prepare each newly - loaded graph once, without changing the running program's phase. Remove - scheduling scratch costs proportional to all previously allocated node IDs. -- Keep field-offset cleanup on the worklist. Repeated peepholes are fine when - driven by changes; avoid repeating global finalization bookkeeping. +- Remove phase-dependent transfer rules from `ArithNode.compute()` and + `ConvertNode.compute()`. Keep the same monotone rule in both pessimistic + and optimistic analysis. +- Remove Eval2 scheduling scratch costs proportional to all previously + allocated node IDs. Scheduling a newly loaded object should cost only for + that graph and any affected existing nodes. +- Reduce repeated scans of all calls and unloaded function summaries in + `StopCUNode.compute()`. Review its phase-dependent escape normalization and + reliance on the previous result before changing propagation. +- Move the remaining encoding fallback out of Export so Encoding prepares + all objects that Export will write. Whole-program TypeCheck is acceptable for now; incremental checking is an optional later improvement. Expensive assertion walks are outside this -performance work. Review the encoding fallback inside Export separately. +performance work. ### Reliable incremental builds @@ -86,10 +59,10 @@ ownership rules and validation cases. Chapter 28b emits one object per compilation unit and reads declarations and `SummaryNode` information without loading bodies. Optimization can request an -object's IR for inlining; Eval2 resolves its object dependencies before -execution, then materializes bodies on executed calls. Suspected -initialization cycles request full loading before type checking completes. -A requested object still reconstructs all of its functions. +object's IR for inlining; Eval2 materializes bodies on executed calls and +assumes a coherent build. Initialization checks reject possible cycles using +the available graph and summaries, without loading more bodies to disprove +them. A requested object still reconstructs all of its functions. Further selectivity would require a function-body index within each object and narrower metadata discovery. Preserve type identity, initialization rules, 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 e6860ccd..6c3659ef 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 @@ -176,7 +176,7 @@ public class EvalRisc5 { case 3: rval = ld8 ( rsval ); break; case 4: rval = ld1z( rsval ); break; case 5: rval = ld2z( rsval ); break; - case 6: rval = ld4s( rsval ); break; + case 6: rval = ld4s( rsval ) & 0xFFFFFFFFL; break; default: trap = (2+1); } } diff --git a/isa/src/test/java/com/seaofnodes/isa/RiscTest.java b/isa/src/test/java/com/seaofnodes/isa/RiscTest.java index 5b354036..4f49cdd2 100644 --- a/isa/src/test/java/com/seaofnodes/isa/RiscTest.java +++ b/isa/src/test/java/com/seaofnodes/isa/RiscTest.java @@ -44,6 +44,16 @@ public class RiscTest { assert unsigned.step(1)==0; assert unsigned.regs[3]==(value==0 ? 1 : 0); } + for( int value : new int[]{0,1,Integer.MAX_VALUE,Integer.MIN_VALUE,-1} ) { + var loads = new EvalRisc5(new byte[1024],512); + loads.st4(4,0x00012183); // lw x3,0(x2) + loads.st4(8,0x00016203); // lwu x4,0(x2) + loads.st4(512,value); + loads._pc = 4; + assert loads.step(2)==0; + assert loads.regs[3]==value; + assert loads.regs[4]==(value & 0xFFFFFFFFL); + } 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 |
