summaryrefslogtreecommitdiff
path: root/lib/ruby_vm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ruby_vm')
-rw-r--r--lib/ruby_vm/mjit/assembler.rb714
-rw-r--r--lib/ruby_vm/mjit/block.rb6
-rw-r--r--lib/ruby_vm/mjit/branch_stub.rb24
-rw-r--r--lib/ruby_vm/mjit/c_pointer.rb343
-rw-r--r--lib/ruby_vm/mjit/c_type.rb91
-rw-r--r--lib/ruby_vm/mjit/code_block.rb86
-rw-r--r--lib/ruby_vm/mjit/compiler.rb188
-rw-r--r--lib/ruby_vm/mjit/context.rb21
-rw-r--r--lib/ruby_vm/mjit/exit_compiler.rb119
-rw-r--r--lib/ruby_vm/mjit/hooks.rb32
-rw-r--r--lib/ruby_vm/mjit/insn_compiler.rb857
-rw-r--r--lib/ruby_vm/mjit/invariants.rb62
-rw-r--r--lib/ruby_vm/mjit/jit_state.rb30
-rw-r--r--lib/ruby_vm/mjit/stats.rb87
14 files changed, 0 insertions, 2660 deletions
diff --git a/lib/ruby_vm/mjit/assembler.rb b/lib/ruby_vm/mjit/assembler.rb
deleted file mode 100644
index f2f5bd77c4..0000000000
--- a/lib/ruby_vm/mjit/assembler.rb
+++ /dev/null
@@ -1,714 +0,0 @@
-# frozen_string_literal: true
-module RubyVM::MJIT
- # https://www.intel.com/content/dam/develop/public/us/en/documents/325383-sdm-vol-2abcd.pdf
- # 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
-
- 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] = [] }
- end
-
- def assemble(addr)
- set_code_addrs(addr)
- resolve_rel32(addr)
- resolve_labels
-
- write_bytes(addr)
-
- @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 [[Symbol => dst_reg], Integer => src_imm] if 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 [Symbol => dst_reg, Integer => src_imm] if 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 [Symbol => dst_reg, Integer => src_imm] if 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 [Symbol => dst_reg, Symbol => src_reg] if 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],
- )
- else
- raise NotImplementedError, "add: not-implemented operands: #{dst.inspect}, #{src.inspect}"
- end
- end
-
- # @param addr [Integer]
- def call(addr)
- # CALL rel32
- # E8 cd
- insn(opcode: 0xe8, imm: rel32(addr))
- end
-
- def cmovl(dst, src)
- case [dst, src]
- # CMOVL r64, r/m64 (Mod 11: reg)
- in [Symbol => dst_reg, Symbol => 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],
- )
- else
- raise NotImplementedError, "cmovl: not-implemented operands: #{dst.inspect}, #{src.inspect}"
- end
- end
-
- def cmp(left, right)
- case [left, right]
- # CMP r/m64 r64 (Mod 01: [reg]+disp8)
- in [[Symbol => left_reg, Integer => left_disp], Symbol => 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,
- )
- else
- raise NotImplementedError, "cmp: not-implemented operands: #{left.inspect}, #{right.inspect}"
- end
- end
-
- def je(dst)
- case dst
- # JE rel32
- in Integer => dst_addr
- # 0F 84 cd
- insn(opcode: [0x0f, 0x84], imm: rel32(dst_addr))
- else
- raise NotImplementedError, "je: not-implemented operands: #{dst.inspect}"
- end
- end
-
- def jmp(dst)
- case dst
- # JMP rel32
- in Integer => dst_addr
- # E9 cd
- insn(opcode: 0xe9, imm: rel32(dst_addr))
- # JMP r/m64 (Mod 01: [reg]+disp8)
- in [Symbol => dst_reg, Integer => dst_disp] if 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 Symbol => dst_reg
- # FF /4
- insn(opcode: 0xff, mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg])
- else
- raise NotImplementedError, "jmp: not-implemented operands: #{dst.inspect}"
- end
- end
-
- def jne(dst)
- case dst
- # JNE rel32
- in Integer => dst_addr
- # 0F 85 cd
- insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr))
- else
- raise NotImplementedError, "jne: not-implemented operands: #{dst.inspect}"
- end
- end
-
- def jnz(dst)
- case dst
- # JNZ rel32
- in Integer => dst_addr
- # 0F 85 cd
- insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr))
- else
- raise NotImplementedError, "jnz: not-implemented operands: #{dst.inspect}"
- end
- end
-
- def jo(dst)
- case dst
- # JO rel32
- in Integer => dst_addr
- # 0F 80 cd
- insn(opcode: [0x0f, 0x80], imm: rel32(dst_addr))
- else
- raise NotImplementedError, "jo: not-implemented operands: #{dst.inspect}"
- 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))
- else
- raise NotImplementedError, "jz: not-implemented operands: #{dst.inspect}"
- end
- end
-
- def lea(dst, src)
- case [dst, src]
- # LEA r64,m (Mod 01: [reg]+disp8)
- in [Symbol => dst_reg, [Symbol => src_reg, Integer => src_disp]] if r64?(dst_reg) && 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: src_disp,
- )
- else
- raise NotImplementedError, "lea: not-implemented operands: #{dst.inspect}, #{src.inspect}"
- end
- end
-
- def mov(dst, src)
- case dst
- in Symbol => dst_reg
- case src
- # MOV r64, r/m64 (Mod 00: [reg])
- in [Symbol => src_reg] if r64?(dst_reg) && 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 [Symbol => src_reg, Integer => src_disp] if r64?(dst_reg) && 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]+disp16)
- in [Symbol => src_reg, Integer => src_disp] if r64?(dst_reg) && 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 Symbol => src_reg if r64?(dst_reg) && 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 r32 r/m32 (Mod 01: [reg]+disp8)
- in [Symbol => src_reg, Integer => src_disp] if r32?(dst_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 Integer => src_imm if r32?(dst_reg) && 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),
- )
- # MOV r/m64, imm32 (Mod 11: reg)
- in Integer => src_imm if r64?(dst_reg) && 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 Integer => src_imm if r64?(dst_reg) && 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),
- )
- else
- raise NotImplementedError, "mov: not-implemented operands: #{dst.inspect}, #{src.inspect}"
- end
- in [Symbol => dst_reg]
- case src
- # MOV r/m64, imm32 (Mod 00: [reg])
- in Integer => src_imm if r64?(dst_reg) && 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 Symbol => src_reg if r64?(dst_reg) && 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],
- )
- else
- raise NotImplementedError, "mov: not-implemented operands: #{dst.inspect}, #{src.inspect}"
- end
- in [Symbol => dst_reg, Integer => 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 Integer => src_imm if r64?(dst_reg) && imm8?(dst_disp) && 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 Symbol => src_reg if r64?(dst_reg) && imm8?(dst_disp) && 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,
- )
- else
- raise NotImplementedError, "mov: not-implemented operands: #{dst.inspect}, #{src.inspect}"
- end
- else
- raise NotImplementedError, "mov: not-implemented operands: #{dst.inspect}, #{src.inspect}"
- end
- end
-
- def push(src)
- case src
- # PUSH r64
- in Symbol => src_reg if r64?(src_reg)
- # 50+rd
- # O: Operand 1: opcode + rd (r)
- insn(opcode: 0x50, rd: src_reg)
- else
- raise NotImplementedError, "push: not-implemented operands: #{src.inspect}"
- end
- end
-
- def pop(dst)
- case dst
- # POP r64
- in Symbol => dst_reg if r64?(dst_reg)
- # 58+ rd
- # O: Operand 1: opcode + rd (r)
- insn(opcode: 0x58, rd: dst_reg)
- else
- raise NotImplementedError, "pop: not-implemented operands: #{dst.inspect}"
- end
- end
-
- def ret
- # RET
- # Near return: A return to a procedure within the current code segment
- insn(opcode: 0xc3)
- end
-
- def sub(dst, src)
- case [dst, src]
- # SUB r/m64, imm8
- in [Symbol => dst_reg, Integer => src_imm] if 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 [Symbol => dst_reg, Symbol => src_reg] if 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],
- )
- else
- raise NotImplementedError, "sub: not-implemented operands: #{dst.inspect}, #{src.inspect}"
- end
- end
-
- def test(left, right)
- case [left, right]
- # TEST r/m8*, imm8 (Mod 01: [reg]+disp8)
- in [[Symbol => left_reg, Integer => left_disp], Integer => right_imm] if imm8?(right_imm) && right_imm >= 0
- # 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 [[Symbol => left_reg, Integer => left_disp], Integer => right_imm] if 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/m32, r32 (Mod 11: reg)
- in [Symbol => left_reg, Symbol => right_reg] if 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],
- )
- else
- raise NotImplementedError, "test: not-implemented operands: #{left.inspect}, #{right.inspect}"
- 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 new_label(name)
- Label.new(id: @label_id += 1, name:)
- end
-
- # @param [RubyVM::MJIT::Assembler::Label] label
- def write_label(label)
- @labels[label] = @bytes.size
- end
-
- def incr_counter(name)
- if C.mjit_opts.stats
- comment("increment counter #{name}")
- mov(:rax, C.rb_mjit_counters[name].to_i)
- add([:rax], 1) # TODO: lock
- end
- end
-
- def imm32?(imm)
- (-0x8000_0000..0x7fff_ffff).include?(imm) # TODO: consider uimm
- 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
-
- 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
-
- # 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 imm8?(imm)
- (-0x80..0x7f).include?(imm)
- 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 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
-end
diff --git a/lib/ruby_vm/mjit/block.rb b/lib/ruby_vm/mjit/block.rb
deleted file mode 100644
index 58c21c9c2a..0000000000
--- a/lib/ruby_vm/mjit/block.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class RubyVM::MJIT::Block < Struct.new(
- :pc, # @param [Integer] Starting PC
- :start_addr, # @param [Integer] Starting address of this block's JIT code
- :entry_exit, # @param [Integer] Address of entry exit (optional)
-)
-end
diff --git a/lib/ruby_vm/mjit/branch_stub.rb b/lib/ruby_vm/mjit/branch_stub.rb
deleted file mode 100644
index 0f015e2f72..0000000000
--- a/lib/ruby_vm/mjit/branch_stub.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module RubyVM::MJIT
- # Branch shapes
- Next0 = :Next0 # target0 is a fallthrough
- Next1 = :Next1 # target1 is a fallthrough
- Default = :Default # neither targets is a fallthrough
-
- class BranchStub < Struct.new(
- :iseq, # @param [RubyVM::MJIT::CPointer::Struct_rb_iseq_struct] Branch target ISEQ
- :shape, # @param [Symbol] Next0, Next1, or Default
- :target0, # @param [RubyVM::MJIT::BranchTarget] First branch target
- :target1, # @param [RubyVM::MJIT::BranchTarget,NilClass] Second branch target (optional)
- :compile, # @param [Proc] A callback to (re-)generate this branch stub
- :start_addr, # @param [Integer] Stub source start address to be re-generated
- :end_addr, # @param [Integer] Stub source end address to be re-generated
- )
- end
-
- class BranchTarget < Struct.new(
- :pc,
- :ctx,
- :address,
- )
- end
-end
diff --git a/lib/ruby_vm/mjit/c_pointer.rb b/lib/ruby_vm/mjit/c_pointer.rb
deleted file mode 100644
index 03742dd53a..0000000000
--- a/lib/ruby_vm/mjit/c_pointer.rb
+++ /dev/null
@@ -1,343 +0,0 @@
-module RubyVM::MJIT
- # Every class under this namespace is a pointer. Even if the type is
- # immediate, it shouldn't be dereferenced until `*` is called.
- module CPointer
- # Note: We'd like to avoid alphabetic method names to avoid a conflict
- # with member methods. to_i and to_s are considered an exception.
- class Struct
- # @param name [String]
- # @param sizeof [Integer]
- # @param members [Hash{ Symbol => [RubyVM::MJIT::CType::*, Integer, TrueClass] }]
- def initialize(addr, sizeof, members)
- @addr = addr
- @sizeof = sizeof
- @members = members
- end
-
- # Get a raw address
- def to_i
- @addr
- end
-
- # Serialized address for generated code
- def to_s
- "0x#{@addr.to_s(16)}"
- end
-
- # Pointer diff
- def -(struct)
- raise ArgumentError if self.class != struct.class
- (@addr - struct.to_i) / @sizeof
- end
-
- # Primitive API that does no automatic dereference
- # TODO: remove this?
- # @param member [Symbol]
- def [](member)
- type, offset = @members.fetch(member)
- type.new(@addr + offset / 8)
- end
-
- private
-
- # @param member [Symbol]
- # @param value [Object]
- def []=(member, value)
- type, offset = @members.fetch(member)
- type[@addr + offset / 8] = value
- end
-
- # @param size [Integer]
- # @param members [Hash{ Symbol => [Integer, RubyVM::MJIT::CType::*] }]
- def self.define(size, members)
- Class.new(self) do
- # Return the size of this type
- define_singleton_method(:size) { size }
-
- # Return the offset to a field
- define_singleton_method(:offsetof) { |field| members.fetch(field).last / 8 }
-
- # Return member names
- define_singleton_method(:members) { members.keys }
-
- define_method(:initialize) do |addr = nil|
- if addr.nil? # TODO: get rid of this feature later
- addr = Fiddle.malloc(size)
- end
- super(addr, size, members)
- end
-
- members.each do |member, (type, offset, to_ruby)|
- # Intelligent API that does automatic dereference
- define_method(member) do
- value = self[member]
- if value.respond_to?(:*)
- value = value.*
- end
- if to_ruby
- value = C.to_ruby(value)
- end
- value
- end
-
- define_method("#{member}=") do |value|
- if to_ruby
- value = C.to_value(value)
- end
- self[member] = value
- end
- end
- end
- end
- end
-
- # Note: We'd like to avoid alphabetic method names to avoid a conflict
- # with member methods. to_i is considered an exception.
- class Union
- # @param _name [String] To be used when it starts defining a union pointer class
- # @param sizeof [Integer]
- # @param members [Hash{ Symbol => RubyVM::MJIT::CType::* }]
- def initialize(addr, sizeof, members)
- @addr = addr
- @sizeof = sizeof
- @members = members
- end
-
- # Get a raw address
- def to_i
- @addr
- end
-
- # Move addr to access this pointer like an array
- def +(index)
- raise ArgumentError unless index.is_a?(Integer)
- self.class.new(@addr + index * @sizeof)
- end
-
- # Pointer diff
- def -(union)
- raise ArgumentError if self.class != union.class
- (@addr - union.instance_variable_get(:@addr)) / @sizeof
- end
-
- # @param sizeof [Integer]
- # @param members [Hash{ Symbol => RubyVM::MJIT::CType::* }]
- def self.define(sizeof, members)
- Class.new(self) do
- # Return the size of this type
- define_singleton_method(:sizeof) { sizeof }
-
- define_method(:initialize) do |addr|
- super(addr, sizeof, members)
- end
-
- members.each do |member, type|
- # Intelligent API that does automatic dereference
- define_method(member) do
- value = type.new(@addr)
- if value.respond_to?(:*)
- value = value.*
- end
- value
- end
- end
- end
- end
- end
-
- class Immediate
- # @param addr [Integer]
- # @param size [Integer]
- # @param pack [String]
- def initialize(addr, size, pack)
- @addr = addr
- @size = size
- @pack = pack
- end
-
- # Get a raw address
- def to_i
- @addr
- end
-
- # Move addr to addess this pointer like an array
- def +(index)
- Immediate.new(@addr + index * @size, @size, @pack)
- end
-
- # Dereference
- def *
- self[0]
- end
-
- # Array access
- def [](index)
- return nil if @addr == 0
- Fiddle::Pointer.new(@addr + index * @size)[0, @size].unpack1(@pack)
- end
-
- # Array set
- def []=(index, value)
- Fiddle::Pointer.new(@addr + index * @size)[0, @size] = [value].pack(@pack)
- end
-
- # Serialized address for generated code. Used for embedding things like body->iseq_encoded.
- def to_s
- "0x#{Integer(@addr).to_s(16)}"
- end
-
- # @param fiddle_type [Integer] Fiddle::TYPE_*
- def self.define(fiddle_type)
- size = Fiddle::PackInfo::SIZE_MAP.fetch(fiddle_type)
- pack = Fiddle::PackInfo::PACK_MAP.fetch(fiddle_type)
-
- Class.new(self) do
- define_method(:initialize) do |addr|
- super(addr, size, pack)
- end
-
- define_singleton_method(:size) do
- size
- end
-
- # Type-level []=: Used by struct fields
- define_singleton_method(:[]=) do |addr, value|
- Fiddle::Pointer.new(addr)[0, size] = [value].pack(pack)
- end
- end
- end
- end
-
- # -Fiddle::TYPE_CHAR Immediate with special handling of true/false
- class Bool < Immediate.define(-Fiddle::TYPE_CHAR)
- # Dereference
- def *
- return nil if @addr == 0
- super != 0
- end
-
- def self.[]=(addr, value)
- super(addr, value ? 1 : 0)
- end
- end
-
- class Pointer
- attr_reader :type
-
- # @param addr [Integer]
- # @param type [Class] RubyVM::MJIT::CType::*
- def initialize(addr, type)
- @addr = addr
- @type = type
- end
-
- # Move addr to addess this pointer like an array
- def +(index)
- raise ArgumentError unless index.is_a?(Integer)
- Pointer.new(@addr + index * Fiddle::SIZEOF_VOIDP, @type)
- end
-
- # Dereference
- def *
- return nil if dest_addr == 0
- @type.new(dest_addr)
- end
-
- # Array access
- def [](index)
- (self + index).*
- end
-
- # Array set
- # @param index [Integer]
- # @param value [Integer, RubyVM::MJIT::CPointer::Struct] an address itself or an object that return an address with to_i
- def []=(index, value)
- Fiddle::Pointer.new(@addr + index * Fiddle::SIZEOF_VOIDP)[0, Fiddle::SIZEOF_VOIDP] =
- [value.to_i].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
- end
-
- # Get a raw address
- def to_i
- @addr
- end
-
- private
-
- def dest_addr
- Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_VOIDP].unpack1(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
- end
-
- def self.define(block)
- Class.new(self) do
- define_method(:initialize) do |addr|
- super(addr, block.call)
- end
-
- # Type-level []=: Used by struct fields
- # @param addr [Integer]
- # @param value [Integer, RubyVM::MJIT::CPointer::Struct] an address itself, or an object that return an address with to_i
- define_singleton_method(:[]=) do |addr, value|
- value = value.to_i
- Fiddle::Pointer.new(addr)[0, Fiddle::SIZEOF_VOIDP] = [value].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
- end
- end
- end
- end
-
- class BitField
- # @param addr [Integer]
- # @param width [Integer]
- # @param offset [Integer]
- def initialize(addr, width, offset)
- @addr = addr
- @width = width
- @offset = offset
- end
-
- # Dereference
- def *
- byte = Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_CHAR].unpack('c').first
- if @width == 1
- bit = (1 & (byte >> @offset))
- bit == 1
- elsif @width <= 8 && @offset == 0
- bitmask = @width.times.map { |i| 1 << i }.sum
- byte & bitmask
- else
- raise NotImplementedError.new("not-implemented bit field access: width=#{@width} offset=#{@offset}")
- end
- end
-
- # @param width [Integer]
- # @param offset [Integer]
- def self.define(width, offset)
- Class.new(self) do
- define_method(:initialize) do |addr|
- super(addr, width, offset)
- end
- end
- end
- end
-
- # Give a name to a dynamic CPointer class to see it on inspect
- def self.with_class_name(prefix, name, cache: false, &block)
- return block.call if name.empty?
-
- # Use a cached result only if cache: true
- class_name = "#{prefix}_#{name}"
- klass =
- if cache && self.const_defined?(class_name)
- self.const_get(class_name)
- else
- block.call
- end
-
- # Give it a name unless it's already defined
- unless self.const_defined?(class_name)
- self.const_set(class_name, klass)
- end
-
- klass
- end
- end
-end
diff --git a/lib/ruby_vm/mjit/c_type.rb b/lib/ruby_vm/mjit/c_type.rb
deleted file mode 100644
index 9e45d8d41c..0000000000
--- a/lib/ruby_vm/mjit/c_type.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require 'fiddle'
-require 'fiddle/pack'
-require_relative 'c_pointer'
-
-module RubyVM::MJIT
- module CType
- module Struct
- # @param name [String]
- # @param members [Hash{ Symbol => [Integer, RubyVM::MJIT::CType::*] }]
- def self.new(name, sizeof, **members)
- name = members.keys.join('_') if name.empty?
- CPointer.with_class_name('Struct', name) do
- CPointer::Struct.define(sizeof, members)
- end
- end
- end
-
- module Union
- # @param name [String]
- # @param members [Hash{ Symbol => RubyVM::MJIT::CType::* }]
- def self.new(name, sizeof, **members)
- name = members.keys.join('_') if name.empty?
- CPointer.with_class_name('Union', name) do
- CPointer::Union.define(sizeof, members)
- end
- end
- end
-
- module Immediate
- # @param fiddle_type [Integer]
- def self.new(fiddle_type)
- name = Fiddle.constants.find do |const|
- const.start_with?('TYPE_') && Fiddle.const_get(const) == fiddle_type.abs
- end&.to_s
- name.delete_prefix!('TYPE_')
- if fiddle_type.negative?
- name.prepend('U')
- end
- CPointer.with_class_name('Immediate', name, cache: true) do
- CPointer::Immediate.define(fiddle_type)
- end
- end
-
- # @param type [String]
- def self.parse(ctype)
- new(Fiddle::Importer.parse_ctype(ctype))
- end
-
- def self.find(size, signed)
- fiddle_type = TYPE_MAP.fetch(size)
- fiddle_type = -fiddle_type unless signed
- new(fiddle_type)
- end
-
- TYPE_MAP = Fiddle::PackInfo::SIZE_MAP.map { |type, size| [size, type.abs] }.to_h
- private_constant :TYPE_MAP
- end
-
- module Bool
- def self.new
- CPointer::Bool
- end
- end
-
- class Pointer
- # This takes a block to avoid "stack level too deep" on a cyclic reference
- # @param block [Proc]
- def self.new(&block)
- CPointer.with_class_name('Pointer', block.object_id.to_s) do
- CPointer::Pointer.define(block)
- end
- end
- end
-
- module BitField
- # @param width [Integer]
- # @param offset [Integer]
- def self.new(width, offset)
- CPointer.with_class_name('BitField', "#{offset}_#{width}") do
- CPointer::BitField.define(width, offset)
- end
- end
- end
-
- # Types that are referenced but not part of code generation targets
- Stub = ::Struct.new(:name)
-
- # Types that it failed to figure out from the header
- Unknown = Module.new
- end
-end
diff --git a/lib/ruby_vm/mjit/code_block.rb b/lib/ruby_vm/mjit/code_block.rb
deleted file mode 100644
index 21ae2386b7..0000000000
--- a/lib/ruby_vm/mjit/code_block.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-module RubyVM::MJIT
- class CodeBlock
- # @param mem_block [Integer] JIT buffer address
- # @param mem_size [Integer] JIT buffer size
- # @param outliend [TrueClass,FalseClass] true for outlined CodeBlock
- def initialize(mem_block:, mem_size:, outlined: false)
- @comments = Hash.new { |h, k| h[k] = [] }
- @mem_block = mem_block
- @mem_size = mem_size
- @write_pos = 0
- @outlined = outlined
- end
-
- # @param asm [RubyVM::MJIT::Assembler]
- def write(asm)
- return 0 if @write_pos + asm.size >= @mem_size
-
- start_addr = write_addr
-
- # Write machine code
- C.mjit_mark_writable
- @write_pos += asm.assemble(start_addr)
- C.mjit_mark_executable
-
- end_addr = write_addr
-
- # Convert comment indexes to addresses
- asm.comments.each do |index, comments|
- @comments[start_addr + index] += comments
- end
- asm.comments.clear
-
- # Dump disasm if --mjit-dump-disasm
- if C.mjit_opts.dump_disasm && start_addr < end_addr
- dump_disasm(start_addr, end_addr)
- end
- start_addr
- end
-
- def set_write_addr(addr)
- @write_pos = addr - @mem_block
- @comments.delete(addr) # TODO: clean up old comments for all the overwritten range?
- end
-
- def with_write_addr(addr)
- old_write_pos = @write_pos
- set_write_addr(addr)
- yield
- ensure
- @write_pos = old_write_pos
- end
-
- def write_addr
- @mem_block + @write_pos
- end
-
- def include?(addr)
- (@mem_block...(@mem_block + @mem_size)).include?(addr)
- end
-
- private
-
- def dump_disasm(from, to)
- C.dump_disasm(from, to).each do |address, mnemonic, op_str|
- @comments.fetch(address, []).each do |comment|
- puts colorize(" # #{comment}", bold: true)
- end
- puts colorize(" 0x#{format("%x", address)}: #{mnemonic} #{op_str}")
- end
- puts
- end
-
- def colorize(text, bold: false)
- buf = +''
- buf << "\e[1m" if bold
- buf << "\e[34m" if @outlined
- buf << text
- buf << "\e[0m"
- buf
- end
-
- def bold(text)
- "\e[1m#{text}\e[0m"
- end
- end
-end
diff --git a/lib/ruby_vm/mjit/compiler.rb b/lib/ruby_vm/mjit/compiler.rb
deleted file mode 100644
index 26e3d75d34..0000000000
--- a/lib/ruby_vm/mjit/compiler.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-require 'ruby_vm/mjit/assembler'
-require 'ruby_vm/mjit/block'
-require 'ruby_vm/mjit/branch_stub'
-require 'ruby_vm/mjit/code_block'
-require 'ruby_vm/mjit/context'
-require 'ruby_vm/mjit/exit_compiler'
-require 'ruby_vm/mjit/insn_compiler'
-require 'ruby_vm/mjit/instruction'
-require 'ruby_vm/mjit/invariants'
-require 'ruby_vm/mjit/jit_state'
-
-module RubyVM::MJIT
- # Compilation status
- KeepCompiling = :KeepCompiling
- CantCompile = :CantCompile
- EndBlock = :EndBlock
-
- # Ruby constants
- Qtrue = Fiddle::Qtrue
- Qfalse = Fiddle::Qfalse
- Qnil = Fiddle::Qnil
- Qundef = Fiddle::Qundef
-
- # Callee-saved registers
- # TODO: support using r12/r13 here
- EC = :r14
- CFP = :r15
- SP = :rbx
-
- # Scratch registers: rax, rcx
-
- class Compiler
- attr_accessor :write_pos
-
- def self.decode_insn(encoded)
- INSNS.fetch(C.rb_vm_insn_decode(encoded))
- end
-
- # @param mem_block [Integer] JIT buffer address
- # @param mem_size [Integer] JIT buffer size
- def initialize(mem_block, mem_size)
- @cb = CodeBlock.new(mem_block: mem_block, mem_size: mem_size / 2)
- @ocb = CodeBlock.new(mem_block: mem_block + mem_size / 2, mem_size: mem_size / 2, outlined: true)
- @exit_compiler = ExitCompiler.new
- @insn_compiler = InsnCompiler.new(@cb, @ocb, @exit_compiler)
-
- @leave_exit = Assembler.new.then do |asm|
- @exit_compiler.compile_leave_exit(asm)
- @ocb.write(asm)
- end
- end
-
- # Compile an ISEQ from its entry point.
- # @param iseq `RubyVM::MJIT::CPointer::Struct_rb_iseq_t`
- # @param cfp `RubyVM::MJIT::CPointer::Struct_rb_control_frame_t`
- def compile(iseq, cfp)
- # TODO: Support has_opt
- return if iseq.body.param.flags.has_opt
-
- jit = JITState.new(iseq:, cfp:)
- asm = Assembler.new
- asm.comment("Block: #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq.body.location.first_lineno}")
- compile_prologue(asm)
- compile_block(asm, jit:)
- iseq.body.jit_func = @cb.write(asm)
- rescue Exception => e
- $stderr.puts e.full_message # TODO: check verbose
- end
-
- # Compile a branch stub.
- # @param branch_stub [RubyVM::MJIT::BranchStub]
- # @param cfp `RubyVM::MJIT::CPointer::Struct_rb_control_frame_t`
- # @param target0_p [TrueClass,FalseClass]
- # @return [Integer] The starting address of the compiled branch stub
- def branch_stub_hit(branch_stub, cfp, target0_p)
- # Update cfp->pc for `jit.at_current_insn?`
- target = target0_p ? branch_stub.target0 : branch_stub.target1
- cfp.pc = target.pc
-
- # Prepare the jump target
- new_asm = Assembler.new.tap do |asm|
- jit = JITState.new(iseq: branch_stub.iseq, cfp:)
- compile_block(asm, jit:, pc: target.pc, ctx: target.ctx.dup)
- end
-
- # Rewrite the branch stub
- if @cb.write_addr == branch_stub.end_addr
- # If the branch stub's jump is the last code, allow overwriting part of
- # the old branch code with the new block code.
- @cb.set_write_addr(branch_stub.start_addr)
- branch_stub.shape = target0_p ? Next0 : Next1
- Assembler.new.tap do |branch_asm|
- branch_stub.compile.call(branch_asm)
- @cb.write(branch_asm)
- end
-
- # Compile a fallthrough right after the new branch code
- if target0_p
- branch_stub.target0.address = @cb.write(new_asm)
- else
- branch_stub.target1.address = @cb.write(new_asm)
- end
- else
- # Otherwise, just prepare the new block somewhere
- if target0_p
- branch_stub.target0.address = @cb.write(new_asm)
- else
- branch_stub.target1.address = @cb.write(new_asm)
- end
-
- # Update jump destinations
- @cb.with_write_addr(branch_stub.start_addr) do
- branch_asm = Assembler.new
- branch_stub.compile.call(branch_asm)
- @cb.write(branch_asm)
- end
- end
-
- if target0_p
- branch_stub.target0.address
- else
- branch_stub.target1.address
- end
- end
-
- private
-
- # Callee-saved: rbx, rsp, rbp, r12, r13, r14, r15
- # Caller-saved: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11
- #
- # @param asm [RubyVM::MJIT::Assembler]
- def compile_prologue(asm)
- asm.comment('MJIT entry point')
-
- # Save callee-saved registers used by JITed code
- asm.push(CFP)
- asm.push(EC)
- asm.push(SP)
-
- # Move arguments EC and CFP to dedicated registers
- asm.mov(EC, :rdi)
- asm.mov(CFP, :rsi)
-
- # Load sp to a dedicated register
- asm.mov(SP, [CFP, C.rb_control_frame_t.offsetof(:sp)]) # rbx = cfp->sp
-
- # Setup cfp->jit_return
- asm.mov(:rax, @leave_exit)
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:jit_return)], :rax)
- end
-
- # @param asm [RubyVM::MJIT::Assembler]
- def compile_block(asm, jit:, pc: jit.iseq.body.iseq_encoded.to_i, ctx: Context.new)
- # Mark the block start address and prepare an exit code storage
- jit.block = Block.new(pc:)
- asm.block(jit.block)
-
- # Compile each insn
- iseq = jit.iseq
- index = (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size
- while index < iseq.body.iseq_size
- insn = self.class.decode_insn(iseq.body.iseq_encoded[index])
- jit.pc = (iseq.body.iseq_encoded + index).to_i
-
- case status = @insn_compiler.compile(jit, ctx, asm, insn)
- when KeepCompiling
- index += insn.len
- when EndBlock
- # TODO: pad nops if entry exit exists
- break
- when CantCompile
- @exit_compiler.compile_side_exit(jit, ctx, asm)
- break
- else
- raise "compiling #{insn.name} returned unexpected status: #{status.inspect}"
- end
- end
-
- incr_counter(:compiled_block_count)
- end
-
- def incr_counter(name)
- if C.mjit_opts.stats
- C.rb_mjit_counters[name][0] += 1
- end
- end
- end
-end
diff --git a/lib/ruby_vm/mjit/context.rb b/lib/ruby_vm/mjit/context.rb
deleted file mode 100644
index 66d5f3b39b..0000000000
--- a/lib/ruby_vm/mjit/context.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module RubyVM::MJIT
- class Context < Struct.new(
- :stack_size, # @param [Integer] The number of values on the stack
- :sp_offset, # @param [Integer] JIT sp offset relative to the interpreter's sp
- )
- def initialize(stack_size: 0, sp_offset: 0) = super
-
- def stack_push(size = 1)
- opnd = [SP, C.VALUE.size * self.sp_offset]
- self.stack_size += size
- self.sp_offset += size
- opnd
- end
-
- def stack_pop(size = 1)
- self.stack_size -= size
- self.sp_offset -= size
- [SP, C.VALUE.size * self.sp_offset]
- end
- end
-end
diff --git a/lib/ruby_vm/mjit/exit_compiler.rb b/lib/ruby_vm/mjit/exit_compiler.rb
deleted file mode 100644
index 0019eda7bd..0000000000
--- a/lib/ruby_vm/mjit/exit_compiler.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-module RubyVM::MJIT
- class ExitCompiler
- def initialize
- # TODO: Use GC offsets
- @gc_refs = []
- end
-
- # Used for invalidating a block on entry.
- # @param pc [Integer]
- # @param asm [RubyVM::MJIT::Assembler]
- def compile_entry_exit(pc, asm, cause:)
- # Increment per-insn exit counter
- incr_insn_exit(pc, asm)
-
- # TODO: Saving pc and sp may be needed later
-
- # Restore callee-saved registers
- asm.comment("#{cause}: entry exit")
- asm.pop(SP)
- asm.pop(EC)
- asm.pop(CFP)
-
- asm.mov(:rax, Qundef)
- asm.ret
- end
-
- # @param ocb [CodeBlock]
- def compile_leave_exit(asm)
- asm.comment('default cfp->jit_return')
-
- # Restore callee-saved registers
- asm.pop(SP)
- asm.pop(EC)
- asm.pop(CFP)
-
- # :rax is written by #leave
- asm.ret
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def compile_side_exit(jit, ctx, asm)
- # Increment per-insn exit counter
- incr_insn_exit(jit.pc, asm)
-
- # Fix pc/sp offsets for the interpreter
- save_pc_and_sp(jit, ctx.dup, asm) # dup to avoid sp_offset update
-
- # Restore callee-saved registers
- asm.comment("exit to interpreter on #{pc_to_insn(jit.pc).name}")
- asm.pop(SP)
- asm.pop(EC)
- asm.pop(CFP)
-
- asm.mov(:rax, Qundef)
- asm.ret
- end
-
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- # @param branch_stub [RubyVM::MJIT::BranchStub]
- # @param target0_p [TrueClass,FalseClass]
- def compile_branch_stub(ctx, asm, branch_stub, target0_p)
- # Call rb_mjit_branch_stub_hit
- asm.comment("branch stub hit: #{branch_stub.iseq.body.location.label}@#{C.rb_iseq_path(branch_stub.iseq)}:#{iseq_lineno(branch_stub.iseq, target0_p ? branch_stub.target0.pc : branch_stub.target1.pc)}")
- asm.mov(:rdi, to_value(branch_stub))
- asm.mov(:esi, ctx.sp_offset)
- asm.mov(:edx, target0_p ? 1 : 0)
- asm.call(C.rb_mjit_branch_stub_hit)
-
- # Jump to the address returned by rb_mjit_stub_hit
- asm.jmp(:rax)
- end
-
- private
-
- def pc_to_insn(pc)
- Compiler.decode_insn(C.VALUE.new(pc).*)
- end
-
- # @param pc [Integer]
- # @param asm [RubyVM::MJIT::Assembler]
- def incr_insn_exit(pc, asm)
- if C.mjit_opts.stats
- insn = Compiler.decode_insn(C.VALUE.new(pc).*)
- asm.comment("increment insn exit: #{insn.name}")
- asm.mov(:rax, (C.mjit_insn_exits + insn.bin).to_i)
- asm.add([:rax], 1) # TODO: lock
- end
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def save_pc_and_sp(jit, ctx, asm)
- # Update pc (TODO: manage PC offset?)
- asm.comment("save PC#{' and SP' if ctx.sp_offset != 0} to CFP")
- asm.mov(:rax, jit.pc) # rax = jit.pc
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax
-
- # Update sp
- if ctx.sp_offset != 0
- asm.add(SP, C.VALUE.size * ctx.sp_offset) # sp += stack_size
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) # cfp->sp = sp
- ctx.sp_offset = 0
- end
- end
-
- def to_value(obj)
- @gc_refs << obj
- C.to_value(obj)
- end
-
- def iseq_lineno(iseq, pc)
- C.rb_iseq_line_no(iseq, (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size)
- end
- end
-end
diff --git a/lib/ruby_vm/mjit/hooks.rb b/lib/ruby_vm/mjit/hooks.rb
deleted file mode 100644
index 6aef14b56a..0000000000
--- a/lib/ruby_vm/mjit/hooks.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module RubyVM::MJIT::Hooks # :nodoc: all
- C = RubyVM::MJIT.const_get(:C, false)
-
- def self.on_bop_redefined(_redefined_flag, _bop)
- # C.mjit_cancel_all("BOP is redefined")
- end
-
- def self.on_cme_invalidate(_cme)
- # to be used later
- end
-
- def self.on_ractor_spawn
- # C.mjit_cancel_all("Ractor is spawned")
- end
-
- def self.on_constant_state_changed(_id)
- # to be used later
- end
-
- def self.on_constant_ic_update(_iseq, _ic, _insn_idx)
- # to be used later
- end
-
- def self.on_tracing_invalidate_all(new_iseq_events)
- # # Stop calling all JIT-ed code. We can't rewrite existing JIT-ed code to trace_ insns for now.
- # # :class events are triggered only in ISEQ_TYPE_CLASS, but mjit_target_iseq_p ignores such iseqs.
- # # Thus we don't need to cancel JIT-ed code for :class events.
- # if new_iseq_events != C.RUBY_EVENT_CLASS
- # C.mjit_cancel_all("TracePoint is enabled")
- # end
- end
-end
diff --git a/lib/ruby_vm/mjit/insn_compiler.rb b/lib/ruby_vm/mjit/insn_compiler.rb
deleted file mode 100644
index 0ea0020f19..0000000000
--- a/lib/ruby_vm/mjit/insn_compiler.rb
+++ /dev/null
@@ -1,857 +0,0 @@
-module RubyVM::MJIT
- class InsnCompiler
- # @param ocb [CodeBlock]
- # @param exit_compiler [RubyVM::MJIT::ExitCompiler]
- def initialize(cb, ocb, exit_compiler)
- @ocb = ocb
- @exit_compiler = exit_compiler
- @invariants = Invariants.new(cb, ocb, exit_compiler)
- # freeze # workaround a binding.irb issue. TODO: resurrect this
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- # @param insn `RubyVM::MJIT::Instruction`
- def compile(jit, ctx, asm, insn)
- asm.incr_counter(:mjit_insns_count)
- asm.comment("Insn: #{insn.name}")
-
- # 11/101
- case insn.name
- # nop
- # getlocal
- # setlocal
- # getblockparam
- # setblockparam
- # getblockparamproxy
- # getspecial
- # setspecial
- # getinstancevariable
- # setinstancevariable
- # getclassvariable
- # setclassvariable
- # opt_getconstant_path
- # getconstant
- # setconstant
- # getglobal
- # setglobal
- when :putnil then putnil(jit, ctx, asm)
- when :putself then putself(jit, ctx, asm)
- when :putobject then putobject(jit, ctx, asm)
- # putspecialobject
- # putstring
- # concatstrings
- # anytostring
- # toregexp
- # intern
- # newarray
- # newarraykwsplat
- # duparray
- # duphash
- # expandarray
- # concatarray
- # splatarray
- # newhash
- # newrange
- # pop
- # dup
- # dupn
- # swap
- # opt_reverse
- # topn
- # setn
- # adjuststack
- # defined
- # checkmatch
- # checkkeyword
- # checktype
- # defineclass
- # definemethod
- # definesmethod
- # send
- when :opt_send_without_block then opt_send_without_block(jit, ctx, asm)
- # objtostring
- # opt_str_freeze
- # opt_nil_p
- # opt_str_uminus
- # opt_newarray_max
- # opt_newarray_min
- # invokesuper
- # invokeblock
- when :leave then leave(jit, ctx, asm)
- # throw
- # jump
- # branchif
- when :branchunless then branchunless(jit, ctx, asm)
- # branchnil
- # once
- # opt_case_dispatch
- when :opt_plus then opt_plus(jit, ctx, asm)
- when :opt_minus then opt_minus(jit, ctx, asm)
- # opt_mult
- # opt_div
- # opt_mod
- # opt_eq
- # opt_neq
- when :opt_lt then opt_lt(jit, ctx, asm)
- # opt_le
- # opt_gt
- # opt_ge
- # opt_ltlt
- # opt_and
- # opt_or
- # opt_aref
- # opt_aset
- # opt_aset_with
- # opt_aref_with
- # opt_length
- # opt_size
- # opt_empty_p
- # opt_succ
- # opt_not
- # opt_regexpmatch2
- # invokebuiltin
- # opt_invokebuiltin_delegate
- # opt_invokebuiltin_delegate_leave
- when :getlocal_WC_0 then getlocal_WC_0(jit, ctx, asm)
- # setlocal_WC_0
- # setlocal_WC_1
- when :putobject_INT2FIX_0_ then putobject_INT2FIX_0_(jit, ctx, asm)
- when :putobject_INT2FIX_1_ then putobject_INT2FIX_1_(jit, ctx, asm)
- else CantCompile
- end
- end
-
- private
-
- #
- # Insns
- #
-
- # nop
- # getlocal
- # setlocal
- # getblockparam
- # setblockparam
- # getblockparamproxy
- # getspecial
- # setspecial
- # getinstancevariable
- # setinstancevariable
- # getclassvariable
- # setclassvariable
- # opt_getconstant_path
- # getconstant
- # setconstant
- # getglobal
- # setglobal
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def putnil(jit, ctx, asm)
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- asm.mov([SP, C.VALUE.size * ctx.stack_size], Qnil)
- ctx.stack_push(1)
- KeepCompiling
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def putself(jit, ctx, asm)
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)])
- asm.mov([SP, C.VALUE.size * ctx.stack_size], :rax)
- ctx.stack_push(1)
- KeepCompiling
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def putobject(jit, ctx, asm, val: jit.operand(0))
- # Push it to the stack
- # TODO: GC offsets
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- if asm.imm32?(val)
- asm.mov([SP, C.VALUE.size * ctx.stack_size], val)
- else # 64-bit immediates can't be directly written to memory
- asm.mov(:rax, val)
- asm.mov([SP, C.VALUE.size * ctx.stack_size], :rax)
- end
-
- ctx.stack_push(1)
- KeepCompiling
- end
-
- # putspecialobject
- # putstring
- # concatstrings
- # anytostring
- # toregexp
- # intern
- # newarray
- # newarraykwsplat
- # duparray
- # duphash
- # expandarray
- # concatarray
- # splatarray
- # newhash
- # newrange
- # pop
- # dup
- # dupn
- # swap
- # opt_reverse
- # topn
- # setn
- # adjuststack
- # defined
- # checkmatch
- # checkkeyword
- # checktype
- # defineclass
- # definemethod
- # definesmethod
- # send
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- # @param cd `RubyVM::MJIT::CPointer::Struct_rb_call_data`
- def opt_send_without_block(jit, ctx, asm)
- cd = C.rb_call_data.new(jit.operand(0))
- jit_call_method(jit, ctx, asm, cd)
- end
-
- # objtostring
- # opt_str_freeze
- # opt_nil_p
- # opt_str_uminus
- # opt_newarray_max
- # opt_newarray_min
- # invokesuper
- # invokeblock
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def leave(jit, ctx, asm)
- assert_equal(ctx.stack_size, 1)
-
- compile_check_ints(jit, ctx, asm)
-
- asm.comment('pop stack frame')
- asm.lea(:rax, [CFP, C.rb_control_frame_t.size])
- asm.mov(CFP, :rax)
- asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], :rax)
-
- # Return a value (for compile_leave_exit)
- ret_opnd = ctx.stack_pop
- asm.mov(:rax, ret_opnd)
-
- # Set caller's SP and push a value to its stack (for JIT)
- asm.mov(SP, [CFP, C.rb_control_frame_t.offsetof(:sp)]) # Note: SP is in the position after popping a receiver and arguments
- asm.mov([SP], :rax)
-
- # Jump to cfp->jit_return
- asm.jmp([CFP, -C.rb_control_frame_t.size + C.rb_control_frame_t.offsetof(:jit_return)])
-
- EndBlock
- end
-
- # throw
- # jump
- # branchif
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def branchunless(jit, ctx, asm)
- # TODO: check ints for backward branches
- # TODO: skip check for known truthy
-
- # This `test` sets ZF only for Qnil and Qfalse, which let jz jump.
- asm.test([SP, C.VALUE.size * (ctx.stack_size - 1)], ~Qnil)
- ctx.stack_pop(1)
-
- # Set stubs
- branch_stub = BranchStub.new(
- iseq: jit.iseq,
- shape: Default,
- target0: BranchTarget.new(ctx:, pc: jit.pc + C.VALUE.size * (jit.insn.len + jit.operand(0))), # branch target
- target1: BranchTarget.new(ctx:, pc: jit.pc + C.VALUE.size * jit.insn.len), # fallthrough
- )
- branch_stub.target0.address = Assembler.new.then do |ocb_asm|
- @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true)
- @ocb.write(ocb_asm)
- end
- branch_stub.target1.address = Assembler.new.then do |ocb_asm|
- @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, false)
- @ocb.write(ocb_asm)
- end
-
- # Jump to target0 on jz
- branch_stub.compile = proc do |branch_asm|
- branch_asm.comment("branchunless #{branch_stub.shape}")
- branch_asm.stub(branch_stub) do
- case branch_stub.shape
- in Default
- branch_asm.jz(branch_stub.target0.address)
- branch_asm.jmp(branch_stub.target1.address)
- in Next0
- branch_asm.jnz(branch_stub.target1.address)
- in Next1
- branch_asm.jz(branch_stub.target0.address)
- end
- end
- end
- branch_stub.compile.call(asm)
-
- EndBlock
- end
-
- # branchnil
- # once
- # opt_case_dispatch
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def opt_plus(jit, ctx, asm)
- unless jit.at_current_insn?
- defer_compilation(jit, ctx, asm)
- return EndBlock
- end
-
- comptime_recv = jit.peek_at_stack(1)
- comptime_obj = jit.peek_at_stack(0)
-
- if fixnum?(comptime_recv) && fixnum?(comptime_obj)
- # Generate a side exit before popping operands
- side_exit = side_exit(jit, ctx)
-
- unless @invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_PLUS)
- return CantCompile
- end
-
- obj_opnd = ctx.stack_pop
- recv_opnd = ctx.stack_pop
-
- asm.comment('guard recv is fixnum') # TODO: skip this with type information
- asm.test(recv_opnd, C.RUBY_FIXNUM_FLAG)
- asm.jz(side_exit)
-
- asm.comment('guard obj is fixnum') # TODO: skip this with type information
- asm.test(obj_opnd, C.RUBY_FIXNUM_FLAG)
- asm.jz(side_exit)
-
- asm.mov(:rax, recv_opnd)
- asm.sub(:rax, 1) # untag
- asm.mov(:rcx, obj_opnd)
- asm.add(:rax, :rcx)
- asm.jo(side_exit)
-
- dst_opnd = ctx.stack_push
- asm.mov(dst_opnd, :rax)
-
- KeepCompiling
- else
- CantCompile # TODO: delegate to send
- end
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def opt_minus(jit, ctx, asm)
- unless jit.at_current_insn?
- defer_compilation(jit, ctx, asm)
- return EndBlock
- end
-
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- comptime_recv = jit.peek_at_stack(1)
- comptime_obj = jit.peek_at_stack(0)
-
- if fixnum?(comptime_recv) && fixnum?(comptime_obj)
- unless @invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_MINUS)
- return CantCompile
- end
-
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- recv_index = ctx.stack_size - 2
- obj_index = ctx.stack_size - 1
-
- asm.comment('guard recv is fixnum') # TODO: skip this with type information
- asm.test([SP, C.VALUE.size * recv_index], C.RUBY_FIXNUM_FLAG)
- asm.jz(side_exit(jit, ctx))
-
- asm.comment('guard obj is fixnum') # TODO: skip this with type information
- asm.test([SP, C.VALUE.size * obj_index], C.RUBY_FIXNUM_FLAG)
- asm.jz(side_exit(jit, ctx))
-
- asm.mov(:rax, [SP, C.VALUE.size * recv_index])
- asm.mov(:rcx, [SP, C.VALUE.size * obj_index])
- asm.sub(:rax, :rcx)
- asm.jo(side_exit(jit, ctx))
- asm.add(:rax, 1) # re-tag
- asm.mov([SP, C.VALUE.size * recv_index], :rax)
-
- ctx.stack_pop(1)
- KeepCompiling
- else
- CantCompile # TODO: delegate to send
- end
- end
-
- # opt_mult
- # opt_div
- # opt_mod
- # opt_eq
- # opt_neq
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def opt_lt(jit, ctx, asm)
- unless jit.at_current_insn?
- defer_compilation(jit, ctx, asm)
- return EndBlock
- end
-
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- comptime_recv = jit.peek_at_stack(1)
- comptime_obj = jit.peek_at_stack(0)
-
- if fixnum?(comptime_recv) && fixnum?(comptime_obj)
- unless @invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_LT)
- return CantCompile
- end
-
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- recv_index = ctx.stack_size - 2
- obj_index = ctx.stack_size - 1
-
- asm.comment('guard recv is fixnum') # TODO: skip this with type information
- asm.test([SP, C.VALUE.size * recv_index], C.RUBY_FIXNUM_FLAG)
- asm.jz(side_exit(jit, ctx))
-
- asm.comment('guard obj is fixnum') # TODO: skip this with type information
- asm.test([SP, C.VALUE.size * obj_index], C.RUBY_FIXNUM_FLAG)
- asm.jz(side_exit(jit, ctx))
-
- asm.mov(:rax, [SP, C.VALUE.size * obj_index])
- asm.cmp([SP, C.VALUE.size * recv_index], :rax)
- asm.mov(:rax, Qfalse)
- asm.mov(:rcx, Qtrue)
- asm.cmovl(:rax, :rcx)
- asm.mov([SP, C.VALUE.size * recv_index], :rax)
-
- ctx.stack_pop(1)
- KeepCompiling
- else
- CantCompile # TODO: delegate to send
- end
- end
-
- # opt_le
- # opt_gt
- # opt_ge
- # opt_ltlt
- # opt_and
- # opt_or
- # opt_aref
- # opt_aset
- # opt_aset_with
- # opt_aref_with
- # opt_length
- # opt_size
- # opt_empty_p
- # opt_succ
- # opt_not
- # opt_regexpmatch2
- # invokebuiltin
- # opt_invokebuiltin_delegate
- # opt_invokebuiltin_delegate_leave
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def getlocal_WC_0(jit, ctx, asm)
- # Get operands
- idx = jit.operand(0)
- level = 0
-
- # Get EP
- asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:ep)])
-
- # Get a local variable
- asm.mov(:rax, [:rax, -idx * C.VALUE.size])
-
- # Push it to the stack
- asm.mov([SP, C.VALUE.size * ctx.stack_size], :rax)
- ctx.stack_push(1)
- KeepCompiling
- end
-
- # getlocal_WC_1
- # setlocal_WC_0
- # setlocal_WC_1
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def putobject_INT2FIX_0_(jit, ctx, asm)
- putobject(jit, ctx, asm, val: C.to_value(0))
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def putobject_INT2FIX_1_(jit, ctx, asm)
- putobject(jit, ctx, asm, val: C.to_value(1))
- end
-
- #
- # Helpers
- #
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def compile_check_ints(jit, ctx, asm)
- asm.comment('RUBY_VM_CHECK_INTS(ec)')
- asm.mov(:eax, [EC, C.rb_execution_context_t.offsetof(:interrupt_flag)])
- asm.test(:eax, :eax)
- asm.jnz(side_exit(jit, ctx))
- end
-
- # vm_call_method (vm_sendish -> vm_call_general -> vm_call_method)
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- # @param cd `RubyVM::MJIT::CPointer::Struct_rb_call_data`
- def jit_call_method(jit, ctx, asm, cd)
- ci = cd.ci
- argc = C.vm_ci_argc(ci)
- mid = C.vm_ci_mid(ci)
- flags = C.vm_ci_flag(ci)
-
- unless jit.at_current_insn?
- defer_compilation(jit, ctx, asm)
- return EndBlock
- end
-
- if flags & C.VM_CALL_KW_SPLAT != 0
- # recv_index calculation may not work for this
- asm.incr_counter(:send_kw_splat)
- return CantCompile
- end
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- recv_depth = argc + ((flags & C.VM_CALL_ARGS_BLOCKARG == 0) ? 0 : 1)
- recv_index = ctx.stack_size - 1 - recv_depth
-
- comptime_recv = jit.peek_at_stack(recv_depth)
- comptime_recv_klass = C.rb_class_of(comptime_recv)
-
- # Guard the receiver class (part of vm_search_method_fastpath)
- if comptime_recv_klass.singleton_class?
- asm.comment('guard known object with singleton class')
- asm.mov(:rax, C.to_value(comptime_recv))
- asm.cmp([SP, C.VALUE.size * recv_index], :rax)
- asm.jne(side_exit(jit, ctx))
- else
- # TODO: support more classes
- asm.incr_counter(:send_guard_known_object)
- return CantCompile
- end
-
- # Do method lookup (vm_cc_cme(cc) != NULL)
- cme = C.rb_callable_method_entry(comptime_recv_klass, mid)
- if cme.nil?
- asm.incr_counter(:send_missing_cme)
- return CantCompile # We don't support vm_call_method_name
- end
-
- # The main check of vm_call_method before vm_call_method_each_type
- case C.METHOD_ENTRY_VISI(cme)
- when C.METHOD_VISI_PUBLIC
- # You can always call public methods
- when C.METHOD_VISI_PRIVATE
- # Allow only callsites without a receiver
- if flags & C.VM_CALL_FCALL == 0
- asm.incr_counter(:send_private)
- return CantCompile
- end
- when C.METHOD_VISI_PROTECTED
- asm.incr_counter(:send_protected)
- return CantCompile # TODO: support this
- else
- # TODO: Change them to a constant and use case-in instead
- raise 'unreachable'
- end
-
- # Invalidate on redefinition (part of vm_search_method_fastpath)
- @invariants.assume_method_lookup_stable(jit, cme)
-
- jit_call_method_each_type(jit, ctx, asm, ci, argc, flags, cme)
- end
-
- # vm_call_method_each_type
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def jit_call_method_each_type(jit, ctx, asm, ci, argc, flags, cme)
- case cme.def.type
- when C.VM_METHOD_TYPE_ISEQ
- jit_call_iseq_setup(jit, ctx, asm, ci, cme, flags, argc)
- else
- asm.incr_counter(:send_not_iseq)
- return CantCompile
- end
- end
-
- # vm_call_iseq_setup
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def jit_call_iseq_setup(jit, ctx, asm, ci, cme, flags, argc)
- iseq = def_iseq_ptr(cme.def)
- opt_pc = jit_callee_setup_arg(jit, ctx, asm, ci, flags, iseq)
- if opt_pc == CantCompile
- # We hit some unsupported path of vm_callee_setup_arg
- return CantCompile
- end
-
- if flags & C.VM_CALL_TAILCALL != 0
- # We don't support vm_call_iseq_setup_tailcall
- asm.incr_counter(:send_tailcall)
- return CantCompile
- end
- jit_call_iseq_setup_normal(jit, ctx, asm, ci, cme, flags, argc, iseq)
- end
-
- # vm_call_iseq_setup_normal (vm_call_iseq_setup_2 -> vm_call_iseq_setup_normal)
- def jit_call_iseq_setup_normal(jit, ctx, asm, ci, cme, flags, argc, iseq)
- # Save caller SP and PC before pushing a callee frame for backtrace and side exits
- asm.comment('save SP to caller CFP')
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- sp_index = ctx.stack_size - 1 - argc - ((flags & C.VM_CALL_ARGS_BLOCKARG == 0) ? 0 : 1) # Pop receiver and arguments for side exits
- asm.lea(:rax, [SP, C.VALUE.size * sp_index])
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], :rax)
-
- asm.comment('save PC to caller CFP')
- next_pc = jit.pc + jit.insn.len * C.VALUE.size # Use the next one for backtrace and side exits
- asm.mov(:rax, next_pc)
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax)
-
- frame_type = C.VM_FRAME_MAGIC_METHOD | C.VM_ENV_FLAG_LOCAL
- jit_push_frame(jit, ctx, asm, ci, cme, flags, argc, iseq, frame_type, next_pc)
- end
-
- # vm_push_frame
- #
- # Frame structure:
- # | args | locals | cme/cref | block_handler/prev EP | frame type (EP here) | stack bottom (SP here)
- def jit_push_frame(jit, ctx, asm, ci, cme, flags, argc, iseq, frame_type, next_pc)
- # TODO: stack overflow check
-
- local_size = iseq.body.local_table_size - iseq.body.param.size
- local_size.times do |i|
- asm.comment('set local variables') if i == 0
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- local_index = ctx.stack_size + i
- asm.mov([SP, C.VALUE.size * local_index], Qnil)
- end
-
- assert_equal(ctx.sp_offset, ctx.stack_size) # TODO: support SP motion
- sp_offset = ctx.stack_size + local_size + 3
- asm.add(SP, C.VALUE.size * sp_offset)
-
- asm.comment('set cme')
- asm.mov(:rax, cme.to_i)
- asm.mov([SP, C.VALUE.size * -3], :rax)
-
- asm.comment('set specval')
- asm.mov([SP, C.VALUE.size * -2], C.VM_BLOCK_HANDLER_NONE)
-
- asm.comment('set frame type')
- asm.mov([SP, C.VALUE.size * -1], frame_type)
-
- asm.comment('move CFP register to callee CFP')
- asm.sub(CFP, C.rb_control_frame_t.size);
-
- # Not setting PC since JIT code will do that as needed
- asm.comment('set SP to callee CFP')
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP)
- asm.comment('set ISEQ to callee CFP')
- asm.mov(:rax, iseq.to_i)
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:iseq)], :rax)
- asm.comment('set self to callee CFP')
- self_index = -(1 + argc + ((flags & C.VM_CALL_ARGS_BLOCKARG == 0) ? 0 : 1) + local_size + 3)
- asm.mov(:rax, [SP, C.VALUE.size * self_index])
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:self)], :rax)
- asm.comment('set EP to callee CFP')
- asm.lea(:rax, [SP, C.VALUE.size * -1])
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:ep)], :rax)
- asm.comment('set block_code to callee CFP')
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:block_code)], 0)
- asm.comment('set BP to callee CFP')
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:__bp__)], SP) # TODO: get rid of this!!
-
- # Stub cfp->jit_return
- return_ctx = ctx.dup
- return_ctx.stack_size -= argc + ((flags & C.VM_CALL_ARGS_BLOCKARG == 0) ? 0 : 1) # Pop args
- return_ctx.sp_offset = 1 # SP is in the position after popping a receiver and arguments
- branch_stub = BranchStub.new(
- iseq: jit.iseq,
- shape: Default,
- target0: BranchTarget.new(ctx: return_ctx, pc: next_pc),
- )
- branch_stub.target0.address = Assembler.new.then do |ocb_asm|
- @exit_compiler.compile_branch_stub(return_ctx, ocb_asm, branch_stub, true)
- @ocb.write(ocb_asm)
- end
- branch_stub.compile = proc do |branch_asm|
- branch_asm.comment('set jit_return to callee CFP')
- branch_asm.stub(branch_stub) do
- case branch_stub.shape
- in Default
- branch_asm.mov(:rax, branch_stub.target0.address)
- branch_asm.mov([CFP, C.rb_control_frame_t.offsetof(:jit_return)], :rax)
- end
- end
- end
- branch_stub.compile.call(asm)
-
- asm.comment('set callee CFP to ec->cfp')
- asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], CFP)
-
- # Jump to a stub for the callee ISEQ
- callee_ctx = Context.new
- stub_next_block(iseq, iseq.body.iseq_encoded.to_i, callee_ctx, asm)
-
- EndBlock
- end
-
- # vm_callee_setup_arg: Set up args and return opt_pc (or CantCompile)
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def jit_callee_setup_arg(jit, ctx, asm, ci, flags, iseq)
- if flags & C.VM_CALL_KW_SPLAT == 0
- if C.rb_simple_iseq_p(iseq)
- if jit_caller_setup_arg(jit, ctx, asm, flags) == CantCompile
- return CantCompile
- end
- if jit_caller_remove_empty_kw_splat(jit, ctx, asm, flags) == CantCompile
- return CantCompile
- end
-
- if C.vm_ci_argc(ci) != iseq.body.param.lead_num
- # argument_arity_error
- return CantCompile
- end
-
- return 0
- else
- # We don't support the remaining `else if`s yet.
- return CantCompile
- end
- end
-
- # We don't support setup_parameters_complex
- return CantCompile
- end
-
- # CALLER_SETUP_ARG: Return CantCompile if not supported
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def jit_caller_setup_arg(jit, ctx, asm, flags)
- if flags & C.VM_CALL_ARGS_SPLAT != 0
- # We don't support vm_caller_setup_arg_splat
- asm.incr_counter(:send_args_splat)
- return CantCompile
- end
- if flags & (C.VM_CALL_KWARG | C.VM_CALL_KW_SPLAT) != 0
- # We don't support keyword args either
- asm.incr_counter(:send_kwarg)
- return CantCompile
- end
- end
-
- # CALLER_REMOVE_EMPTY_KW_SPLAT: Return CantCompile if not supported
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def jit_caller_remove_empty_kw_splat(jit, ctx, asm, flags)
- if (flags & C.VM_CALL_KW_SPLAT) > 0
- # We don't support removing the last Hash argument
- return CantCompile
- end
- end
-
- def assert_equal(left, right)
- if left != right
- raise "'#{left.inspect}' was not '#{right.inspect}'"
- end
- end
-
- def fixnum?(obj)
- flag = C.RUBY_FIXNUM_FLAG
- (C.to_value(obj) & flag) == flag
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- # @param asm [RubyVM::MJIT::Assembler]
- def defer_compilation(jit, ctx, asm)
- # Make a stub to compile the current insn
- stub_next_block(jit.iseq, jit.pc, ctx, asm, comment: 'defer_compilation')
- end
-
- def stub_next_block(iseq, pc, ctx, asm, comment: 'stub_next_block')
- branch_stub = BranchStub.new(
- iseq:,
- shape: Default,
- target0: BranchTarget.new(ctx:, pc:),
- )
- branch_stub.target0.address = Assembler.new.then do |ocb_asm|
- @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true)
- @ocb.write(ocb_asm)
- end
- branch_stub.compile = proc do |branch_asm|
- branch_asm.comment(comment)
- branch_asm.stub(branch_stub) do
- case branch_stub.shape
- in Default
- branch_asm.jmp(branch_stub.target0.address)
- in Next0
- # Just write the block without a jump
- end
- end
- end
- branch_stub.compile.call(asm)
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param ctx [RubyVM::MJIT::Context]
- def side_exit(jit, ctx)
- if side_exit = jit.side_exits[jit.pc]
- return side_exit
- end
- asm = Assembler.new
- @exit_compiler.compile_side_exit(jit, ctx, asm)
- jit.side_exits[jit.pc] = @ocb.write(asm)
- end
-
- def def_iseq_ptr(cme_def)
- C.rb_iseq_check(cme_def.body.iseq.iseqptr)
- end
- end
-end
diff --git a/lib/ruby_vm/mjit/invariants.rb b/lib/ruby_vm/mjit/invariants.rb
deleted file mode 100644
index 49cd2dee4d..0000000000
--- a/lib/ruby_vm/mjit/invariants.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'set'
-
-module RubyVM::MJIT
- class Invariants
- # @param cb [CodeBlock]
- # @param ocb [CodeBlock]
- # @param exit_compiler [RubyVM::MJIT::ExitCompiler]
- def initialize(cb, ocb, exit_compiler)
- @cb = cb
- @ocb = ocb
- @exit_compiler = exit_compiler
- @bop_blocks = Set.new # TODO: actually invalidate this
- @cme_blocks = Hash.new { |h, k| h[k] = Set.new }
-
- invariants = self
- hooks = Module.new
- hooks.define_method(:on_cme_invalidate) do |cme|
- invariants.on_cme_invalidate(cme)
- end
- Hooks.singleton_class.prepend(hooks)
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- # @param klass [Integer]
- # @param op [Integer]
- def assume_bop_not_redefined(jit, klass, op)
- return false unless C.BASIC_OP_UNREDEFINED_P(klass, op)
-
- ensure_block_entry_exit(jit.block, cause: 'assume_bop_not_redefined')
- @bop_blocks << jit.block
- true
- end
-
- # @param jit [RubyVM::MJIT::JITState]
- def assume_method_lookup_stable(jit, cme)
- ensure_block_entry_exit(jit.block, cause: 'assume_method_lookup_stable')
- @cme_blocks[cme.to_i] << jit.block
- end
-
- def on_cme_invalidate(cme)
- @cme_blocks.fetch(cme.to_i, []).each do |block|
- @cb.with_write_addr(block.start_addr) do
- asm = Assembler.new
- asm.jmp(block.entry_exit)
- @cb.write(asm)
- end
- # TODO: re-generate branches that refer to this block
- end
- end
-
- private
-
- # @param block [RubyVM::MJIT::Block]
- def ensure_block_entry_exit(block, cause:)
- if block.entry_exit.nil?
- asm = Assembler.new
- @exit_compiler.compile_entry_exit(block.pc, asm, cause:)
- block.entry_exit = @ocb.write(asm)
- end
- end
- end
-end
diff --git a/lib/ruby_vm/mjit/jit_state.rb b/lib/ruby_vm/mjit/jit_state.rb
deleted file mode 100644
index 1f888a02ae..0000000000
--- a/lib/ruby_vm/mjit/jit_state.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module RubyVM::MJIT
- class JITState < Struct.new(
- :iseq, # @param `RubyVM::MJIT::CPointer::Struct_rb_iseq_t`
- :pc, # @param [Integer] The JIT target PC
- :cfp, # @param `RubyVM::MJIT::CPointer::Struct_rb_control_frame_t` The JIT source CFP (before MJIT is called)
- :block, # @param [RubyVM::MJIT::Block]
- :side_exits, # @param [Hash{ Integer => Integer }] { PC => address }
- )
- def initialize(side_exits: {}, **) = super
-
- def insn
- Compiler.decode_insn(C.VALUE.new(pc).*)
- end
-
- def operand(index)
- C.VALUE.new(pc)[index + 1]
- end
-
- def at_current_insn?
- pc == cfp.pc.to_i
- end
-
- def peek_at_stack(depth_from_top)
- raise 'not at current insn' unless at_current_insn?
- offset = -(1 + depth_from_top)
- value = (cfp.sp + offset).*
- C.to_ruby(value)
- end
- end
-end
diff --git a/lib/ruby_vm/mjit/stats.rb b/lib/ruby_vm/mjit/stats.rb
deleted file mode 100644
index bc5a30738e..0000000000
--- a/lib/ruby_vm/mjit/stats.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-# frozen_string_literal: true
-module RubyVM::MJIT
- def self.runtime_stats
- stats = {}
-
- # Insn exits
- INSNS.each_value do |insn|
- exits = C.mjit_insn_exits[insn.bin]
- if exits > 0
- stats[:"exit_#{insn.name}"] = exits
- end
- end
-
- # Runtime stats
- C.rb_mjit_runtime_counters.members.each do |member|
- stats[member] = C.rb_mjit_counters.public_send(member)
- end
-
- # Other stats are calculated here
- stats[:side_exit_count] = stats.select { |name, _count| name.start_with?('exit_') }.sum(&:last)
- if stats[:vm_insns_count] > 0
- retired_in_mjit = stats[:mjit_insns_count] - stats[:side_exit_count]
- stats[:total_insns_count] = retired_in_mjit + stats[:vm_insns_count]
- stats[:ratio_in_mjit] = 100.0 * retired_in_mjit / stats[:total_insns_count]
- end
-
- stats
- end
-
- class << self
- private
-
- def print_stats
- stats = runtime_stats
- $stderr.puts("***MJIT: Printing MJIT statistics on exit***")
-
- print_counters(stats, prefix: 'send_', prompt: 'method call exit reasons')
-
- $stderr.puts "compiled_block_count: #{format('%10d', stats[:compiled_block_count])}"
- $stderr.puts "side_exit_count: #{format('%10d', stats[:side_exit_count])}"
- $stderr.puts "total_insns_count: #{format('%10d', stats[:total_insns_count])}" if stats.key?(:total_insns_count)
- $stderr.puts "vm_insns_count: #{format('%10d', stats[:vm_insns_count])}" if stats.key?(:vm_insns_count)
- $stderr.puts "mjit_insns_count: #{format('%10d', stats[:mjit_insns_count])}"
- $stderr.puts "ratio_in_mjit: #{format('%9.1f', stats[:ratio_in_mjit])}%" if stats.key?(:ratio_in_mjit)
-
- print_exit_counts(stats)
- end
-
- def print_counters(stats, prefix:, prompt:)
- $stderr.puts("#{prompt}: ")
- counters = stats.filter { |key, _| key.start_with?(prefix) }
- counters.filter! { |_, value| value != 0 }
- counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) }
-
- if counters.empty?
- $stderr.puts(" (all relevant counters are zero)")
- return
- end
-
- counters = counters.to_a
- counters.sort_by! { |(_, counter_value)| counter_value }
- longest_name_length = counters.max_by { |(name, _)| name.length }.first.length
- total = counters.sum { |(_, counter_value)| counter_value }
-
- counters.reverse_each do |(name, value)|
- percentage = value.fdiv(total) * 100
- $stderr.printf(" %*s %10d (%4.1f%%)\n", longest_name_length, name, value, percentage)
- end
- end
-
- def print_exit_counts(stats, how_many: 20, padding: 2)
- exits = stats.filter_map { |name, count| [name.to_s.delete_prefix('exit_'), count] if name.start_with?('exit_') }.to_h
- return if exits.empty?
-
- top_exits = exits.sort_by { |_name, count| -count }.first(how_many).to_h
- total_exits = exits.values.sum
- $stderr.puts "Top-#{top_exits.size} most frequent exit ops (#{format("%.1f", 100.0 * top_exits.values.sum / total_exits)}% of exits):"
-
- name_width = top_exits.map { |name, _count| name.length }.max + padding
- count_width = top_exits.map { |_name, count| count.to_s.length }.max + padding
- top_exits.each do |name, count|
- ratio = 100.0 * count / total_exits
- $stderr.puts "#{format("%#{name_width}s", name)}: #{format("%#{count_width}d", count)} (#{format('%.1f', ratio)}%)"
- end
- end
- end
-end