Class: RubyVM::RJIT::Compiler

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_vm/rjit/compiler.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCompiler

Returns a new instance of Compiler.



48
49
50
51
52
53
54
55
56
# File 'lib/ruby_vm/rjit/compiler.rb', line 48

def initialize
  mem_size = C.rjit_opts.exec_mem_size * 1024 * 1024
  mem_block = C.mmap(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)
  Invariants.initialize(@cb, @ocb, self, @exit_compiler)
end

Instance Attribute Details

#write_posObject

Returns the value of attribute write_pos.



42
43
44
# File 'lib/ruby_vm/rjit/compiler.rb', line 42

def write_pos
  @write_pos
end

Class Method Details

.decode_insn(encoded) ⇒ Object



44
45
46
# File 'lib/ruby_vm/rjit/compiler.rb', line 44

def self.decode_insn(encoded)
  INSNS.fetch(C.rb_vm_insn_decode(encoded))
end

Instance Method Details

#branch_stub_hit(branch_stub, cfp, target0_p) ⇒ Integer

Compile a branch stub.

Parameters:

  • branch_stub (RubyVM::RJIT::BranchStub)
  • cfp

    ‘RubyVM::RJIT::CPointer::Struct_rb_control_frame_t`

  • target0_p (TrueClass, FalseClass)

Returns:

  • (Integer)

    The starting address of the compiled branch stub



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/ruby_vm/rjit/compiler.rb', line 122

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

  # Reuse an existing block if it already exists
  block = find_block(branch_stub.iseq, target.pc, target.ctx)

  # If the branch stub's jump is the last code, allow overwriting part of
  # the old branch code with the new block code.
  fallthrough = block.nil? && @cb.write_addr == branch_stub.end_addr
  if fallthrough
    # 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
  end

  # Reuse or generate a block
  if block
    target.address = block.start_addr
  else
    jit = JITState.new(iseq: branch_stub.iseq, cfp:)
    target.address = Assembler.new.then do |asm|
      compile_block(asm, jit:, pc: target.pc, ctx: target.ctx.dup)
      @cb.write(asm)
    end
    block = jit.block
  end
  block.incoming << branch_stub # prepare for invalidate_block

  # Re-generate the branch code for non-fallthrough cases
  unless fallthrough
    @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

  return target.address
rescue Exception => e
  STDERR.puts e.full_message
  exit 1
end

#compile(iseq, cfp) ⇒ Object

Compile an ISEQ from its entry point.

Parameters:

  • iseq

    ‘RubyVM::RJIT::CPointer::Struct_rb_iseq_t`

  • cfp

    ‘RubyVM::RJIT::CPointer::Struct_rb_control_frame_t`



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/ruby_vm/rjit/compiler.rb', line 61

def compile(iseq, cfp)
  return unless supported_platform?
  pc = cfp.pc.to_i
  jit = JITState.new(iseq:, cfp:)
  asm = Assembler.new
  compile_prologue(asm, iseq, pc)
  compile_block(asm, jit:, pc:)
  iseq.body.jit_entry = @cb.write(asm)
rescue Exception => e
  STDERR.puts "#{e.class}: #{e.message}"
  STDERR.puts e.backtrace
  exit 1
end

#entry_stub_hit(entry_stub, cfp) ⇒ Object

Compile an entry.

Parameters:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/ruby_vm/rjit/compiler.rb', line 77

def entry_stub_hit(entry_stub, cfp)
  # Compile a new entry guard as a next entry
  pc = cfp.pc.to_i
  next_entry = Assembler.new.then do |asm|
    compile_entry_chain_guard(asm, cfp.iseq, pc)
    @cb.write(asm)
  end

  # Try to find an existing compiled version of this block
  ctx = Context.new
  block = find_block(cfp.iseq, pc, ctx)
  if block
    # If an existing block is found, generate a jump to the block.
    asm = Assembler.new
    asm.jmp(block.start_addr)
    @cb.write(asm)
  else
    # If this block hasn't yet been compiled, generate blocks after the entry guard.
    asm = Assembler.new
    jit = JITState.new(iseq: cfp.iseq, cfp:)
    compile_block(asm, jit:, pc:, ctx:)
    @cb.write(asm)

    block = jit.block
  end

  # Regenerate the previous entry
  @cb.with_write_addr(entry_stub.start_addr) do
    # The last instruction of compile_entry_chain_guard is jne
    asm = Assembler.new
    asm.jne(next_entry)
    @cb.write(asm)
  end

  return block.start_addr
rescue Exception => e
  STDERR.puts e.full_message
  exit 1
end

#invalidate_block(block) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/ruby_vm/rjit/compiler.rb', line 186

def invalidate_block(block)
  iseq = block.iseq
  # Avoid touching GCed ISEQs. We assume it won't be re-entered.
  return unless C.imemo_type_p(iseq, C.imemo_iseq)

  # Remove this block from the version array
  remove_block(iseq, block)

  # Invalidate the block with entry exit
  unless block.invalidated
    @cb.with_write_addr(block.start_addr) do
      asm = Assembler.new
      asm.comment('invalidate_block')
      asm.jmp(block.entry_exit)
      @cb.write(asm)
    end
    block.invalidated = true
  end

  # Re-stub incoming branches
  block.incoming.each do |branch_stub|
    target = [branch_stub.target0, branch_stub.target1].compact.find do |target|
      target.pc == block.pc && target.ctx == block.ctx
    end
    next if target.nil?
    # TODO: Could target.address be a stub address? Is invalidation not needed in that case?

    # If the target being re-generated is currently a fallthrough block,
    # the fallthrough code must be rewritten with a jump to the stub.
    if target.address == branch_stub.end_addr
      branch_stub.shape = Default
    end

    target.address = Assembler.new.then do |ocb_asm|
      @exit_compiler.compile_branch_stub(block.ctx, ocb_asm, branch_stub, target == branch_stub.target0)
      @ocb.write(ocb_asm)
    end
    @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
end

#invalidate_blocks(iseq, pc) ⇒ Object

Parameters:

  • iseq

    ‘RubyVM::RJIT::CPointer::Struct_rb_iseq_t`

  • pc (Integer)


174
175
176
177
178
179
180
181
182
183
184
# File 'lib/ruby_vm/rjit/compiler.rb', line 174

def invalidate_blocks(iseq, pc)
  list_blocks(iseq, pc).each do |block|
    invalidate_block(block)
  end

  # If they were the ISEQ's first blocks, re-compile RJIT entry as well
  if iseq.body.iseq_encoded.to_i == pc
    iseq.body.jit_entry = 0
    iseq.body.jit_entry_calls = 0
  end
end