summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java17
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java22
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/codegen/Machine.java11
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java30
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java7
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java31
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java33
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java17
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java73
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java21
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/progs/c_returns.c28
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/codegen/Machine.java11
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java30
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java7
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java31
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java33
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java17
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java73
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java21
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/progs/c_returns.c28
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/codegen/Machine.java11
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java30
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java7
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java31
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java33
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java17
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java73
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java21
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/progs/c_returns.c28
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java30
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java17
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java71
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java21
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/progs/c_returns.c28
-rw-r--r--docs/chapter-backports.md19
-rw-r--r--skills/chapter25-codex-notes.md26
49 files changed, 1027 insertions, 73 deletions
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 72041bd7..6c943904 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -11,6 +11,23 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+
+ @Test public void testArmBitfieldExtracts() {
+ // Independent SXT/UXT instruction words, plus ASR, LSR and LSL aliases.
+ int[] ops = {0x93401c20,0xd3401c20,0x93403c20,0xd3403c20,
+ 0x93407c20,0xd3407c20,0x9344fc20,0xd344fc20,0xd37cec20};
+ long value = 0xa5a5a5a580008080L;
+ long[] expected = {(byte)value,value&255,(short)value,value&65535,
+ (int)value,value&0xffffffffL,value>>4,value>>>4,value<<4};
+ for( int i=0; i<ops.length; i++ ) {
+ EvalArm64 cpu = new EvalArm64(new byte[1<<16],1<<15);
+ cpu.st4(0,ops[i]); cpu.regs[1]=value;
+ assertEquals(0,cpu.step(1));
+ assertEquals(expected[i],cpu.regs[0]);
+ }
+ }
+
+
@Test public void testReturnedFunctionEncoding() throws IOException {
String src = "if(arg) return {->42;}; return {->43;};";
CodeGen code = new CodeGen(src).driver("riscv","SystemV",null);
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
index 75525133..e5aa729b 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -641,20 +641,18 @@ public class EvalArm64 {
rval = opcode1==0x91 ? regs[rn] + immediate : regs[rn] - immediate;
break;
}
- case 0xD3: {
- // lsl(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] << (64 - imm);
- break;
- }
- case 0x93: {
- // asr(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] >> (imm);
+ case 0x93, 0xD3: { // SBFM/UBFM: shifts and signed/unsigned extracts
+ int immr = (ir >>> 16) & 63;
+ int imms = (ir >>> 10) & 63;
+ int rn = (ir >>> 5) & 31;
+ boolean signed = (ir & 0x40000000)==0;
+ long value = regs[rn] << (63-imms);
+ value = signed ? value >> (63-imms) : value >>> (63-imms);
+ rval = immr > imms ? value << (64-immr)
+ : signed ? value >> immr : value >>> immr;
break;
}
+
case 0x92: {
// and(immediate)
int imm12 = (ir >> 10) & 0x1FFF;
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter22/src/main/java/com/seaofnodes/simple/codegen/Machine.java
index 2766414f..a95faefc 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeInteger;
abstract public class Machine {
// Human readable machine name. Something like "x86-64" or "arm" or "risc5"
@@ -94,4 +95,14 @@ Pre-alloc Post-Alloc SP+48 RPC
// registers.
public abstract short maxArgSlot(TypeFunPtr tfp);
+ // Only the declared C result width describes the physical return register.
+ // Simple functions already return canonical full-width integer values.
+ protected static TypeInteger cReturn(ProjNode prj) {
+ if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend &&
+ cend.call().fptr() instanceof ExternNode ext &&
+ ((TypeFunPtr)ext._con).ret() instanceof TypeInteger ret && ret.log_size()<3 )
+ return ret;
+ return null;
+ }
+
}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
new file mode 100644
index 00000000..84075edb
--- /dev/null
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendARM extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendARM(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjARM(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // SBFM/UBFM aliases: sign/zero extend the low byte, short or int.
+ enc.add4(arm.imm_shift(_signed ? arm.OPI_ASR : arm.OPI_LSR,0,_bits-1,src,dst));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 578901c1..839ab50e 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -674,7 +674,7 @@ public class arm extends Machine {
case OrNode or -> or(or);
case ParmNode parm -> new ParmARM(parm);
case PhiNode phi -> new PhiNode(phi);
- case ProjNode prj -> new ProjARM(prj);
+ case ProjNode prj -> prj(prj);
case ReadOnlyNode read -> new ReadOnlyMach(read);
case ReturnNode ret -> new RetARM(ret,ret.fun());
case SarNode sar -> asr(sar);
@@ -818,4 +818,9 @@ public class arm extends Machine {
}
+ private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret);
+ }
+
}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
new file mode 100644
index 00000000..4d487942
--- /dev/null
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendRISC extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendRISC(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjRISC(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ int shift = 64-_bits;
+ enc.add4(riscv.i_type(riscv.OP_IMM,dst,1,src,shift));
+ enc.add4(riscv.i_type(riscv.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 9c5bfa10..8775f228 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -509,6 +509,8 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendRISC(prj,ret);
return new ProjRISC(prj);
}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
new file mode 100644
index 00000000..3997d768
--- /dev/null
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendX86 extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendX86(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjX86(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // MOVSX/MOVZX for bytes and shorts; MOVSXD/MOV r32 for ints.
+ enc.add1(x86_64_v2.rex(dst,src,0,_signed || _bits<32));
+ if( _bits==32 ) enc.add1(_signed ? 0x63 : 0x8b);
+ else enc.add1(0x0f).add1((_signed ? 0xbe : 0xb6) + (_bits==16 ? 1 : 0));
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT,dst,src));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
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 6706635f..8a6ae188 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
@@ -548,6 +548,8 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendX86(prj,ret);
return new ProjX86(prj);
}
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 9f901dc2..28804cfd 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -12,6 +12,23 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+
+ @Test public void testArmBitfieldExtracts() {
+ // Independent SXT/UXT instruction words, plus ASR, LSR and LSL aliases.
+ int[] ops = {0x93401c20,0xd3401c20,0x93403c20,0xd3403c20,
+ 0x93407c20,0xd3407c20,0x9344fc20,0xd344fc20,0xd37cec20};
+ long value = 0xa5a5a5a580008080L;
+ long[] expected = {(byte)value,value&255,(short)value,value&65535,
+ (int)value,value&0xffffffffL,value>>4,value>>>4,value<<4};
+ for( int i=0; i<ops.length; i++ ) {
+ EvalArm64 cpu = new EvalArm64(new byte[1<<16],1<<15);
+ cpu.st4(0,ops[i]); cpu.regs[1]=value;
+ assertEquals(0,cpu.step(1));
+ assertEquals(expected[i],cpu.regs[0]);
+ }
+ }
+
+
@Test public void testReturnedFunctionEncoding() throws IOException {
String src = "if(arg) return {->42;}; return {->43;};";
CodeGen code = new CodeGen(src).driver("riscv","SystemV",null);
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index b61e972f..5c6dc601 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -14,6 +14,79 @@ import static org.junit.Assert.*;
public class Chapter22Test {
+ @Test public void testNarrowCReturnsEmulated() {
+ String[] types = {"i8","u8","i16","u16","i32","u32","int"};
+ long[] values = {0xa5a5a5a500000000L,0xa5a5a5a50000007fL,0xa5a5a5a500000080L,
+ 0xa5a5a5a500007fffL,0xa5a5a5a500008000L,0xa5a5a5a57fffffffL,
+ 0xa5a5a5a580000000L,0xa5a5a5a5ffffffffL};
+ for( String target : new String[]{"arm","riscv"} )
+ for( int i=0; i<types.length; i++ ) {
+ String src = "{"+types[i]+"} c_narrow=\"C\"; return c_narrow();";
+ CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,target,"SystemV");
+ var enc = code._encoding;
+ // Link the external call to a tiny independent native stub.
+ var call = code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.CallNode ? n : null);
+ int start = enc._opStart[call._nid];
+ ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc._opLen[call._nid],0x1000-start);
+ for( long value : values ) {
+ long expected = switch(i) {
+ case 0 -> (byte)value; case 1 -> value&255;
+ case 2 -> (short)value; case 3 -> value&65535;
+ case 4 -> (int)value; case 5 -> value&0xffffffffL;
+ default -> value;
+ };
+ byte[] image = new byte[1<<16];
+ byte[] bits = enc.bits();
+ System.arraycopy(bits,0,image,0,bits.length);
+ if( target.equals("arm") ) {
+ EvalArm64 cpu = new EvalArm64(image,1<<15);
+ // MOVZ X0,0x1100; LDR X0,[X0]; RET X30.
+ cpu._pc = enc._opStart[code.link(code._main)._nid];
+ cpu.st4(0x1000,0xd2822000); cpu.st4(0x1004,0xf9400000);
+ cpu.st4(0x1008,0xd65f03c0);
+ cpu.st8(0x1100,value);
+ assertEquals(0,cpu.step(1000));
+ assertEquals(0,cpu._pc);
+ assertEquals(types[i],expected,cpu.regs[0]);
+ } else {
+ EvalRisc5 cpu = new EvalRisc5(image,1<<15);
+ // AUIPC a0,0; LD a0,12(a0); RET.
+ cpu._pc = enc._opStart[code.link(code._main)._nid];
+ cpu.st4(0x1000,0x00000517); cpu.st4(0x1004,0x00c53503);
+ cpu.st4(0x1008,0x00008067);
+ // RV64 widens even u32 returns by sign-extending bit 31.
+ cpu.st8(0x100c,i==6 ? value : (int)expected);
+ assertEquals(0,cpu.step(1000));
+ assertEquals(0,cpu._pc);
+ assertEquals(types[i],expected,cpu.regs[riscv.A0]);
+ }
+ }
+ }
+ }
+
+ @Test public void testNarrowCReturns() throws IOException {
+ String src = """
+ {i8} c_i8="C"; {u8} c_u8="C";
+ {i16} c_i16="C"; {u16} c_u16="C";
+ {i32} c_i32="C"; {u32} c_u32="C";
+ {int} c_i64="C";
+ val test_i8={->return c_i8();};
+ val test_u8={->return c_u8();};
+ val test_i16={->return c_i16();};
+ val test_u16={->return c_u16();};
+ val test_i32={->return c_i32();};
+ val test_u32={->return c_u32();};
+ val test_i64={->return c_i64();};
+ val test_cmp={->return c_i32()==-1;};
+ """;
+ for( String conv : new String[]{"SystemV","Win64"} )
+ TestC.run(src,conv,conv.equals("SystemV") ? "sysv_abi" : "ms_abi",
+ "src/test/java/com/seaofnodes/simple/progs/c_returns.c",
+ "build/objs/c_returns"+conv,"S","",-1);
+ }
+
+
+
@Test public void testReturnedFunctions() throws IOException {
for( String src : new String[]{"return {->42;};", "val f={->42;}; return f;", "return sys.io.p;"} )
for( String target : new String[]{"riscv","arm"} ) {
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java
index c6151340..cd5a8371 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -320,11 +320,15 @@ public class EvalArm64 {
rval = regs[rn] & immediate;
break;
}
- case 0x93: {
- // asr(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] >> (imm);
+ case 0x93, 0xD3: { // SBFM/UBFM: shifts and signed/unsigned extracts
+ int immr = (ir >>> 16) & 63;
+ int imms = (ir >>> 10) & 63;
+ int rn = (ir >>> 5) & 31;
+ boolean signed = (ir & 0x40000000)==0;
+ long value = regs[rn] << (63-imms);
+ value = signed ? value >> (63-imms) : value >>> (63-imms);
+ rval = immr > imms ? value << (64-immr)
+ : signed ? value >> immr : value >>> immr;
break;
}
@@ -565,13 +569,6 @@ public class EvalArm64 {
break;
}
- case 0xD3: {
- // lsl(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] << (64 - imm);
- break;
- }
case 0xD6: {
int op = ir & 0xFFFFFC1F;
int rn = (ir >>> 5) & 31;
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/progs/c_returns.c b/chapter22/src/test/java/com/seaofnodes/simple/progs/c_returns.c
new file mode 100644
index 00000000..6d0ff2fe
--- /dev/null
+++ b/chapter22/src/test/java/com/seaofnodes/simple/progs/c_returns.c
@@ -0,0 +1,28 @@
+#include <stdint.h>
+#include <stdio.h>
+
+uint64_t c_bits;
+// Legal narrow C returns: unused RAX bits deliberately contain garbage.
+#define RET(name) __asm__(".text\n.globl " #name "\n" #name ":\nmovq c_bits(%rip), %rax\nret\n");
+RET(c_i8) RET(c_u8) RET(c_i16) RET(c_u16) RET(c_i32) RET(c_u32) RET(c_i64)
+#define DECL(name) extern int64_t __attribute__((CALL_CONV)) name(void);
+DECL(test_i8) DECL(test_u8) DECL(test_i16) DECL(test_u16)
+DECL(test_i32) DECL(test_u32) DECL(test_i64) DECL(test_cmp)
+
+int main(void) {
+ uint64_t values[] = {0xa5a5a5a500000000ULL, 0xa5a5a5a50000007fULL,
+ 0xa5a5a5a500000080ULL, 0xa5a5a5a500007fffULL, 0xa5a5a5a500008000ULL,
+ 0xa5a5a5a57fffffffULL, 0xa5a5a5a580000000ULL, 0xa5a5a5a5ffffffffULL};
+ for(unsigned i=0; i<sizeof(values)/sizeof(values[0]); i++) {
+ c_bits=values[i];
+ int64_t actual[]={test_i8(),test_u8(),test_i16(),test_u16(),test_i32(),test_u32(),test_i64(),test_cmp()};
+ int64_t expected[]={(int8_t)c_bits,(uint8_t)c_bits,(int16_t)c_bits,(uint16_t)c_bits,
+ (int32_t)c_bits,(uint32_t)c_bits,(int64_t)c_bits,(int32_t)c_bits==-1};
+ for(unsigned j=0; j<8; j++)
+ if(actual[j]!=expected[j]) {
+ printf("case %u type %u: %lld != %lld\n",i,j,(long long)actual[j],(long long)expected[j]);
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter23/src/main/java/com/seaofnodes/simple/codegen/Machine.java
index 2766414f..a95faefc 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeInteger;
abstract public class Machine {
// Human readable machine name. Something like "x86-64" or "arm" or "risc5"
@@ -94,4 +95,14 @@ Pre-alloc Post-Alloc SP+48 RPC
// registers.
public abstract short maxArgSlot(TypeFunPtr tfp);
+ // Only the declared C result width describes the physical return register.
+ // Simple functions already return canonical full-width integer values.
+ protected static TypeInteger cReturn(ProjNode prj) {
+ if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend &&
+ cend.call().fptr() instanceof ExternNode ext &&
+ ((TypeFunPtr)ext._con).ret() instanceof TypeInteger ret && ret.log_size()<3 )
+ return ret;
+ return null;
+ }
+
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
new file mode 100644
index 00000000..26a237d0
--- /dev/null
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendARM extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendARM(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjARM(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // SBFM/UBFM aliases: sign/zero extend the low byte, short or int.
+ enc.add4(arm.imm_shift(_signed ? arm.OPI_ASR : arm.OPI_LSR,0,_bits-1,src,dst));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index db179754..bca3dfa4 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -674,7 +674,7 @@ public class arm extends Machine {
case OrNode or -> or(or);
case ParmNode parm -> new ParmARM(parm);
case PhiNode phi -> new PhiNode(phi);
- case ProjNode prj -> new ProjARM(prj);
+ case ProjNode prj -> prj(prj);
case ReadOnlyNode read -> new ReadOnlyMach(read);
case ReturnNode ret -> new RetARM(ret,ret.fun());
case SarNode sar -> asr(sar);
@@ -818,4 +818,9 @@ public class arm extends Machine {
}
+ private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret);
+ }
+
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
new file mode 100644
index 00000000..22ac2a69
--- /dev/null
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendRISC extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendRISC(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjRISC(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ int shift = 64-_bits;
+ enc.add4(riscv.i_type(riscv.OP_IMM,dst,1,src,shift));
+ enc.add4(riscv.i_type(riscv.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 7a6361bc..48cb5d23 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -509,6 +509,8 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendRISC(prj,ret);
return new ProjRISC(prj);
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
new file mode 100644
index 00000000..e905e2e3
--- /dev/null
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendX86 extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendX86(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjX86(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // MOVSX/MOVZX for bytes and shorts; MOVSXD/MOV r32 for ints.
+ enc.add1(x86_64_v2.rex(dst,src,0,_signed || _bits<32));
+ if( _bits==32 ) enc.add1(_signed ? 0x63 : 0x8b);
+ else enc.add1(0x0f).add1((_signed ? 0xbe : 0xb6) + (_bits==16 ? 1 : 0));
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT,dst,src));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
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 026e12aa..7ac7fc19 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
@@ -549,6 +549,8 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendX86(prj,ret);
return new ProjX86(prj);
}
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 2665215c..6438801b 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -13,6 +13,23 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+
+ @Test public void testArmBitfieldExtracts() {
+ // Independent SXT/UXT instruction words, plus ASR, LSR and LSL aliases.
+ int[] ops = {0x93401c20,0xd3401c20,0x93403c20,0xd3403c20,
+ 0x93407c20,0xd3407c20,0x9344fc20,0xd344fc20,0xd37cec20};
+ long value = 0xa5a5a5a580008080L;
+ long[] expected = {(byte)value,value&255,(short)value,value&65535,
+ (int)value,value&0xffffffffL,value>>4,value>>>4,value<<4};
+ for( int i=0; i<ops.length; i++ ) {
+ EvalArm64 cpu = new EvalArm64(new byte[1<<16],1<<15);
+ cpu.st4(0,ops[i]); cpu.regs[1]=value;
+ assertEquals(0,cpu.step(1));
+ assertEquals(expected[i],cpu.regs[0]);
+ }
+ }
+
+
@Test public void testReturnedFunctionEncoding() throws IOException {
String src = "if(arg) return {->42;}; return {->43;};";
CodeGen code = new CodeGen(src).driver("riscv","SystemV",null);
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 386565a2..def21258 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -14,6 +14,79 @@ import static org.junit.Assert.*;
public class Chapter22Test {
+ @Test public void testNarrowCReturnsEmulated() {
+ String[] types = {"i8","u8","i16","u16","i32","u32","int"};
+ long[] values = {0xa5a5a5a500000000L,0xa5a5a5a50000007fL,0xa5a5a5a500000080L,
+ 0xa5a5a5a500007fffL,0xa5a5a5a500008000L,0xa5a5a5a57fffffffL,
+ 0xa5a5a5a580000000L,0xa5a5a5a5ffffffffL};
+ for( String target : new String[]{"arm","riscv"} )
+ for( int i=0; i<types.length; i++ ) {
+ String src = "{"+types[i]+"} c_narrow=\"C\"; return c_narrow();";
+ CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,target,"SystemV");
+ var enc = code._encoding;
+ // Link the external call to a tiny independent native stub.
+ var call = code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.CallNode ? n : null);
+ int start = enc._opStart[call._nid];
+ ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc._opLen[call._nid],0x1000-start);
+ for( long value : values ) {
+ long expected = switch(i) {
+ case 0 -> (byte)value; case 1 -> value&255;
+ case 2 -> (short)value; case 3 -> value&65535;
+ case 4 -> (int)value; case 5 -> value&0xffffffffL;
+ default -> value;
+ };
+ byte[] image = new byte[1<<16];
+ byte[] bits = enc.bits();
+ System.arraycopy(bits,0,image,0,bits.length);
+ if( target.equals("arm") ) {
+ EvalArm64 cpu = new EvalArm64(image,1<<15);
+ // MOVZ X0,0x1100; LDR X0,[X0]; RET X30.
+ cpu._pc = enc._opStart[code.link(code._main)._nid];
+ cpu.st4(0x1000,0xd2822000); cpu.st4(0x1004,0xf9400000);
+ cpu.st4(0x1008,0xd65f03c0);
+ cpu.st8(0x1100,value);
+ assertEquals(0,cpu.step(1000));
+ assertEquals(0,cpu._pc);
+ assertEquals(types[i],expected,cpu.regs[0]);
+ } else {
+ EvalRisc5 cpu = new EvalRisc5(image,1<<15);
+ // AUIPC a0,0; LD a0,12(a0); RET.
+ cpu._pc = enc._opStart[code.link(code._main)._nid];
+ cpu.st4(0x1000,0x00000517); cpu.st4(0x1004,0x00c53503);
+ cpu.st4(0x1008,0x00008067);
+ // RV64 widens even u32 returns by sign-extending bit 31.
+ cpu.st8(0x100c,i==6 ? value : (int)expected);
+ assertEquals(0,cpu.step(1000));
+ assertEquals(0,cpu._pc);
+ assertEquals(types[i],expected,cpu.regs[riscv.A0]);
+ }
+ }
+ }
+ }
+
+ @Test public void testNarrowCReturns() throws IOException {
+ String src = """
+ {i8} c_i8="C"; {u8} c_u8="C";
+ {i16} c_i16="C"; {u16} c_u16="C";
+ {i32} c_i32="C"; {u32} c_u32="C";
+ {int} c_i64="C";
+ val test_i8={->return c_i8();};
+ val test_u8={->return c_u8();};
+ val test_i16={->return c_i16();};
+ val test_u16={->return c_u16();};
+ val test_i32={->return c_i32();};
+ val test_u32={->return c_u32();};
+ val test_i64={->return c_i64();};
+ val test_cmp={->return c_i32()==-1;};
+ """;
+ for( String conv : new String[]{"SystemV","Win64"} )
+ TestC.run(src,conv,com.seaofnodes.simple.type.TypeInteger.BOT,conv.equals("SystemV") ? "sysv_abi" : "ms_abi",
+ "src/test/java/com/seaofnodes/simple/progs/c_returns.c",
+ "build/objs/c_returns"+conv,"S","",-1);
+ }
+
+
+
@Test public void testReturnedFunctions() throws IOException {
for( String src : new String[]{"return {->42;};", "val f={->42;}; return f;", "return sys.io.p;"} )
for( String target : new String[]{"riscv","arm"} ) {
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java
index 46827235..1ab3aa6c 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -321,11 +321,15 @@ public class EvalArm64 {
rval = regs[rn] & immediate;
break;
}
- case 0x93: {
- // asr(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] >> (imm);
+ case 0x93, 0xD3: { // SBFM/UBFM: shifts and signed/unsigned extracts
+ int immr = (ir >>> 16) & 63;
+ int imms = (ir >>> 10) & 63;
+ int rn = (ir >>> 5) & 31;
+ boolean signed = (ir & 0x40000000)==0;
+ long value = regs[rn] << (63-imms);
+ value = signed ? value >> (63-imms) : value >>> (63-imms);
+ rval = immr > imms ? value << (64-immr)
+ : signed ? value >> immr : value >>> immr;
break;
}
@@ -566,13 +570,6 @@ public class EvalArm64 {
break;
}
- case 0xD3: {
- // lsl(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] << (64 - imm);
- break;
- }
case 0xD6: {
int op = ir & 0xFFFFFC1F;
int rn = (ir >>> 5) & 31;
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/progs/c_returns.c b/chapter23/src/test/java/com/seaofnodes/simple/progs/c_returns.c
new file mode 100644
index 00000000..6d0ff2fe
--- /dev/null
+++ b/chapter23/src/test/java/com/seaofnodes/simple/progs/c_returns.c
@@ -0,0 +1,28 @@
+#include <stdint.h>
+#include <stdio.h>
+
+uint64_t c_bits;
+// Legal narrow C returns: unused RAX bits deliberately contain garbage.
+#define RET(name) __asm__(".text\n.globl " #name "\n" #name ":\nmovq c_bits(%rip), %rax\nret\n");
+RET(c_i8) RET(c_u8) RET(c_i16) RET(c_u16) RET(c_i32) RET(c_u32) RET(c_i64)
+#define DECL(name) extern int64_t __attribute__((CALL_CONV)) name(void);
+DECL(test_i8) DECL(test_u8) DECL(test_i16) DECL(test_u16)
+DECL(test_i32) DECL(test_u32) DECL(test_i64) DECL(test_cmp)
+
+int main(void) {
+ uint64_t values[] = {0xa5a5a5a500000000ULL, 0xa5a5a5a50000007fULL,
+ 0xa5a5a5a500000080ULL, 0xa5a5a5a500007fffULL, 0xa5a5a5a500008000ULL,
+ 0xa5a5a5a57fffffffULL, 0xa5a5a5a580000000ULL, 0xa5a5a5a5ffffffffULL};
+ for(unsigned i=0; i<sizeof(values)/sizeof(values[0]); i++) {
+ c_bits=values[i];
+ int64_t actual[]={test_i8(),test_u8(),test_i16(),test_u16(),test_i32(),test_u32(),test_i64(),test_cmp()};
+ int64_t expected[]={(int8_t)c_bits,(uint8_t)c_bits,(int16_t)c_bits,(uint16_t)c_bits,
+ (int32_t)c_bits,(uint32_t)c_bits,(int64_t)c_bits,(int32_t)c_bits==-1};
+ for(unsigned j=0; j<8; j++)
+ if(actual[j]!=expected[j]) {
+ printf("case %u type %u: %lld != %lld\n",i,j,(long long)actual[j],(long long)expected[j]);
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter24/src/main/java/com/seaofnodes/simple/codegen/Machine.java
index 2766414f..a95faefc 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeInteger;
abstract public class Machine {
// Human readable machine name. Something like "x86-64" or "arm" or "risc5"
@@ -94,4 +95,14 @@ Pre-alloc Post-Alloc SP+48 RPC
// registers.
public abstract short maxArgSlot(TypeFunPtr tfp);
+ // Only the declared C result width describes the physical return register.
+ // Simple functions already return canonical full-width integer values.
+ protected static TypeInteger cReturn(ProjNode prj) {
+ if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend &&
+ cend.call().fptr() instanceof ExternNode ext &&
+ ((TypeFunPtr)ext._con).ret() instanceof TypeInteger ret && ret.log_size()<3 )
+ return ret;
+ return null;
+ }
+
}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
new file mode 100644
index 00000000..26a237d0
--- /dev/null
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendARM extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendARM(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjARM(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // SBFM/UBFM aliases: sign/zero extend the low byte, short or int.
+ enc.add4(arm.imm_shift(_signed ? arm.OPI_ASR : arm.OPI_LSR,0,_bits-1,src,dst));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 74dc70d4..70034227 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -678,7 +678,7 @@ public class arm extends Machine {
case OrNode or -> or(or);
case ParmNode parm -> new ParmARM(parm);
case PhiNode phi -> new PhiNode(phi);
- case ProjNode prj -> new ProjARM(prj);
+ case ProjNode prj -> prj(prj);
case ReadOnlyNode read -> new ReadOnlyMach(read);
case ReturnNode ret -> new RetARM(ret,ret.fun());
case SarNode sar -> asr(sar);
@@ -822,4 +822,9 @@ public class arm extends Machine {
}
+ private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret);
+ }
+
}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
new file mode 100644
index 00000000..22ac2a69
--- /dev/null
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendRISC extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendRISC(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjRISC(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ int shift = 64-_bits;
+ enc.add4(riscv.i_type(riscv.OP_IMM,dst,1,src,shift));
+ enc.add4(riscv.i_type(riscv.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 7a6361bc..48cb5d23 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -509,6 +509,8 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendRISC(prj,ret);
return new ProjRISC(prj);
}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
new file mode 100644
index 00000000..e905e2e3
--- /dev/null
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendX86 extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendX86(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjX86(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // MOVSX/MOVZX for bytes and shorts; MOVSXD/MOV r32 for ints.
+ enc.add1(x86_64_v2.rex(dst,src,0,_signed || _bits<32));
+ if( _bits==32 ) enc.add1(_signed ? 0x63 : 0x8b);
+ else enc.add1(0x0f).add1((_signed ? 0xbe : 0xb6) + (_bits==16 ? 1 : 0));
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT,dst,src));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 026e12aa..7ac7fc19 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -549,6 +549,8 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendX86(prj,ret);
return new ProjX86(prj);
}
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index afa3ce6a..dcdda33b 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -13,6 +13,23 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+
+ @Test public void testArmBitfieldExtracts() {
+ // Independent SXT/UXT instruction words, plus ASR, LSR and LSL aliases.
+ int[] ops = {0x93401c20,0xd3401c20,0x93403c20,0xd3403c20,
+ 0x93407c20,0xd3407c20,0x9344fc20,0xd344fc20,0xd37cec20};
+ long value = 0xa5a5a5a580008080L;
+ long[] expected = {(byte)value,value&255,(short)value,value&65535,
+ (int)value,value&0xffffffffL,value>>4,value>>>4,value<<4};
+ for( int i=0; i<ops.length; i++ ) {
+ EvalArm64 cpu = new EvalArm64(new byte[1<<16],1<<15);
+ cpu.st4(0,ops[i]); cpu.regs[1]=value;
+ assertEquals(0,cpu.step(1));
+ assertEquals(expected[i],cpu.regs[0]);
+ }
+ }
+
+
@Test public void testReturnedFunctionEncoding() throws IOException {
String src = "if(arg) return {->42;}; return {->43;};";
CodeGen code = new CodeGen(src).driver("riscv","SystemV",null);
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 6571b4f9..b1b5ad3e 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -14,6 +14,79 @@ import static org.junit.Assert.*;
public class Chapter22Test {
+ @Test public void testNarrowCReturnsEmulated() {
+ String[] types = {"i8","u8","i16","u16","i32","u32","int"};
+ long[] values = {0xa5a5a5a500000000L,0xa5a5a5a50000007fL,0xa5a5a5a500000080L,
+ 0xa5a5a5a500007fffL,0xa5a5a5a500008000L,0xa5a5a5a57fffffffL,
+ 0xa5a5a5a580000000L,0xa5a5a5a5ffffffffL};
+ for( String target : new String[]{"arm","riscv"} )
+ for( int i=0; i<types.length; i++ ) {
+ String src = "{"+types[i]+"} c_narrow=\"C\"; return c_narrow();";
+ CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,target,"SystemV");
+ var enc = code._encoding;
+ // Link the external call to a tiny independent native stub.
+ var call = code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.CallNode ? n : null);
+ int start = enc._opStart[call._nid];
+ ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc._opLen[call._nid],0x1000-start);
+ for( long value : values ) {
+ long expected = switch(i) {
+ case 0 -> (byte)value; case 1 -> value&255;
+ case 2 -> (short)value; case 3 -> value&65535;
+ case 4 -> (int)value; case 5 -> value&0xffffffffL;
+ default -> value;
+ };
+ byte[] image = new byte[1<<16];
+ byte[] bits = enc.bits();
+ System.arraycopy(bits,0,image,0,bits.length);
+ if( target.equals("arm") ) {
+ EvalArm64 cpu = new EvalArm64(image,1<<15);
+ // MOVZ X0,0x1100; LDR X0,[X0]; RET X30.
+ cpu._pc = enc._opStart[code.link(code._main)._nid];
+ cpu.st4(0x1000,0xd2822000); cpu.st4(0x1004,0xf9400000);
+ cpu.st4(0x1008,0xd65f03c0);
+ cpu.st8(0x1100,value);
+ assertEquals(0,cpu.step(1000));
+ assertEquals(0,cpu._pc);
+ assertEquals(types[i],expected,cpu.regs[0]);
+ } else {
+ EvalRisc5 cpu = new EvalRisc5(image,1<<15);
+ // AUIPC a0,0; LD a0,12(a0); RET.
+ cpu._pc = enc._opStart[code.link(code._main)._nid];
+ cpu.st4(0x1000,0x00000517); cpu.st4(0x1004,0x00c53503);
+ cpu.st4(0x1008,0x00008067);
+ // RV64 widens even u32 returns by sign-extending bit 31.
+ cpu.st8(0x100c,i==6 ? value : (int)expected);
+ assertEquals(0,cpu.step(1000));
+ assertEquals(0,cpu._pc);
+ assertEquals(types[i],expected,cpu.regs[riscv.A0]);
+ }
+ }
+ }
+ }
+
+ @Test public void testNarrowCReturns() throws IOException {
+ String src = """
+ {i8} c_i8="C"; {u8} c_u8="C";
+ {i16} c_i16="C"; {u16} c_u16="C";
+ {i32} c_i32="C"; {u32} c_u32="C";
+ {int} c_i64="C";
+ val test_i8={->return c_i8();};
+ val test_u8={->return c_u8();};
+ val test_i16={->return c_i16();};
+ val test_u16={->return c_u16();};
+ val test_i32={->return c_i32();};
+ val test_u32={->return c_u32();};
+ val test_i64={->return c_i64();};
+ val test_cmp={->return c_i32()==-1;};
+ """;
+ for( String conv : new String[]{"SystemV","Win64"} )
+ TestC.run(src,conv,com.seaofnodes.simple.type.TypeInteger.BOT,conv.equals("SystemV") ? "sysv_abi" : "ms_abi",
+ "src/test/java/com/seaofnodes/simple/progs/c_returns.c",
+ "build/objs/c_returns"+conv,"S","",-1);
+ }
+
+
+
@Test public void testReturnedFunctions() throws IOException {
for( String src : new String[]{"return {->42;};", "val f={->42;}; return f;", "return sys.io.p;"} )
for( String target : new String[]{"riscv","arm"} ) {
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java
index 5dc56ef7..bd425908 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -320,11 +320,15 @@ public class EvalArm64 {
rval = regs[rn] & immediate;
break;
}
- case 0x93: {
- // asr(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] >> (imm);
+ case 0x93, 0xD3: { // SBFM/UBFM: shifts and signed/unsigned extracts
+ int immr = (ir >>> 16) & 63;
+ int imms = (ir >>> 10) & 63;
+ int rn = (ir >>> 5) & 31;
+ boolean signed = (ir & 0x40000000)==0;
+ long value = regs[rn] << (63-imms);
+ value = signed ? value >> (63-imms) : value >>> (63-imms);
+ rval = immr > imms ? value << (64-immr)
+ : signed ? value >> immr : value >>> immr;
break;
}
@@ -573,13 +577,6 @@ public class EvalArm64 {
break;
}
- case 0xD3: {
- // lsl(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] << (64 - imm);
- break;
- }
case 0xD6: {
int op = ir & 0xFFFFFC1F;
int rn = (ir >>> 5) & 31;
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/progs/c_returns.c b/chapter24/src/test/java/com/seaofnodes/simple/progs/c_returns.c
new file mode 100644
index 00000000..6d0ff2fe
--- /dev/null
+++ b/chapter24/src/test/java/com/seaofnodes/simple/progs/c_returns.c
@@ -0,0 +1,28 @@
+#include <stdint.h>
+#include <stdio.h>
+
+uint64_t c_bits;
+// Legal narrow C returns: unused RAX bits deliberately contain garbage.
+#define RET(name) __asm__(".text\n.globl " #name "\n" #name ":\nmovq c_bits(%rip), %rax\nret\n");
+RET(c_i8) RET(c_u8) RET(c_i16) RET(c_u16) RET(c_i32) RET(c_u32) RET(c_i64)
+#define DECL(name) extern int64_t __attribute__((CALL_CONV)) name(void);
+DECL(test_i8) DECL(test_u8) DECL(test_i16) DECL(test_u16)
+DECL(test_i32) DECL(test_u32) DECL(test_i64) DECL(test_cmp)
+
+int main(void) {
+ uint64_t values[] = {0xa5a5a5a500000000ULL, 0xa5a5a5a50000007fULL,
+ 0xa5a5a5a500000080ULL, 0xa5a5a5a500007fffULL, 0xa5a5a5a500008000ULL,
+ 0xa5a5a5a57fffffffULL, 0xa5a5a5a580000000ULL, 0xa5a5a5a5ffffffffULL};
+ for(unsigned i=0; i<sizeof(values)/sizeof(values[0]); i++) {
+ c_bits=values[i];
+ int64_t actual[]={test_i8(),test_u8(),test_i16(),test_u16(),test_i32(),test_u32(),test_i64(),test_cmp()};
+ int64_t expected[]={(int8_t)c_bits,(uint8_t)c_bits,(int16_t)c_bits,(uint16_t)c_bits,
+ (int32_t)c_bits,(uint32_t)c_bits,(int64_t)c_bits,(int32_t)c_bits==-1};
+ for(unsigned j=0; j<8; j++)
+ if(actual[j]!=expected[j]) {
+ printf("case %u type %u: %lld != %lld\n",i,j,(long long)actual[j],(long long)expected[j]);
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java
index 91d07d57..3eee8fc3 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeInteger;
abstract public class Machine {
// Human readable machine name. Something like "x86-64" or "arm" or "risc5"
@@ -112,4 +113,15 @@ Pre-alloc Post-Alloc SP+48 RPC
// Calling convention used for external C symbols.
public String cCallingConv(String callingConv) { return callingConv; }
+ // Only the declared C result width describes the physical return register.
+ // Simple functions already return canonical full-width integer values.
+ protected static TypeInteger cReturn(ProjNode prj) {
+ if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend &&
+ cend.call().fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() &&
+ CodeGen.CODE.externFunc(tfp.fidx())!=null &&
+ tfp.ret() instanceof TypeInteger ret && ret.log_size()<3 )
+ return ret;
+ return null;
+ }
+
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
new file mode 100644
index 00000000..26a237d0
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendARM extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendARM(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjARM(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // SBFM/UBFM aliases: sign/zero extend the low byte, short or int.
+ enc.add4(arm.imm_shift(_signed ? arm.OPI_ASR : arm.OPI_LSR,0,_bits-1,src,dst));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index d4e10404..044d0352 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -846,6 +846,8 @@ public class arm extends Machine {
}
private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendARM(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
new file mode 100644
index 00000000..22ac2a69
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendRISC extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendRISC(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjRISC(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ int shift = 64-_bits;
+ enc.add4(riscv.i_type(riscv.OP_IMM,dst,1,src,shift));
+ enc.add4(riscv.i_type(riscv.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 27141d21..03f7371b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -531,6 +531,8 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendRISC(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj);
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
new file mode 100644
index 00000000..e905e2e3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendX86 extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendX86(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjX86(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // MOVSX/MOVZX for bytes and shorts; MOVSXD/MOV r32 for ints.
+ enc.add1(x86_64_v2.rex(dst,src,0,_signed || _bits<32));
+ if( _bits==32 ) enc.add1(_signed ? 0x63 : 0x8b);
+ else enc.add1(0x0f).add1((_signed ? 0xbe : 0xb6) + (_bits==16 ? 1 : 0));
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT,dst,src));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
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 5b70834c..99010ffd 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
@@ -580,6 +580,8 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendX86(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 4307a3b6..88e963c0 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -10,6 +10,23 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter21Test {
+
+ @Test public void testArmBitfieldExtracts() {
+ // Independent SXT/UXT instruction words, plus ASR, LSR and LSL aliases.
+ int[] ops = {0x93401c20,0xd3401c20,0x93403c20,0xd3403c20,
+ 0x93407c20,0xd3407c20,0x9344fc20,0xd344fc20,0xd37cec20};
+ long value = 0xa5a5a5a580008080L;
+ long[] expected = {(byte)value,value&255,(short)value,value&65535,
+ (int)value,value&0xffffffffL,value>>4,value>>>4,value<<4};
+ for( int i=0; i<ops.length; i++ ) {
+ EvalArm64 cpu = new EvalArm64(new byte[1<<16],1<<15);
+ cpu.st4(0,ops[i]); cpu.regs[1]=value;
+ assertEquals(0,cpu.step(1));
+ assertEquals(expected[i],cpu.regs[0]);
+ }
+ }
+
+
@Test public void testArmFloatMemory() throws IOException {
for( String type : new String[]{"f32","f64"} ) {
String src = type+"[] !a=new "+type+"[3]; a[arg]=1.25; return a[1];";
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 4b036e62..35e0144a 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -11,6 +11,77 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter22Test {
+
+ @Test public void testNarrowCReturnsEmulated() {
+ String[] types = {"i8","u8","i16","u16","i32","u32","int"};
+ long[] values = {0xa5a5a5a500000000L,0xa5a5a5a50000007fL,0xa5a5a5a500000080L,
+ 0xa5a5a5a500007fffL,0xa5a5a5a500008000L,0xa5a5a5a57fffffffL,
+ 0xa5a5a5a580000000L,0xa5a5a5a5ffffffffL};
+ for( String target : new String[]{"arm","riscv"} )
+ for( int i=0; i<types.length; i++ ) {
+ String src = "{"+types[i]+"} c_narrow=\"C\"; return c_narrow();";
+ CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,target,"SystemV");
+ var enc = code._encoding;
+ // Link the external call to a tiny independent native stub.
+ var call = code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.CallNode ? n : null);
+ int start = enc.opStart(call);
+ ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc.opLen(call),0x1000-start);
+ for( long value : values ) {
+ long expected = switch(i) {
+ case 0 -> (byte)value; case 1 -> value&255;
+ case 2 -> (short)value; case 3 -> value&65535;
+ case 4 -> (int)value; case 5 -> value&0xffffffffL;
+ default -> value;
+ };
+ byte[] image = new byte[1<<16];
+ byte[] bits = enc.bits();
+ System.arraycopy(bits,0,image,0,bits.length);
+ if( target.equals("arm") ) {
+ EvalArm64 cpu = new EvalArm64(image,1<<15);
+ // MOVZ X0,0x1100; LDR X0,[X0]; RET X30.
+ cpu.st4(0x1000,0xd2822000); cpu.st4(0x1004,0xf9400000);
+ cpu.st4(0x1008,0xd65f03c0);
+ cpu.st8(0x1100,value);
+ assertEquals(0,cpu.step(1000));
+ assertEquals(0,cpu._pc);
+ assertEquals(types[i],expected,cpu.regs[0]);
+ } else {
+ EvalRisc5 cpu = new EvalRisc5(image,1<<15);
+ // AUIPC a0,0; LD a0,12(a0); RET.
+ cpu.st4(0x1000,0x00000517); cpu.st4(0x1004,0x00c53503);
+ cpu.st4(0x1008,0x00008067);
+ // RV64 widens even u32 returns by sign-extending bit 31.
+ cpu.st8(0x100c,i==6 ? value : (int)expected);
+ assertEquals(0,cpu.step(1000));
+ assertEquals(0,cpu._pc);
+ assertEquals(types[i],expected,cpu.regs[riscv.A0]);
+ }
+ }
+ }
+ }
+
+ @Test public void testNarrowCReturns() throws IOException {
+ String src = """
+ {i8} c_i8="C"; {u8} c_u8="C";
+ {i16} c_i16="C"; {u16} c_u16="C";
+ {i32} c_i32="C"; {u32} c_u32="C";
+ {int} c_i64="C";
+ val test_i8={->return c_i8();};
+ val test_u8={->return c_u8();};
+ val test_i16={->return c_i16();};
+ val test_u16={->return c_u16();};
+ val test_i32={->return c_i32();};
+ val test_u32={->return c_u32();};
+ val test_i64={->return c_i64();};
+ val test_cmp={->return c_i32()==-1;};
+ """;
+ for( String conv : new String[]{"SystemV","win64"} )
+ TestC.run(src,"c_returns"+conv,null,conv,
+ conv.equals("SystemV") ? "sysv_abi" : "ms_abi",
+ TestC.C_DRIVERS_DIR+"c_returns.c","",-1);
+ }
+
+
@Test public void testSubZeroTypeError() {
try {
new CodeGen("return null-0;").parse().opto().typeCheck();
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index bde25ab7..346c503a 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -394,7 +394,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2);
@Test
public void testErrnoAccessor() throws IOException {
- String src = "sys.libc.close(-1); return sys.libc.errno()==0;";
+ String src = "val status=sys.libc.close(-1); return status != -1 || sys.libc.errno()==0;";
TestC.run(src,"errnoAccessor",new Ary<>(new String[]{SYS_BLDDIR}),
TestC.CALL_CONVENTION,null,null,"",-1);
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java
index e8121927..86fcf96f 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -321,11 +321,15 @@ public class EvalArm64 {
rval = regs[rn] & immediate;
break;
}
- case 0x93: {
- // asr(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] >> (imm);
+ case 0x93, 0xD3: { // SBFM/UBFM: shifts and signed/unsigned extracts
+ int immr = (ir >>> 16) & 63;
+ int imms = (ir >>> 10) & 63;
+ int rn = (ir >>> 5) & 31;
+ boolean signed = (ir & 0x40000000)==0;
+ long value = regs[rn] << (63-imms);
+ value = signed ? value >> (63-imms) : value >>> (63-imms);
+ rval = immr > imms ? value << (64-immr)
+ : signed ? value >> immr : value >>> immr;
break;
}
@@ -568,13 +572,6 @@ public class EvalArm64 {
break;
}
- case 0xD3: {
- // lsl(immediate)
- int imm = (ir >> 16) & 0x3F;
- int rn = (ir >> 5) & 0x1F;
- rval = regs[rn] << (64 - imm);
- break;
- }
case 0xD6: {
int op = ir & 0xFFFFFC1F;
int rn = (ir >>> 5) & 31;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/c_returns.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/c_returns.c
new file mode 100644
index 00000000..6d0ff2fe
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/c_returns.c
@@ -0,0 +1,28 @@
+#include <stdint.h>
+#include <stdio.h>
+
+uint64_t c_bits;
+// Legal narrow C returns: unused RAX bits deliberately contain garbage.
+#define RET(name) __asm__(".text\n.globl " #name "\n" #name ":\nmovq c_bits(%rip), %rax\nret\n");
+RET(c_i8) RET(c_u8) RET(c_i16) RET(c_u16) RET(c_i32) RET(c_u32) RET(c_i64)
+#define DECL(name) extern int64_t __attribute__((CALL_CONV)) name(void);
+DECL(test_i8) DECL(test_u8) DECL(test_i16) DECL(test_u16)
+DECL(test_i32) DECL(test_u32) DECL(test_i64) DECL(test_cmp)
+
+int main(void) {
+ uint64_t values[] = {0xa5a5a5a500000000ULL, 0xa5a5a5a50000007fULL,
+ 0xa5a5a5a500000080ULL, 0xa5a5a5a500007fffULL, 0xa5a5a5a500008000ULL,
+ 0xa5a5a5a57fffffffULL, 0xa5a5a5a580000000ULL, 0xa5a5a5a5ffffffffULL};
+ for(unsigned i=0; i<sizeof(values)/sizeof(values[0]); i++) {
+ c_bits=values[i];
+ int64_t actual[]={test_i8(),test_u8(),test_i16(),test_u16(),test_i32(),test_u32(),test_i64(),test_cmp()};
+ int64_t expected[]={(int8_t)c_bits,(uint8_t)c_bits,(int16_t)c_bits,(uint16_t)c_bits,
+ (int32_t)c_bits,(uint32_t)c_bits,(int64_t)c_bits,(int32_t)c_bits==-1};
+ for(unsigned j=0; j<8; j++)
+ if(actual[j]!=expected[j]) {
+ printf("case %u type %u: %lld != %lld\n",i,j,(long long)actual[j],(long long)expected[j]);
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md
index bef83637..c2a4b56c 100644
--- a/docs/chapter-backports.md
+++ b/docs/chapter-backports.md
@@ -12,6 +12,14 @@ SSA with incomplete types in Chapter 25 for now; moving that architecture or
renumbering chapters is deferred. Cliff wants to revisit splitting Chapter 25
into smaller chapters; escape analysis remains in 25 until that larger review.
+Reset checkpoint (2026-09-28): Cliff committed the completed FunPtr work as
+`75390f47` (`Backport FunPtr`), following `586c8f65` (direct C data bindings) and
+`8212a66f` (constructor checks). The working tree was clean before this notes
+update. No FunPtr implementation or validation remains outstanding; the full
+21-25 results are recorded below. The subsequent narrow C integer-return ABI
+correction starts in 22 and is forwarded through 25; see the validation record.
+Splitting Chapter 25 is deferred, not an instruction to renumber.
+
## Pending corrections
- **Chapter 24 Load BOTTOM-on-error backport, unwound/deferred (2026-09-28).**
@@ -63,15 +71,6 @@ into smaller chapters; escape analysis remains in 25 until that larger review.
known-null regression is already rejected
during parsing in 10-24. Keep this separate from the constructor/memory fixes.
-- **Chapter 25 narrow C return values need ABI extension.** During the direct
- data-binding checks, `sys.libc.close(-1)==-1` evaluated false on x86/Cygwin.
- Disassembly compares the full RAX to -1 immediately after `close`, without
- sign-extending its declared i32 result. C can return -1 in EAX with the high
- half zero. This is independent of C data loads (which do sign-extend). The
- errno accessor regression calls `close(-1)` and checks errno directly; it
- does not claim to validate negative C return values. Investigate the ABI
- return normalization separately.
-
## AOT class initialization: larger independent work
Proposed on 2026-09-19; deferred behind the smaller queue above. This is
@@ -224,6 +223,8 @@ and its separate validation are recorded independently.
| Completed work | Scope and evidence retained |
|---|---|
+| Narrow C integer returns | On 2026-09-28, instruction selection in 22-25 sign/zero-extends direct C call results according to the declared 8/16/32-bit return type. x86 uses MOVSX/MOVZX/MOVSXD or a 32-bit MOV; ARM uses SBFM/UBFM; RISC-V uses shifts, including zero-extension of its sign-extended u32 ABI result. Chapter 25 consults external function identities because optimization can replace ExternNode with an ordinary constant. Native regressions run both x86 calling conventions with garbage upper return bits; ARM/RISC-V stubs exercise signed/unsigned boundaries and unchanged i64 results. Chapter 25's errno test now also requires `close(-1)==-1`. Original selectors reproduce failures in 22 and 25, including the libc case (`build/c-return-negative-{22,25}.log`). Full Make suites passed: 22 (433), 23 (455), 24 (485), 25 (479), plus each default fuzzer wrapper; 25 rebuilt sys.o (`build/c-return-final2.log`). |
+| ARM bitfield emulation | SBFM/UBFM decoding now handles both immediate fields, covering signed/unsigned extracts as well as ASR/LSR/LSL. The previous emulator treated all SBFM as ASR and all UBFM as LSL, hiding a logical-right-shift error and rejecting the new extension results. Introduced the correction and independent instruction-word regression in 21 and forwarded through 25. Full 21 suite passed (412 tests plus fuzzer; baseline 411); 22-25 validation is included above. |
| FunPtr lifetime and callable entries | On 2026-09-28, introduced Return-linked FunPtrNode in 22-24, including semantic constant folding. Direct call targets retain existing call-graph treatment; all other address uses conservatively retain unknown callers, without escape analysis. Stop retains each callable Return through instruction selection; machine operands omit the lifetime edge. Anonymous/library returns failed relocation on all three targets before the change; 24 also failed named returns. New tests call returned anonymous/named/library pointers on ARM/RISC-V and from native C, and check discarded anonymous bodies disappear. Full Make suites passed: 21 (411), 22 (430), 23 (452), 24 (482), each plus its fuzzer wrapper. Baselines passed at 410/427/449/479. Chapter 24's Phi-selected functions retain general parameter types; graph expectations reflect that conservative rule. |
| Function-address backend and worklist corrections | RISC-V TFPRISC now patches ADDI at opStart+4, rounds AUIPC for signed low bits, and reports its actual eight-byte size (21-24). ELF export emits anonymous bodies without exported names (21-24). Actual invocation exposed both bugs after retention was repaired; Chapter 21's reduced conditional-return probe trapped before the encoding fix. Chapter 22 gains the later inlining dominator null guard. In 24, killing Call#618 reduced FunPtr#540 to one use without waking dependent CallEnd#560; Node.kill now wakes the remaining definition's dependencies after removing a use (24-25). The no-return String workload loses unused hash bodies; its spill expectations become zero on all targets. Chapter 24 spill-stats passes all 212 original compilation entries at 1,351 moves / 3,115 weighted moves; its README separates this measurement from the original allocator ablation. Full Chapter 25 Make validation also passed: 476 ordinary tests plus its 17-seed fuzzer wrapper, with sys.o rebuilt. |
| Chapter 25 direct C data bindings | On 2026-09-28, full `make tests` passed after the Field/ExternOffset refactor: 476 ordinary tests plus the fuzzer wrapper (17 seeds). Coverage checks zero layout space for external fields, native shared integer/float storage across C calls, compound updates, source/precompiled imports sharing aliases and preserving storage metadata, ARM/RISC-V emulated address relocation and signed loads, and the errno accessor after a failed native call. Direct bindings remain in 25: 22-24 have different singleton-pointer constant and instruction-selection contracts. Serialization retains magic C0DE; the changed layout requires rebuilding all objects. Only the independent RISC-V fixes below were selected for backport. |
diff --git a/skills/chapter25-codex-notes.md b/skills/chapter25-codex-notes.md
index 1f75814c..5cb7dc53 100644
--- a/skills/chapter25-codex-notes.md
+++ b/skills/chapter25-codex-notes.md
@@ -438,9 +438,20 @@ Consequences:
selection, which removes the lifetime edge from machine operands before
scheduling and cloning. Never substitute relocation-array growth or a linker
address scan for correct retention.
+- The direct-call exemption requires the pointer to occur only as the call
+ target: `call.fptr()==ptr && call._inputs.find(ptr)==call.nIns()-1`. A pointer
+ also passed as an argument still needs unknown callers retained.
- Validate returned addresses by calling them. Include anonymous and library
functions, plus unused-body removal. Methods gain a receiver argument in 23;
native/emulator probes must honor it even when the body does not use self.
+ A wrong native signature initially passed because a stale argument register
+ happened to contain the right pointer; a passing call alone cannot prove ABI
+ correctness.
+- Chapter 21's empty-main heuristic can remove main for `return {->42;};`.
+ Use `if(arg) return {->42;}; return {->43;};` to probe returned addresses,
+ and invoke main explicitly at its encoding offset. Its CodeGen.driver cannot
+ resume after an explicit opto() call; use the remaining phases explicitly or
+ start a fresh driver.
- Function-address encoding begins in 21. RISC-V AUIPC/ADDI occupies eight bytes;
patch the second instruction at opStart+4 and round the high part for signed
low bits. Anonymous bodies need emitted code, but no exported ELF symbol.
@@ -467,6 +478,21 @@ Consequences:
## Serialization and code generation
+- Narrow direct C integer returns are normalized during instruction selection
+ in 22-25, using the declaration's width and signedness. An ideal cast would
+ fold away because the CallEnd already advertises the narrow semantic type.
+ In 25, identify external functions through their FIDX mapping: ExternNode can
+ fold into an ordinary ConstantNode. x86 upper return bits can be garbage;
+ RV64 sign-extends even u32 returns, which Simple must zero-extend again.
+ Native/emulator regressions start in Chapter22Test; Chapter25Test's errno
+ check also verifies the negative close result after importing sys.o.
+- Machine expansions must use the copying constructor and directly populate
+ input arrays, leaving reverse edges to CodeGen._instOuts. Using the ordinary
+ edge-registering constructor duplicates uses; the narrow-return expansion
+ exposed this during FileIO register splitting.
+- Keep Serialize's magic C0DE unchanged for now. Cliff explicitly unwound the
+ proposed magic bump; a real format version is future work. Rebuild all object
+ files after incompatible layout changes while there are no external users.
- Class storage must be writable during `<clinit>`, even when all final field
values are constant. `Encoding.Relo.readOnly()` owns this distinction for
pool emission, ELF symbols, in-memory linking, and assembly printing. Ordinary