summaryrefslogtreecommitdiff
path: root/chapter22
diff options
context:
space:
mode:
Diffstat (limited to 'chapter22')
-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
11 files changed, 242 insertions, 13 deletions
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;
+}