From 41be002e89358649da46c33c8245aa786afdad6a Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 28 Sep 2026 16:25:01 -0700 Subject: Extend smaller-width C results to full width --- .../com/seaofnodes/simple/codegen/Machine.java | 11 ++++ .../seaofnodes/simple/node/cpus/arm/ExtendARM.java | 30 +++++++++ .../com/seaofnodes/simple/node/cpus/arm/arm.java | 7 ++- .../simple/node/cpus/riscv/ExtendRISC.java | 31 +++++++++ .../seaofnodes/simple/node/cpus/riscv/riscv.java | 2 + .../simple/node/cpus/x86_64_v2/ExtendX86.java | 33 ++++++++++ .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 2 + .../java/com/seaofnodes/simple/Chapter21Test.java | 17 +++++ .../java/com/seaofnodes/simple/Chapter22Test.java | 73 ++++++++++++++++++++++ .../test/java/com/seaofnodes/simple/EvalArm64.java | 21 +++---- .../java/com/seaofnodes/simple/progs/c_returns.c | 28 +++++++++ 11 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java create mode 100644 chapter22/src/test/java/com/seaofnodes/simple/progs/c_returns.c (limited to 'chapter22/src') 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 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 +#include + +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