summaryrefslogtreecommitdiff
path: root/isa/src/main/java/com/seaofnodes
diff options
context:
space:
mode:
Diffstat (limited to 'isa/src/main/java/com/seaofnodes')
-rw-r--r--isa/src/main/java/com/seaofnodes/isa/Arm64.java399
-rw-r--r--isa/src/main/java/com/seaofnodes/isa/CodeSink.java9
-rw-r--r--isa/src/main/java/com/seaofnodes/isa/RiscV.java117
-rw-r--r--isa/src/main/java/com/seaofnodes/isa/X86.java256
4 files changed, 781 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.
+ }
+ }
+}