summaryrefslogtreecommitdiff
path: root/lib/ruby_vm/rjit/assembler.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ruby_vm/rjit/assembler.rb')
-rw-r--r--lib/ruby_vm/rjit/assembler.rb1140
1 files changed, 1140 insertions, 0 deletions
diff --git a/lib/ruby_vm/rjit/assembler.rb b/lib/ruby_vm/rjit/assembler.rb
new file mode 100644
index 0000000000..645072d11b
--- /dev/null
+++ b/lib/ruby_vm/rjit/assembler.rb
@@ -0,0 +1,1140 @@
+# frozen_string_literal: true
+module RubyVM::RJIT
+ # 8-bit memory access
+ class BytePtr < Data.define(:reg, :disp); end
+
+ # 32-bit memory access
+ class DwordPtr < Data.define(:reg, :disp); end
+
+ # 64-bit memory access
+ QwordPtr = Array
+
+ # SystemV x64 calling convention
+ C_ARGS = [:rdi, :rsi, :rdx, :rcx, :r8, :r9]
+ C_RET = :rax
+
+ # https://cdrdv2.intel.com/v1/dl/getContent/671110
+ # Mostly an x86_64 assembler, but this also has some stuff that is useful for any architecture.
+ class Assembler
+ # rel8 jumps are made with labels
+ class Label < Data.define(:id, :name); end
+
+ # rel32 is inserted as [Rel32, Rel32Pad..] and converted on #resolve_rel32
+ class Rel32 < Data.define(:addr); end
+ Rel32Pad = Object.new
+
+ # A set of ModR/M values encoded on #insn
+ class ModRM < Data.define(:mod, :reg, :rm); end
+ Mod00 = 0b00 # Mod 00: [reg]
+ Mod01 = 0b01 # Mod 01: [reg]+disp8
+ Mod10 = 0b10 # Mod 10: [reg]+disp32
+ Mod11 = 0b11 # Mod 11: reg
+
+ # REX = 0100WR0B
+ REX_B = 0b01000001
+ REX_R = 0b01000100
+ REX_W = 0b01001000
+
+ # Operand matchers
+ R32 = -> (op) { op.is_a?(Symbol) && r32?(op) }
+ R64 = -> (op) { op.is_a?(Symbol) && r64?(op) }
+ IMM8 = -> (op) { op.is_a?(Integer) && imm8?(op) }
+ IMM32 = -> (op) { op.is_a?(Integer) && imm32?(op) }
+ IMM64 = -> (op) { op.is_a?(Integer) && imm64?(op) }
+
+ def initialize
+ @bytes = []
+ @labels = {}
+ @label_id = 0
+ @comments = Hash.new { |h, k| h[k] = [] }
+ @blocks = Hash.new { |h, k| h[k] = [] }
+ @stub_starts = Hash.new { |h, k| h[k] = [] }
+ @stub_ends = Hash.new { |h, k| h[k] = [] }
+ @pos_markers = Hash.new { |h, k| h[k] = [] }
+ end
+
+ def assemble(addr)
+ set_code_addrs(addr)
+ resolve_rel32(addr)
+ resolve_labels
+
+ write_bytes(addr)
+
+ @pos_markers.each do |write_pos, markers|
+ markers.each { |marker| marker.call(addr + write_pos) }
+ end
+ @bytes.size
+ ensure
+ @bytes.clear
+ end
+
+ def size
+ @bytes.size
+ end
+
+ #
+ # Instructions
+ #
+
+ def add(dst, src)
+ case [dst, src]
+ # ADD r/m64, imm8 (Mod 00: [reg])
+ in [QwordPtr[R64 => dst_reg], IMM8 => src_imm]
+ # REX.W + 83 /0 ib
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x83,
+ mod_rm: ModRM[mod: Mod00, reg: 0, rm: dst_reg],
+ imm: imm8(src_imm),
+ )
+ # ADD r/m64, imm8 (Mod 11: reg)
+ in [R64 => dst_reg, IMM8 => src_imm]
+ # REX.W + 83 /0 ib
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x83,
+ mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg],
+ imm: imm8(src_imm),
+ )
+ # ADD r/m64 imm32 (Mod 11: reg)
+ in [R64 => dst_reg, IMM32 => src_imm]
+ # REX.W + 81 /0 id
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x81,
+ mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg],
+ imm: imm32(src_imm),
+ )
+ # ADD r/m64, r64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 01 /r
+ # MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x01,
+ mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg],
+ )
+ end
+ end
+
+ def and(dst, src)
+ case [dst, src]
+ # AND r/m64, imm8 (Mod 11: reg)
+ in [R64 => dst_reg, IMM8 => src_imm]
+ # REX.W + 83 /4 ib
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x83,
+ mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg],
+ imm: imm8(src_imm),
+ )
+ # AND r/m64, imm32 (Mod 11: reg)
+ in [R64 => dst_reg, IMM32 => src_imm]
+ # REX.W + 81 /4 id
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x81,
+ mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg],
+ imm: imm32(src_imm),
+ )
+ # AND r64, r/m64 (Mod 01: [reg]+disp8)
+ in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]]
+ # REX.W + 23 /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x23,
+ mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
+ disp: imm8(src_disp),
+ )
+ end
+ end
+
+ def call(dst)
+ case dst
+ # CALL rel32
+ in Integer => dst_addr
+ # E8 cd
+ # D: Operand 1: Offset
+ insn(opcode: 0xe8, imm: rel32(dst_addr))
+ # CALL r/m64 (Mod 11: reg)
+ in R64 => dst_reg
+ # FF /2
+ # M: Operand 1: ModRM:r/m (r)
+ insn(
+ opcode: 0xff,
+ mod_rm: ModRM[mod: Mod11, reg: 2, rm: dst_reg],
+ )
+ end
+ end
+
+ def cmove(dst, src)
+ case [dst, src]
+ # CMOVE r64, r/m64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 0F 44 /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x44],
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ end
+ end
+
+ def cmovg(dst, src)
+ case [dst, src]
+ # CMOVG r64, r/m64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 0F 4F /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x4f],
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ end
+ end
+
+ def cmovge(dst, src)
+ case [dst, src]
+ # CMOVGE r64, r/m64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 0F 4D /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x4d],
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ end
+ end
+
+ def cmovl(dst, src)
+ case [dst, src]
+ # CMOVL r64, r/m64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 0F 4C /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x4c],
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ end
+ end
+
+ def cmovle(dst, src)
+ case [dst, src]
+ # CMOVLE r64, r/m64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 0F 4E /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x4e],
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ end
+ end
+
+ def cmovne(dst, src)
+ case [dst, src]
+ # CMOVNE r64, r/m64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 0F 45 /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x45],
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ end
+ end
+
+ def cmovnz(dst, src)
+ case [dst, src]
+ # CMOVNZ r64, r/m64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 0F 45 /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x45],
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ end
+ end
+
+ def cmovz(dst, src)
+ case [dst, src]
+ # CMOVZ r64, r/m64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 0F 44 /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x44],
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ # CMOVZ r64, r/m64 (Mod 01: [reg]+disp8)
+ in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]]
+ # REX.W + 0F 44 /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: [0x0f, 0x44],
+ mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
+ disp: imm8(src_disp),
+ )
+ end
+ end
+
+ def cmp(left, right)
+ case [left, right]
+ # CMP r/m8, imm8 (Mod 01: [reg]+disp8)
+ in [BytePtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm]
+ # 80 /7 ib
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ opcode: 0x80,
+ mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
+ disp: left_disp,
+ imm: imm8(right_imm),
+ )
+ # CMP r/m32, imm32 (Mod 01: [reg]+disp8)
+ in [DwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm]
+ # 81 /7 id
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ opcode: 0x81,
+ mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
+ disp: left_disp,
+ imm: imm32(right_imm),
+ )
+ # CMP r/m64, imm8 (Mod 01: [reg]+disp8)
+ in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm]
+ # REX.W + 83 /7 ib
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x83,
+ mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
+ disp: left_disp,
+ imm: imm8(right_imm),
+ )
+ # CMP r/m64, imm32 (Mod 01: [reg]+disp8)
+ in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm]
+ # REX.W + 81 /7 id
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x81,
+ mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
+ disp: left_disp,
+ imm: imm32(right_imm),
+ )
+ # CMP r/m64, imm8 (Mod 10: [reg]+disp32)
+ in [QwordPtr[R64 => left_reg, IMM32 => left_disp], IMM8 => right_imm]
+ # REX.W + 83 /7 ib
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x83,
+ mod_rm: ModRM[mod: Mod10, reg: 7, rm: left_reg],
+ disp: imm32(left_disp),
+ imm: imm8(right_imm),
+ )
+ # CMP r/m64, imm8 (Mod 11: reg)
+ in [R64 => left_reg, IMM8 => right_imm]
+ # REX.W + 83 /7 ib
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x83,
+ mod_rm: ModRM[mod: Mod11, reg: 7, rm: left_reg],
+ imm: imm8(right_imm),
+ )
+ # CMP r/m64, imm32 (Mod 11: reg)
+ in [R64 => left_reg, IMM32 => right_imm]
+ # REX.W + 81 /7 id
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x81,
+ mod_rm: ModRM[mod: Mod11, reg: 7, rm: left_reg],
+ imm: imm32(right_imm),
+ )
+ # CMP r/m64, r64 (Mod 01: [reg]+disp8)
+ in [QwordPtr[R64 => left_reg, IMM8 => left_disp], R64 => right_reg]
+ # REX.W + 39 /r
+ # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x39,
+ mod_rm: ModRM[mod: Mod01, reg: right_reg, rm: left_reg],
+ disp: left_disp,
+ )
+ # CMP r/m64, r64 (Mod 10: [reg]+disp32)
+ in [QwordPtr[R64 => left_reg, IMM32 => left_disp], R64 => right_reg]
+ # REX.W + 39 /r
+ # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x39,
+ mod_rm: ModRM[mod: Mod10, reg: right_reg, rm: left_reg],
+ disp: imm32(left_disp),
+ )
+ # CMP r/m64, r64 (Mod 11: reg)
+ in [R64 => left_reg, R64 => right_reg]
+ # REX.W + 39 /r
+ # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x39,
+ mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg],
+ )
+ end
+ end
+
+ def jbe(dst)
+ case dst
+ # JBE rel8
+ in Label => dst_label
+ # 76 cb
+ insn(opcode: 0x76, imm: dst_label)
+ # JBE rel32
+ in Integer => dst_addr
+ # 0F 86 cd
+ insn(opcode: [0x0f, 0x86], imm: rel32(dst_addr))
+ end
+ end
+
+ def je(dst)
+ case dst
+ # JE rel8
+ in Label => dst_label
+ # 74 cb
+ insn(opcode: 0x74, imm: dst_label)
+ # JE rel32
+ in Integer => dst_addr
+ # 0F 84 cd
+ insn(opcode: [0x0f, 0x84], imm: rel32(dst_addr))
+ end
+ end
+
+ def jl(dst)
+ case dst
+ # JL rel32
+ in Integer => dst_addr
+ # 0F 8C cd
+ insn(opcode: [0x0f, 0x8c], imm: rel32(dst_addr))
+ end
+ end
+
+ def jmp(dst)
+ case dst
+ # JZ rel8
+ in Label => dst_label
+ # EB cb
+ insn(opcode: 0xeb, imm: dst_label)
+ # JMP rel32
+ in Integer => dst_addr
+ # E9 cd
+ insn(opcode: 0xe9, imm: rel32(dst_addr))
+ # JMP r/m64 (Mod 01: [reg]+disp8)
+ in QwordPtr[R64 => dst_reg, IMM8 => dst_disp]
+ # FF /4
+ insn(opcode: 0xff, mod_rm: ModRM[mod: Mod01, reg: 4, rm: dst_reg], disp: dst_disp)
+ # JMP r/m64 (Mod 11: reg)
+ in R64 => dst_reg
+ # FF /4
+ insn(opcode: 0xff, mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg])
+ end
+ end
+
+ def jne(dst)
+ case dst
+ # JNE rel8
+ in Label => dst_label
+ # 75 cb
+ insn(opcode: 0x75, imm: dst_label)
+ # JNE rel32
+ in Integer => dst_addr
+ # 0F 85 cd
+ insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr))
+ end
+ end
+
+ def jnz(dst)
+ case dst
+ # JE rel8
+ in Label => dst_label
+ # 75 cb
+ insn(opcode: 0x75, imm: dst_label)
+ # JNZ rel32
+ in Integer => dst_addr
+ # 0F 85 cd
+ insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr))
+ end
+ end
+
+ def jo(dst)
+ case dst
+ # JO rel32
+ in Integer => dst_addr
+ # 0F 80 cd
+ insn(opcode: [0x0f, 0x80], imm: rel32(dst_addr))
+ end
+ end
+
+ def jz(dst)
+ case dst
+ # JZ rel8
+ in Label => dst_label
+ # 74 cb
+ insn(opcode: 0x74, imm: dst_label)
+ # JZ rel32
+ in Integer => dst_addr
+ # 0F 84 cd
+ insn(opcode: [0x0f, 0x84], imm: rel32(dst_addr))
+ end
+ end
+
+ def lea(dst, src)
+ case [dst, src]
+ # LEA r64,m (Mod 01: [reg]+disp8)
+ in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]]
+ # REX.W + 8D /r
+ # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x8d,
+ mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
+ disp: imm8(src_disp),
+ )
+ # LEA r64,m (Mod 10: [reg]+disp32)
+ in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM32 => src_disp]]
+ # REX.W + 8D /r
+ # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x8d,
+ mod_rm: ModRM[mod: Mod10, reg: dst_reg, rm: src_reg],
+ disp: imm32(src_disp),
+ )
+ end
+ end
+
+ def mov(dst, src)
+ case dst
+ in R32 => dst_reg
+ case src
+ # MOV r32 r/m32 (Mod 01: [reg]+disp8)
+ in DwordPtr[R64 => src_reg, IMM8 => src_disp]
+ # 8B /r
+ # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
+ insn(
+ opcode: 0x8b,
+ mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
+ disp: src_disp,
+ )
+ # MOV r32, imm32 (Mod 11: reg)
+ in IMM32 => src_imm
+ # B8+ rd id
+ # OI: Operand 1: opcode + rd (w), Operand 2: imm8/16/32/64
+ insn(
+ opcode: 0xb8,
+ rd: dst_reg,
+ imm: imm32(src_imm),
+ )
+ end
+ in R64 => dst_reg
+ case src
+ # MOV r64, r/m64 (Mod 00: [reg])
+ in QwordPtr[R64 => src_reg]
+ # REX.W + 8B /r
+ # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x8b,
+ mod_rm: ModRM[mod: Mod00, reg: dst_reg, rm: src_reg],
+ )
+ # MOV r64, r/m64 (Mod 01: [reg]+disp8)
+ in QwordPtr[R64 => src_reg, IMM8 => src_disp]
+ # REX.W + 8B /r
+ # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x8b,
+ mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
+ disp: src_disp,
+ )
+ # MOV r64, r/m64 (Mod 10: [reg]+disp32)
+ in QwordPtr[R64 => src_reg, IMM32 => src_disp]
+ # REX.W + 8B /r
+ # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x8b,
+ mod_rm: ModRM[mod: Mod10, reg: dst_reg, rm: src_reg],
+ disp: imm32(src_disp),
+ )
+ # MOV r64, r/m64 (Mod 11: reg)
+ in R64 => src_reg
+ # REX.W + 8B /r
+ # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x8b,
+ mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
+ )
+ # MOV r/m64, imm32 (Mod 11: reg)
+ in IMM32 => src_imm
+ # REX.W + C7 /0 id
+ # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
+ insn(
+ prefix: REX_W,
+ opcode: 0xc7,
+ mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg],
+ imm: imm32(src_imm),
+ )
+ # MOV r64, imm64
+ in IMM64 => src_imm
+ # REX.W + B8+ rd io
+ # OI: Operand 1: opcode + rd (w), Operand 2: imm8/16/32/64
+ insn(
+ prefix: REX_W,
+ opcode: 0xb8,
+ rd: dst_reg,
+ imm: imm64(src_imm),
+ )
+ end
+ in DwordPtr[R64 => dst_reg, IMM8 => dst_disp]
+ case src
+ # MOV r/m32, imm32 (Mod 01: [reg]+disp8)
+ in IMM32 => src_imm
+ # C7 /0 id
+ # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
+ insn(
+ opcode: 0xc7,
+ mod_rm: ModRM[mod: Mod01, reg: 0, rm: dst_reg],
+ disp: dst_disp,
+ imm: imm32(src_imm),
+ )
+ end
+ in QwordPtr[R64 => dst_reg]
+ case src
+ # MOV r/m64, imm32 (Mod 00: [reg])
+ in IMM32 => src_imm
+ # REX.W + C7 /0 id
+ # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
+ insn(
+ prefix: REX_W,
+ opcode: 0xc7,
+ mod_rm: ModRM[mod: Mod00, reg: 0, rm: dst_reg],
+ imm: imm32(src_imm),
+ )
+ # MOV r/m64, r64 (Mod 00: [reg])
+ in R64 => src_reg
+ # REX.W + 89 /r
+ # MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x89,
+ mod_rm: ModRM[mod: Mod00, reg: src_reg, rm: dst_reg],
+ )
+ end
+ in QwordPtr[R64 => dst_reg, IMM8 => dst_disp]
+ # Optimize encoding when disp is 0
+ return mov([dst_reg], src) if dst_disp == 0
+
+ case src
+ # MOV r/m64, imm32 (Mod 01: [reg]+disp8)
+ in IMM32 => src_imm
+ # REX.W + C7 /0 id
+ # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
+ insn(
+ prefix: REX_W,
+ opcode: 0xc7,
+ mod_rm: ModRM[mod: Mod01, reg: 0, rm: dst_reg],
+ disp: dst_disp,
+ imm: imm32(src_imm),
+ )
+ # MOV r/m64, r64 (Mod 01: [reg]+disp8)
+ in R64 => src_reg
+ # REX.W + 89 /r
+ # MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x89,
+ mod_rm: ModRM[mod: Mod01, reg: src_reg, rm: dst_reg],
+ disp: dst_disp,
+ )
+ end
+ in QwordPtr[R64 => dst_reg, IMM32 => dst_disp]
+ case src
+ # MOV r/m64, imm32 (Mod 10: [reg]+disp32)
+ in IMM32 => src_imm
+ # REX.W + C7 /0 id
+ # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
+ insn(
+ prefix: REX_W,
+ opcode: 0xc7,
+ mod_rm: ModRM[mod: Mod10, reg: 0, rm: dst_reg],
+ disp: imm32(dst_disp),
+ imm: imm32(src_imm),
+ )
+ # MOV r/m64, r64 (Mod 10: [reg]+disp32)
+ in R64 => src_reg
+ # REX.W + 89 /r
+ # MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x89,
+ mod_rm: ModRM[mod: Mod10, reg: src_reg, rm: dst_reg],
+ disp: imm32(dst_disp),
+ )
+ end
+ end
+ end
+
+ def or(dst, src)
+ case [dst, src]
+ # OR r/m64, imm8 (Mod 11: reg)
+ in [R64 => dst_reg, IMM8 => src_imm]
+ # REX.W + 83 /1 ib
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x83,
+ mod_rm: ModRM[mod: Mod11, reg: 1, rm: dst_reg],
+ imm: imm8(src_imm),
+ )
+ # OR r/m64, imm32 (Mod 11: reg)
+ in [R64 => dst_reg, IMM32 => src_imm]
+ # REX.W + 81 /1 id
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x81,
+ mod_rm: ModRM[mod: Mod11, reg: 1, rm: dst_reg],
+ imm: imm32(src_imm),
+ )
+ # OR r64, r/m64 (Mod 01: [reg]+disp8)
+ in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]]
+ # REX.W + 0B /r
+ # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x0b,
+ mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
+ disp: imm8(src_disp),
+ )
+ end
+ end
+
+ def push(src)
+ case src
+ # PUSH r64
+ in R64 => src_reg
+ # 50+rd
+ # O: Operand 1: opcode + rd (r)
+ insn(opcode: 0x50, rd: src_reg)
+ end
+ end
+
+ def pop(dst)
+ case dst
+ # POP r64
+ in R64 => dst_reg
+ # 58+ rd
+ # O: Operand 1: opcode + rd (r)
+ insn(opcode: 0x58, rd: dst_reg)
+ end
+ end
+
+ def ret
+ # RET
+ # Near return: A return to a procedure within the current code segment
+ insn(opcode: 0xc3)
+ end
+
+ def sar(dst, src)
+ case [dst, src]
+ in [R64 => dst_reg, IMM8 => src_imm]
+ # REX.W + C1 /7 ib
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8
+ insn(
+ prefix: REX_W,
+ opcode: 0xc1,
+ mod_rm: ModRM[mod: Mod11, reg: 7, rm: dst_reg],
+ imm: imm8(src_imm),
+ )
+ end
+ end
+
+ def sub(dst, src)
+ case [dst, src]
+ # SUB r/m64, imm8 (Mod 11: reg)
+ in [R64 => dst_reg, IMM8 => src_imm]
+ # REX.W + 83 /5 ib
+ # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0x83,
+ mod_rm: ModRM[mod: Mod11, reg: 5, rm: dst_reg],
+ imm: imm8(src_imm),
+ )
+ # SUB r/m64, r64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 29 /r
+ # MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x29,
+ mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg],
+ )
+ end
+ end
+
+ def test(left, right)
+ case [left, right]
+ # TEST r/m8*, imm8 (Mod 01: [reg]+disp8)
+ in [BytePtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm]
+ # REX + F6 /0 ib
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ opcode: 0xf6,
+ mod_rm: ModRM[mod: Mod01, reg: 0, rm: left_reg],
+ disp: left_disp,
+ imm: imm8(right_imm),
+ )
+ # TEST r/m64, imm32 (Mod 01: [reg]+disp8)
+ in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm]
+ # REX.W + F7 /0 id
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0xf7,
+ mod_rm: ModRM[mod: Mod01, reg: 0, rm: left_reg],
+ disp: left_disp,
+ imm: imm32(right_imm),
+ )
+ # TEST r/m64, imm32 (Mod 10: [reg]+disp32)
+ in [QwordPtr[R64 => left_reg, IMM32 => left_disp], IMM32 => right_imm]
+ # REX.W + F7 /0 id
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0xf7,
+ mod_rm: ModRM[mod: Mod10, reg: 0, rm: left_reg],
+ disp: imm32(left_disp),
+ imm: imm32(right_imm),
+ )
+ # TEST r/m64, imm32 (Mod 11: reg)
+ in [R64 => left_reg, IMM32 => right_imm]
+ # REX.W + F7 /0 id
+ # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
+ insn(
+ prefix: REX_W,
+ opcode: 0xf7,
+ mod_rm: ModRM[mod: Mod11, reg: 0, rm: left_reg],
+ imm: imm32(right_imm),
+ )
+ # TEST r/m32, r32 (Mod 11: reg)
+ in [R32 => left_reg, R32 => right_reg]
+ # 85 /r
+ # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
+ insn(
+ opcode: 0x85,
+ mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg],
+ )
+ # TEST r/m64, r64 (Mod 11: reg)
+ in [R64 => left_reg, R64 => right_reg]
+ # REX.W + 85 /r
+ # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x85,
+ mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg],
+ )
+ end
+ end
+
+ def xor(dst, src)
+ case [dst, src]
+ # XOR r/m64, r64 (Mod 11: reg)
+ in [R64 => dst_reg, R64 => src_reg]
+ # REX.W + 31 /r
+ # MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r)
+ insn(
+ prefix: REX_W,
+ opcode: 0x31,
+ mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg],
+ )
+ end
+ end
+
+ #
+ # Utilities
+ #
+
+ attr_reader :comments
+
+ def comment(message)
+ @comments[@bytes.size] << message
+ end
+
+ # Mark the starting address of a block
+ def block(block)
+ @blocks[@bytes.size] << block
+ end
+
+ # Mark the starting/ending addresses of a stub
+ def stub(stub)
+ @stub_starts[@bytes.size] << stub
+ yield
+ ensure
+ @stub_ends[@bytes.size] << stub
+ end
+
+ def pos_marker(&block)
+ @pos_markers[@bytes.size] << block
+ end
+
+ def new_label(name)
+ Label.new(id: @label_id += 1, name:)
+ end
+
+ # @param [RubyVM::RJIT::Assembler::Label] label
+ def write_label(label)
+ @labels[label] = @bytes.size
+ end
+
+ def incr_counter(name)
+ if C.rjit_opts.stats
+ comment("increment counter #{name}")
+ mov(:rax, C.rb_rjit_counters[name].to_i)
+ add([:rax], 1) # TODO: lock
+ end
+ end
+
+ private
+
+ def insn(prefix: 0, opcode:, rd: nil, mod_rm: nil, disp: nil, imm: nil)
+ # Determine prefix
+ if rd
+ prefix |= REX_B if extended_reg?(rd)
+ opcode += reg_code(rd)
+ end
+ if mod_rm
+ prefix |= REX_R if mod_rm.reg.is_a?(Symbol) && extended_reg?(mod_rm.reg)
+ prefix |= REX_B if mod_rm.rm.is_a?(Symbol) && extended_reg?(mod_rm.rm)
+ end
+
+ # Encode insn
+ if prefix > 0
+ @bytes.push(prefix)
+ end
+ @bytes.push(*Array(opcode))
+ if mod_rm
+ mod_rm_byte = encode_mod_rm(
+ mod: mod_rm.mod,
+ reg: mod_rm.reg.is_a?(Symbol) ? reg_code(mod_rm.reg) : mod_rm.reg,
+ rm: mod_rm.rm.is_a?(Symbol) ? reg_code(mod_rm.rm) : mod_rm.rm,
+ )
+ @bytes.push(mod_rm_byte)
+ end
+ if disp
+ @bytes.push(*Array(disp))
+ end
+ if imm
+ @bytes.push(*imm)
+ end
+ end
+
+ def reg_code(reg)
+ reg_code_extended(reg).first
+ end
+
+ # Table 2-2. 32-Bit Addressing Forms with the ModR/M Byte
+ #
+ # 7 6 5 4 3 2 1 0
+ # +--+--+--+--+--+--+--+--+
+ # | Mod | Reg/ | R/M |
+ # | | Opcode | |
+ # +--+--+--+--+--+--+--+--+
+ #
+ # The r/m field can specify a register as an operand or it can be combined
+ # with the mod field to encode an addressing mode.
+ #
+ # /0: R/M is 0 (not used)
+ # /r: R/M is a register
+ def encode_mod_rm(mod:, reg: 0, rm: 0)
+ if mod > 0b11
+ raise ArgumentError, "too large Mod: #{mod}"
+ end
+ if reg > 0b111
+ raise ArgumentError, "too large Reg/Opcode: #{reg}"
+ end
+ if rm > 0b111
+ raise ArgumentError, "too large R/M: #{rm}"
+ end
+ (mod << 6) + (reg << 3) + rm
+ end
+
+ # ib: 1 byte
+ def imm8(imm)
+ unless imm8?(imm)
+ raise ArgumentError, "unexpected imm8: #{imm}"
+ end
+ [imm].pack('c').unpack('c*') # TODO: consider uimm
+ end
+
+ # id: 4 bytes
+ def imm32(imm)
+ unless imm32?(imm)
+ raise ArgumentError, "unexpected imm32: #{imm}"
+ end
+ [imm].pack('l').unpack('c*') # TODO: consider uimm
+ end
+
+ # io: 8 bytes
+ def imm64(imm)
+ unless imm64?(imm)
+ raise ArgumentError, "unexpected imm64: #{imm}"
+ end
+ imm_bytes(imm, 8)
+ end
+
+ def imm_bytes(imm, num_bytes)
+ bytes = []
+ bits = imm
+ num_bytes.times do
+ bytes << (bits & 0xff)
+ bits >>= 8
+ end
+ if bits != 0
+ raise ArgumentError, "unexpected imm with #{num_bytes} bytes: #{imm}"
+ end
+ bytes
+ end
+
+ def rel32(addr)
+ [Rel32.new(addr), Rel32Pad, Rel32Pad, Rel32Pad]
+ end
+
+ def set_code_addrs(write_addr)
+ (@bytes.size + 1).times do |index|
+ @blocks.fetch(index, []).each do |block|
+ block.start_addr = write_addr + index
+ end
+ @stub_starts.fetch(index, []).each do |stub|
+ stub.start_addr = write_addr + index
+ end
+ @stub_ends.fetch(index, []).each do |stub|
+ stub.end_addr = write_addr + index
+ end
+ end
+ end
+
+ def resolve_rel32(write_addr)
+ @bytes.each_with_index do |byte, index|
+ if byte.is_a?(Rel32)
+ src_addr = write_addr + index + 4 # offset 4 bytes for rel32 itself
+ dst_addr = byte.addr
+ rel32 = dst_addr - src_addr
+ raise "unexpected offset: #{rel32}" unless imm32?(rel32)
+ imm32(rel32).each_with_index do |rel_byte, rel_index|
+ @bytes[index + rel_index] = rel_byte
+ end
+ end
+ end
+ end
+
+ def resolve_labels
+ @bytes.each_with_index do |byte, index|
+ if byte.is_a?(Label)
+ src_index = index + 1 # offset 1 byte for rel8 itself
+ dst_index = @labels.fetch(byte)
+ rel8 = dst_index - src_index
+ raise "unexpected offset: #{rel8}" unless imm8?(rel8)
+ @bytes[index] = rel8
+ end
+ end
+ end
+
+ def write_bytes(addr)
+ Fiddle::Pointer.new(addr)[0, @bytes.size] = @bytes.pack('c*')
+ end
+ end
+
+ module OperandMatcher
+ def imm8?(imm)
+ (-0x80..0x7f).include?(imm)
+ end
+
+ def imm32?(imm)
+ (-0x8000_0000..0x7fff_ffff).include?(imm) # TODO: consider uimm
+ end
+
+ def imm64?(imm)
+ (-0x8000_0000_0000_0000..0xffff_ffff_ffff_ffff).include?(imm)
+ end
+
+ def r32?(reg)
+ if extended_reg?(reg)
+ reg.end_with?('d')
+ else
+ reg.start_with?('e')
+ end
+ end
+
+ def r64?(reg)
+ if extended_reg?(reg)
+ reg.match?(/\Ar\d+\z/)
+ else
+ reg.start_with?('r')
+ end
+ end
+
+ def extended_reg?(reg)
+ reg_code_extended(reg).last
+ end
+
+ def reg_code_extended(reg)
+ case reg
+ # Not extended
+ when :al, :ax, :eax, :rax then [0, false]
+ when :cl, :cx, :ecx, :rcx then [1, false]
+ when :dl, :dx, :edx, :rdx then [2, false]
+ when :bl, :bx, :ebx, :rbx then [3, false]
+ when :ah, :sp, :esp, :rsp then [4, false]
+ when :ch, :bp, :ebp, :rbp then [5, false]
+ when :dh, :si, :esi, :rsi then [6, false]
+ when :bh, :di, :edi, :rdi then [7, false]
+ # Extended
+ when :r8b, :r8w, :r8d, :r8 then [0, true]
+ when :r9b, :r9w, :r9d, :r9 then [1, true]
+ when :r10b, :r10w, :r10d, :r10 then [2, true]
+ when :r11b, :r11w, :r11d, :r11 then [3, true]
+ when :r12b, :r12w, :r12d, :r12 then [4, true]
+ when :r13b, :r13w, :r13d, :r13 then [5, true]
+ when :r14b, :r14w, :r14d, :r14 then [6, true]
+ when :r15b, :r15w, :r15d, :r15 then [7, true]
+ else raise ArgumentError, "unexpected reg: #{reg.inspect}"
+ end
+ end
+ end
+
+ class Assembler
+ include OperandMatcher
+ extend OperandMatcher
+ end
+end