summaryrefslogtreecommitdiff
path: root/lib/mjit
diff options
context:
space:
mode:
authorTakashi Kokubun <takashikkbn@gmail.com>2022-09-04 21:53:46 -0700
committerGitHub <noreply@github.com>2022-09-04 21:53:46 -0700
commit3767c6a90d8970f9b39e9ed116a7b9bbac3f9f26 (patch)
tree36e1c581b45edebcba4b33e1ca3f35c6fffa05f4 /lib/mjit
parent277498e2a2b62b564e3d39ca54aa15b6e8a2c41a (diff)
Ruby MJIT (#6028)
Notes
Notes: Merged-By: k0kubun <takashikkbn@gmail.com>
Diffstat (limited to 'lib/mjit')
-rw-r--r--lib/mjit/c_32.rb436
-rw-r--r--lib/mjit/c_64.rb437
-rw-r--r--lib/mjit/c_pointer.rb333
-rw-r--r--lib/mjit/c_type.rb77
-rw-r--r--lib/mjit/compiler.rb1019
5 files changed, 2302 insertions, 0 deletions
diff --git a/lib/mjit/c_32.rb b/lib/mjit/c_32.rb
new file mode 100644
index 0000000000..ee8324ba4d
--- /dev/null
+++ b/lib/mjit/c_32.rb
@@ -0,0 +1,436 @@
+require_relative 'c_type'
+
+module RubyVM::MJIT
+ C = Object.new
+
+ def C.NOT_COMPILED_STACK_SIZE = - 1
+
+ def C.USE_LAZY_LOAD = false
+
+ def C.USE_RVARGC = true
+
+ def C.VM_CALL_KW_SPLAT = ( 0x01 << self.VM_CALL_KW_SPLAT_bit )
+
+ def C.VM_CALL_TAILCALL = ( 0x01 << self.VM_CALL_TAILCALL_bit )
+
+ def C.VM_METHOD_TYPE_CFUNC = 1
+
+ def C.VM_METHOD_TYPE_ISEQ = 0
+
+ def C.VM_CALL_KW_SPLAT_bit = 7
+
+ def C.VM_CALL_TAILCALL_bit = 8
+
+ def C.CALL_DATA
+ @CALL_DATA ||= self.rb_call_data
+ end
+
+ def C.IC
+ @IC ||= self.iseq_inline_constant_cache
+ end
+
+ def C.IVC
+ @IVC ||= self.iseq_inline_iv_cache_entry
+ end
+
+ def C.RB_BUILTIN
+ @RB_BUILTIN ||= self.rb_builtin_function
+ end
+
+ def C.VALUE
+ @VALUE ||= CType::Immediate.new(-5)
+ end
+
+ def C.compile_branch
+ @compile_branch ||= CType::Struct.new(
+ "compile_branch", 8,
+ stack_size: [0, CType::Immediate.new(-4)],
+ finish_p: [32, self._Bool],
+ )
+ end
+
+ def C.compile_status
+ @compile_status ||= CType::Struct.new(
+ "compile_status", 68,
+ success: [0, self._Bool],
+ stack_size_for_pos: [32, CType::Pointer.new { CType::Immediate.new(4) }],
+ local_stack_p: [64, self._Bool],
+ is_entries: [96, CType::Pointer.new { self.iseq_inline_storage_entry }],
+ cc_entries_index: [128, CType::Immediate.new(4)],
+ compiled_iseq: [160, CType::Pointer.new { self.rb_iseq_constant_body }],
+ compiled_id: [192, CType::Immediate.new(4)],
+ compile_info: [224, CType::Pointer.new { self.rb_mjit_compile_info }],
+ merge_ivar_guards_p: [256, self._Bool],
+ ivar_serial: [288, self.rb_serial_t],
+ max_ivar_index: [352, CType::Immediate.new(-4)],
+ inlined_iseqs: [384, CType::Pointer.new { CType::Pointer.new { self.rb_iseq_constant_body } }],
+ inline_context: [416, self.inlined_call_context],
+ )
+ end
+
+ def C.inlined_call_context
+ @inlined_call_context ||= CType::Struct.new(
+ "inlined_call_context", 16,
+ orig_argc: [0, CType::Immediate.new(4)],
+ me: [32, self.VALUE],
+ param_size: [64, CType::Immediate.new(4)],
+ local_size: [96, CType::Immediate.new(4)],
+ )
+ end
+
+ def C.iseq_inline_constant_cache
+ @iseq_inline_constant_cache ||= CType::Struct.new(
+ "iseq_inline_constant_cache", 8,
+ entry: [0, CType::Pointer.new { self.iseq_inline_constant_cache_entry }],
+ segments: [32, CType::Pointer.new { self.ID }],
+ )
+ end
+
+ def C.iseq_inline_constant_cache_entry
+ @iseq_inline_constant_cache_entry ||= CType::Struct.new(
+ "iseq_inline_constant_cache_entry", 20,
+ flags: [0, self.VALUE],
+ value: [32, self.VALUE],
+ _unused1: [64, self.VALUE],
+ _unused2: [96, self.VALUE],
+ ic_cref: [128, CType::Pointer.new { self.rb_cref_t }],
+ )
+ end
+
+ def C.iseq_inline_iv_cache_entry
+ @iseq_inline_iv_cache_entry ||= CType::Struct.new(
+ "iseq_inline_iv_cache_entry", 4,
+ entry: [0, CType::Pointer.new { self.rb_iv_index_tbl_entry }],
+ )
+ end
+
+ def C.iseq_inline_storage_entry
+ @iseq_inline_storage_entry ||= CType::Union.new(
+ "iseq_inline_storage_entry", 8,
+ once: CType::Struct.new(
+ "", 8,
+ running_thread: [0, CType::Pointer.new { self.rb_thread_struct }],
+ value: [32, self.VALUE],
+ ),
+ ic_cache: self.iseq_inline_constant_cache,
+ iv_cache: self.iseq_inline_iv_cache_entry,
+ )
+ end
+
+ def C.mjit_options
+ @mjit_options ||= CType::Struct.new(
+ "mjit_options", 24,
+ on: [0, self._Bool],
+ save_temps: [8, self._Bool],
+ warnings: [16, self._Bool],
+ debug: [24, self._Bool],
+ debug_flags: [32, CType::Pointer.new { CType::Immediate.new(2) }],
+ wait: [64, self._Bool],
+ min_calls: [96, CType::Immediate.new(-4)],
+ verbose: [128, CType::Immediate.new(4)],
+ max_cache_size: [160, CType::Immediate.new(4)],
+ )
+ end
+
+ def C.rb_builtin_function
+ @rb_builtin_function ||= CType::Struct.new(
+ "rb_builtin_function", 20,
+ func_ptr: [0, CType::Pointer.new { CType::Immediate.new(0) }],
+ argc: [32, CType::Immediate.new(4)],
+ index: [64, CType::Immediate.new(4)],
+ name: [96, CType::Pointer.new { CType::Immediate.new(2) }],
+ compiler: [128, CType::Immediate.new(1)],
+ )
+ end
+
+ def C.rb_call_data
+ @rb_call_data ||= CType::Struct.new(
+ "rb_call_data", 8,
+ ci: [0, CType::Pointer.new { self.rb_callinfo }],
+ cc: [32, CType::Pointer.new { self.rb_callcache }],
+ )
+ end
+
+ def C.rb_callable_method_entry_struct
+ @rb_callable_method_entry_struct ||= CType::Struct.new(
+ "rb_callable_method_entry_struct", 20,
+ flags: [0, self.VALUE],
+ defined_class: [32, self.VALUE],
+ def: [64, CType::Pointer.new { self.rb_method_definition_struct }],
+ called_id: [96, self.ID],
+ owner: [128, self.VALUE],
+ )
+ end
+
+ def C.rb_callcache
+ @rb_callcache ||= CType::Struct.new(
+ "rb_callcache", 20,
+ flags: [0, self.VALUE],
+ klass: [32, self.VALUE],
+ cme_: [64, CType::Pointer.new { self.rb_callable_method_entry_struct }],
+ call_: [96, self.vm_call_handler],
+ aux_: [128, CType::Union.new(
+ "", 4,
+ attr_index: CType::Immediate.new(-4),
+ method_missing_reason: self.method_missing_reason,
+ v: self.VALUE,
+ )],
+ )
+ end
+
+ def C.rb_callinfo
+ @rb_callinfo ||= CType::Struct.new(
+ "rb_callinfo", 20,
+ flags: [0, self.VALUE],
+ kwarg: [32, CType::Pointer.new { self.rb_callinfo_kwarg }],
+ mid: [64, self.VALUE],
+ flag: [96, self.VALUE],
+ argc: [128, self.VALUE],
+ )
+ end
+
+ def C.rb_cref_t
+ @rb_cref_t ||= CType::Struct.new(
+ "rb_cref_struct", 20,
+ flags: [0, self.VALUE],
+ refinements: [32, self.VALUE],
+ klass_or_self: [64, self.VALUE],
+ next: [96, CType::Pointer.new { self.rb_cref_struct }],
+ scope_visi: [128, self.rb_scope_visibility_t],
+ )
+ end
+
+ def C.rb_iseq_constant_body
+ @rb_iseq_constant_body ||= CType::Struct.new(
+ "rb_iseq_constant_body", 204,
+ type: [0, self.rb_iseq_type],
+ iseq_size: [32, CType::Immediate.new(-4)],
+ iseq_encoded: [64, CType::Pointer.new { self.VALUE }],
+ param: [96, CType::Struct.new(
+ "", 40,
+ flags: [0, CType::Struct.new(
+ "", 4,
+ has_lead: [0, CType::BitField.new(1, 0)],
+ has_opt: [1, CType::BitField.new(1, 1)],
+ has_rest: [2, CType::BitField.new(1, 2)],
+ has_post: [3, CType::BitField.new(1, 3)],
+ has_kw: [4, CType::BitField.new(1, 4)],
+ has_kwrest: [5, CType::BitField.new(1, 5)],
+ has_block: [6, CType::BitField.new(1, 6)],
+ ambiguous_param0: [7, CType::BitField.new(1, 7)],
+ accepts_no_kwarg: [8, CType::BitField.new(1, 0)],
+ ruby2_keywords: [9, CType::BitField.new(1, 1)],
+ )],
+ size: [32, CType::Immediate.new(-4)],
+ lead_num: [64, CType::Immediate.new(4)],
+ opt_num: [96, CType::Immediate.new(4)],
+ rest_start: [128, CType::Immediate.new(4)],
+ post_start: [160, CType::Immediate.new(4)],
+ post_num: [192, CType::Immediate.new(4)],
+ block_start: [224, CType::Immediate.new(4)],
+ opt_table: [256, CType::Pointer.new { self.VALUE }],
+ keyword: [288, CType::Pointer.new { self.rb_iseq_param_keyword }],
+ )],
+ location: [416, self.rb_iseq_location_t],
+ insns_info: [704, self.iseq_insn_info],
+ local_table: [832, CType::Pointer.new { self.ID }],
+ catch_table: [864, CType::Pointer.new { self.iseq_catch_table }],
+ parent_iseq: [896, CType::Pointer.new { self.rb_iseq_struct }],
+ local_iseq: [928, CType::Pointer.new { self.rb_iseq_struct }],
+ is_entries: [960, CType::Pointer.new { self.iseq_inline_storage_entry }],
+ call_data: [992, CType::Pointer.new { self.rb_call_data }],
+ variable: [1024, CType::Struct.new(
+ "", 20,
+ flip_count: [0, self.rb_snum_t],
+ script_lines: [32, self.VALUE],
+ coverage: [64, self.VALUE],
+ pc2branchindex: [96, self.VALUE],
+ original_iseq: [128, CType::Pointer.new { self.VALUE }],
+ )],
+ local_table_size: [1184, CType::Immediate.new(-4)],
+ ic_size: [1216, CType::Immediate.new(-4)],
+ ise_size: [1248, CType::Immediate.new(-4)],
+ ivc_size: [1280, CType::Immediate.new(-4)],
+ icvarc_size: [1312, CType::Immediate.new(-4)],
+ ci_size: [1344, CType::Immediate.new(-4)],
+ stack_max: [1376, CType::Immediate.new(-4)],
+ mark_bits: [1408, CType::Union.new(
+ "", 4,
+ list: CType::Pointer.new { self.iseq_bits_t },
+ single: self.iseq_bits_t,
+ )],
+ catch_except_p: [1440, self._Bool],
+ builtin_inline_p: [1448, self._Bool],
+ outer_variables: [1472, CType::Pointer.new { self.rb_id_table }],
+ mandatory_only_iseq: [1504, CType::Pointer.new { self.rb_iseq_t }],
+ jit_func: [1536, CType::Immediate.new(1)],
+ total_calls: [1568, CType::Immediate.new(-5)],
+ jit_unit: [1600, CType::Pointer.new { self.rb_mjit_unit }],
+ )
+ end
+
+ def C.rb_iseq_location_t
+ @rb_iseq_location_t ||= CType::Struct.new(
+ "rb_iseq_location_struct", 36,
+ pathobj: [0, self.VALUE, true],
+ base_label: [32, self.VALUE, true],
+ label: [64, self.VALUE, true],
+ first_lineno: [96, self.VALUE, true],
+ node_id: [128, CType::Immediate.new(4)],
+ code_location: [160, self.rb_code_location_t],
+ )
+ end
+
+ def C.rb_iseq_struct
+ @rb_iseq_struct ||= CType::Struct.new(
+ "rb_iseq_struct", 20,
+ flags: [0, self.VALUE],
+ wrapper: [32, self.VALUE],
+ body: [64, CType::Pointer.new { self.rb_iseq_constant_body }],
+ aux: [96, CType::Union.new(
+ "", 8,
+ compile_data: CType::Pointer.new { self.iseq_compile_data },
+ loader: CType::Struct.new(
+ "", 8,
+ obj: [0, self.VALUE],
+ index: [32, CType::Immediate.new(4)],
+ ),
+ exec: CType::Struct.new(
+ "", 8,
+ local_hooks: [0, CType::Pointer.new { self.rb_hook_list_struct }],
+ global_trace_events: [32, self.rb_event_flag_t],
+ ),
+ )],
+ )
+ end
+
+ def C.rb_iseq_t
+ @rb_iseq_t ||= self.rb_iseq_struct
+ end
+
+ def C.rb_iv_index_tbl_entry
+ @rb_iv_index_tbl_entry ||= CType::Struct.new(
+ "rb_iv_index_tbl_entry", 16,
+ index: [0, CType::Immediate.new(-4)],
+ class_serial: [32, self.rb_serial_t],
+ class_value: [96, self.VALUE],
+ )
+ end
+
+ def C.rb_method_definition_struct
+ @rb_method_definition_struct ||= CType::Struct.new(
+ "rb_method_definition_struct", 28,
+ type: [0, self.rb_method_type_t],
+ iseq_overload: [4, CType::BitField.new(1, 4)],
+ alias_count: [5, CType::BitField.new(27, 5)],
+ complemented_count: [32, CType::BitField.new(28, 0)],
+ no_redef_warning: [60, CType::BitField.new(1, 4)],
+ body: [64, CType::Union.new(
+ "", 12,
+ iseq: self.rb_method_iseq_t,
+ cfunc: self.rb_method_cfunc_t,
+ attr: self.rb_method_attr_t,
+ alias: self.rb_method_alias_t,
+ refined: self.rb_method_refined_t,
+ bmethod: self.rb_method_bmethod_t,
+ optimized: self.rb_method_optimized_t,
+ )],
+ original_id: [160, self.ID],
+ method_serial: [192, CType::Immediate.new(-4)],
+ )
+ end
+
+ def C.rb_method_iseq_t
+ @rb_method_iseq_t ||= CType::Struct.new(
+ "rb_method_iseq_struct", 8,
+ iseqptr: [0, CType::Pointer.new { self.rb_iseq_t }],
+ cref: [32, CType::Pointer.new { self.rb_cref_t }],
+ )
+ end
+
+ def C.rb_method_type_t
+ @rb_method_type_t ||= CType::Immediate.new(4)
+ end
+
+ def C.rb_mjit_compile_info
+ @rb_mjit_compile_info ||= CType::Struct.new(
+ "rb_mjit_compile_info", 5,
+ disable_ivar_cache: [0, self._Bool],
+ disable_exivar_cache: [8, self._Bool],
+ disable_send_cache: [16, self._Bool],
+ disable_inlining: [24, self._Bool],
+ disable_const_cache: [32, self._Bool],
+ )
+ end
+
+ def C.rb_mjit_unit
+ @rb_mjit_unit ||= CType::Struct.new(
+ "rb_mjit_unit", 36,
+ unode: [0, self.ccan_list_node],
+ id: [64, CType::Immediate.new(4)],
+ handle: [96, CType::Pointer.new { CType::Immediate.new(0) }],
+ iseq: [128, CType::Pointer.new { self.rb_iseq_t }],
+ used_code_p: [160, self._Bool],
+ compact_p: [168, self._Bool],
+ compile_info: [176, self.rb_mjit_compile_info],
+ cc_entries: [224, CType::Pointer.new { CType::Pointer.new { self.rb_callcache } }],
+ cc_entries_size: [256, CType::Immediate.new(-4)],
+ )
+ end
+
+ def C.rb_serial_t
+ @rb_serial_t ||= CType::Immediate.new(-6)
+ end
+
+ def C._Bool = CType::Bool.new
+
+ def C.ID = CType::Stub.new(:ID)
+
+ def C.rb_thread_struct = CType::Stub.new(:rb_thread_struct)
+
+ def C.vm_call_handler = CType::Stub.new(:vm_call_handler)
+
+ def C.method_missing_reason = CType::Stub.new(:method_missing_reason)
+
+ def C.rb_callinfo_kwarg = CType::Stub.new(:rb_callinfo_kwarg)
+
+ def C.rb_cref_struct = CType::Stub.new(:rb_cref_struct)
+
+ def C.rb_scope_visibility_t = CType::Stub.new(:rb_scope_visibility_t)
+
+ def C.rb_iseq_type = CType::Stub.new(:rb_iseq_type)
+
+ def C.rb_iseq_param_keyword = CType::Stub.new(:rb_iseq_param_keyword)
+
+ def C.iseq_insn_info = CType::Stub.new(:iseq_insn_info)
+
+ def C.iseq_catch_table = CType::Stub.new(:iseq_catch_table)
+
+ def C.rb_snum_t = CType::Stub.new(:rb_snum_t)
+
+ def C.iseq_bits_t = CType::Stub.new(:iseq_bits_t)
+
+ def C.rb_id_table = CType::Stub.new(:rb_id_table)
+
+ def C.rb_code_location_t = CType::Stub.new(:rb_code_location_t)
+
+ def C.iseq_compile_data = CType::Stub.new(:iseq_compile_data)
+
+ def C.rb_hook_list_struct = CType::Stub.new(:rb_hook_list_struct)
+
+ def C.rb_event_flag_t = CType::Stub.new(:rb_event_flag_t)
+
+ def C.rb_method_cfunc_t = CType::Stub.new(:rb_method_cfunc_t)
+
+ def C.rb_method_attr_t = CType::Stub.new(:rb_method_attr_t)
+
+ def C.rb_method_alias_t = CType::Stub.new(:rb_method_alias_t)
+
+ def C.rb_method_refined_t = CType::Stub.new(:rb_method_refined_t)
+
+ def C.rb_method_bmethod_t = CType::Stub.new(:rb_method_bmethod_t)
+
+ def C.rb_method_optimized_t = CType::Stub.new(:rb_method_optimized_t)
+
+ def C.ccan_list_node = CType::Stub.new(:ccan_list_node)
+end
diff --git a/lib/mjit/c_64.rb b/lib/mjit/c_64.rb
new file mode 100644
index 0000000000..cd0fd47e9e
--- /dev/null
+++ b/lib/mjit/c_64.rb
@@ -0,0 +1,437 @@
+require_relative 'c_type'
+
+module RubyVM::MJIT
+ C = Object.new
+
+ def C.NOT_COMPILED_STACK_SIZE = - 1
+
+ def C.USE_LAZY_LOAD = false
+
+ def C.USE_RVARGC = true
+
+ def C.VM_CALL_KW_SPLAT = ( 0x01 << self.VM_CALL_KW_SPLAT_bit )
+
+ def C.VM_CALL_TAILCALL = ( 0x01 << self.VM_CALL_TAILCALL_bit )
+
+ def C.VM_METHOD_TYPE_CFUNC = 1
+
+ def C.VM_METHOD_TYPE_ISEQ = 0
+
+ def C.VM_CALL_KW_SPLAT_bit = 7
+
+ def C.VM_CALL_TAILCALL_bit = 8
+
+ def C.CALL_DATA
+ @CALL_DATA ||= self.rb_call_data
+ end
+
+ def C.IC
+ @IC ||= self.iseq_inline_constant_cache
+ end
+
+ def C.IVC
+ @IVC ||= self.iseq_inline_iv_cache_entry
+ end
+
+ def C.RB_BUILTIN
+ @RB_BUILTIN ||= self.rb_builtin_function
+ end
+
+ def C.VALUE
+ @VALUE ||= CType::Immediate.new(-5)
+ end
+
+ def C.compile_branch
+ @compile_branch ||= CType::Struct.new(
+ "compile_branch", 8,
+ stack_size: [0, CType::Immediate.new(-4)],
+ finish_p: [32, self._Bool],
+ )
+ end
+
+ def C.compile_status
+ @compile_status ||= CType::Struct.new(
+ "compile_status", 120,
+ success: [0, self._Bool],
+ stack_size_for_pos: [64, CType::Pointer.new { CType::Immediate.new(4) }],
+ local_stack_p: [128, self._Bool],
+ is_entries: [192, CType::Pointer.new { self.iseq_inline_storage_entry }],
+ cc_entries_index: [256, CType::Immediate.new(4)],
+ compiled_iseq: [320, CType::Pointer.new { self.rb_iseq_constant_body }],
+ compiled_id: [384, CType::Immediate.new(4)],
+ compile_info: [448, CType::Pointer.new { self.rb_mjit_compile_info }],
+ merge_ivar_guards_p: [512, self._Bool],
+ ivar_serial: [576, self.rb_serial_t],
+ max_ivar_index: [640, CType::Immediate.new(-5)],
+ inlined_iseqs: [704, CType::Pointer.new { CType::Pointer.new { self.rb_iseq_constant_body } }],
+ inline_context: [768, self.inlined_call_context],
+ )
+ end
+
+ def C.inlined_call_context
+ @inlined_call_context ||= CType::Struct.new(
+ "inlined_call_context", 24,
+ orig_argc: [0, CType::Immediate.new(4)],
+ me: [64, self.VALUE],
+ param_size: [128, CType::Immediate.new(4)],
+ local_size: [160, CType::Immediate.new(4)],
+ )
+ end
+
+ def C.iseq_inline_constant_cache
+ @iseq_inline_constant_cache ||= CType::Struct.new(
+ "iseq_inline_constant_cache", 16,
+ entry: [0, CType::Pointer.new { self.iseq_inline_constant_cache_entry }],
+ segments: [64, CType::Pointer.new { self.ID }],
+ )
+ end
+
+ def C.iseq_inline_constant_cache_entry
+ @iseq_inline_constant_cache_entry ||= CType::Struct.new(
+ "iseq_inline_constant_cache_entry", 40,
+ flags: [0, self.VALUE],
+ value: [64, self.VALUE],
+ _unused1: [128, self.VALUE],
+ _unused2: [192, self.VALUE],
+ ic_cref: [256, CType::Pointer.new { self.rb_cref_t }],
+ )
+ end
+
+ def C.iseq_inline_iv_cache_entry
+ @iseq_inline_iv_cache_entry ||= CType::Struct.new(
+ "iseq_inline_iv_cache_entry", 8,
+ entry: [0, CType::Pointer.new { self.rb_iv_index_tbl_entry }],
+ )
+ end
+
+ def C.iseq_inline_storage_entry
+ @iseq_inline_storage_entry ||= CType::Union.new(
+ "iseq_inline_storage_entry", 16,
+ once: CType::Struct.new(
+ "", 16,
+ running_thread: [0, CType::Pointer.new { self.rb_thread_struct }],
+ value: [64, self.VALUE],
+ ),
+ ic_cache: self.iseq_inline_constant_cache,
+ iv_cache: self.iseq_inline_iv_cache_entry,
+ )
+ end
+
+ def C.mjit_options
+ @mjit_options ||= CType::Struct.new(
+ "mjit_options", 32,
+ on: [0, self._Bool],
+ save_temps: [8, self._Bool],
+ warnings: [16, self._Bool],
+ debug: [24, self._Bool],
+ debug_flags: [64, CType::Pointer.new { CType::Immediate.new(2) }],
+ wait: [128, self._Bool],
+ min_calls: [160, CType::Immediate.new(-4)],
+ verbose: [192, CType::Immediate.new(4)],
+ max_cache_size: [224, CType::Immediate.new(4)],
+ )
+ end
+
+ def C.rb_builtin_function
+ @rb_builtin_function ||= CType::Struct.new(
+ "rb_builtin_function", 32,
+ func_ptr: [0, CType::Pointer.new { CType::Immediate.new(0) }],
+ argc: [64, CType::Immediate.new(4)],
+ index: [96, CType::Immediate.new(4)],
+ name: [128, CType::Pointer.new { CType::Immediate.new(2) }],
+ compiler: [192, CType::Immediate.new(1)],
+ )
+ end
+
+ def C.rb_call_data
+ @rb_call_data ||= CType::Struct.new(
+ "rb_call_data", 16,
+ ci: [0, CType::Pointer.new { self.rb_callinfo }],
+ cc: [64, CType::Pointer.new { self.rb_callcache }],
+ )
+ end
+
+ def C.rb_callable_method_entry_struct
+ @rb_callable_method_entry_struct ||= CType::Struct.new(
+ "rb_callable_method_entry_struct", 40,
+ flags: [0, self.VALUE],
+ defined_class: [64, self.VALUE],
+ def: [128, CType::Pointer.new { self.rb_method_definition_struct }],
+ called_id: [192, self.ID],
+ owner: [256, self.VALUE],
+ )
+ end
+
+ def C.rb_callcache
+ @rb_callcache ||= CType::Struct.new(
+ "rb_callcache", 40,
+ flags: [0, self.VALUE],
+ klass: [64, self.VALUE],
+ cme_: [128, CType::Pointer.new { self.rb_callable_method_entry_struct }],
+ call_: [192, self.vm_call_handler],
+ aux_: [256, CType::Union.new(
+ "", 8,
+ attr_index: CType::Immediate.new(-4),
+ method_missing_reason: self.method_missing_reason,
+ v: self.VALUE,
+ )],
+ )
+ end
+
+ def C.rb_callinfo
+ @rb_callinfo ||= CType::Struct.new(
+ "rb_callinfo", 40,
+ flags: [0, self.VALUE],
+ kwarg: [64, CType::Pointer.new { self.rb_callinfo_kwarg }],
+ mid: [128, self.VALUE],
+ flag: [192, self.VALUE],
+ argc: [256, self.VALUE],
+ )
+ end
+
+ def C.rb_cref_t
+ @rb_cref_t ||= CType::Struct.new(
+ "rb_cref_struct", 40,
+ flags: [0, self.VALUE],
+ refinements: [64, self.VALUE],
+ klass_or_self: [128, self.VALUE],
+ next: [192, CType::Pointer.new { self.rb_cref_struct }],
+ scope_visi: [256, self.rb_scope_visibility_t],
+ )
+ end
+
+ def C.rb_iseq_constant_body
+ @rb_iseq_constant_body ||= CType::Struct.new(
+ "rb_iseq_constant_body", 336,
+ type: [0, self.rb_iseq_type],
+ iseq_size: [32, CType::Immediate.new(-4)],
+ iseq_encoded: [64, CType::Pointer.new { self.VALUE }],
+ param: [128, CType::Struct.new(
+ "", 48,
+ flags: [0, CType::Struct.new(
+ "", 4,
+ has_lead: [0, CType::BitField.new(1, 0)],
+ has_opt: [1, CType::BitField.new(1, 1)],
+ has_rest: [2, CType::BitField.new(1, 2)],
+ has_post: [3, CType::BitField.new(1, 3)],
+ has_kw: [4, CType::BitField.new(1, 4)],
+ has_kwrest: [5, CType::BitField.new(1, 5)],
+ has_block: [6, CType::BitField.new(1, 6)],
+ ambiguous_param0: [7, CType::BitField.new(1, 7)],
+ accepts_no_kwarg: [8, CType::BitField.new(1, 0)],
+ ruby2_keywords: [9, CType::BitField.new(1, 1)],
+ )],
+ size: [32, CType::Immediate.new(-4)],
+ lead_num: [64, CType::Immediate.new(4)],
+ opt_num: [96, CType::Immediate.new(4)],
+ rest_start: [128, CType::Immediate.new(4)],
+ post_start: [160, CType::Immediate.new(4)],
+ post_num: [192, CType::Immediate.new(4)],
+ block_start: [224, CType::Immediate.new(4)],
+ opt_table: [256, CType::Pointer.new { self.VALUE }],
+ keyword: [320, CType::Pointer.new { self.rb_iseq_param_keyword }],
+ )],
+ location: [512, self.rb_iseq_location_t],
+ insns_info: [960, self.iseq_insn_info],
+ local_table: [1216, CType::Pointer.new { self.ID }],
+ catch_table: [1280, CType::Pointer.new { self.iseq_catch_table }],
+ parent_iseq: [1344, CType::Pointer.new { self.rb_iseq_struct }],
+ local_iseq: [1408, CType::Pointer.new { self.rb_iseq_struct }],
+ is_entries: [1472, CType::Pointer.new { self.iseq_inline_storage_entry }],
+ call_data: [1536, CType::Pointer.new { self.rb_call_data }],
+ variable: [1600, CType::Struct.new(
+ "", 40,
+ flip_count: [0, self.rb_snum_t],
+ script_lines: [64, self.VALUE],
+ coverage: [128, self.VALUE],
+ pc2branchindex: [192, self.VALUE],
+ original_iseq: [256, CType::Pointer.new { self.VALUE }],
+ )],
+ local_table_size: [1920, CType::Immediate.new(-4)],
+ ic_size: [1952, CType::Immediate.new(-4)],
+ ise_size: [1984, CType::Immediate.new(-4)],
+ ivc_size: [2016, CType::Immediate.new(-4)],
+ icvarc_size: [2048, CType::Immediate.new(-4)],
+ ci_size: [2080, CType::Immediate.new(-4)],
+ stack_max: [2112, CType::Immediate.new(-4)],
+ mark_bits: [2176, CType::Union.new(
+ "", 8,
+ list: CType::Pointer.new { self.iseq_bits_t },
+ single: self.iseq_bits_t,
+ )],
+ catch_except_p: [2240, self._Bool],
+ builtin_inline_p: [2248, self._Bool],
+ outer_variables: [2304, CType::Pointer.new { self.rb_id_table }],
+ mandatory_only_iseq: [2368, CType::Pointer.new { self.rb_iseq_t }],
+ jit_func: [2432, CType::Immediate.new(1)],
+ total_calls: [2496, CType::Immediate.new(-5)],
+ jit_unit: [2560, CType::Pointer.new { self.rb_mjit_unit }],
+ yjit_payload: [2624, CType::Pointer.new { CType::Immediate.new(0) }],
+ )
+ end
+
+ def C.rb_iseq_location_t
+ @rb_iseq_location_t ||= CType::Struct.new(
+ "rb_iseq_location_struct", 56,
+ pathobj: [0, self.VALUE, true],
+ base_label: [64, self.VALUE, true],
+ label: [128, self.VALUE, true],
+ first_lineno: [192, self.VALUE, true],
+ node_id: [256, CType::Immediate.new(4)],
+ code_location: [288, self.rb_code_location_t],
+ )
+ end
+
+ def C.rb_iseq_struct
+ @rb_iseq_struct ||= CType::Struct.new(
+ "rb_iseq_struct", 40,
+ flags: [0, self.VALUE],
+ wrapper: [64, self.VALUE],
+ body: [128, CType::Pointer.new { self.rb_iseq_constant_body }],
+ aux: [192, CType::Union.new(
+ "", 16,
+ compile_data: CType::Pointer.new { self.iseq_compile_data },
+ loader: CType::Struct.new(
+ "", 16,
+ obj: [0, self.VALUE],
+ index: [64, CType::Immediate.new(4)],
+ ),
+ exec: CType::Struct.new(
+ "", 16,
+ local_hooks: [0, CType::Pointer.new { self.rb_hook_list_struct }],
+ global_trace_events: [64, self.rb_event_flag_t],
+ ),
+ )],
+ )
+ end
+
+ def C.rb_iseq_t
+ @rb_iseq_t ||= self.rb_iseq_struct
+ end
+
+ def C.rb_iv_index_tbl_entry
+ @rb_iv_index_tbl_entry ||= CType::Struct.new(
+ "rb_iv_index_tbl_entry", 24,
+ index: [0, CType::Immediate.new(-4)],
+ class_serial: [64, self.rb_serial_t],
+ class_value: [128, self.VALUE],
+ )
+ end
+
+ def C.rb_method_definition_struct
+ @rb_method_definition_struct ||= CType::Struct.new(
+ "rb_method_definition_struct", 48,
+ type: [0, self.rb_method_type_t],
+ iseq_overload: [4, CType::BitField.new(1, 4)],
+ alias_count: [5, CType::BitField.new(27, 5)],
+ complemented_count: [32, CType::BitField.new(28, 0)],
+ no_redef_warning: [60, CType::BitField.new(1, 4)],
+ body: [64, CType::Union.new(
+ "", 24,
+ iseq: self.rb_method_iseq_t,
+ cfunc: self.rb_method_cfunc_t,
+ attr: self.rb_method_attr_t,
+ alias: self.rb_method_alias_t,
+ refined: self.rb_method_refined_t,
+ bmethod: self.rb_method_bmethod_t,
+ optimized: self.rb_method_optimized_t,
+ )],
+ original_id: [256, self.ID],
+ method_serial: [320, CType::Immediate.new(-5)],
+ )
+ end
+
+ def C.rb_method_iseq_t
+ @rb_method_iseq_t ||= CType::Struct.new(
+ "rb_method_iseq_struct", 16,
+ iseqptr: [0, CType::Pointer.new { self.rb_iseq_t }],
+ cref: [64, CType::Pointer.new { self.rb_cref_t }],
+ )
+ end
+
+ def C.rb_method_type_t
+ @rb_method_type_t ||= CType::Immediate.new(4)
+ end
+
+ def C.rb_mjit_compile_info
+ @rb_mjit_compile_info ||= CType::Struct.new(
+ "rb_mjit_compile_info", 5,
+ disable_ivar_cache: [0, self._Bool],
+ disable_exivar_cache: [8, self._Bool],
+ disable_send_cache: [16, self._Bool],
+ disable_inlining: [24, self._Bool],
+ disable_const_cache: [32, self._Bool],
+ )
+ end
+
+ def C.rb_mjit_unit
+ @rb_mjit_unit ||= CType::Struct.new(
+ "rb_mjit_unit", 64,
+ unode: [0, self.ccan_list_node],
+ id: [128, CType::Immediate.new(4)],
+ handle: [192, CType::Pointer.new { CType::Immediate.new(0) }],
+ iseq: [256, CType::Pointer.new { self.rb_iseq_t }],
+ used_code_p: [320, self._Bool],
+ compact_p: [328, self._Bool],
+ compile_info: [336, self.rb_mjit_compile_info],
+ cc_entries: [384, CType::Pointer.new { CType::Pointer.new { self.rb_callcache } }],
+ cc_entries_size: [448, CType::Immediate.new(-4)],
+ )
+ end
+
+ def C.rb_serial_t
+ @rb_serial_t ||= CType::Immediate.new(-6)
+ end
+
+ def C._Bool = CType::Bool.new
+
+ def C.ID = CType::Stub.new(:ID)
+
+ def C.rb_thread_struct = CType::Stub.new(:rb_thread_struct)
+
+ def C.vm_call_handler = CType::Stub.new(:vm_call_handler)
+
+ def C.method_missing_reason = CType::Stub.new(:method_missing_reason)
+
+ def C.rb_callinfo_kwarg = CType::Stub.new(:rb_callinfo_kwarg)
+
+ def C.rb_cref_struct = CType::Stub.new(:rb_cref_struct)
+
+ def C.rb_scope_visibility_t = CType::Stub.new(:rb_scope_visibility_t)
+
+ def C.rb_iseq_type = CType::Stub.new(:rb_iseq_type)
+
+ def C.rb_iseq_param_keyword = CType::Stub.new(:rb_iseq_param_keyword)
+
+ def C.iseq_insn_info = CType::Stub.new(:iseq_insn_info)
+
+ def C.iseq_catch_table = CType::Stub.new(:iseq_catch_table)
+
+ def C.rb_snum_t = CType::Stub.new(:rb_snum_t)
+
+ def C.iseq_bits_t = CType::Stub.new(:iseq_bits_t)
+
+ def C.rb_id_table = CType::Stub.new(:rb_id_table)
+
+ def C.rb_code_location_t = CType::Stub.new(:rb_code_location_t)
+
+ def C.iseq_compile_data = CType::Stub.new(:iseq_compile_data)
+
+ def C.rb_hook_list_struct = CType::Stub.new(:rb_hook_list_struct)
+
+ def C.rb_event_flag_t = CType::Stub.new(:rb_event_flag_t)
+
+ def C.rb_method_cfunc_t = CType::Stub.new(:rb_method_cfunc_t)
+
+ def C.rb_method_attr_t = CType::Stub.new(:rb_method_attr_t)
+
+ def C.rb_method_alias_t = CType::Stub.new(:rb_method_alias_t)
+
+ def C.rb_method_refined_t = CType::Stub.new(:rb_method_refined_t)
+
+ def C.rb_method_bmethod_t = CType::Stub.new(:rb_method_bmethod_t)
+
+ def C.rb_method_optimized_t = CType::Stub.new(:rb_method_optimized_t)
+
+ def C.ccan_list_node = CType::Stub.new(:ccan_list_node)
+end
diff --git a/lib/mjit/c_pointer.rb b/lib/mjit/c_pointer.rb
new file mode 100644
index 0000000000..75ed8babd5
--- /dev/null
+++ b/lib/mjit/c_pointer.rb
@@ -0,0 +1,333 @@
+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
+ # Patched PACK_MAP to support unsigned operations
+ PACK_MAP = Fiddle::PackInfo::PACK_MAP.dup
+ PACK_MAP.keys.each do |type|
+ if type.negative? || type == Fiddle::TYPE_VOIDP
+ PACK_MAP[type] = PACK_MAP[type].upcase
+ end
+ end
+
+ # 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 => [Integer, RubyVM::MJIT::CType::*] }]
+ 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)
+ offset, type = @members.fetch(member)
+ type.new(@addr + offset / 8)
+ end
+
+ private
+
+ # @param member [Symbol]
+ # @param value [Object]
+ def []=(member, value)
+ offset, type = @members.fetch(member)
+ type[@addr + offset / 8] = value
+ end
+
+ # @param sizeof [Integer]
+ # @param members [Hash{ Symbol => [Integer, 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 = nil|
+ if addr.nil? # TODO: get rid of this feature later
+ addr = Fiddle.malloc(sizeof)
+ end
+ super(addr, sizeof, members)
+ end
+
+ members.each do |member, (offset, type, 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|
+ 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 = 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(PACK_MAP[Fiddle::TYPE_VOIDP])
+ end
+
+ private
+
+ def dest_addr
+ Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_VOIDP].unpack1(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(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 *
+ if @width != 1
+ raise NotImplementedError.new("Non-1 width is not implemented yet")
+ end
+ byte = Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_CHAR].unpack('c').first
+ bit = 1 & (byte >> @offset)
+ bit == 1
+ 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/mjit/c_type.rb b/lib/mjit/c_type.rb
new file mode 100644
index 0000000000..be93b36e90
--- /dev/null
+++ b/lib/mjit/c_type.rb
@@ -0,0 +1,77 @@
+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] Fiddle::TYPE_*
+ 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
+ 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/mjit/compiler.rb b/lib/mjit/compiler.rb
new file mode 100644
index 0000000000..254ecc885f
--- /dev/null
+++ b/lib/mjit/compiler.rb
@@ -0,0 +1,1019 @@
+module RubyVM::MJIT
+ USE_RVARGC = C.USE_RVARGC
+ ROBJECT_EMBED_LEN_MAX = C.ROBJECT_EMBED_LEN_MAX
+
+ UNSUPPORTED_INSNS = [
+ :defineclass, # low priority
+ ]
+
+ # Available variables and macros in JIT-ed function:
+ # ec: the first argument of _mjitXXX
+ # reg_cfp: the second argument of _mjitXXX
+ # GET_CFP(): refers to `reg_cfp`
+ # GET_EP(): refers to `reg_cfp->ep`
+ # GET_SP(): refers to `reg_cfp->sp`, or `(stack + stack_size)` if local_stack_p
+ # GET_SELF(): refers to `cfp_self`
+ # GET_LEP(): refers to `VM_EP_LEP(reg_cfp->ep)`
+ # EXEC_EC_CFP(): refers to `val = vm_exec(ec, true)` with frame setup
+ # CALL_METHOD(): using `GET_CFP()` and `EXEC_EC_CFP()`
+ # TOPN(): refers to `reg_cfp->sp`, or `*(stack + (stack_size - num - 1))` if local_stack_p
+ # STACK_ADDR_FROM_TOP(): refers to `reg_cfp->sp`, or `stack + (stack_size - num)` if local_stack_p
+ # DISPATCH_ORIGINAL_INSN(): expanded in _mjit_compile_insn.erb
+ # THROW_EXCEPTION(): specially defined for JIT
+ # RESTORE_REGS(): specially defined for `leave`
+ class << Compiler = Module.new
+ # mjit_compile
+ # @param funcname [String]
+ def compile(f, iseq, funcname, id)
+ status = C.compile_status.new # not freed for now
+ status.compiled_iseq = iseq.body
+ status.compiled_id = id
+ init_compile_status(status, iseq.body, true) # not freed for now
+ if iseq.body.ci_size > 0 && status.cc_entries_index == -1
+ return false
+ end
+
+ init_ivar_compile_status(iseq.body, status)
+
+ if !status.compile_info.disable_send_cache && !status.compile_info.disable_inlining
+ unless precompile_inlinable_iseqs(f, iseq, status)
+ return false
+ end
+ end
+
+ C.fprintf(f, "VALUE\n#{funcname}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp)\n{\n")
+ success = compile_body(f, iseq, status)
+ C.fprintf(f, "\n} // end of #{funcname}\n")
+
+ return success
+ rescue => e # Should rb_rescue be called in C?
+ if C.mjit_opts.warnings || C.mjit_opts.verbose > 0
+ $stderr.puts e.full_message
+ end
+ return false
+ end
+
+ # mjit_compile_body
+ def compile_body(f, iseq, status)
+ status.success = true
+ status.local_stack_p = !iseq.body.catch_except_p
+
+ if status.local_stack_p
+ src = +" VALUE stack[#{iseq.body.stack_max}];\n"
+ else
+ src = +" VALUE *stack = reg_cfp->sp;\n"
+ end
+
+ unless status.inlined_iseqs.nil? # i.e. compile root
+ src << " static const rb_iseq_t *original_iseq = (const rb_iseq_t *)#{iseq};\n"
+ end
+ src << " static const VALUE *const original_body_iseq = (VALUE *)#{iseq.body.iseq_encoded};\n"
+
+ src << " VALUE cfp_self = reg_cfp->self;\n" # cache self across the method
+ src << "#undef GET_SELF\n"
+ src << "#define GET_SELF() cfp_self\n"
+
+ # Generate merged ivar guards first if needed
+ if !status.compile_info.disable_ivar_cache && status.merge_ivar_guards_p
+ src << " if (UNLIKELY(!(RB_TYPE_P(GET_SELF(), T_OBJECT) && (rb_serial_t)#{status.ivar_serial} == RCLASS_SERIAL(RBASIC(GET_SELF())->klass) &&"
+ if USE_RVARGC
+ src << "#{status.max_ivar_index} < ROBJECT_NUMIV(GET_SELF())" # index < ROBJECT_NUMIV(obj)
+ else
+ if status.max_ivar_index >= ROBJECT_EMBED_LEN_MAX
+ src << "#{status.max_ivar_index} < ROBJECT_NUMIV(GET_SELF())" # index < ROBJECT_NUMIV(obj) && !RB_FL_ANY_RAW(obj, ROBJECT_EMBED)
+ else
+ src << "ROBJECT_EMBED_LEN_MAX == ROBJECT_NUMIV(GET_SELF())" # index < ROBJECT_NUMIV(obj) && RB_FL_ANY_RAW(obj, ROBJECT_EMBED)
+ end
+ end
+ src << "))) {\n"
+ src << " goto ivar_cancel;\n"
+ src << " }\n"
+ end
+
+ # Simulate `opt_pc` in setup_parameters_complex. Other PCs which may be passed by catch tables
+ # are not considered since vm_exec doesn't call jit_exec for catch tables.
+ if iseq.body.param.flags.has_opt
+ src << "\n"
+ src << " switch (reg_cfp->pc - ISEQ_BODY(reg_cfp->iseq)->iseq_encoded) {\n"
+ (0..iseq.body.param.opt_num).each do |i|
+ pc_offset = iseq.body.param.opt_table[i]
+ src << " case #{pc_offset}:\n"
+ src << " goto label_#{pc_offset};\n"
+ end
+ src << " }\n"
+ end
+
+ C.fprintf(f, src)
+ compile_insns(0, 0, status, iseq.body, f)
+ compile_cancel_handler(f, iseq.body, status)
+ C.fprintf(f, "#undef GET_SELF\n")
+ return status.success
+ end
+
+ # Compile one conditional branch. If it has branchXXX insn, this should be
+ # called multiple times for each branch.
+ def compile_insns(stack_size, pos, status, body, f)
+ branch = C.compile_branch.new # not freed for now
+ branch.stack_size = stack_size
+ branch.finish_p = false
+
+ while pos < body.iseq_size && !already_compiled?(status, pos) && !branch.finish_p
+ insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos]))
+ status.stack_size_for_pos[pos] = branch.stack_size
+
+ C.fprintf(f, "\nlabel_#{pos}: /* #{insn.name} */\n")
+ pos = compile_insn(insn, pos, status, body.iseq_encoded + (pos+1), body, branch, f)
+ if status.success && branch.stack_size > body.stack_max
+ if mjit_opts.warnings || mjit_opts.verbose > 0
+ $stderr.puts "MJIT warning: JIT stack size (#{branch.stack_size}) exceeded its max size (#{body.stack_max})"
+ end
+ status.success = false
+ end
+ break unless status.success
+ end
+ end
+
+ # Main function of JIT compilation, vm_exec_core counterpart for JIT. Compile one insn to `f`, may modify
+ # b->stack_size and return next position.
+ #
+ # When you add a new instruction to insns.def, it would be nice to have JIT compilation support here but
+ # it's optional. This JIT compiler just ignores ISeq which includes unknown instruction, and ISeq which
+ # does not have it can be compiled as usual.
+ def compile_insn(insn, pos, status, operands, body, b, f)
+ sp_inc = C.mjit_call_attribute_sp_inc(insn.bin, operands)
+ next_pos = pos + insn.len
+
+ result = compile_insn_entry(f, insn, b.stack_size, sp_inc, status.local_stack_p, pos, next_pos, insn.len,
+ status.inlined_iseqs.nil?, status, operands, body)
+ if result.nil?
+ if C.mjit_opts.warnings || C.mjit_opts.verbose > 0
+ $stderr.puts "MJIT warning: Skipped to compile unsupported instruction: #{insn.name}"
+ end
+ status.success = false
+ else
+ src, next_pos, finish_p, compile_insns_p = result
+
+ C.fprintf(f, src)
+ b.stack_size += sp_inc
+
+ if finish_p
+ b.finish_p = true
+ end
+ if compile_insns_p
+ if already_compiled?(status, pos + insn.len)
+ C.fprintf(f, "goto label_#{pos + insn.len};\n")
+ else
+ compile_insns(b.stack_size, pos + insn.len, status, body, f)
+ end
+ end
+ end
+
+ # If next_pos is already compiled and this branch is not finished yet,
+ # next instruction won't be compiled in C code next and will need `goto`.
+ if !b.finish_p && next_pos < body.iseq_size && already_compiled?(status, next_pos)
+ C.fprintf(f, "goto label_#{next_pos};\n")
+
+ # Verify stack size assumption is the same among multiple branches
+ if status.stack_size_for_pos[next_pos] != b.stack_size
+ if mjit_opts.warnings || mjit_opts.verbose > 0
+ $stderr.puts "MJIT warning: JIT stack assumption is not the same between branches (#{status.stack_size_for_pos[next_pos]} != #{b.stack_size})\n"
+ end
+ status.success = false
+ end
+ end
+
+ return next_pos
+ end
+
+ # mjit_comiple.inc.erb
+ def compile_insn_entry(f, insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, status, operands, body)
+ finish_p = false
+ compile_insns = false
+
+ # TODO: define this outside this method, or at least cache it
+ opt_send_without_block = INSNS.values.find { |i| i.name == :opt_send_without_block }
+ if opt_send_without_block.nil?
+ raise 'opt_send_without_block not found'
+ end
+ send_compatible_opt_insns = INSNS.values.select do |insn|
+ insn.name.start_with?('opt_') && opt_send_without_block.opes == insn.opes &&
+ insn.expr.lines.any? { |l| l.match(/\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/) }
+ end.map(&:name)
+
+ case insn.name
+ when *UNSUPPORTED_INSNS
+ return nil
+ when :opt_send_without_block, :send
+ if src = compile_send(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body)
+ return src, next_pos, finish_p, compile_insns
+ end
+ when *send_compatible_opt_insns
+ if C.has_cache_for_send(captured_cc_entries(status)[call_data_index(C.CALL_DATA.new(operands[0]), body)], insn.bin) &&
+ src = compile_send(opt_send_without_block, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body)
+ return src, next_pos, finish_p, compile_insns
+ end
+ when :getinstancevariable, :setinstancevariable
+ if src = compile_ivar(insn.name, stack_size, pos, status, operands, body)
+ return src, next_pos, finish_p, compile_insns
+ end
+ when :invokebuiltin, :opt_invokebuiltin_delegate
+ if compile_invokebuiltin(f, insn, stack_size, sp_inc, body, operands)
+ return '', next_pos, finish_p, compile_insns
+ end
+ when :opt_getconstant_path
+ if src = compile_getconstant_path(stack_size, pos, insn_len, operands, status)
+ return src, next_pos, finish_p, compile_insns
+ end
+ when :leave, :opt_invokebuiltin_delegate_leave
+ src = +''
+
+ # opt_invokebuiltin_delegate_leave also implements leave insn. We need to handle it here for inlining.
+ if insn.name == :opt_invokebuiltin_delegate_leave
+ compile_invokebuiltin(f, insn, stack_size, sp_inc, body, operands)
+ else
+ if stack_size != 1
+ $stderr.puts "MJIT warning: Unexpected JIT stack_size on leave: #{stack_size}" # TODO: check mjit_opts?
+ return nil
+ end
+ end
+
+ # Skip vm_pop_frame for inlined call
+ unless inlined_iseq_p
+ # Cancel on interrupts to make leave insn leaf
+ src << " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n"
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
+ src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
+ src << " rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);\n"
+ src << " }\n"
+ src << " ec->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(reg_cfp);\n" # vm_pop_frame
+ end
+ src << " return stack[0];\n"
+ finish_p = true
+ return src, next_pos, finish_p, compile_insns
+ end
+
+ return compile_insn_default(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, operands)
+ rescue => e
+ puts e.full_message
+ nil
+ end
+
+ private
+
+ # Optimized case of send / opt_send_without_block instructions.
+ # _mjit_compile_send.erb
+ def compile_send(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body)
+ # compiler: Use captured cc to avoid race condition
+ cd = C.CALL_DATA.new(operands[0])
+ cd_index = call_data_index(cd, body)
+ captured_cc = captured_cc_entries(status)[cd_index]
+
+ # compiler: Inline send insn where some supported fastpath is used.
+ ci = cd.ci
+ kw_splat = (C.vm_ci_flag(ci) & C.VM_CALL_KW_SPLAT) > 0
+ if !status.compile_info.disable_send_cache && has_valid_method_type?(captured_cc) && (
+ # `CC_SET_FASTPATH(cd->cc, vm_call_cfunc_with_frame, ...)` in `vm_call_cfunc`
+ (vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_CFUNC && !C.rb_splat_or_kwargs_p(ci) && !kw_splat) ||
+ # `CC_SET_FASTPATH(cc, vm_call_iseq_setup_func(...), vm_call_iseq_optimizable_p(...))` in `vm_callee_setup_arg`,
+ # and support only non-VM_CALL_TAILCALL path inside it
+ (vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_ISEQ &&
+ C.fastpath_applied_iseq_p(ci, captured_cc, iseq = def_iseq_ptr(vm_cc_cme(captured_cc).def)) &&
+ (C.vm_ci_flag(ci) & C.VM_CALL_TAILCALL) == 0)
+ )
+ src = +"{\n"
+
+ # JIT: Invalidate call cache if it requires vm_search_method. This allows to inline some of following things.
+ src << " const struct rb_callcache *cc = (struct rb_callcache *)#{captured_cc};\n"
+ src << " const rb_callable_method_entry_t *cc_cme = (rb_callable_method_entry_t *)#{vm_cc_cme(captured_cc)};\n"
+ src << " const VALUE recv = stack[#{stack_size + sp_inc - 1}];\n"
+ # If opt_class_of is true, use RBASIC_CLASS instead of CLASS_OF to reduce code size
+ opt_class_of = !maybe_special_const?(captured_cc.klass)
+ src << " if (UNLIKELY(#{opt_class_of ? 'RB_SPECIAL_CONST_P(recv)' : 'false'} || !vm_cc_valid_p(cc, cc_cme, #{opt_class_of ? 'RBASIC_CLASS' : 'CLASS_OF'}(recv)))) {\n"
+ src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
+ src << " goto send_cancel;\n"
+ src << " }\n"
+
+ # JIT: move sp and pc if necessary
+ pc_moved_p = compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
+
+ # JIT: If ISeq is inlinable, call the inlined method without pushing a frame.
+ if iseq && status.inlined_iseqs && iseq.body.to_i == status.inlined_iseqs[pos]&.to_i
+ src << " {\n"
+ src << " VALUE orig_self = reg_cfp->self;\n"
+ src << " reg_cfp->self = stack[#{stack_size + sp_inc - 1}];\n"
+ src << " stack[#{stack_size + sp_inc - 1}] = _mjit#{status.compiled_id}_inlined_#{pos}(ec, reg_cfp, orig_self, original_iseq);\n"
+ src << " reg_cfp->self = orig_self;\n"
+ src << " }\n"
+ else
+ # JIT: Forked `vm_sendish` (except method_explorer = vm_search_method_wrap) to inline various things
+ src << " {\n"
+ src << " VALUE val;\n"
+ src << " struct rb_calling_info calling;\n"
+ if insn.name == :send
+ src << " calling.block_handler = vm_caller_setup_arg_block(ec, reg_cfp, (const struct rb_callinfo *)#{ci}, (rb_iseq_t *)0x#{operands[1].to_s(16)}, FALSE);\n"
+ else
+ src << " calling.block_handler = VM_BLOCK_HANDLER_NONE;\n"
+ end
+ src << " calling.kw_splat = #{kw_splat ? 1 : 0};\n"
+ src << " calling.recv = stack[#{stack_size + sp_inc - 1}];\n"
+ src << " calling.argc = #{C.vm_ci_argc(ci)};\n"
+
+ if vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_CFUNC
+ # TODO: optimize this more
+ src << " calling.ci = (const struct rb_callinfo *)#{ci};\n" # creating local cd here because operand's cd->cc may not be the same as inlined cc.
+ src << " calling.cc = cc;"
+ src << " val = vm_call_cfunc_with_frame(ec, reg_cfp, &calling);\n"
+ else # :iseq
+ # fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
+ src << " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, cc_cme, 0, #{iseq.body.param.size}, #{iseq.body.local_table_size});\n"
+ if iseq.body.catch_except_p
+ src << " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n"
+ src << " val = vm_exec(ec, true);\n"
+ else
+ src << " if ((val = jit_exec(ec)) == Qundef) {\n"
+ src << " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n" # This is vm_call0_body's code after vm_call_iseq_setup
+ src << " val = vm_exec(ec, false);\n"
+ src << " }\n"
+ end
+ end
+ src << " stack[#{stack_size + sp_inc - 1}] = val;\n"
+ src << " }\n"
+
+ # JIT: We should evaluate ISeq modified for TracePoint if it's enabled. Note: This is slow.
+ src << " if (UNLIKELY(!mjit_call_p)) {\n"
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size + sp_inc};\n"
+ if !pc_moved_p
+ src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n"
+ end
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n"
+ src << " goto cancel;\n"
+ src << " }\n"
+ end
+
+ src << "}\n"
+ return src
+ else
+ return nil
+ end
+ end
+
+ # _mjit_compile_ivar.erb
+ def compile_ivar(insn_name, stack_size, pos, status, operands, body)
+ ic_copy = (status.is_entries + (C.iseq_inline_storage_entry.new(operands[1]) - body.is_entries)).iv_cache
+
+ src = +''
+ if !status.compile_info.disable_ivar_cache && ic_copy.entry
+ # JIT: optimize away motion of sp and pc. This path does not call rb_warning() and so it's always leaf and not `handles_sp`.
+ # compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
+
+ # JIT: prepare vm_getivar/vm_setivar arguments and variables
+ src << "{\n"
+ src << " VALUE obj = GET_SELF();\n"
+ src << " const uint32_t index = #{ic_copy.entry.index};\n"
+ if status.merge_ivar_guards_p
+ # JIT: Access ivar without checking these VM_ASSERTed prerequisites as we checked them in the beginning of `mjit_compile_body`
+ src << " VM_ASSERT(RB_TYPE_P(obj, T_OBJECT));\n"
+ src << " VM_ASSERT((rb_serial_t)#{ic_copy.entry.class_serial} == RCLASS_SERIAL(RBASIC(obj)->klass));\n"
+ src << " VM_ASSERT(index < ROBJECT_NUMIV(obj));\n"
+ if insn_name == :setinstancevariable
+ if USE_RVARGC
+ src << " if (LIKELY(!RB_OBJ_FROZEN_RAW(obj) && index < ROBJECT_NUMIV(obj))) {\n"
+ src << " RB_OBJ_WRITE(obj, &ROBJECT_IVPTR(obj)[index], stack[#{stack_size - 1}]);\n"
+ else
+ heap_ivar_p = status.max_ivar_index >= ROBJECT_EMBED_LEN_MAX
+ src << " if (LIKELY(!RB_OBJ_FROZEN_RAW(obj) && #{heap_ivar_p ? 'true' : 'RB_FL_ANY_RAW(obj, ROBJECT_EMBED)'})) {\n"
+ src << " RB_OBJ_WRITE(obj, &ROBJECT(obj)->as.#{heap_ivar_p ? 'heap.ivptr[index]' : 'ary[index]'}, stack[#{stack_size - 1}]);\n"
+ end
+ src << " }\n"
+ else
+ src << " VALUE val;\n"
+ if USE_RVARGC
+ src << " if (LIKELY(index < ROBJECT_NUMIV(obj) && (val = ROBJECT_IVPTR(obj)[index]) != Qundef)) {\n"
+ else
+ heap_ivar_p = status.max_ivar_index >= ROBJECT_EMBED_LEN_MAX
+ src << " if (LIKELY(#{heap_ivar_p ? 'true' : 'RB_FL_ANY_RAW(obj, ROBJECT_EMBED)'} && (val = ROBJECT(obj)->as.#{heap_ivar_p ? 'heap.ivptr[index]' : 'ary[index]'}) != Qundef)) {\n"
+ end
+ src << " stack[#{stack_size}] = val;\n"
+ src << " }\n"
+ end
+ else
+ src << " const rb_serial_t ic_serial = (rb_serial_t)#{ic_copy.entry.class_serial};\n"
+ # JIT: cache hit path of vm_getivar/vm_setivar, or cancel JIT (recompile it with exivar)
+ if insn_name == :setinstancevariable
+ src << " if (LIKELY(RB_TYPE_P(obj, T_OBJECT) && ic_serial == RCLASS_SERIAL(RBASIC(obj)->klass) && index < ROBJECT_NUMIV(obj) && !RB_OBJ_FROZEN_RAW(obj))) {\n"
+ src << " VALUE *ptr = ROBJECT_IVPTR(obj);\n"
+ src << " RB_OBJ_WRITE(obj, &ptr[index], stack[#{stack_size - 1}]);\n"
+ src << " }\n"
+ else
+ src << " VALUE val;\n"
+ src << " if (LIKELY(RB_TYPE_P(obj, T_OBJECT) && ic_serial == RCLASS_SERIAL(RBASIC(obj)->klass) && index < ROBJECT_NUMIV(obj) && (val = ROBJECT_IVPTR(obj)[index]) != Qundef)) {\n"
+ src << " stack[#{stack_size}] = val;\n"
+ src << " }\n"
+ end
+ end
+ src << " else {\n"
+ src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
+ src << " goto ivar_cancel;\n"
+ src << " }\n"
+ src << "}\n"
+ return src
+ elsif insn_name == :getinstancevariable && !status.compile_info.disable_exivar_cache && ic_copy.entry
+ # JIT: optimize away motion of sp and pc. This path does not call rb_warning() and so it's always leaf and not `handles_sp`.
+ # compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
+
+ # JIT: prepare vm_getivar's arguments and variables
+ src << "{\n"
+ src << " VALUE obj = GET_SELF();\n"
+ src << " const rb_serial_t ic_serial = (rb_serial_t)#{ic_copy.entry.class_serial};\n"
+ src << " const uint32_t index = #{ic_copy.entry.index};\n"
+ # JIT: cache hit path of vm_getivar, or cancel JIT (recompile it without any ivar optimization)
+ src << " struct gen_ivtbl *ivtbl;\n"
+ src << " VALUE val;\n"
+ src << " if (LIKELY(FL_TEST_RAW(obj, FL_EXIVAR) && ic_serial == RCLASS_SERIAL(RBASIC(obj)->klass) && rb_ivar_generic_ivtbl_lookup(obj, &ivtbl) && index < ivtbl->numiv && (val = ivtbl->ivptr[index]) != Qundef)) {\n"
+ src << " stack[#{stack_size}] = val;\n"
+ src << " }\n"
+ src << " else {\n"
+ src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
+ src << " goto exivar_cancel;\n"
+ src << " }\n"
+ src << "}\n"
+ return src
+ else
+ return nil
+ end
+ end
+
+ # _mjit_compile_invokebulitin.erb
+ def compile_invokebuiltin(f, insn, stack_size, sp_inc, body, operands)
+ bf = C.RB_BUILTIN.new(operands[0])
+ if bf.compiler > 0
+ C.fprintf(f, "{\n")
+ C.fprintf(f, " VALUE val;\n")
+ C.builtin_compiler(f, bf, operands[1], stack_size, body.builtin_inline_p)
+ C.fprintf(f, " stack[#{stack_size + sp_inc - 1}] = val;\n")
+ C.fprintf(f, "}\n")
+ return true
+ else
+ return false
+ end
+ end
+
+ # _mjit_compile_getconstant_path.erb
+ def compile_getconstant_path(stack_size, pos, insn_len, operands, status)
+ ice = C.IC.new(operands[0]).entry
+ if !status.compile_info.disable_const_cache && ice
+ # JIT: Inline everything in IC, and cancel the slow path
+ src = +" if (vm_inlined_ic_hit_p(#{ice.flags}, #{ice.value}, (const rb_cref_t *)#{to_addr(ice.ic_cref)}, reg_cfp->ep)) {\n"
+ src << " stack[#{stack_size}] = #{ice.value};\n"
+ src << " }\n"
+ src << " else {\n"
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
+ src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
+ src << " goto const_cancel;\n"
+ src << " }\n"
+ return src
+ else
+ return nil
+ end
+ end
+
+ # _mjit_compile_insn.erb
+ def compile_insn_default(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, operands)
+ src = +''
+ finish_p = false
+ compile_insns = false
+
+ # JIT: Declare stack_size to be used in some macro of _mjit_compile_insn_body.erb
+ src << "{\n"
+ if local_stack_p
+ src << " MAYBE_UNUSED(unsigned int) stack_size = #{stack_size};\n"
+ end
+
+ # JIT: Declare variables for operands, popped values and return values
+ insn.declarations.each do |decl|
+ src << " #{decl};\n"
+ end
+
+ # JIT: Set const expressions for `RubyVM::OperandsUnifications` insn
+ insn.preamble.each do |amble|
+ src << "#{amble.sub(/const \S+\s+/, '')}\n"
+ end
+
+ # JIT: Initialize operands
+ insn.opes.each_with_index do |ope, i|
+ src << " #{ope.fetch(:name)} = (#{ope.fetch(:type)})#{operands[i]};\n"
+ # TODO: resurrect comment_id
+ end
+
+ # JIT: Initialize popped values
+ insn.pops.reverse_each.with_index.reverse_each do |pop, i|
+ src << " #{pop.fetch(:name)} = stack[#{stack_size - (i + 1)}];\n"
+ end
+
+ # JIT: move sp and pc if necessary
+ pc_moved_p = compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
+
+ # JIT: Print insn body in insns.def
+ next_pos = compile_insn_body(src, insn, pos, next_pos, insn_len, local_stack_p, stack_size, sp_inc, operands)
+
+ # JIT: Set return values
+ insn.rets.reverse_each.with_index do |ret, i|
+ # TOPN(n) = ...
+ src << " stack[#{stack_size + sp_inc - (i + 1)}] = #{ret.fetch(:name)};\n"
+ end
+
+ # JIT: We should evaluate ISeq modified for TracePoint if it's enabled. Note: This is slow.
+ # leaf insn may not cancel JIT. leaf_without_check_ints is covered in RUBY_VM_CHECK_INTS of _mjit_compile_insn_body.erb.
+ unless insn.always_leaf? || insn.leaf_without_check_ints?
+ src << " if (UNLIKELY(!mjit_call_p)) {\n"
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size + sp_inc};\n"
+ if !pc_moved_p
+ src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n"
+ end
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n"
+ src << " goto cancel;\n"
+ src << " }\n"
+ end
+
+ src << "}\n"
+
+ # compiler: If insn has conditional JUMP, the code should go to the branch not targeted by JUMP next.
+ if insn.expr.match?(/if\s+\([^{}]+\)\s+\{[^{}]+JUMP\([^)]+\);[^{}]+\}/)
+ compile_insns = true
+ end
+
+ # compiler: If insn returns (leave) or does longjmp (throw), the branch should no longer be compiled. TODO: create attr for it?
+ if insn.expr.match?(/\sTHROW_EXCEPTION\([^)]+\);/) || insn.expr.match?(/\bvm_pop_frame\(/)
+ finish_p = true
+ end
+
+ return src, next_pos, finish_p, compile_insns
+ end
+
+ # _mjit_compile_insn_body.erb
+ def compile_insn_body(src, insn, pos, next_pos, insn_len, local_stack_p, stack_size, sp_inc, operands)
+ # Print a body of insn, but with macro expansion.
+ expand_simple_macros(insn.expr).each_line do |line|
+ # Expand dynamic macro here
+ # TODO: support combination of following macros in the same line
+ case line
+ when /\A\s+RUBY_VM_CHECK_INTS\(ec\);\s+\z/
+ if insn.leaf_without_check_ints? # lazily move PC and optionalize mjit_call_p here
+ src << " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n"
+ src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n" # ADD_PC(INSN_ATTR(width));
+ src << " rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);\n"
+ src << " if (UNLIKELY(!mjit_call_p)) {\n"
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n"
+ src << " goto cancel;\n"
+ src << " }\n"
+ src << " }\n"
+ else
+ src << to_cstr(line)
+ end
+ when /\A\s+JUMP\((?<dest>[^)]+)\);\s+\z/
+ dest = Regexp.last_match[:dest]
+ if insn.name == :opt_case_dispatch # special case... TODO: use another macro to avoid checking name
+ else_offset = cast_offset(operands[1])
+ cdhash = C.cdhash_to_hash(operands[0])
+ base_pos = pos + insn_len
+
+ src << " switch (#{dest}) {\n"
+ cdhash.each do |_key, offset|
+ src << " case #{offset}:\n"
+ src << " goto label_#{base_pos + offset};\n"
+ end
+ src << " case #{else_offset}:\n"
+ src << " goto label_#{base_pos + else_offset};\n"
+ src << " }\n"
+ else
+ # Before we `goto` next insn, we need to set return values, especially for getinlinecache
+ insn.rets.reverse_each.with_index do |ret, i|
+ # TOPN(n) = ...
+ src << " stack[#{stack_size + sp_inc - (i + 1)}] = #{ret.fetch(:name)};\n"
+ end
+
+ next_pos = pos + insn_len + cast_offset(operands[0]) # workaround: assuming dest == operands[0]. TODO: avoid relying on it
+ src << " goto label_#{next_pos};\n"
+ end
+ when /\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/
+ # For `opt_xxx`'s fallbacks.
+ if local_stack_p
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
+ end
+ src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel_opt_insn);\n"
+ src << " goto cancel;\n"
+ when /\A(?<prefix>.+\b)INSN_LABEL\((?<name>[^)]+)\)(?<suffix>.+)\z/m
+ prefix, name, suffix = Regexp.last_match[:prefix], Regexp.last_match[:name], Regexp.last_match[:suffix]
+ src << "#{prefix}INSN_LABEL(#{name}_#{pos})#{suffix}"
+ else
+ if insn.handles_sp?
+ # If insn.handles_sp? is true, cfp->sp might be changed inside insns (like vm_caller_setup_arg_block)
+ # and thus we need to use cfp->sp, even when local_stack_p is TRUE. When insn.handles_sp? is true,
+ # cfp->sp should be available too because _mjit_compile_pc_and_sp.erb sets it.
+ src << to_cstr(line)
+ else
+ # If local_stack_p is TRUE and insn.handles_sp? is false, stack values are only available in local variables
+ # for stack. So we need to replace those macros if local_stack_p is TRUE here.
+ case line
+ when /\bGET_SP\(\)/
+ # reg_cfp->sp
+ src << to_cstr(line.sub(/\bGET_SP\(\)/, local_stack_p ? '(stack + stack_size)' : 'GET_SP()'))
+ when /\bSTACK_ADDR_FROM_TOP\((?<num>[^)]+)\)/
+ # #define STACK_ADDR_FROM_TOP(n) (GET_SP()-(n))
+ num = Regexp.last_match[:num]
+ src << to_cstr(line.sub(/\bSTACK_ADDR_FROM_TOP\(([^)]+)\)/, local_stack_p ? "(stack + (stack_size - (#{num})))" : "STACK_ADDR_FROM_TOP(#{num})"))
+ when /\bTOPN\((?<num>[^)]+)\)/
+ # #define TOPN(n) (*(GET_SP()-(n)-1))
+ num = Regexp.last_match[:num]
+ src << to_cstr(line.sub(/\bTOPN\(([^)]+)\)/, local_stack_p ? "*(stack + (stack_size - (#{num}) - 1))" : "TOPN(#{num})"))
+ else
+ src << to_cstr(line)
+ end
+ end
+ end
+ end
+ return next_pos
+ end
+
+ # _mjit_compile_pc_and_sp.erb
+ def compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
+ # JIT: When an insn is leaf, we don't need to Move pc for a catch table on catch_except_p, #caller_locations,
+ # and rb_profile_frames. For check_ints, we lazily move PC when we have interruptions.
+ pc_moved_p = false
+ unless insn.always_leaf? || insn.leaf_without_check_ints?
+ src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n" # ADD_PC(INSN_ATTR(width));
+ pc_moved_p = true
+ end
+
+ # JIT: move sp to use or preserve stack variables
+ if local_stack_p
+ # sp motion is optimized away for `handles_sp? #=> false` case.
+ # Thus sp should be set properly before `goto cancel`.
+ if insn.handles_sp?
+ # JIT-only behavior (pushing JIT's local variables to VM's stack):
+ push_size = -sp_inc + insn.rets.size - insn.pops.size
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{push_size};\n"
+ push_size.times do |i|
+ src << " *(reg_cfp->sp + #{i - push_size}) = stack[#{stack_size - push_size + i}];\n"
+ end
+ end
+ else
+ if insn.handles_sp?
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size - insn.pops.size};\n" # POPN(INSN_ATTR(popn));
+ else
+ src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
+ end
+ end
+ return pc_moved_p
+ end
+
+ # Print the block to cancel inlined method call. It's supporting only `opt_send_without_block` for now.
+ def compile_inlined_cancel_handler(f, body, inline_context)
+ src = +"\ncancel:\n"
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel);\n"
+ src << " rb_mjit_recompile_inlining(original_iseq);\n"
+
+ # Swap pc/sp set on cancel with original pc/sp.
+ src << " const VALUE *current_pc = reg_cfp->pc;\n"
+ src << " VALUE *current_sp = reg_cfp->sp;\n"
+ src << " reg_cfp->pc = orig_pc;\n"
+ src << " reg_cfp->sp = orig_sp;\n\n"
+
+ # Lazily push the current call frame.
+ src << " struct rb_calling_info calling;\n"
+ src << " calling.block_handler = VM_BLOCK_HANDLER_NONE;\n" # assumes `opt_send_without_block`
+ src << " calling.argc = #{inline_context.orig_argc};\n"
+ src << " calling.recv = reg_cfp->self;\n"
+ src << " reg_cfp->self = orig_self;\n"
+ # fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
+ src << " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)#{inline_context.me}, 0, #{inline_context.param_size}, #{inline_context.local_size});\n\n"
+
+ # Start usual cancel from here.
+ src << " reg_cfp = ec->cfp;\n" # work on the new frame
+ src << " reg_cfp->pc = current_pc;\n"
+ src << " reg_cfp->sp = current_sp;\n"
+ (0...body.stack_max).each do |i| # should be always `status->local_stack_p`
+ src << " *(vm_base_ptr(reg_cfp) + #{i}) = stack[#{i}];\n"
+ end
+ # We're not just returning Qundef here so that caller's normal cancel handler can
+ # push back `stack` to `cfp->sp`.
+ src << " return vm_exec(ec, false);\n"
+ C.fprintf(f, src)
+ end
+
+ # Print the block to cancel JIT execution.
+ def compile_cancel_handler(f, body, status)
+ if status.inlined_iseqs.nil? # the current ISeq is being inlined
+ compile_inlined_cancel_handler(f, body, status.inline_context)
+ return
+ end
+
+ src = +"\nsend_cancel:\n"
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel_send_inline);\n"
+ src << " rb_mjit_recompile_send(original_iseq);\n"
+ src << " goto cancel;\n"
+
+ src << "\nivar_cancel:\n"
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel_ivar_inline);\n"
+ src << " rb_mjit_recompile_ivar(original_iseq);\n"
+ src << " goto cancel;\n"
+
+ src << "\nexivar_cancel:\n"
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel_exivar_inline);\n"
+ src << " rb_mjit_recompile_exivar(original_iseq);\n"
+ src << " goto cancel;\n"
+
+ src << "\nconst_cancel:\n"
+ src << " rb_mjit_recompile_const(original_iseq);\n"
+ src << " goto cancel;\n"
+
+ src << "\ncancel:\n"
+ src << " RB_DEBUG_COUNTER_INC(mjit_cancel);\n"
+ if status.local_stack_p
+ (0...body.stack_max).each do |i|
+ src << " *(vm_base_ptr(reg_cfp) + #{i}) = stack[#{i}];\n"
+ end
+ end
+ src << " return Qundef;\n"
+ C.fprintf(f, src)
+ end
+
+ def precompile_inlinable_child_iseq(f, child_iseq, status, ci, cc, pos)
+ child_status = C.compile_status.new # not freed for now
+ child_status.compiled_iseq = status.compiled_iseq
+ child_status.compiled_id = status.compiled_id
+ init_compile_status(child_status, child_iseq.body, false) # not freed for now
+ child_status.inline_context.orig_argc = C.vm_ci_argc(ci)
+ child_status.inline_context.me = vm_cc_cme(cc).to_i
+ child_status.inline_context.param_size = child_iseq.body.param.size
+ child_status.inline_context.local_size = child_iseq.body.local_table_size
+ if child_iseq.body.ci_size > 0 && child_status.cc_entries_index == -1
+ return false
+ end
+ init_ivar_compile_status(child_iseq.body, child_status)
+
+ src = +"ALWAYS_INLINE(static VALUE _mjit#{status.compiled_id}_inlined_#{pos}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VALUE orig_self, const rb_iseq_t *original_iseq));\n"
+ src << "static inline VALUE\n_mjit#{status.compiled_id}_inlined_#{pos}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VALUE orig_self, const rb_iseq_t *original_iseq)\n{\n"
+ src << " const VALUE *orig_pc = reg_cfp->pc;\n"
+ src << " VALUE *orig_sp = reg_cfp->sp;\n"
+ C.fprintf(f, src)
+
+ success = compile_body(f, child_iseq, child_status)
+
+ C.fprintf(f, "\n} /* end of _mjit#{status.compiled_id}_inlined_#{pos} */\n\n")
+
+ return success;
+ end
+
+ def precompile_inlinable_iseqs(f, iseq, status)
+ body = iseq.body
+ pos = 0
+ while pos < body.iseq_size
+ insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos]))
+ if insn.name == :opt_send_without_block || insn.name == :opt_size # `compile_inlined_cancel_handler` supports only `opt_send_without_block`
+ cd = C.CALL_DATA.new(body.iseq_encoded[pos + 1])
+ ci = cd.ci
+ cc = captured_cc_entries(status)[call_data_index(cd, body)] # use copy to avoid race condition
+
+ if (child_iseq = rb_mjit_inlinable_iseq(ci, cc)) != nil
+ status.inlined_iseqs[pos] = child_iseq.body
+
+ if C.mjit_opts.verbose >= 1 # print beforehand because ISeq may be GCed during copy job.
+ child_location = child_iseq.body.location
+ $stderr.puts "JIT inline: #{child_location.label}@#{C.rb_iseq_path(child_iseq)}:#{child_location.first_lineno} " \
+ "=> #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq.body.location.first_lineno}"
+ end
+ if !precompile_inlinable_child_iseq(f, child_iseq, status, ci, cc, pos)
+ return false
+ end
+ end
+ end
+ pos += insn.len
+ end
+ return true
+ end
+
+ def init_compile_status(status, body, compile_root_p)
+ status.stack_size_for_pos = Fiddle.malloc(Fiddle::SIZEOF_INT * body.iseq_size)
+ body.iseq_size.times do |i|
+ status.stack_size_for_pos[i] = C.NOT_COMPILED_STACK_SIZE
+ end
+ if compile_root_p
+ status.inlined_iseqs = Fiddle.malloc(Fiddle::SIZEOF_VOIDP * body.iseq_size)
+ body.iseq_size.times do |i|
+ status.inlined_iseqs[i] = nil
+ end
+ end
+ if ISEQ_IS_SIZE(body) > 0
+ status.is_entries = Fiddle.malloc(C.iseq_inline_storage_entry.sizeof * ISEQ_IS_SIZE(body))
+ end
+ if body.ci_size > 0
+ status.cc_entries_index = C.mjit_capture_cc_entries(status.compiled_iseq, body)
+ else
+ status.cc_entries_index = -1
+ end
+ if compile_root_p
+ status.compile_info = rb_mjit_iseq_compile_info(body)
+ else
+ status.compile_info = Fiddle.malloc(C.rb_mjit_compile_info.sizeof)
+ status.compile_info.disable_ivar_cache = false
+ status.compile_info.disable_exivar_cache = false
+ status.compile_info.disable_send_cache = false
+ status.compile_info.disable_inlining = false
+ status.compile_info.disable_const_cache = false
+ end
+ end
+
+ def init_ivar_compile_status(body, status)
+ C.mjit_capture_is_entries(body, status.is_entries)
+
+ num_ivars = 0
+ pos = 0
+ status.max_ivar_index = 0
+ status.ivar_serial = 0
+
+ while pos < body.iseq_size
+ insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos]))
+ if insn.name == :getinstancevariable || insn.name == :setinstancevariable
+ ic = body.iseq_encoded[pos+2]
+ ic_copy = (status.is_entries + (C.iseq_inline_storage_entry.new(ic) - body.is_entries)).iv_cache
+ if ic_copy.entry # Only initialized (ic_serial > 0) IVCs are optimized
+ num_ivars += 1
+
+ if status.max_ivar_index < ic_copy.entry.index
+ status.max_ivar_index = ic_copy.entry.index
+ end
+
+ if status.ivar_serial == 0
+ status.ivar_serial = ic_copy.entry.class_serial
+ elsif status.ivar_serial != ic_copy.entry.class_serial
+ # Multiple classes have used this ISeq. Give up assuming one serial.
+ status.merge_ivar_guards_p = false
+ return
+ end
+ end
+ end
+ pos += insn.len
+ end
+ status.merge_ivar_guards_p = status.ivar_serial > 0 && num_ivars >= 2
+ end
+
+ # Expand simple macro that doesn't require dynamic C code.
+ def expand_simple_macros(arg_expr)
+ arg_expr.dup.tap do |expr|
+ # For `leave`. We can't proceed next ISeq in the same JIT function.
+ expr.gsub!(/^(?<indent>\s*)RESTORE_REGS\(\);\n/) do
+ indent = Regexp.last_match[:indent]
+ <<-end.gsub(/^ {12}/, '')
+ #if OPT_CALL_THREADED_CODE
+ #{indent}rb_ec_thread_ptr(ec)->retval = val;
+ #{indent}return 0;
+ #else
+ #{indent}return val;
+ #endif
+ end
+ end
+ expr.gsub!(/^(?<indent>\s*)NEXT_INSN\(\);\n/) do
+ indent = Regexp.last_match[:indent]
+ <<-end.gsub(/^ {12}/, '')
+ #{indent}UNREACHABLE_RETURN(Qundef);
+ end
+ end
+ end
+ end
+
+ def to_cstr(expr)
+ expr.gsub(/^(?!#)/, ' ') # indent everything but preprocessor lines
+ end
+
+ # Interpret unsigned long as signed long (VALUE -> OFFSET)
+ def cast_offset(offset)
+ bits = "%0#{8 * Fiddle::SIZEOF_VOIDP}d" % offset.to_s(2)
+ if bits[0] == '1' # negative
+ offset = -bits.chars.map { |i| i == '0' ? '1' : '0' }.join.to_i(2) - 1
+ end
+ offset
+ end
+
+ def captured_cc_entries(status)
+ status.compiled_iseq.jit_unit.cc_entries + status.cc_entries_index
+ end
+
+ def call_data_index(cd, body)
+ cd - body.call_data
+ end
+
+ def vm_cc_cme(cc)
+ # TODO: add VM_ASSERT like actual vm_cc_cme
+ cc.cme_
+ end
+
+ def def_iseq_ptr(method_def)
+ C.rb_iseq_check(method_def.body.iseq.iseqptr)
+ end
+
+ def rb_mjit_iseq_compile_info(body)
+ body.jit_unit.compile_info
+ end
+
+ def ISEQ_IS_SIZE(body)
+ body.ic_size + body.ivc_size + body.ise_size + body.icvarc_size
+ end
+
+ # Return true if an object of the class may be a special const (immediate).
+ # It's "maybe" because Integer and Float are not guaranteed to be an immediate.
+ # If this returns false, rb_class_of could be optimzied to RBASIC_CLASS.
+ def maybe_special_const?(klass)
+ [
+ C.rb_cFalseClass,
+ C.rb_cNilClass,
+ C.rb_cTrueClass,
+ C.rb_cInteger,
+ C.rb_cSymbol,
+ C.rb_cFloat,
+ ].include?(klass)
+ end
+
+ def has_valid_method_type?(cc)
+ vm_cc_cme(cc) != nil
+ end
+
+ def already_compiled?(status, pos)
+ status.stack_size_for_pos[pos] != C.NOT_COMPILED_STACK_SIZE
+ end
+
+ # Return an iseq pointer if cc has inlinable iseq.
+ def rb_mjit_inlinable_iseq(ci, cc)
+ if has_valid_method_type?(cc) &&
+ C.vm_ci_flag(ci) & C.VM_CALL_TAILCALL == 0 && # inlining only non-tailcall path
+ vm_cc_cme(cc).def.type == C.VM_METHOD_TYPE_ISEQ &&
+ C.fastpath_applied_iseq_p(ci, cc, iseq = def_iseq_ptr(vm_cc_cme(cc).def)) &&
+ inlinable_iseq_p(iseq.body) # CC_SET_FASTPATH in vm_callee_setup_arg
+ return iseq
+ end
+ return nil
+ end
+
+ # Return true if the ISeq can be inlined without pushing a new control frame.
+ def inlinable_iseq_p(body)
+ # 1) If catch_except_p, caller frame should be preserved when callee catches an exception.
+ # Then we need to wrap `vm_exec()` but then we can't inline the call inside it.
+ #
+ # 2) If `body->catch_except_p` is false and `handles_sp?` of an insn is false,
+ # sp is not moved as we assume `status->local_stack_p = !body->catch_except_p`.
+ #
+ # 3) If `body->catch_except_p` is false and `always_leaf?` of an insn is true,
+ # pc is not moved.
+ if body.catch_except_p
+ return false
+ end
+
+ pos = 0
+ while pos < body.iseq_size
+ insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos]))
+ # All insns in the ISeq except `leave` (to be overridden in the inlined code)
+ # should meet following strong assumptions:
+ # * Do not require `cfp->sp` motion
+ # * Do not move `cfp->pc`
+ # * Do not read any `cfp->pc`
+ if insn.name == :invokebuiltin || insn.name == :opt_invokebuiltin_delegate || insn.name == :opt_invokebuiltin_delegate_leave
+ # builtin insn's inlinability is handled by `Primitive.attr! 'inline'` per iseq
+ if !body.builtin_inline_p
+ return false;
+ end
+ elsif insn.name != :leave && C.insn_may_depend_on_sp_or_pc(insn.bin, body.iseq_encoded + (pos + 1))
+ return false
+ end
+ # At this moment, `cfp->ep` in an inlined method is not working.
+ case insn.name
+ when :getlocal,
+ :getlocal_WC_0,
+ :getlocal_WC_1,
+ :setlocal,
+ :setlocal_WC_0,
+ :setlocal_WC_1,
+ :getblockparam,
+ :getblockparamproxy,
+ :setblockparam
+ return false
+ end
+ pos += insn.len
+ end
+ return true
+ end
+
+ # CPointer::Struct could be nil on field reference, and this is a helper to
+ # handle that case while using CPointer::Struct#to_s in most cases.
+ # @param struct [RubyVM::MJIT::CPointer::Struct]
+ def to_addr(struct)
+ struct&.to_s || 'NULL'
+ end
+ end
+
+ private_constant(*constants)
+end