diff options
| author | Cliff Click <cliffc@acm.org> | 2026-10-01 14:58:31 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-01 14:58:31 -0700 |
| commit | 5dd5b7a2d03885cb767bbf2b609d56f8ef897301 (patch) | |
| tree | f524a9d000c588fa99fc3808e0d37012885cdf7e /isa/src | |
| parent | 154dd8b03010a96a48d7903e69394bcc4606c027 (diff) | |
| download | Simple-5dd5b7a2d03885cb767bbf2b609d56f8ef897301.tar.gz Simple-5dd5b7a2d03885cb767bbf2b609d56f8ef897301.zip | |
Move much of the ISA support to shared code
The ISA shared handlers here are required for a proper compiler, but contain only trivial bit-massaging operations.
Diffstat (limited to 'isa/src')
| -rw-r--r-- | isa/src/main/java/com/seaofnodes/isa/Arm64.java | 399 | ||||
| -rw-r--r-- | isa/src/main/java/com/seaofnodes/isa/CodeSink.java | 9 | ||||
| -rw-r--r-- | isa/src/main/java/com/seaofnodes/isa/RiscV.java | 117 | ||||
| -rw-r--r-- | isa/src/main/java/com/seaofnodes/isa/X86.java | 256 | ||||
| -rw-r--r-- | isa/src/test-support/isa-eval.iml | 13 | ||||
| -rw-r--r-- | isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java | 775 | ||||
| -rw-r--r-- | isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java | 370 | ||||
| -rw-r--r-- | isa/src/test/java/com/seaofnodes/isa/RiscTest.java | 59 | ||||
| -rw-r--r-- | isa/src/test/java/com/seaofnodes/isa/X86Test.java | 124 |
9 files changed, 2122 insertions, 0 deletions
diff --git a/isa/src/main/java/com/seaofnodes/isa/Arm64.java b/isa/src/main/java/com/seaofnodes/isa/Arm64.java new file mode 100644 index 00000000..2c969da0 --- /dev/null +++ b/isa/src/main/java/com/seaofnodes/isa/Arm64.java @@ -0,0 +1,399 @@ +package com.seaofnodes.isa; + +/** Instruction fields and concrete operand encodings; no compiler state. */ +public final class Arm64 { + private Arm64() {} + + public static final int OP_ADD = 0b10_001_011; + public static final int OPF_ADD = 0b00_011_110; + public static final int OPF_OP_ADD = 0b00_101_0; + public static final int OPI_ADD = 0b10_010_00100; + public static final int OPI_SUB = 0b11_010_00100; + public static final int OP_UJMP = 0b000101; + public static final int OP_ADRP = 0b10000; + public static final int OP_ASR = 0b1010; + public static final int OPI_ASR = 0b10_0100_1101; + public static final int OP_LSL = 0b1000; + public static final int OPI_LSL =0b1101001101; + public static final int OP_LSR = 0b1001; + public static final int OPI_LSR = 0b1101001101; + public static final int OP_BRANCH = 0b01_0101_00; + public static final int OP_CALL = 0b10_010_1; + public static final int OP_CALLRARM = 0b1101011000111111000000; + public static final int OP_SUBS = 0b1111000100; + public static final int OP_CSET = 0b10011010100; + public static final int OP_CMP = 0b11101011; + public static final int OPI_CMP = 0b1111000100; + public static final int OP_DIV = 0b10011010110; + public static final int OPF_DIV = 0b000110; + public static final int OP_MUL = 0b10011011000; + public static final int OPF_MUL = 0b10; + public static final int OPF_ARM = 0b01011100; + public static final int OP_FLOAT_C = 0b10011110; + public static final int OP_XOR = 0b11001010; + public static final int OPI_XOR = 0b110100100; + public static final int OP_AND = 0b10_0010_10; + public static final int OPI_AND = 0b10_0100_100; + public static final int OP_OR = 0b10101010; + public static final int OPI_OR = 0b101100100; + public static final int OP_SUB = 0b11001011; + public static final int OPF_SUB = 0b1110; + public static final int OP_MOVK = 0b111100101; + public static final int OP_MOVN = 0b100100101; + public static final int OP_MOVZ = 0b110100101; + public static final int OP_RET = 0b1101011001011111000000; + public static final int OP_LOAD_R_64 = 0b11111000011; + public static final int OP_LOAD_R_32 = 0b10111000011; + public static final int OP_LOAD_R_16 = 0b01111000010; + public static final int OP_LOAD_R_8 = 0b00111000011; + public static final int OP_LOAD_IMM_64 = 0b1111100101; + public static final int OP_LOAD_IMM_32 = 0b1011100101; + public static final int OP_LOAD_IMM_16 = 0b0111100101; + public static final int OP_LOAD_IMM_8 = 0b0011100101; + public static final int OPF_LOAD_R_64 = 0b11111100011; + public static final int OPF_LOAD_R_32 = 0b10111100011; + public static final int OPF_LOAD_IMM_64 = 0b1111110101; + public static final int OPF_LOAD_IMM_32 = 0b1011110101; + public static final int OP_STORE_R_64 = 0b11111000001; + public static final int OP_STORE_R_32 = 0b10111000001; + public static final int OP_STORE_R_16 = 0b01111000001; + public static final int OP_STORE_R_8 = 0b00111000001; + public static final int OP_STORE_IMM_64 = 0b1111100100; + public static final int OP_STORE_IMM_32 = 0b1011100100; + public static final int OP_STORE_IMM_16 = 0b0111100100; + public static final int OP_STORE_IMM_8 = 0b0011100100; + public static final int OPF_STORE_R_64 = 0b11111100001; + public static final int OPF_STORE_R_32 = 0b10111100001; + public static final int OPF_STORE_IMM_32 = 0b1011110100; + public static final int OPF_STORE_IMM_64 = 0b1111110100; + public static final int OP_FMOV = 0b10011110; + public static final int OP_FMOV_REG = 0b00011110; + public static final int OP_MOV = 0b10101010000; + + public enum OPTION { + UXTB, + UXTH, + UXTX, + SXTB, + SXTH, + SXTW, + SXTX, + } + + public enum STORE_LOAD_OPTION { + UXTW, // base+ u32 index [<< logsize] + UXTX, // base+ u64 index [<< logsize] + SXTW, // base+ s32 index [<< logsize] + SXTX, // base+ s64 index [<< logsize] + } + + public enum COND { + EQ, + NE, + CS, + CC, + MI, + PL, + VS, + VC, + HI, + LS, + GE, + LT, + GT, + LE, + AL, + NV + } + + static public int cset(int opcode, int rm, COND cond, int rn, int rd) { + assert 0 <= rm && rm < 32; + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + // Is one of the standard conditions, excluding AL and NV, encoded in the "cond" field with its least significant bit inverted. + int actualCond = cond.ordinal() ^ 1; + return (opcode << 21) | (rm << 16) | (actualCond << 12) | (0b01 << 10) | (rn << 5) | rd; + } + + static public int cset(int opcode, COND cond, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + return (opcode << 16) | (cond.ordinal() << 12) | (rn << 5) | rd; + } + + public static int imm_inst(int opcode, int imm12, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + assert opcode >=0 && imm12 >= 0; // Caller zeros high order bits + return (opcode << 22) | (imm12 << 10) | (rn << 5) | rd; + } + + public static int imm_shift(int opcode, int imm, int imms, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + return (opcode << 22) | (1 << 22) | (imm << 16) | (imms << 10) | (rn << 5) | rd; + } + + public static int imm_inst_n(int opcode, int imm13, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + assert 0 <= imm13 && imm13 <= 0x1FFF; + return (opcode << 23) | (imm13 << 10) | (rn << 5) | rd; + } + + public static int imm_inst_l(int opcode, int imm12, int self) { + int body = imm_inst(opcode, imm12&0xFFF, self, self); + return body; + } + + public static int r_reg(int opcode, int shift, int rm, int imm6, int rn, int rd) { + assert 0 <= rm && rm < 32; + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + return (opcode << 24) | (shift << 21) | (rm << 16) | (imm6 << 10) | (rn << 5) | rd; + } + + public static int shift_reg(int opcode, int rm, int op2, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rm && rm < 32; + assert 0 <= rd && rd < 32; + return (opcode << 21) | (rm << 16) | (op2 << 10) | (rn << 5) | rd; + } + + public static int madd(int opcode, int rm, int ra, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + assert 0 <= rm && rm < 32; + return (opcode << 21) | (rm << 16) | (ra << 10) | (rn << 5) | rd; + } + + public static int mov(int opcode, int shift, int imm16, int rd) { + assert 0 <= rd && rd < 32; + return (opcode << 23) | (shift << 21) | (imm16 << 5) | rd; + } + + public static int mov_reg(int opcode, int src, int rd) { + assert 0 <= rd && rd < 32; + return (opcode << 21) | (src << 16) | 0b11111 << 5 | rd; + } + + public static int ret(int opcode) { + return (opcode << 10) | (30 << 5); + } + + public static int f_scalar(int opcode, int ftype, int rm, int op, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + assert 0 <= rm && rm < 32; + return (opcode << 24) | (ftype << 22) | (1 << 21) | (rm << 16) | (op << 10) | (rn << 5) | rd; + } + + public static int f_mov_reg(int opcode, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + return (opcode << 24) | (0b01100000010000 << 10) | (rn << 5) | rd; + } + + public static int f_mov_general(int opcode, int ftype, int rmode, int opcode1, int rn, int rd) { + assert 0 <= rn && rn < 32; + assert 0 <= rd && rd < 32; + return (opcode << 24) |(ftype << 22) | (1 << 21) | (rmode << 19) | (opcode1 << 16) | (rn << 5) | rd; + } + + public static int load_pc(int opcode, int offset, int rt) { + assert -(1<<20) <= offset && offset < (1<<20); + assert (offset&3)==0; + offset = (offset>>2)&0x7FFFF; + return (opcode << 24) | (offset << 5) | rt; + } + + public static int adrp(int op, int imlo,int opcode, int imhi, int rd) { + assert 0 <= rd && rd < 32; + assert 0 <= imlo && imlo <= 0x3; // 2 bits + assert 0 <= imhi && imhi <= 0x7FFFF; // 19 bits + return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd; + } + + public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) { + assert 0 <= ptr && ptr < 32; + assert 0 <= rt && rt < 32; + return (opcode << 21) | (off << 16) | (option.ordinal() << 13) | (s << 12) | (2 << 10) | (ptr << 5) | rt; + } + + public static int load_str_imm(int opcode, int imm12, int ptr, int rt, int size) { + assert 0 <= ptr && ptr < 32; + assert 0 <= rt && rt < 32; + + if(size == 8) imm12 = imm12 >> 3; + if(size == 4) imm12 = imm12 >> 2; + if(size == 2) imm12 = imm12 >> 1; + // size == 1 imm12 = imm12 + return (opcode << 22) | ((imm12) << 10) | (ptr << 5) | rt; + } + + public static int f_convert(int opcode_1, int opcode_2, int opcode_3, int opcode_4, int vd, int vm) { + return (opcode_1 << 28) | (opcode_2 << 24) | (opcode_3 << 20) | (opcode_4 << 16) | + (vd << 12) | (0x01100010 << 4) | vm; + } + + public static int float_cast(int opcode, int ftype, int rn, int rd) { + assert 0 <= rd && rd < 32; + assert 0 <= rn && rn < 32; + return (opcode << 24) | (ftype << 22) | (2176 << 10) | (rn << 5) | rd; + } + + public static int f_cmp(int opcode, int ftype, int rm, int rn) { + assert 0 <= rn && rn < 32; + assert 0 <= rm && rm < 32; + // Todo: |8 is not needed + return (opcode << 24) | (ftype << 21) | (rm << 16) | (8 << 10) | (rn << 5); + } + + public static COND make_condition(String bop) { return make_condition(bop,false); } + + public static COND make_condition(String bop, boolean fp) { + // One of {N,Z,C} and !V + // OR { !N, !Z, C, V } + return fp + ? switch (bop) { // FP uses unsigned encodings + case "==" -> COND.EQ; // Z + case "!=" -> COND.NE; // !Z + case "<" -> COND.MI; // N + case "<=" -> COND.LS; // Z || !C + case ">=" -> COND.PL; // !N + case ">" -> COND.HI; // !Z && C + default -> throw new UnsupportedOperationException(); + } + : switch (bop) { + case "==" -> COND.EQ; // Z + case "!=" -> COND.NE; // !Z + case "<" -> COND.LT; // N != V + case "<=" -> COND.LE; // Z || N != V + case ">=" -> COND.GE; // N == V + case ">" -> COND.GT; // !Z && N == V + default -> throw new UnsupportedOperationException(); + }; + } + + public static int b_cond(int opcode, int delta, COND cond) { + // 24-5 == 19bits offset range + assert -(1<<20) <= delta && delta < (1<<20); + assert (delta&3)==0; + delta>>=2; + delta &= (1L<<19)-1; // Zero extend + return (opcode << 24) | ((delta)<< 5) | cond.ordinal(); + } + + public static int cond_set(int opcode, int rm, COND cond, int rn, int rd) { + assert 0 <= rd && rd < 32; + assert 0 <= rn && rn < 32; + return (opcode << 21) | (rm << 16) | (cond.ordinal() << 12) | (rn << 5) | rd; + } + + public static int blr(int opcode, int rd) { + assert 0 <= rd && rd < 32; + return opcode << 10 | rd << 5; + } + + public static int b(int opcode, int delta) { + assert -(1<<27) <= delta && delta < (1<<27); + assert (delta&3)==0; + delta>>=2; + delta &= (1L<<26)-1; // Zero extend + return (opcode << 26) | delta; + } + + public static int b_calloc(int opcode, int delta) { + return b(opcode,delta); + } + + public static long decodeImm12(int imm12) { + int immr = (imm12 >> 6) & 0x3F; + int imms = imm12 & 0x3F; + int size; + if ((imm12 & 0x1000) != 0) { + size = 64; + } else { + size = 31-(imms >> 1); + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size++; + imms &= ~((32-size) << 1); + } + long val = (2L << imms)-1; + while (size < 64) { + val |= val << size; + size <<= 1; + } + val = (val >>> immr) | val << (64-immr); + return val; + } + + public static int logicalImmediate(long val) { + if (val == 0 || val == -1) return -1; // Special cases are not allowed + int immr = 0; + // Rotate until we have 0[...]1 + + // Rotate until: + // The number is negative (MSB is 1) + // Or the LSB is not 1 + while (val < 0 || (val & 1)==0) { + // circular rotation + val = (val >>> 63) | (val << 1); + immr++; + } + // Start by assuming that val might be made of two 32-bit chunks that repeat. + int size = 32; + long pattern = val; + // Is upper half of pattern the same as the lower? + while ((pattern & ((1L<<size)-1)) == (pattern >> size)) { + // Then only take one half + pattern >>= size; + size >>= 1; + } + size <<= 1; + int imms = Long.bitCount(pattern); + // Pattern should now be zeros followed by ones 0000011111 + if (pattern != (1L<<imms)-1) return -1; + imms--; + if (size == 64) return 0x1000 | immr << 6 | imms; + return (32-size)<<1 | immr << 6 | imms; + } + /** Materialize a 64-bit constant with MOVZ/MOVN and MOVK. */ + public static void constant(CodeSink out, int self, long x) { + int nb0 = 0; + int nb1 = 0; + // Count number of 0000 and FFFF blocks + for (int i=0; i<64; i+=16) { + int block = (int)(x >> i) & 0xFFFF; + if (block == 0) nb0++; + if (block == 0xFFFF) nb1++; + } + int pattern; + int op; + if(nb0 >= nb1) { + // More 0 blocks then F blocks, use movz + pattern = 0; + op = OP_MOVZ; + } else { + // More F blocks then 0 blocks, use movn + pattern = 0xFFFF; + op = OP_MOVN; + } + int invert = pattern; + for (int i=0; i<4; i++) { + int block = (int)x & 0xFFFF; + x >>= 16; + if (block != pattern) { + out.add4(mov(op, i, block ^ invert, self)); + op = OP_MOVK; + invert = 0; + } + } + if (op != OP_MOVK) { + // All blocks are the same, special case + out.add4(mov(op, 0, 0, self)); + } + } +} diff --git a/isa/src/main/java/com/seaofnodes/isa/CodeSink.java b/isa/src/main/java/com/seaofnodes/isa/CodeSink.java new file mode 100644 index 00000000..0c2d4936 --- /dev/null +++ b/isa/src/main/java/com/seaofnodes/isa/CodeSink.java @@ -0,0 +1,9 @@ +package com.seaofnodes.isa; + +/** Instruction bytes go straight into the chapter's code buffer. */ +public interface CodeSink { + CodeSink add1(int value); + default CodeSink add2(int value) { return add1(value).add1(value>>8); } + default CodeSink add4(int value) { return add2(value).add2(value>>16); } + default CodeSink add8(long value) { return add4((int)value).add4((int)(value>>32)); } +} diff --git a/isa/src/main/java/com/seaofnodes/isa/RiscV.java b/isa/src/main/java/com/seaofnodes/isa/RiscV.java new file mode 100644 index 00000000..cb446a6a --- /dev/null +++ b/isa/src/main/java/com/seaofnodes/isa/RiscV.java @@ -0,0 +1,117 @@ +package com.seaofnodes.isa; + +/** Instruction fields and concrete operand encodings; no compiler state. */ +public final class RiscV { + private RiscV() {} + + public static final int OP_LOAD = 0b00_000_11; + public static final int OP_LOADFP = 0b00_001_11; + public static final int OP_CUSTOM0 = 0b00_010_11; + public static final int OP_IMM = 0b00_100_11; + public static final int OP_AUIPC = 0b00_101_11; + public static final int OP_STORE = 0b01_000_11; + public static final int OP_STOREFP = 0b01_001_11; + public static final int OP_CUSTOM1 = 0b01_010_11; + public static final int OP = 0b01_100_11; + public static final int OP_LUI = 0b01_101_11; + public static final int OP_CUSTOM2 = 0b10_010_11; + public static final int OP_FP = 0b10_100_11; + public static final int OP_BRANCH = 0b11_000_11; + public static final int OP_JALR = 0b11_001_11; + public static final int OP_RESERVED= 0b11_010_11; + public static final int OP_JAL = 0b11_011_11; + + public enum RM { + RNE, // Round to Nearest, ties to Even + RTZ, // Round towards Zero + RDN, // Round Down + RUP, // Round Up + DIRECT, // Round to Nearest, ties to Max Magnitude + RESERVED1, // Reserved for futue use + RESERVED2, // Reserved for future use + DYN, // In instruction’s rm field, selects dynamic rounding mode; In Rounding Mode register, reserved + } + + public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) { + assert 0 <= rs1 && rs1 < 32; + assert 0 <= rs2 && rs2 < 32; + assert 0 <= rd && rd < 32; + return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; + } + + public static int u_type(int opcode, int rd, int imm20) { + assert 0 <= rd && rd < 32; + return (imm20 << 12) | (rd << 7) | opcode; + } + + public static int j_type(int opcode, int rd, int delta) { + assert -(1L<<20) <= delta && delta < (1L<<20); + assert 0 <= rd && rd < 32; + // Messy branch offset encoding + // 31 30-21 20 19-12 11-7 6-0 + // 20 10- 1 11 19-12 rpc JAL + assert (delta&1)==0; // Low bit is always zero, not encoded + int imm10_01 = (delta>> 1) & 0x3FF; + int imm11 = (delta>>11) & 1; + int imm12_19 = (delta>>12) & 0xFF; + int imm20 = (delta>>20) & 1; + int bits = imm20<<19 | imm10_01 << 9 | imm11 << 8 | imm12_19; + return bits << 12 | rd << 7 | opcode; + } + + public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) { + assert 0 <= rd && rd < 32; + assert 0 <= rs1 && rs1 < 32; + assert opcode >= 0 && func3 >=0 && imm12 >= 0; // Zero-extend by caller + return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; + } + + public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) { + assert 0 <= rs1 && rs1 < 32; + assert 0 <= rs2 && rs2 < 32; + assert 0 <= func3; + + assert imm12 >= 0; // Masked to high zero bits by caller + int imm_lo = imm12 & 0x1F; + int imm_hi = imm12 >> 5; + return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode; + } + + public static int b_type(int opcode, int func3, short rs1, short rs2, int delta) { + assert 0 <= rs1 && rs1 < 32; + assert 0 <= rs2 && rs2 < 32; + assert -4*1024 <= delta && delta < 4*1024; + assert (delta&1)==0; // Low bit is always zero, not encoded + // Messy branch offset encoding + // 31 30 29 28 27 26 25 24-20 19-15 14-12 11 10 9 8 7 6-0 + // 12 10 9 8 7 6 5 SRC2 SRC1 FUNC3 4 3 2 1 11 OP + int imm4_1 = (delta>> 1) & 0xF; + int imm10_5= (delta>> 5) &0x3F; + int imm11 = (delta>>11) & 1; + int imm12 = (delta>>12) & 1; + int imm5 = imm4_1<<1 | imm11; + int imm7 = imm12<<6 | imm10_5; + return (imm7 << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm5 << 7) | opcode; + } + + static public int jumpop(String op) { + return switch(op) { + case "==" -> 0x0; + case "!=" -> 0x1; + case "<" -> 0x4; + case ">=" -> 0x5; + case "u<" -> 0x6; + case "u<=" -> 0x7; + default -> throw new UnsupportedOperationException(); + }; + } + + static public int fsetop(String op) { + return switch(op) { + case "<" -> 1; + case "<=" -> 0; + case "==" -> 2; + default -> throw new UnsupportedOperationException(); + }; + } +} diff --git a/isa/src/main/java/com/seaofnodes/isa/X86.java b/isa/src/main/java/com/seaofnodes/isa/X86.java new file mode 100644 index 00000000..99d61dff --- /dev/null +++ b/isa/src/main/java/com/seaofnodes/isa/X86.java @@ -0,0 +1,256 @@ +package com.seaofnodes.isa; + +/** x86-64 instruction bytes. Registers are hardware numbers 0..15, in the chosen bank. */ +public final class X86 { + private X86() {} + public enum MOD { INDIRECT, INDIRECT_disp8, INDIRECT_disp32, DIRECT } + + public static int modrm(MOD mod, int reg, int rm) { + if( reg==-1 ) reg=0; + assert 0<=reg && reg<16; + assert 0<=rm && rm<16; + return (mod.ordinal()<<6) | ((reg&7)<<3) | (rm&7); + } + public static int sib(int scale, int index, int base) { + assert 0<=scale && scale<=3; + assert 0<=index && index<16; + assert 0<=base && base<16; + return (scale<<6) | ((index&7)<<3) | (base&7); + } + public static int rex(int reg, int base, int index, boolean wide) { + assert -1<=reg && reg<16; + assert -1<=base && base<16; + assert -1<=index && index<16; + return (wide ? 0x48 : 0x40) | (reg>=8 ? 4 : 0) | + (index>=8 ? 2 : 0) | (base>=8 ? 1 : 0); + } + public static int rex(int reg, int base, int index) { return rex(reg,base,index,true); } + + /** Omit a redundant REX; byte-register instructions sometimes need it explicitly. */ + public static byte rexF(int reg, int base, int index, boolean wide, CodeSink out) { + int bits=rex(reg,base,index,wide); + if( bits==0x40 ) return 0; + out.add1(bits); + return 1; + } + + /** ModRM/SIB/displacement for [base + index*2^scale + offset]; -1 means no index. */ + public static void indirectAdr(int scale, int index, int base, int offset, int reg, CodeSink out) { + assert 0<=base && base<16; + assert -1<=index && index<16 && index!=4; + MOD mod=offset==0 ? MOD.INDIRECT : imm8(offset) ? MOD.INDIRECT_disp8 : MOD.INDIRECT_disp32; + // The no-displacement r/m=5 form is RIP-relative, even with REX.B. + if( mod==MOD.INDIRECT && (base==5 || base==13) ) mod=MOD.INDIRECT_disp8; + // r/m=4 always introduces SIB. Index=4 with no REX.X means no index. + if( index==-1 && (base==4 || base==12) ) index=4; + if( index==-1 ) out.add1(modrm(mod,reg,base)); + else out.add1(modrm(mod,reg,4)).add1(sib(scale,index,base)); + if( mod==MOD.INDIRECT_disp8 ) out.add1(offset); + if( mod==MOD.INDIRECT_disp32 ) out.add4(offset); + } + private static boolean imm8(int value) { return -128<=value && value<=127; } + public static void unary(CodeSink out, int op, int digit, int dst) { + out.add1(rex(0,dst,-1)).add1(op).add1(modrm(MOD.DIRECT,digit,dst)); + } + public static void shift(CodeSink out, int digit, int dst, int count) { + unary(out,0xC1,digit,dst); + out.add1(count); // A shift always takes imm8; the processor masks its count. + } + public static void divide(CodeSink out, int src) { + out.add1(0x48).add1(0x99); // CQO: sign-extend rax into rdx:rax. + unary(out,0xF7,7,src); + } + public static void sse(CodeSink out, int prefix, int op, boolean wide, int dst, int src) { + if( prefix!=0 ) out.add1(prefix); + rexF(dst,src,-1,wide,out); + opcode(out,op); + out.add1(modrm(MOD.DIRECT,dst,src)); + } + public static void memory(CodeSink out, int prefix, int op, boolean wide, + int reg, int base, int index, int offset, int scale) { + if( prefix!=0 ) out.add1(prefix); + rexF(reg,base,index,wide,out); + opcode(out,op); + indirectAdr(scale,index,base,offset,reg,out); + } + public static void cmpMem(CodeSink out, int logSize, int reg, int base, int index, + int offset, int scale, boolean swap) { + if( logSize==1 ) out.add1(0x66); + if( logSize==0 && reg>=4 ) out.add1(rex(reg,base,index,false)); + else rexF(reg,base,index,logSize==3,out); + out.add1((logSize==0 ? 0x38 : 0x39)+(swap ? 2 : 0)); + indirectAdr(scale,index,base,offset,reg,out); + } + public static void addMem(CodeSink out, int src, int value, int base, int index, int offset, int scale) { + memory(out,0,src==-1 ? (value==1 ? 0xFF : 0x81) : 0x01,true, + src==-1 ? 0 : src,base,index,offset,scale); + if( src==-1 && value!=1 ) out.add4(value); + } + public static void extend(CodeSink out, int bits, boolean signed, int dst, int src) { + out.add1(rex(dst,src,-1,signed || bits<32)); + opcode(out,bits==32 ? (signed ? 0x63 : 0x8B) : (signed ? 0x0FBE : 0x0FB6)+(bits==16 ? 1 : 0)); + out.add1(modrm(MOD.DIRECT,dst,src)); + } + /** SETcc dstb; MOVZX dst32,dstb. Condition is the low four bits of Jcc/SETcc. */ + public static void set(CodeSink out, int condition, int dst) { + if( dst>=4 ) out.add1(rex(0,dst,-1,false)); + out.add1(0x0F).add1(0x90|condition).add1(modrm(MOD.DIRECT,0,dst)); + if( dst>=4 ) out.add1(rex(dst,dst,-1,false)); + out.add1(0x0F).add1(0xB6).add1(modrm(MOD.DIRECT,dst,dst)); + } + public static void constant(CodeSink out, int dst, long value) { + if( value==0 ) { + if( dst>=8 ) out.add1(rex(dst,dst,-1)); + out.add1(0x33).add1(modrm(MOD.DIRECT,dst,dst)); + } else if( Integer.MIN_VALUE<=value && value<0 ) { + unary(out,0xC7,0,dst); + out.add4((int)value); + } else if( 0<=value && value<=0xFFFFFFFFL ) { + rexF(0,dst,-1,false,out); + out.add1(0xB8+(dst&7)).add4((int)value); + } else { + out.add1(rex(0,dst,-1)).add1(0xB8+(dst&7)).add8(value); + } + } + /** RIP-relative operand; returns length so the chapter can record its relocation. */ + public static byte rip(CodeSink out, int prefix, int op, boolean wide, int dst) { + int size=4+1+(op>255 ? 2 : 1); + if( prefix!=0 ) { out.add1(prefix); size++; } + size+=rexF(dst,-1,-1,wide,out); + opcode(out,op); + out.add1(modrm(MOD.INDIRECT,dst,5)).add4(0); + return (byte)size; + } + public static void call(CodeSink out) { out.add1(0xE8).add4(0); } + public static void callRegister(CodeSink out, int src) { + rexF(0,src,-1,false,out); + out.add1(0xFF).add1(modrm(MOD.DIRECT,2,src)); + } + public static void ret(CodeSink out) { out.add1(0xC3); } + public static void branch(CodeSink out, int condition) { + out.add1(condition<0 ? 0xEB : 0x70|condition).add1(0); + } + public static byte branchSize(int delta, boolean conditional) { + return (byte)(imm8(delta-2) ? 2 : conditional ? 6 : 5); + } + public static void patchBranch(byte[] bytes, int start, int size, int delta, int condition) { + delta-=size; + if( size==2 ) { + assert (byte)delta==delta; + bytes[start+1]=(byte)delta; + } else { + int pos=start; + if( condition<0 ) bytes[pos++]=(byte)0xE9; + else { bytes[pos++]=0x0F; bytes[pos++]=(byte)(0x80|condition); } + for( int i=0; i<4; i++ ) bytes[pos+i]=(byte)(delta>>(8*i)); + } + } + public static void flags(CodeSink out, int reg, boolean toFlags) { + if( !toFlags ) out.add1(0x9C); // PUSHF + rexF(0,reg,-1,false,out); + out.add1((toFlags ? 0x50 : 0x58)+(reg&7)); + if( toFlags ) out.add1(0x9D); // POPF + } + public static void stackCopy(CodeSink out, int srcOffset, int dstOffset) { + memory(out,0,0xFF,false,6,4,-1,srcOffset,0); // PUSH [rsp+srcOffset] + memory(out,0,0x8F,false,0,4,-1,dstOffset,0); // POP uses the restored rsp. + } + public static void move(CodeSink out, boolean dstXmm, boolean srcXmm, int dst, int src) { + if( dstXmm ^ srcXmm ) out.add1(0x66); + if( !dstXmm && srcXmm ) { int tmp=dst; dst=src; src=tmp; } + reg(out,!dstXmm && !srcXmm ? 0x8B : dstXmm && srcXmm ? 0x0F28 : dstXmm ? 0x0F6E : 0x0F7E,dst,src); + } + private static void opcode(CodeSink out, int op) { + if( op>255 ) out.add1(op>>8); + out.add1(op); + } + + /** REX.W + opcode + /r, with the destination in ModRM.reg. */ + public static void reg(CodeSink out, int op, int dst, int src) { + out.add1(rex(dst,src,-1)); + opcode(out,op); + out.add1(modrm(MOD.DIRECT,dst,src)); + } + /** Group-1 arithmetic: 81 /digit id or 83 /digit ib. */ + public static void imm(CodeSink out, int op, int digit, int dst, int value) { + out.add1(rex(0,dst,-1)).add1(op+(imm8(value) ? 2 : 0)); + out.add1(modrm(MOD.DIRECT,digit,dst)); + if( imm8(value) ) out.add1(value); else out.add4(value); + } + public static void imul(CodeSink out, int dst, int src, int value) { + out.add1(rex(dst,src,-1)).add1(imm8(value) ? 0x6B : 0x69); + out.add1(modrm(MOD.DIRECT,dst,src)); + if( imm8(value) ) out.add1(value); else out.add4(value); + } + public static void not(CodeSink out, int dst, int src) { + reg(out,0x85,src,src); // TEST src,src + set(out,4,dst); // SETZ; MOVZX + } + public static void lea(CodeSink out, int dst, int base, int index, int offset, int scale) { + out.add1(rex(dst,base,index)).add1(0x8D); + if( base==-1 ) { + assert 0<=index && index<16 && index!=4; + out.add1(modrm(MOD.INDIRECT,dst,4)).add1(sib(scale,index,5)).add4(offset); + } else indirectAdr(scale,index,base,offset,dst,out); + } + + /** Load into a GPR (extending to 64 bits) or an XMM register (scalar 32/64 bits). */ + public static void load(CodeSink out, int logSize, boolean signed, boolean xmm, + int dst, int base, int index, int offset, int scale) { + assert 0<=logSize && logSize<=3; + if( xmm ) { + assert logSize==2 || logSize==3; + out.add1(logSize==2 ? 0xF3 : 0xF2); + rexF(dst,base,index,false,out); + out.add1(0x0F).add1(0x10); + } else { + rexF(dst,base,index,logSize!=2 || signed,out); + int op=switch(logSize) { + case 0 -> signed ? 0x0FBE : 0x0FB6; + case 1 -> signed ? 0x0FBF : 0x0FB7; + case 2 -> signed ? 0x63 : 0x8B; + default -> 0x8B; + }; + opcode(out,op); + } + indirectAdr(scale,index,base,offset,dst,out); + } + public static void store(CodeSink out, int logSize, boolean xmm, + int src, int base, int index, int offset, int scale) { + assert 0<=logSize && logSize<=3; + if( xmm ) { + assert logSize==2 || logSize==3; + out.add1(logSize==2 ? 0xF3 : 0xF2); + rexF(src,base,index,false,out); + out.add1(0x0F).add1(0x11); + } else { + // Legacy prefixes precede REX, which must immediately precede the opcode. + if( logSize==1 ) out.add1(0x66); + if( logSize==0 && src>=4 ) out.add1(rex(src,base,index,false)); + else rexF(src,base,index,logSize==3,out); + out.add1(logSize==0 ? 0x88 : 0x89); + } + indirectAdr(scale,index,base,offset,src,out); + } + public static void storeImm(CodeSink out, int logSize, int value, + int base, int index, int offset, int scale) { + memImm(out,logSize,0xC6,0xC7,0,value,base,index,offset,scale); + } + public static void cmpImm(CodeSink out, int logSize, int value, + int base, int index, int offset, int scale) { + memImm(out,logSize,0x80,0x81,7,value,base,index,offset,scale); + } + private static void memImm(CodeSink out, int logSize, int op8, int op, int digit, int value, + int base, int index, int offset, int scale) { + assert 0<=logSize && logSize<=3; + if( logSize==1 ) out.add1(0x66); + rexF(-1,base,index,logSize==3,out); + out.add1(logSize==0 ? op8 : op); + indirectAdr(scale,index,base,offset,digit,out); + switch(logSize) { + case 0 -> out.add1(value); + case 1 -> out.add2(value); + default -> out.add4(value); // Even a 64-bit operand takes only imm32. + } + } +} diff --git a/isa/src/test-support/isa-eval.iml b/isa/src/test-support/isa-eval.iml new file mode 100644 index 00000000..733c5919 --- /dev/null +++ b/isa/src/test-support/isa-eval.iml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/../../../build/idea/production/isa-eval" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/java" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="isa" /> + </component> +</module> diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java new file mode 100644 index 00000000..2a4a2708 --- /dev/null +++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java @@ -0,0 +1,775 @@ +package com.seaofnodes.isa.eval; + +import com.seaofnodes.isa.Arm64; +import java.io.ByteArrayOutputStream; + +public class EvalArm64 { + // Memory image, always 0-based + public final byte[] _buf; + + // GPRs + public final long[] regs; + // FRs + public final double[] fregs; + + public int _pc; + + // Start of free memory for allocation + public int _heap; + + // Standard out and err streams + public ByteArrayOutputStream _stdout, _stderr; + + // Cycle counters + public int _cycle; + + // flags + public boolean N = false; + public boolean Z = false; + public boolean C = false; + public boolean V = false; + + // Default test-runtime entry points; callers can supply another link convention. + private final int _calloc, _write; + public EvalArm64(byte[] buf, int stackSize) { this(buf,stackSize,-4,-8); } + public EvalArm64(byte[] buf, int stackSize, int calloc, int write) { + _calloc = calloc; + _write = write; + _buf = buf; + regs = new long[32]; + fregs = new double[32]; + // Stack grows down, heap grows up + regs[31] = _heap = stackSize; + _pc = 0; + } + + // Little endian + public int ld1s(int x) { return _buf[x ] ; } + public int ld1z(int x) { return _buf[x ]&0xFF; } + public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; } + public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; } + public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; } + public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; } + + + public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); } + public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); } + + public void st1(int x, int val) { _buf[x] = (byte)val; } + public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); } + public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); } + public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); } + + public int step(int maxops) { + int trap = 0; + long rval = 0; + double frval = 0; + int pc = _pc; + int cycle = _cycle; + outer: + for(int icount = 0; icount < maxops; icount++) { + boolean is_f = false; + int ir = 0; + rval = 0; + frval = 0; + cycle++; + if( pc >= _buf.length ) { + trap = 1 + 1; // Handle access violation on instruction read. + break; + } + if( (pc & 3)!=0 ) { + trap = 1; // Handle PC-misaligned access + break; + } + + // Load instruction from image buffer at PC + ir = ld4s( pc ); + + int rdid = ir & 0x1f; + // [31:24] + int opcode1 = (ir >> 24) & 0xFF; + + // for the opcode we just match for bits[31:24]; + switch( opcode1 ) { + case 0x14, 0x15, 0x16, 0x17: { //b + int imm26 = ir << 6 >> 6; + pc = pc + (imm26 * 4) - 4; + rdid = -1; + break; + } + case 0x1E: { // floats + is_f = true; + int rs1 = (ir >> 5) & 0x1F; + int rs2 = (ir >> 16) & 0x1F; + + int op = (ir >> 10) & 0x3F; + switch(op) { + case 16: frval = fregs[rs1]; break; // fmov + case 10: frval = fregs[rs1] + fregs[rs2]; break; // fadd + + case 6: if (fregs[rs2] == 0) frval = 0; else frval = fregs[rs1] / fregs[rs2]; break; // fdiv + case 8: { + // fcmp + double lhs = fregs[rs1]; + double rhs = fregs[rs2]; + + Z = (lhs == rhs); + N = (lhs < rhs); + C = (lhs > rhs); + V = Double.isNaN(lhs) || Double.isNaN(rhs); + rdid = -1; + break; + } + case 2: frval = fregs[rs1] * fregs[rs2]; break; // fmul + case 14: frval = fregs[rs1] - fregs[rs2]; break; // fsub + default: trap = (2+1); + } + break; + } + + case 0x38: { + // STRB/LDRB(register) + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch( ir >> 21 & 0x7 ) { + case 0x1 -> { // store + if( opcode1 == Arm64.OP_STORE_R_8 ) st1((int)regs[rn] + (int)regs[rm], (int)regs[rdid]); + else trap = (2+1); + rdid = -1; + } + case 0x3 -> { // load + if( opcode1 == Arm64.OP_LOAD_R_8 ) rval = ld1s((int)regs[rn] + (int)regs[rm]); + else trap = (2+1); + } + default -> trap = (2+1); + } + break; + } + case 0x39: { + // STRB/LDRB(immediate) + // str/ldr(immediate) + // rn + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OP_STORE_IMM_8) { + st1((int)rval, (int)regs[rdid]); + } else trap = (2+1); + rdid = -1; + + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OP_LOAD_IMM_8) { + rval = ld1s((int)rval); + } else trap = (2+1); + rdid = -1; + break; + } + default: trap = (2+1); + } + break; + } + case 0x54: { + // b.cond + int imm19 = ir << 8 >> 13; + imm19 = pc + (imm19 * 4) - 4; + rdid = -1; + + switch(ir & 0xF) { + case 0x0: if(Z) pc = imm19; break; // eq + case 0x1: if(!Z) pc = imm19; break; // ne + case 0x2: if(C) pc = imm19; break; // cs + case 0x3: if(!C) pc = imm19; break; + case 0x4: if(N) pc = imm19; break; // mi + case 0x5: if(!N) pc = imm19; break; // pl + case 0x6: if(V) pc = imm19; break; // vs + case 0x7: if(!V) pc = imm19; break; // vc + case 0x8: if(C && !Z) pc = imm19; break; // hi + case 0x9: if(!C || Z) pc = imm19; break; // ls + case 0xA: if(N == V) pc = imm19; break; // ge + case 0xB: if(N != V) pc = imm19; break; // lt + case 0xC: if(!Z && N == V) pc = imm19; break; // gt + case 0xD: if(Z || N != V) pc = imm19; break; // le + case 0xE: pc = imm19; break; // always executed(al) + default: trap = (2+1); + } + break; + } + + case 0x5c: { + // ldr + is_f = true; + // address = delta + int address = ir << 8 >> 13; + address *= 4; + if(address >= _buf.length-3) trap = (5 + 1); + frval = ld8f(pc + address); + break; + } + + case 0x78: { + // STRH/LDRH(register) + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + // store + if(opcode1 == Arm64.OP_STORE_R_16) { + st1((int)regs[rn] + (int)regs[rm], (int)regs[rdid]); + }else { + trap = (2+1); + } + rdid = -1; + break; + } + case 0x3: { + // load + if(opcode1 == Arm64.OP_LOAD_R_16) { + rval = ld1s((int)regs[rn] + (int)regs[rm]); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + break; + } + + case 0x79: { + // STRH/LDRH(immediate) + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + imm *= 2; + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OP_STORE_IMM_16) { + st2((int)rval, (int)regs[rdid]); + } else trap = (2+1); + rdid = -1; + + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OP_LOAD_IMM_16) { + rval = ld2s((int)rval); + } else trap = (2+1); + rdid = -1; + break; + } + default: trap = (2+1); + } + break; + } + + case 0x8A: { // AND + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + rval = regs[rn] & regs[rm]; + break; + } + + case 0x8B, 0xCB: { + // add/sub(register), unshifted form emitted by Simple + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + long lhs = regs[rn]; + long rhs = regs[rm]; + rval = opcode1==0x8B ? lhs + rhs : lhs - rhs; + break; + } + + case 0x90, 0xB0, 0xD0, 0xF0: { // adrp + int immhi = ( ir << 8 ) >>> (8+5); + int immlo = (ir>>29)& 3; + int imm = ((immhi<<2) | immlo); + imm = (imm << 11) >> 11; + imm <<= 12; + rval = (pc & ~0xFFF) + imm; + break; + } + + case 0x91, 0xD1: { // ADD/SUB (immediate), unsigned imm12, optional LSL #12 + int rn = (ir >> 5) & 0x1F; + long immediate = (ir >>> 10) & 0xFFF; + if( (ir & (1<<22))!=0 ) immediate <<= 12; + rval = opcode1==0x91 ? regs[rn] + immediate : regs[rn] - immediate; + break; + } + case 0x92: { // MOVN or AND (immediate) + if( (ir & 0x00800000)!=0 ) { + int shift = ((ir >> 21) & 3)*16; + rval = ~(((ir >> 5) & 0xFFFFL) << shift); + break; + } + int imm12 = (ir >> 10) & 0x1FFF; + long immediate = Arm64.decodeImm12(imm12); + int rn = (ir >> 5) & 0x1F; + rval = regs[rn] & immediate; + break; + } + 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 0x94, 0x95, 0x96, 0x97: { // bl + rdid = -1; // BL writes only X30, not a displacement-selected register. + rval = pc + 4; + regs[30] = rval; + int imm26 = (ir & 0x03FFFFFF); + int imm = (imm26 << 6) >> 6; + pc = pc + (imm << 2) ; + if( pc == _calloc ) { + assert (_heap&7) == 0; // 8-byte aligned + long size = regs[0]*regs[1]; + size = (size+7) & -8; // 8-byte aligned + regs[0] = _heap; + _heap += (int)size; + pc = (int)rval; + } + if( pc == _write ) { + ByteArrayOutputStream baos = switch((int)regs[0]) { + case 1 -> _stdout==null ? (_stdout = new ByteArrayOutputStream()) : _stdout; + case 2 -> _stderr==null ? (_stderr = new ByteArrayOutputStream()) : _stderr; + default -> throw new IllegalArgumentException(); + }; + baos.write(_buf,(int)regs[1],(int)regs[2]); + regs[0] = regs[2]; + pc = (int)rval; + } + pc -= 4; + rdid = -1; + break; + } + + case 0x9A: { + int encodedCond = (ir >> 12) & 0xF; + int decodedCond = encodedCond ^ 1; + // conditional select(csel) + boolean cond = switch (decodedCond) { + case 0x0 -> Z ; // eq + case 0x1 -> !Z ; // ne + case 0x2 -> C ; // cs + case 0x3 -> !C ; // cc + case 0x4 -> N ; // mi + case 0x5 -> !N ; // pl + case 0x6 -> V ; // vs + case 0x7 -> !V ; // vc + case 0x8 -> ( C && !Z ) ; // hi + case 0x9 -> (!C || Z ) ; // ls + case 0xA -> ( N == V) ; // ge + case 0xB -> ( N != V) ; // lt + case 0xC -> (!Z && N == V) ; // gt + case 0xD -> ( Z || N != V ); // le + case 0xE -> true ; // always + case 0xF -> false ; // never + default -> throw new UnsupportedOperationException(); + }; + if( cond ) rval = 1; + break; + } + case 0x9B: { // mul + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + rval = regs[rn] * regs[rm]; + break; + } + case 0x9E: { + // scvtf + is_f = true; + int rs1 = (ir >> 5) & 0x1F; + frval = (double)regs[rs1]; + break; + } + + case 0xAA: { + // MOV(register) + int rm = (ir >> 16) & 0x1F; + rval = regs[rm]; + break; + } + + case 0xB8: { + // LDR/STR (register + index) 32 bits + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + if(opcode1 == Arm64.OP_STORE_R_32) { + st4((int)regs[rn] + (int)regs[rm], (int)regs[rdid]); + }else { + trap = (2+1); + } + // store(expected to store the length here) + + rdid = -1; + break; + } + case 0x3: { + // load + if( opcode1 == Arm64.OP_LOAD_R_32) { + rval = ld4s((int) (regs[rn] + regs[rm])); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + + break; + } + + case 0xB9: { + // str/ldr(immediate) 32 bits + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + imm *= 4; + + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + + opcode1 = (ir >> 22) & 0x3FF; + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OP_STORE_IMM_32) { + st4((int)rval, (int)regs[rdid]); + } else trap = (2+1); + + rdid = -1; + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OP_LOAD_IMM_32) { + rval = ld4s((int) rval); + } else trap = (2+1); + break; + } + default: trap = (2+1); + } + break; + } + + case 0xBC: { + // LDR/STR (register + index) float 32 bits + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + is_f = true; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + if(opcode1 == Arm64.OPF_STORE_R_32) { + st4((int)regs[rn] + (int)regs[rm], Float.floatToRawIntBits((float)fregs[rdid])); + }else { + trap = (2+1); + } + // store(expected to store the length here) + + rdid = -1; + break; + } + case 0x3: { + // load + if(opcode1 == Arm64.OPF_LOAD_R_32) { + frval = ld4f((int) (regs[rn] + regs[rm])); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + + break; + } + + case 0xBD: { + // LDR/STR(immediate) 32 bits floats + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + + imm *= 4; + is_f = true; + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OPF_STORE_IMM_32) { + st4((int)rval, Float.floatToRawIntBits((float)fregs[rdid])); + } else trap = (2+1); + + rdid = -1; + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OPF_LOAD_IMM_32) { + frval = ld4f((int)rval); + } else trap = (2+1); + break; + } + default: trap = (2+1); + } + break; + } + + case 0xD2: // MOVZ + case 0xF2: { // MOVK preserves the other halfwords + // movz + // get immediate + int shift = ((ir >> 21) & 3)*16; + rval = ((ir >> 5) & 0xFFFFL) << shift; + if( opcode1==0xF2 ) rval |= regs[rdid] & ~(0xFFFFL << shift); + break; + } + + case 0xEB: { + // subs(shifted register) + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + long lhs = regs[rn]; + long rhs = regs[rm]; + rval = lhs - rhs; + + // set flags as a side effect + Z = (rval == 0); + N = (rval < 0); + C = lhs >= rhs; + + boolean sign_lhs = (lhs >>> 63) != 0; + boolean sign_rhs = (rhs >>> 63) != 0; + boolean sign_res = (rval >>> 63) != 0; + V = (sign_lhs != sign_rhs) && (sign_res != sign_lhs); + rdid = -1; + break; + } + + case 0xD6: { + int op = ir & 0xFFFFFC1F; + int rn = (ir >>> 5) & 31; + int target = (int)regs[rn]; // Read before BLR overwrites X30. + rdid = -1; + if( op==0xD63F0000 ) regs[30] = pc+4; // BLR + else if( op!=0xD65F0000 ) { trap = 3; break; } // RET + pc = target; + if( op==0xD65F0000 && pc==0 ) break outer; + pc -= 4; + break; + } + + case 0xF1: { + // subs(immediate) + int rn = (ir >> 5) & 0x1F; + int immediate = ir << 10 >> 20; + long lhs = regs[rn]; + + rval = lhs - immediate; + + // set flags as a side effect + Z = (rval == 0); + N = (rval < 0); + C = lhs >= immediate; + + boolean sign_lhs = (lhs >>> 63) != 0; + boolean sign_rhs = (immediate >>> 63) != 0; + boolean sign_res = (rval >>> 63) != 0; + V = (sign_lhs != sign_rhs) && (sign_res != sign_lhs); + rdid = -1; + break; + } + case 0xF8: { + // LDR/STR (register + index) 64 bits + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + if(opcode1 == Arm64.OP_STORE_R_64) { + st8((int)regs[rn] + (int)regs[rm], regs[rdid]); + }else { + trap = (2+1); + } + // store(expected to store the length here) + + rdid = -1; + break; + } + case 0x3: { + // load + if(opcode1 == Arm64.OP_LOAD_R_64) { + rval = ld8((int) (regs[rn] + regs[rm])); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + + break; + } + case 0xF9: { + // str/ldr(immediate) 64 bits + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + + imm *= 8; + + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OP_STORE_IMM_64) { + st8((int)rval, regs[rdid]); + } else trap = (2+1); + + rdid = -1; + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OP_LOAD_IMM_64) { + rval = ld8((int)rval); + } else trap = (2+1); + break; + } + default: trap = (2+1); + } + break; + } + + case 0xFC: { + // LDR/STR (register + index) float 64 bits + int opc = ir >> 21 & 0x7; + int rn = (ir >> 5) & 0x1F; + int rm = (ir >> 16) & 0x1F; + is_f = true; + // full opcode + opcode1 = (ir >> 21) & 0x7FF; + switch(opc) { + case 0x1: { + if(opcode1 == Arm64.OPF_STORE_R_64) { + st8((int)regs[rn] + (int)regs[rm], Double.doubleToRawLongBits(fregs[rdid])); + }else { + trap = (2+1); + } + // store(expected to store the length here) + + rdid = -1; + break; + } + case 0x3: { + // load + if(opcode1 == Arm64.OPF_LOAD_R_64) { + frval = ld8f((int) (regs[rn] + regs[rm])); + } else { + trap = (2+1); + } + break; + } + default: trap = (2+1); + } + + break; + } + case 0xFD: { + // LDR/STR(immediate) 64 bits floats + int base = ir >> 5 & 0x1F; + int imm = (ir >> 10) & 0xFFF; + + imm *= 8; + is_f = true; + rval = regs[base] + imm; + int opc = (ir >> 22) & 0x3; + // full opcode + opcode1 = (ir >> 22) & 0x3FF; + switch(opc) { + case 0x0: { + // store + if(opcode1 == Arm64.OPF_STORE_IMM_64) { + st8((int)rval, Double.doubleToRawLongBits(fregs[rdid])); + } else trap = (2+1); + + rdid = -1; + break; + } + case 0x1: { + // LDR (immediate) + if(opcode1 == Arm64.OPF_LOAD_IMM_64) { + frval = ld8f((int)rval); + } else trap = (2+1); + break; + } + default: trap = (2+1); + } + break; + } + + default: trap = (2+1); + } + + if(trap != 0) { + _pc = pc; + break; + } + if(rdid != -1) { + if(is_f) fregs[rdid] = frval; + else regs[rdid] = rval; + } + + pc += 4; + } + // Handle traps and interrupts. + if(trap != 0) + return trap; + _cycle = cycle; + _pc = pc; + return 0; + } + + +} diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java new file mode 100644 index 00000000..f618075d --- /dev/null +++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java @@ -0,0 +1,370 @@ + package com.seaofnodes.isa.eval; + +/** Simple RISC5 CPU Emulator + * + * Original works credit Charles Lohr. + * This is a complete rewrite in Java. + * + * Load code into memory and go! + */ + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; + +public class EvalRisc5 { + + // Memory image, always 0-based + public final byte[] _buf; + + // GPRs + public final long[] regs; + // FRs + public final double[] fregs; + // PC + public int _pc; + + // Start of free memory for allocation + public int _heap; + + // Standard out and err streams + public ByteArrayOutputStream _stdout, _stderr; + + // Cycle counters + public int _cycle; + + // Default test-runtime entry points; callers can supply another link convention. + private final int _calloc, _write; + public EvalRisc5(byte[] buf, int stackSize) { this(buf,stackSize,-4,-8); } + public EvalRisc5(byte[] buf, int stackSize, int calloc, int write) { + _calloc = calloc; + _write = write; + _buf = buf; + regs = new long[32]; + fregs = new double[32]; + // Stack grows down, heap grows up + regs[2] = _heap = stackSize; + _pc = 0; + } + + // Little endian + public int ld1s(int x) { return _buf[x ] ; } + public int ld1z(int x) { return _buf[x ]&0xFF; } + public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; } + public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; } + public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; } + public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; } + + public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); } + public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); } + + + public void st1(int x, int val) { _buf[x] = (byte)val; } + public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); } + public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); } + public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); } + + // Note: only a few bits are used. (Machine = 3, User = 0) + // Bits 0..1 = privilege. + // Bit 2 = WFI (Wait for interrupt) + // Bit 3+ = Load/Store reservation LSBs. + public int _extraflags; + + public int step( int maxops ) { + int trap = 0; + long rval = 0; + double frval = 0; + int pc = _pc; + int cycle = _cycle; + boolean is_f = false; + outer: + for( int icount = 0; icount < maxops; icount++ ) { + int ir = 0; + rval = 0; + cycle++; + + if( pc >= _buf.length ) { + trap = 1 + 1; // Handle access violation on instruction read. + break; + } + if( (pc & 3)!=0 ) { + trap = 1; // Handle PC-misaligned access + break; + } + // Load instruction from image buffer at PC + ir = ld4s( pc ); + int rdid = (ir >> 7) & 0x1f; + + switch( ir & 0x7f ) { + case 0x37: // LUI (0b0110111) + rval = ( ir & 0xfffff000 ); + break; + case 0x17: // AUIPC (0b0010111) + rval = pc + ( ir & 0xfffff000 ); + break; + case 0x6F: { // JAL (0b1101111) + int reladdy = ((ir & 0x80000000)>>11) | ((ir & 0x7fe00000)>>20) | ((ir & 0x00100000)>>9) | ((ir&0x000ff000)); + if( (reladdy & 0x00100000)!=0 ) reladdy |= 0xffe00000; // Sign extension. + rval = pc + 4; + pc = pc + reladdy - 4; + if( pc+4 == _write ) { + ByteArrayOutputStream baos = switch((int)regs[10]) { + case 1 -> _stdout==null ? (_stdout = new ByteArrayOutputStream()) : _stdout; + case 2 -> _stderr==null ? (_stderr = new ByteArrayOutputStream()) : _stderr; + default -> throw new IllegalArgumentException(); + }; + baos.write(_buf,(int)regs[11],(int)regs[12]); + regs[10] = regs[12]; + rdid = 0; + pc = (int)(rval - 4); // Unwind PC, as-if returned from write + } + break; + } + case 0x67: { // JALR (0b1100111) + int imm_se = ir >> 20; // Sign extend 12 bits + rval = pc + 4; + pc = ((int)regs[ (ir >> 15) & 0x1f ] + imm_se) & ~1; + // Return from top-level ; exit sim + if( pc==0 ) { + if( rdid!=0 ) regs[rdid] = rval; // Write PC into register before exit + break outer; + } + // Inline CALLOC effect + if( pc == _calloc ) { + assert (_heap&7) == 0; // 8-byte aligned + int size = (int)(regs[10]*regs[11]); + size = (size+7) & -8; // 8-byte aligned + regs[10] = _heap; // Return next free address + _heap += size; + pc = (int)(rval - 4); // Unwind PC, as-if returned from calloc + } else + pc -= 4; + break; + } + case 0x63: { // Branch (0b1100011) + int immm4 = ((ir & 0xf00)>>7) | ((ir & 0x7e000000)>>20) | ((ir & 0x80) << 4) | ((ir >> 31)<<12); + if( (immm4 & 0x1000)!=0 ) immm4 |= 0xffffe000; + long rs1 = regs[(ir >> 15) & 0x1f]; + long rs2 = regs[(ir >> 20) & 0x1f]; + immm4 = pc + immm4 - 4; + rdid = 0; + if( switch( (ir >> 12) & 0x7 ) { + case 0 -> rs1 == rs2; + case 1 -> rs1 != rs2; + case 4 -> rs1 < rs2; + case 5 -> rs1 >= rs2; + case 6 -> Long.compareUnsigned(rs1,rs2) < 0; //BLTU + case 7 -> Long.compareUnsigned(rs1,rs2) >= 0; //BGEU + default -> { trap = (2+1); yield false; } + } ) pc = immm4; + break; + } + case 0x03: { // Load (0b0000011) + int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b + int imm = ir >> 20; + int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 ); + int rsval = rs1 + imm_se; + + if( rsval >= _buf.length-3 ) { + trap = (5+1); + rval = rsval; + } else { + switch( ( ir >> 12 ) & 0x7 ) { + //LB, LH, LW, LBU, LHU + case 0: rval = ld1s( rsval ); break; + case 1: rval = ld2s( rsval ); break; + case 2: rval = ld4s( rsval ); break; + case 3: rval = ld8 ( rsval ); break; + case 4: rval = ld1z( rsval ); break; + case 5: rval = ld2z( rsval ); break; + case 6: rval = ld4s( rsval ); break; + default: trap = (2+1); + } + } + break; + } + // floats + case 0x7: { + is_f = true; + int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b + int imm = ir >> 20; + int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 ); + int rsval = rs1 + imm_se; + + if( rsval >= _buf.length-3 ) { + trap = (5+1); + rval = rsval; + } else { + switch( ( ir >> 12 ) & 0x7 ) { + case 2: frval = ld4f( rsval ); break; + case 3: frval = ld8f( rsval ); break; + default: trap = (2+1); + } + } + break; + } + + case 0x23: { // Store 0b0100011 + int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b + long rs2 = regs[(ir >> 20) & 0x1f]; + int addy = ( ( ir >> 7 ) & 0x1f ) | ( ( ir & 0xfe000000 ) >> 20 ); + if( (addy & 0x800)!=0 ) addy |= 0xfffff000; + addy += rs1; + rdid = 0; + if( addy >= _buf.length-3 ) { + trap = (7+1); // Store access fault. + rval = addy; + } else { + switch( ( ir >> 12 ) & 0x7 ) { //SB, SH, SW + case 0: st1( addy, (int)rs2 ); break; + case 1: st2( addy, (int)rs2 ); break; + case 2: st4( addy, (int)rs2 ); break; + case 3: st8( addy, rs2 ); break; + default: trap = (2+1); + } + } + break; + } + case 0x13: // Op-immediate 0b0010011 + case 0x33: { // Op 0b0110011 + int imm = ir >> 20; + imm = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0); + long rs1 = regs[(ir >> 15) & 0x1f]; + boolean is_reg = (ir & 0x20)!=0; + long rs2 = is_reg ? regs[imm & 0x1f] : imm; + + // Insert the detection here + int rs1id = (ir >> 15) & 0x1f; + int funct3 = (ir >> 12) & 0x7; + + if( is_reg && ( ir & 0x02000000 )!=0 ) { + switch( (ir>>12)&7 ) { //0x02000000 = RV32M + case 0: rval = rs1 * rs2; break; // MUL + case 1: rval = (int)((long)((int)rs1) * (long)((int)rs2)) >> 32; break; // MULH + case 2: rval = (int)((long)((int)rs1) * (long)rs2) >> 32; break; // MULHSU + case 3: rval = (int)((long)rs1 * (long)rs2) >> 32; break; // MULHU + case 4: if( rs2 == 0 ) rval = -1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? rs1 : ((int)rs1 / (int)rs2); break; // DIV + case 5: if( rs2 == 0 ) rval = 0xffffffff; else rval = rs1 / rs2; break; // DIVU + case 6: if( rs2 == 0 ) rval = rs1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? 0 : ((int)((int)rs1 % (int)rs2)); break; // REM + case 7: if( rs2 == 0 ) rval = rs1; else rval = rs1 % rs2; break; // REMU + } + } else { + rval = switch( (ir >> 12) & 7 ) { // These could be either op-immediate or op commands. Be careful. + case 0 -> (is_reg && (ir & 0x40000000) != 0) ? (rs1 - rs2) : (rs1 + rs2); + case 1 -> rs1 << (rs2 & 0x3F); + case 2 -> (rs1 < rs2) ? 1 : 0; + case 3 -> (rs1 < rs2) ? 1 : 0; + case 4 -> rs1 ^ rs2; + case 5 -> (ir & 0x40000000) != 0 ? (rs1 >> (rs2 & 0x3F)) : (rs1 >>> (rs2 & 0x3F)); + case 6 -> rs1 | rs2; + case 7 -> rs1 & rs2; + default -> rval; + }; + } + break; + } + case 0x0f: // 0b0001111 + rdid = 0; // fencetype = (ir >> 12) & 0b111; We ignore fences in this impl. + break; + case 0x73: // Zifencei+Zicsr (0b1110011) + trap = (2+1); // Note micrrop 0b100 == undefined. + break; + + case 0x2f: { // RV32A (0b00101111) + long rs1 = regs[(ir >> 15) & 0x1f]; + long rs2 = regs[(ir >> 20) & 0x1f]; + int irmid = ( ir>>27 ) & 0x1f; + + // We don't implement load/store from UART or CLNT with RV32A here. + if( rs1 >= _buf.length-3 ) { + trap = (7+1); //Store/AMO access fault + rval = rs1; + } else { + rval = ld4s( (int)rs1 ); + + // Referenced a little bit of https://github.com/franzflasch/riscv_em/blob/master/src/core/core.c + boolean dowrite = true; + switch( irmid ) { + case 2: //LR.W (0b00010) + dowrite = false; + _extraflags = (_extraflags & 0x07) | (int)(rs1<<3); + break; + case 3: { //SC.W (0b00011) (Make sure we have a slot, and, it's valid) + boolean xflag = _extraflags >> 3 != ( rs1 & 0x1fffffff ); + rval = xflag ? 1 : 0; // Validate that our reservation slot is OK. + dowrite = !xflag; // Only write if slot is valid. + break; + } + case 0: rs2 += rval; break; // AMOADD.W (0b00000) + case 1: break; // AMOSWAP.W (0b00001) + case 4: rs2 ^= rval; break; // AMOXOR.W (0b00100) + case 8: rs2 |= rval; break; // AMOOR.W (0b01000) + case 12: rs2 &= rval; break; // AMOAND.W (0b01100) + case 16: rs2 = Math.min( rs2, rval ); break; //AMOMIN.W (0b10000) + case 20: rs2 = Math.max( rs2, rval ); break; //AMOMAX.W (0b10100) + case 24: rs2 = Math.min( rs2, rval ); break; //AMOMINU.W (0b11000) + case 28: rs2 = Math.max( rs2, rval ); break; //AMOMAXU.W (0b11100) + default: trap = (2+1); dowrite = false; break; //Not supported. + } + if( dowrite ) st4( (int)rs1, (int)rs2 ); + } + break; + } + + case 0x53: { + is_f = true; + int rs1 = (ir >> 15) & 0x1F; + int rs2 = (ir >> 20) & 0x1F; + double lhs = fregs[rs1]; + double rhs = fregs[rs2]; + int func5 = (ir >> 27) & 0x1F; + int func2 = (ir >> 25) & 0x3; + if( func2 != 1 ) throw new UnsupportedOperationException(); // 1==double + switch( func5 ) { + case 0: frval = lhs + rhs; break; // fadd.d + case 1: frval = lhs - rhs; break; // fsub.d + case 2: frval = lhs * rhs; break; // fmul.d + case 3: frval = lhs / rhs; break; // fdiv.d + case 5: frval = Math.min(lhs,rhs); break; // fmin.d, also used for move + case 20: + is_f = false; // Output into a GPR + rval = switch( (ir >> 12) & 7 ) { + case 0 -> lhs <= rhs ? 1 : 0; + case 1 -> lhs == rhs ? 1 : 0; + case 2 -> lhs < rhs ? 1 : 0; + default -> throw new UnsupportedOperationException(); + }; + break; + case 26: frval = (double)regs[rs1]; break; // fcvt.w.d + default: + trap = (2+1); // Fault: Invalid opcode. + throw new UnsupportedOperationException(); + } + break; + } + + default: trap = (2+1); // Fault: Invalid opcode. + } + + // If there was a trap, do NOT allow register writeback. + if( trap!=0 ) { + _pc = pc; + break; + } + + if( rdid!=0 || is_f) { + if(is_f) {fregs[rdid] = frval; is_f = false;} // Write back register. + else regs[rdid] = rval; // Write back register. + } + + pc += 4; + } + + // Handle traps and interrupts. + if( trap!=0 ) + return trap; + + _cycle = cycle; + _pc = pc; + return 0; + } +} diff --git a/isa/src/test/java/com/seaofnodes/isa/RiscTest.java b/isa/src/test/java/com/seaofnodes/isa/RiscTest.java new file mode 100644 index 00000000..e26eb20b --- /dev/null +++ b/isa/src/test/java/com/seaofnodes/isa/RiscTest.java @@ -0,0 +1,59 @@ +package com.seaofnodes.isa; + +import com.seaofnodes.isa.eval.EvalArm64; +import com.seaofnodes.isa.eval.EvalRisc5; + +/** Fixed instruction words plus execution checks of the shared test emulators. */ +public class RiscTest { + static void word(int expected, int actual) { + assert expected==actual : String.format("Expected %08x, got %08x",expected,actual); + } + public static void main(String[] args) { + word(0x8B020020,Arm64.r_reg(Arm64.OP_ADD,0,2,0,1,0)); // add x0,x1,x2 + word(0x9100A420,Arm64.imm_inst(Arm64.OPI_ADD,41,1,0)); + word(0xD2800540,Arm64.mov(Arm64.OP_MOVZ,0,42,0)); + word(0xF9400BE0,Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64,16,31,0,8)); + word(0xF9000BE0,Arm64.load_str_imm(Arm64.OP_STORE_IMM_64,16,31,0,8)); + word(0xD65F03C0,Arm64.ret(Arm64.OP_RET)); + word(0x17FFFFFF,Arm64.b(Arm64.OP_UJMP,-4)); + word(0x16000000,Arm64.b(Arm64.OP_UJMP,-(1<<27))); + word(0x54000040,Arm64.b_cond(Arm64.OP_BRANCH,8,Arm64.COND.EQ)); + word(0x54800001,Arm64.b_cond(Arm64.OP_BRANCH,-(1<<20),Arm64.COND.NE)); + word(0x5CFFFFE0,Arm64.load_pc(Arm64.OPF_ARM,-4,0)); + + word(0x002081B3,RiscV.r_type(RiscV.OP,3,0,1,2,0)); // add x3,x1,x2 + word(0x02A00513,RiscV.i_type(RiscV.OP_IMM,10,0,0,42)); + word(0x123451B7,RiscV.u_type(RiscV.OP_LUI,3,0x12345)); + word(0x00B13823,RiscV.s_type(RiscV.OP_STORE,3,2,11,16)); + word(0x00208463,RiscV.b_type(RiscV.OP_BRANCH,0,(short)1,(short)2,8)); + word(0xFE208EE3,RiscV.b_type(RiscV.OP_BRANCH,0,(short)1,(short)2,-4)); + word(0x008000EF,RiscV.j_type(RiscV.OP_JAL,1,8)); + word(0xFFDFF0EF,RiscV.j_type(RiscV.OP_JAL,1,-4)); + word(0x000800EF,RiscV.j_type(RiscV.OP_JAL,1,1<<19)); + word(0x800000EF,RiscV.j_type(RiscV.OP_JAL,1,-(1<<20))); + + // Constants exercise MOVN, MOVZ and multiple MOVK lanes, including all 0/1. + for( long x : new long[]{0,-1,42,0xFFFF0000FFFFL,0x1122334455667788L,Long.MIN_VALUE} ) { + var bytes=new X86Test.Bytes(); + Arm64.constant(bytes,0,x); + byte[] image=new byte[1024]; + System.arraycopy(bytes.toByteArray(),0,image,4,bytes.size()); + var arm=new EvalArm64(image,512); + arm._pc=4; + arm.step(bytes.size()/4); + assert arm.regs[0]==x : String.format("Constant %016x became %016x",x,arm.regs[0]); + } + // Raw instructions make these emulator checks independent of the encoder. + var arm=new EvalArm64(new byte[1024],512); + arm.st4(4,0xD2800540); // mov x0,#42 + arm._pc=4; + arm.step(1); + assert arm.regs[0]==42; + var risc=new EvalRisc5(new byte[1024],512); + risc.st4(4,0x02A00513); // addi a0,zero,42 + risc._pc=4; + risc.step(1); + assert risc.regs[10]==42; + System.out.println("Shared ARM/RISC-V encoding and evaluator tests passed"); + } +} diff --git a/isa/src/test/java/com/seaofnodes/isa/X86Test.java b/isa/src/test/java/com/seaofnodes/isa/X86Test.java new file mode 100644 index 00000000..e3aa2699 --- /dev/null +++ b/isa/src/test/java/com/seaofnodes/isa/X86Test.java @@ -0,0 +1,124 @@ +package com.seaofnodes.isa; + +import java.io.ByteArrayOutputStream; +import java.util.HexFormat; +import java.util.function.Consumer; + +/** Fixed instruction bytes, independent of the compiler and register allocator. */ +public class X86Test { + static class Bytes extends ByteArrayOutputStream implements CodeSink { + @Override public Bytes add1(int value) { write(value); return this; } + } + static void check(String expected, Consumer<CodeSink> emit) { + var bytes=new Bytes(); + emit.accept(bytes); + String actual=HexFormat.of().formatHex(bytes.toByteArray()); + assert expected.equals(actual) : "Expected "+expected+", got "+actual; + } + public static void main(String[] args) { + check("4d03ca", b -> X86.reg(b,0x03,9,10)); + check("4d0fafca", b -> X86.reg(b,0x0FAF,9,10)); + check("4981c17fffffff", b -> X86.imm(b,0x81,0,9,-129)); + check("4983c180", b -> X86.imm(b,0x81,0,9,-128)); + check("4983c17f", b -> X86.imm(b,0x81,0,9,127)); + check("4981c180000000", b -> X86.imm(b,0x81,0,9,128)); + check("4d6bca80", b -> X86.imul(b,9,10,-128)); + check("4d69ca80000000", b -> X86.imul(b,9,10,128)); + + // TEST; SETZ; MOVZX. Both overlapping and distinct input/output registers. + int[][] regs={{0,0},{1,1},{7,7},{9,9},{1,9},{9,1},{6,0},{15,8}}; + String[] not={"4885c00f94c00fb6c0","4885c90f94c10fb6c9", + "4885ff400f94c7400fb6ff","4d85c9410f94c1450fb6c9", + "4d85c90f94c10fb6c9","4885c9410f94c1450fb6c9", + "4885c0400f94c6400fb6f6","4d85c0410f94c7450fb6ff"}; + for( int i=0; i<regs.length; i++ ) { + int dst=regs[i][0], src=regs[i][1]; + check(not[i],b -> X86.not(b,dst,src)); + } + + // SIB, rbp/r13 zero displacement, displacement boundaries, and an extended index. + check("488b0424", b -> X86.load(b,3,false,false,0,4,-1,0,0)); + check("498b0424", b -> X86.load(b,3,false,false,0,12,-1,0,0)); + check("488b4500", b -> X86.load(b,3,false,false,0,5,-1,0,0)); + check("498b4500", b -> X86.load(b,3,false,false,0,13,-1,0,0)); + check("488b437f", b -> X86.load(b,3,false,false,0,3,-1,127,0)); + check("488b8380000000", b -> X86.load(b,3,false,false,0,3,-1,128,0)); + check("488b4380", b -> X86.load(b,3,false,false,0,3,-1,-128,0)); + check("488b837fffffff", b -> X86.load(b,3,false,false,0,3,-1,-129,0)); + check("4b8b44e508", b -> X86.load(b,3,false,false,0,13,12,8,3)); + check("4f8d4ca508", b -> X86.lea(b,9,13,12,8,2)); + check("4e8d0ce520000000", b -> X86.lea(b,9,-1,12,32,3)); + + check("480fbe03", b -> X86.load(b,0,true,false,0,3,-1,0,0)); + check("480fb603", b -> X86.load(b,0,false,false,0,3,-1,0,0)); + check("480fbf03", b -> X86.load(b,1,true,false,0,3,-1,0,0)); + check("480fb703", b -> X86.load(b,1,false,false,0,3,-1,0,0)); + check("486303", b -> X86.load(b,2,true,false,0,3,-1,0,0)); + check("8b03", b -> X86.load(b,2,false,false,0,3,-1,0,0)); + check("f3470f104ca508", b -> X86.load(b,2,false,true,9,13,12,8,2)); + check("f2470f104ca508", b -> X86.load(b,3,false,true,9,13,12,8,2)); + + check("408823", b -> X86.store(b,0,false,4,3,-1,0,0)); // spl, not ah + check("408833", b -> X86.store(b,0,false,6,3,-1,0,0)); // sil, not dh + check("44883b", b -> X86.store(b,0,false,15,3,-1,0,0)); + check("6647894ca508", b -> X86.store(b,1,false,9,13,12,8,2)); + check("47894ca508", b -> X86.store(b,2,false,9,13,12,8,2)); + check("4f894ca508", b -> X86.store(b,3,false,9,13,12,8,2)); + check("f3470f114ca508", b -> X86.store(b,2,true,9,13,12,8,2)); + check("f2470f114ca508", b -> X86.store(b,3,true,9,13,12,8,2)); + check("c603ff", b -> X86.storeImm(b,0,-1,3,-1,0,0)); + check("6643c744a5083412", b -> X86.storeImm(b,1,0x1234,13,12,8,2)); + check("c70378563412", b -> X86.storeImm(b,2,0x12345678,3,-1,0,0)); + check("48c703ffffffff", b -> X86.storeImm(b,3,-1,3,-1,0,0)); + check("803bff", b -> X86.cmpImm(b,0,-1,3,-1,0,0)); + check("6643817ca5083412", b -> X86.cmpImm(b,1,0x1234,13,12,8,2)); + check("813b78563412", b -> X86.cmpImm(b,2,0x12345678,3,-1,0,0)); + check("48813b15cd5b07", b -> X86.cmpImm(b,3,123456789,3,-1,0,0)); + check("49f7da", b -> X86.unary(b,0xF7,3,10)); + check("49c1e1ff", b -> X86.shift(b,4,9,255)); + check("489949f7fa", b -> X86.divide(b,10)); + check("f2450f58ca", b -> X86.sse(b,0xF2,0x0F58,false,9,10)); + check("f24d0f2aca", b -> X86.sse(b,0xF2,0x0F2A,true,9,10)); + check("66450f2eca", b -> X86.sse(b,0x66,0x0F2E,false,9,10)); + check("4d0fbeca", b -> X86.extend(b,8,true,9,10)); + check("4c0fb6ce", b -> X86.extend(b,8,false,9,6)); + check("6647394ca508", b -> X86.cmpMem(b,1,9,13,12,8,2,false)); + check("66473b4ca508", b -> X86.cmpMem(b,1,9,13,12,8,2,true)); + check("403833", b -> X86.cmpMem(b,0,6,3,-1,0,0,false)); + check("400f95c6400fb6f6", b -> X86.set(b,5,6)); + check("410f95c1450fb6c9", b -> X86.set(b,5,9)); + check("4d33c9", b -> X86.constant(b,9,0)); + check("49c7c1ffffffff", b -> X86.constant(b,9,-1)); + check("41b9ffffffff", b -> X86.constant(b,9,0xFFFFFFFFL)); + check("49b98877665544332211", b -> X86.constant(b,9,0x1122334455667788L)); + check("4c8d0d00000000", b -> { assert X86.rip(b,0,0x8D,true,9)==7; }); + check("f2440f100d00000000", b -> { assert X86.rip(b,0xF2,0x0F10,false,9)==9; }); + check("e800000000c3", b -> { X86.call(b); X86.ret(b); }); + check("ffd0", b -> X86.callRegister(b,0)); + check("41ffd2", b -> X86.callRegister(b,10)); + check("41519d", b -> X86.flags(b,9,true)); + check("9c4159", b -> X86.flags(b,9,false)); + check("ff7424088f442410", b -> X86.stackCopy(b,8,16)); + check("4d8bca", b -> X86.move(b,false,false,9,10)); + check("4d0f28ca", b -> X86.move(b,true,true,9,10)); + check("664d0f6eca", b -> X86.move(b,true,false,9,10)); + check("664d0f7ed1", b -> X86.move(b,false,true,9,10)); + branch("eb7f",129,-1); + branch("eb80",-126,-1); + branch("e97d000000",130,-1); + branch("e97cffffff",-127,-1); + branch("757f",129,5); + branch("0f857c000000",130,5); + branch("0f857bffffff",-127,5); + System.out.println("Shared x86 encoding tests passed"); + } + static void branch(String expected, int delta, int condition) { + int size=X86.branchSize(delta,condition>=0); + byte[] bytes=new byte[size]; + var shortForm=new Bytes(); + X86.branch(shortForm,condition); + System.arraycopy(shortForm.toByteArray(),0,bytes,0,2); + X86.patchBranch(bytes,0,size,delta,condition); + assert expected.equals(HexFormat.of().formatHex(bytes)); + } +} |
