diff options
Diffstat (limited to 'isa/src/main/java')
| -rw-r--r-- | isa/src/main/java/com/seaofnodes/isa/Arm64.java | 405 | ||||
| -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 | 127 | ||||
| -rw-r--r-- | isa/src/main/java/com/seaofnodes/isa/X86.java | 272 |
4 files changed, 813 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..ce7f7b11 --- /dev/null +++ b/isa/src/main/java/com/seaofnodes/isa/Arm64.java @@ -0,0 +1,405 @@ +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; + } + + /** FCVTZS Xd,Dn: truncate, saturate overflow, and convert NaN to zero. */ + public static int floatToInteger(int rn, int rd) { + assert 0<=rn && rn<32 && 0<=rd && rd<32; + return 0x9E780000 | (rn<<5) | rd; + } + + 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..0ecd1cec --- /dev/null +++ b/isa/src/main/java/com/seaofnodes/isa/RiscV.java @@ -0,0 +1,127 @@ +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; + } + + /** FCVT.L.D with RTZ saturates overflow; explicitly map either NaN to zero. */ + public static void floatToInteger(CodeSink out, int dst, int src) { + out.add4(r_type(OP_FP,dst,1,src,0,0x71)); // FCLASS.D + out.add4(i_type(OP_IMM,dst,7,dst,0x300)); // ANDI: NaN classification bits + out.add4(b_type(OP_BRANCH,1,(short)dst,(short)0,12)); // BNE nan + out.add4(r_type(OP_FP,dst,RM.RTZ.ordinal(),src,2,0x61)); // FCVT.L.D + out.add4(j_type(OP_JAL,0,8)); // J done + out.add4(i_type(OP_IMM,dst,0,0,0)); // nan: LI dst,0 + } + + 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..78fa3da5 --- /dev/null +++ b/isa/src/main/java/com/seaofnodes/isa/X86.java @@ -0,0 +1,272 @@ +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); + } + /** Truncate f64 to i64, saturating overflow and mapping NaN to zero. */ + public static void floatToInteger(CodeSink out, int dst, int src) { + sse(out,0xF2,0x0F2C,true,dst,src); // CVTTSD2SI + imm(out,0x81,7,dst,1); // CMP dst,1: OF iff dst is MIN_VALUE + out.add1(0x71).add1((src>=8 ? 5 : 4)+24); // JNO done + sse(out,0x66,0x0F2E,false,src,src); // UCOMISD: unordered iff NaN + out.add1(0x7A).add1(19); // JP nan + sse(out,0x66,0x0F7E,true,src,dst); // MOVQ: inspect the float's sign + shift(out,7,dst,63); // SAR: 0 or -1 + unary(out,0xF7,2,dst); // NOT: -1 or 0 + reg(out,0x0FBA,7,dst); // BTC sign bit: MAX_VALUE or MIN_VALUE + out.add1(63); + out.add1(0xEB).add1(3); // JMP done + reg(out,0x33,dst,dst); // nan: XOR dst,dst + } + + 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. + } + } +} |
