summaryrefslogtreecommitdiff
path: root/tool
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 /tool
parent277498e2a2b62b564e3d39ca54aa15b6e8a2c41a (diff)
Ruby MJIT (#6028)
Notes
Notes: Merged-By: k0kubun <takashikkbn@gmail.com>
Diffstat (limited to 'tool')
-rw-r--r--tool/mjit/.gitignore1
-rw-r--r--tool/mjit/Gemfile4
-rwxr-xr-xtool/mjit/bindgen.rb391
-rw-r--r--tool/ruby_vm/helpers/dumper.rb2
-rw-r--r--tool/ruby_vm/views/_mjit_compile_getconstant_path.erb30
-rw-r--r--tool/ruby_vm/views/_mjit_compile_insn.erb92
-rw-r--r--tool/ruby_vm/views/_mjit_compile_insn_body.erb129
-rw-r--r--tool/ruby_vm/views/_mjit_compile_invokebuiltin.erb29
-rw-r--r--tool/ruby_vm/views/_mjit_compile_ivar.erb110
-rw-r--r--tool/ruby_vm/views/_mjit_compile_pc_and_sp.erb38
-rw-r--r--tool/ruby_vm/views/_mjit_compile_send.erb119
-rw-r--r--tool/ruby_vm/views/mjit_compile.inc.erb110
-rw-r--r--tool/ruby_vm/views/mjit_compile_attr.inc.erb17
-rw-r--r--tool/ruby_vm/views/mjit_instruction.rb.erb40
-rwxr-xr-xtool/update-deps2
15 files changed, 455 insertions, 659 deletions
diff --git a/tool/mjit/.gitignore b/tool/mjit/.gitignore
new file mode 100644
index 0000000000..66f8ed35a4
--- /dev/null
+++ b/tool/mjit/.gitignore
@@ -0,0 +1 @@
+/Gemfile.lock
diff --git a/tool/mjit/Gemfile b/tool/mjit/Gemfile
new file mode 100644
index 0000000000..6a3d0aec81
--- /dev/null
+++ b/tool/mjit/Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+gem 'ffi-clang', git: 'https://github.com/ioquatix/ffi-clang'
+gem 'pry-byebug'
diff --git a/tool/mjit/bindgen.rb b/tool/mjit/bindgen.rb
new file mode 100755
index 0000000000..d2a63581e9
--- /dev/null
+++ b/tool/mjit/bindgen.rb
@@ -0,0 +1,391 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+require 'etc'
+require 'fiddle/import'
+require 'set'
+
+arch_bits = Integer(ARGV.first || 64)
+
+# Help ffi-clang find libclang
+if arch_bits == 64
+ # apt install libclang1
+ ENV['LIBCLANG'] ||= Dir.glob("/lib/#{RUBY_PLATFORM}-gnu/libclang-*.so*").grep_v(/-cpp/).sort.last
+else
+ # apt install libclang1:i386
+ ENV['LIBCLANG'] ||= Dir.glob("/lib/i386-linux-gnu/libclang-*.so*").sort.last
+end
+require 'ffi/clang'
+
+class Node < Struct.new(
+ :kind,
+ :spelling,
+ :type,
+ :typedef_type,
+ :bitwidth,
+ :sizeof_type,
+ :offsetof,
+ :tokens,
+ :enum_value,
+ :children,
+ keyword_init: true,
+)
+end
+
+# Parse a C header with ffi-clang and return Node objects.
+# To ease the maintenance, ffi-clang should be used only inside this class.
+class HeaderParser
+ def initialize(header, cflags:)
+ @translation_unit = FFI::Clang::Index.new.parse_translation_unit(
+ header, cflags, [], { detailed_preprocessing_record: true }
+ )
+ end
+
+ def parse
+ parse_children(@translation_unit.cursor)
+ end
+
+ private
+
+ def parse_children(cursor)
+ children = []
+ cursor.visit_children do |cursor, _parent|
+ child = parse_cursor(cursor)
+ if child.kind != :macro_expansion
+ children << child
+ end
+ next :continue
+ end
+ children
+ end
+
+ def parse_cursor(cursor)
+ unless cursor.kind.start_with?('cursor_')
+ raise "unexpected cursor kind: #{cursor.kind}"
+ end
+ kind = cursor.kind.to_s.delete_prefix('cursor_').to_sym
+ children = parse_children(cursor)
+
+ offsetof = {}
+ if kind == :struct
+ children.select { |c| c.kind == :field_decl }.each do |child|
+ offsetof[child.spelling] = cursor.type.offsetof(child.spelling)
+ end
+ end
+
+ sizeof_type = nil
+ if %i[struct union].include?(kind)
+ sizeof_type = cursor.type.sizeof
+ end
+
+ tokens = nil
+ if kind == :macro_definition
+ tokens = @translation_unit.tokenize(cursor.extent).map(&:spelling)
+ end
+
+ enum_value = nil
+ if kind == :enum_constant_decl
+ enum_value = cursor.enum_value
+ end
+
+ Node.new(
+ kind: kind,
+ spelling: cursor.spelling,
+ type: cursor.type.spelling,
+ typedef_type: cursor.typedef_type.spelling,
+ bitwidth: cursor.bitwidth,
+ sizeof_type: sizeof_type,
+ offsetof: offsetof,
+ tokens: tokens,
+ enum_value: enum_value,
+ children: children,
+ )
+ end
+end
+
+# Convert Node objects to a Ruby binding source.
+class BindingGenerator
+ DEFAULTS = { '_Bool' => 'CType::Bool.new' }
+ DEFAULTS.default_proc = proc { |_h, k| "CType::Stub.new(:#{k})" }
+
+ attr_reader :src
+
+ # @param macros [Array<String>] Imported macros
+ # @param enums [Hash{ Symbol => Array<String> }] Imported enum values
+ # @param types [Array<String>] Imported types
+ # @param ruby_fields [Hash{ Symbol => Array<String> }] Struct VALUE fields that are considered Ruby objects
+ def initialize(macros:, enums:, types:, ruby_fields:)
+ @src = String.new
+ @macros = macros.sort
+ @enums = enums.transform_keys(&:to_s).transform_values(&:sort).sort.to_h
+ @types = types.sort
+ @ruby_fields = ruby_fields.transform_keys(&:to_s)
+ @references = Set.new
+ end
+
+ def generate(nodes)
+ # TODO: Support nested declarations
+ nodes_index = nodes.group_by(&:spelling).transform_values(&:last)
+
+ println "require_relative 'c_type'"
+ println
+ println "module RubyVM::MJIT"
+ println " C = Object.new"
+ println
+
+ # Define macros
+ @macros.each do |macro|
+ unless definition = generate_macro(nodes_index[macro])
+ raise "Failed to generate macro: #{macro}"
+ end
+ println " def C.#{macro} = #{definition}"
+ println
+ end
+
+ # Define enum values
+ @enums.each do |enum, values|
+ values.each do |value|
+ unless definition = generate_enum(nodes_index[enum], value)
+ raise "Failed to generate enum value: #{value}"
+ end
+ println " def C.#{value} = #{definition}"
+ println
+ end
+ end
+
+ # Define types
+ @types.each do |type|
+ unless definition = generate_node(nodes_index[type])
+ raise "Failed to generate type: #{type}"
+ end
+ println " def C.#{type}"
+ println "@#{type} ||= #{definition}".gsub(/^/, " ").chomp
+ println " end"
+ println
+ end
+
+ # Leave a stub for types that are referenced but not targeted
+ (@references - @types).each do |type|
+ println " def C.#{type} = #{DEFAULTS[type]}"
+ println
+ end
+
+ chomp
+ println "end"
+ end
+
+ private
+
+ def generate_macro(node)
+ if node.spelling.start_with?('USE_')
+ # Special case: Always force USE_* to be true or false
+ case node
+ in Node[kind: :macro_definition, tokens: [_, '0' | '1' => token], children: []]
+ (Integer(token) == 1).to_s
+ end
+ else
+ # Otherwise, convert a C expression to a Ruby expression when possible
+ case node
+ in Node[kind: :macro_definition, tokens: tokens, children: []]
+ if tokens.first != node.spelling
+ raise "unexpected first token: '#{tokens.first}' != '#{node.spelling}'"
+ end
+ tokens.drop(1).map do |token|
+ case token
+ when /\A(0x)?\d+\z/, '(', '-', '<<', ')'
+ token
+ when *@enums.values.flatten
+ "self.#{token}"
+ else
+ raise "unexpected macro token: #{token}"
+ end
+ end.join(' ')
+ end
+ end
+ end
+
+ def generate_enum(node, value)
+ case node
+ in Node[kind: :enum_decl, children:]
+ children.find { |c| c.spelling == value }&.enum_value
+ in Node[kind: :typedef_decl, children: [child]]
+ generate_enum(child, value)
+ end
+ end
+
+ # Generate code from a node. Used for constructing a complex nested node.
+ # @param node [Node]
+ def generate_node(node)
+ case node&.kind
+ when :struct, :union
+ # node.spelling is often empty for union, but we'd like to give it a name when it has one.
+ buf = +"CType::#{node.kind.to_s.sub(/\A[a-z]/, &:upcase)}.new(\n"
+ buf << " \"#{node.spelling}\", #{node.sizeof_type},\n"
+ node.children.each do |child|
+ field_builder = proc do |field, type|
+ if node.kind == :struct
+ to_ruby = @ruby_fields.fetch(node.spelling, []).include?(field)
+ " #{field}: [#{node.offsetof.fetch(field)}, #{type}#{', true' if to_ruby}],\n"
+ else
+ " #{field}: #{type},\n"
+ end
+ end
+
+ case child
+ # BitField is struct-specific. So it must be handled here.
+ in Node[kind: :field_decl, spelling:, bitwidth:, children: [_grandchild]] if bitwidth > 0
+ buf << field_builder.call(spelling, "CType::BitField.new(#{bitwidth}, #{node.offsetof.fetch(spelling) % 8})")
+ # In most cases, we'd like to let generate_type handle the type unless it's "(unnamed ...)".
+ in Node[kind: :field_decl, spelling:, type:] if !type.empty? && !type.match?(/\(unnamed [^)]+\)\z/)
+ buf << field_builder.call(spelling, generate_type(type))
+ # Lastly, "(unnamed ...)" struct and union are handled here, which are also struct-specific.
+ in Node[kind: :field_decl, spelling:, children: [grandchild]]
+ buf << field_builder.call(spelling, generate_node(grandchild).gsub(/^/, ' ').sub(/\A +/, ''))
+ else # forward declarations are ignored
+ end
+ end
+ buf << ")"
+ when :typedef_decl
+ case node.children
+ in [child]
+ generate_node(child)
+ in [child, Node[kind: :integer_literal]]
+ generate_node(child)
+ in _ unless node.typedef_type.empty?
+ generate_type(node.typedef_type)
+ end
+ when :enum_decl
+ generate_type('int')
+ when :type_ref
+ generate_type(node.spelling)
+ end
+ end
+
+ # Generate code from a type name. Used for resolving the name of a simple leaf node.
+ # @param type [String]
+ def generate_type(type)
+ if type.match?(/\[\d+\]\z/)
+ return "CType::Pointer.new { #{generate_type(type.sub!(/\[\d+\]\z/, ''))} }"
+ end
+ type = type.delete_suffix('const')
+ if type.end_with?('*')
+ return "CType::Pointer.new { #{generate_type(type.delete_suffix('*').rstrip)} }"
+ end
+
+ type = type.gsub(/((const|volatile) )+/, '').rstrip
+ if type.start_with?(/(struct|union|enum) /)
+ target = type.split(' ', 2).last
+ push_target(target)
+ "self.#{target}"
+ else
+ begin
+ ctype = Fiddle::Importer.parse_ctype(type)
+ "CType::Immediate.new(#{ctype})"
+ rescue Fiddle::DLError
+ push_target(type)
+ "self.#{type}"
+ end
+ end
+ end
+
+ def print(str)
+ @src << str
+ end
+
+ def println(str = "")
+ @src << str << "\n"
+ end
+
+ def chomp
+ @src.delete_suffix!("\n")
+ end
+
+ def rstrip!
+ @src.rstrip!
+ end
+
+ def push_target(target)
+ unless target.match?(/\A\w+\z/)
+ raise "invalid target: #{target}"
+ end
+ @references << target
+ end
+end
+
+src_dir = File.expand_path('../..', __dir__)
+if arch_bits == 64
+ build_dir = File.join(src_dir, '.ruby')
+ ruby_platform = RUBY_PLATFORM
+else
+ build_dir = File.join(src_dir, '.ruby-m32')
+ ruby_platform = 'i686-linux'
+end
+cflags = [
+ src_dir,
+ build_dir,
+ File.join(src_dir, 'include'),
+ File.join(build_dir, ".ext/include/#{ruby_platform}"),
+].map { |dir| "-I#{dir}" }
+
+nodes = HeaderParser.new(File.join(src_dir, 'mjit_compiler.h'), cflags: cflags).parse
+generator = BindingGenerator.new(
+ macros: %w[
+ USE_LAZY_LOAD
+ USE_RVARGC
+ VM_CALL_KW_SPLAT
+ VM_CALL_TAILCALL
+ NOT_COMPILED_STACK_SIZE
+ ],
+ enums: {
+ rb_method_type_t: %w[
+ VM_METHOD_TYPE_ISEQ
+ VM_METHOD_TYPE_CFUNC
+ ],
+ vm_call_flag_bits: %w[
+ VM_CALL_KW_SPLAT_bit
+ VM_CALL_TAILCALL_bit
+ ],
+ },
+ types: %w[
+ IC
+ IVC
+ RB_BUILTIN
+ VALUE
+ compile_status
+ iseq_inline_constant_cache
+ iseq_inline_constant_cache_entry
+ iseq_inline_iv_cache_entry
+ iseq_inline_storage_entry
+ rb_builtin_function
+ rb_cref_t
+ rb_iseq_constant_body
+ rb_iseq_struct
+ rb_iseq_t
+ rb_iv_index_tbl_entry
+ rb_mjit_compile_info
+ rb_serial_t
+ rb_mjit_unit
+ CALL_DATA
+ rb_call_data
+ rb_callcache
+ rb_callable_method_entry_struct
+ rb_method_definition_struct
+ rb_method_iseq_t
+ rb_callinfo
+ rb_method_type_t
+ mjit_options
+ compile_branch
+ inlined_call_context
+ rb_iseq_location_t
+ ],
+ ruby_fields: {
+ rb_iseq_location_struct: %w[
+ pathobj
+ base_label
+ label
+ first_lineno
+ ]
+ },
+)
+generator.generate(nodes)
+
+File.write(File.join(src_dir, "lib/mjit/c_#{arch_bits}.rb"), generator.src)
diff --git a/tool/ruby_vm/helpers/dumper.rb b/tool/ruby_vm/helpers/dumper.rb
index 98104f4b92..7aec9c7631 100644
--- a/tool/ruby_vm/helpers/dumper.rb
+++ b/tool/ruby_vm/helpers/dumper.rb
@@ -28,7 +28,7 @@ class RubyVM::Dumper
path = Pathname.new(__FILE__)
path = (path.relative_path_from(Pathname.pwd) rescue path).dirname
path += '../views'
- path += spec
+ path += File.basename(spec)
src = path.read mode: 'rt:utf-8:utf-8'
rescue Errno::ENOENT
raise "don't know how to generate #{path}"
diff --git a/tool/ruby_vm/views/_mjit_compile_getconstant_path.erb b/tool/ruby_vm/views/_mjit_compile_getconstant_path.erb
deleted file mode 100644
index c321da9a52..0000000000
--- a/tool/ruby_vm/views/_mjit_compile_getconstant_path.erb
+++ /dev/null
@@ -1,30 +0,0 @@
-% # -*- C -*-
-% # Copyright (c) 2020 Takashi Kokubun. All rights reserved.
-% #
-% # This file is a part of the programming language Ruby. Permission is hereby
-% # granted, to either redistribute and/or modify this file, provided that the
-% # conditions mentioned in the file COPYING are met. Consult the file for
-% # details.
-%
-% # compiler: Declare dst and ic
-% insn.opes.each_with_index do |ope, i|
- <%= ope.fetch(:decl) %> = (<%= ope.fetch(:type) %>)operands[<%= i %>];
-% end
-
-% # compiler: Capture IC values, locking getinlinecache
- struct iseq_inline_constant_cache_entry *ice = ic->entry;
- if (ice != NULL && !status->compile_info->disable_const_cache) {
-% # JIT: Inline everything in IC, and cancel the slow path
- fprintf(f, " if (vm_inlined_ic_hit_p(0x%"PRIxVALUE", 0x%"PRIxVALUE", (const rb_cref_t *)0x%"PRIxVALUE", reg_cfp->ep)) {", ice->flags, ice->value, (VALUE)ice->ic_cref);
- fprintf(f, " stack[%d] = 0x%"PRIxVALUE";\n", b->stack_size, ice->value);
- fprintf(f, " }");
- fprintf(f, " else {");
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", pos);
- fprintf(f, " goto const_cancel;\n");
- fprintf(f, " }");
-
-% # compiler: Move JIT compiler's internal stack pointer
- b->stack_size += <%= insn.call_attribute('sp_inc') %>;
- break;
- }
diff --git a/tool/ruby_vm/views/_mjit_compile_insn.erb b/tool/ruby_vm/views/_mjit_compile_insn.erb
deleted file mode 100644
index f54d1b0e0e..0000000000
--- a/tool/ruby_vm/views/_mjit_compile_insn.erb
+++ /dev/null
@@ -1,92 +0,0 @@
-% # -*- C -*-
-% # Copyright (c) 2018 Takashi Kokubun. All rights reserved.
-% #
-% # This file is a part of the programming language Ruby. Permission is hereby
-% # granted, to either redistribute and/or modify this file, provided that the
-% # conditions mentioned in the file COPYING are met. Consult the file for
-% # details.
- fprintf(f, "{\n");
- {
-% # compiler: Prepare operands which may be used by `insn.call_attribute`
-% insn.opes.each_with_index do |ope, i|
- MAYBE_UNUSED(<%= ope.fetch(:decl) %>) = (<%= ope.fetch(:type) %>)operands[<%= i %>];
-% end
-%
-% # JIT: Declare stack_size to be used in some macro of _mjit_compile_insn_body.erb
- if (status->local_stack_p) {
- fprintf(f, " MAYBE_UNUSED(unsigned int) stack_size = %u;\n", b->stack_size);
- }
-%
-% # JIT: Declare variables for operands, popped values and return values
-% insn.declarations.each do |decl|
- fprintf(f, " <%= decl %>;\n");
-% end
-
-% # JIT: Set const expressions for `RubyVM::OperandsUnifications` insn
-% insn.preamble.each do |amble|
- fprintf(f, "<%= amble.expr.sub(/const \S+\s+/, '') %>\n");
-% end
-%
-% # JIT: Initialize operands
-% insn.opes.each_with_index do |ope, i|
- fprintf(f, " <%= ope.fetch(:name) %> = (<%= ope.fetch(:type) %>)0x%"PRIxVALUE";", operands[<%= i %>]);
-% case ope.fetch(:type)
-% when 'ID'
- comment_id(f, (ID)operands[<%= i %>]);
-% when 'CALL_DATA'
- comment_id(f, vm_ci_mid(((CALL_DATA)operands[<%= i %>])->ci));
-% when 'VALUE'
- if (SYMBOL_P((VALUE)operands[<%= i %>])) comment_id(f, SYM2ID((VALUE)operands[<%= i %>]));
-% end
- fprintf(f, "\n");
-% end
-%
-% # JIT: Initialize popped values
-% insn.pops.reverse_each.with_index.reverse_each do |pop, i|
- fprintf(f, " <%= pop.fetch(:name) %> = stack[%d];\n", b->stack_size - <%= i + 1 %>);
-% end
-%
-% # JIT: move sp and pc if necessary
-<%= render 'mjit_compile_pc_and_sp', locals: { insn: insn } -%>
-%
-% # JIT: Print insn body in insns.def
-<%= render 'mjit_compile_insn_body', locals: { insn: insn } -%>
-%
-% # JIT: Set return values
-% insn.rets.reverse_each.with_index do |ret, i|
-% # TOPN(n) = ...
- fprintf(f, " stack[%d] = <%= ret.fetch(:name) %>;\n", b->stack_size + (int)<%= insn.call_attribute('sp_inc') %> - <%= i + 1 %>);
-% 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?
- fprintf(f, " if (UNLIKELY(!mjit_call_p)) {\n");
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size + (int)<%= insn.call_attribute('sp_inc') %>);
- if (!pc_moved_p) {
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", next_pos);
- }
- fprintf(f, " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n");
- fprintf(f, " goto cancel;\n");
- fprintf(f, " }\n");
-% end
-%
-% # compiler: Move JIT compiler's internal stack pointer
- b->stack_size += <%= insn.call_attribute('sp_inc') %>;
- }
- fprintf(f, "}\n");
-%
-% # compiler: If insn has conditional JUMP, the code should go to the branch not targeted by JUMP next.
-% if insn.expr.expr =~ /if\s+\([^{}]+\)\s+\{[^{}]+JUMP\([^)]+\);[^{}]+\}/
- if (ALREADY_COMPILED_P(status, pos + insn_len(insn))) {
- fprintf(f, "goto label_%d;\n", pos + insn_len(insn));
- }
- else {
- compile_insns(f, body, b->stack_size, pos + insn_len(insn), status);
- }
-% 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.expr =~ /\sTHROW_EXCEPTION\([^)]+\);/ || insn.expr.expr =~ /\bvm_pop_frame\(/
- b->finish_p = TRUE;
-% end
diff --git a/tool/ruby_vm/views/_mjit_compile_insn_body.erb b/tool/ruby_vm/views/_mjit_compile_insn_body.erb
deleted file mode 100644
index 187e043837..0000000000
--- a/tool/ruby_vm/views/_mjit_compile_insn_body.erb
+++ /dev/null
@@ -1,129 +0,0 @@
-% # -*- C -*-
-% # Copyright (c) 2018 Takashi Kokubun. All rights reserved.
-% #
-% # This file is a part of the programming language Ruby. Permission is hereby
-% # granted, to either redistribute and/or modify this file, provided that the
-% # conditions mentioned in the file COPYING are met. Consult the file for
-% # details.
-%
-% to_cstr = lambda do |line|
-% normalized = line.gsub(/\t/, ' ' * 8)
-% indented = normalized.sub(/\A(?!#)/, ' ') # avoid indenting preprocessor
-% rstring2cstr(indented.rstrip).sub(/"\z/, '\\n"')
-% end
-%
-% #
-% # Expand simple macro, which doesn't require dynamic C code.
-% #
-% expand_simple_macros = lambda do |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(/^ +/, '')
-% #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(/^ +/, '')
-% #{indent}UNREACHABLE_RETURN(Qundef);
-% end
-% end
-% end
-% end
-%
-% #
-% # Print a body of insn, but with macro expansion.
-% #
-% expand_simple_macros.call(insn.expr.expr).each_line do |line|
-% #
-% # Expand dynamic macro here (only JUMP for now)
-% #
-% # 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
- fprintf(f, " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n");
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", next_pos); /* ADD_PC(INSN_ATTR(width)); */
- fprintf(f, " rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);\n");
- fprintf(f, " if (UNLIKELY(!mjit_call_p)) {\n");
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
- fprintf(f, " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n");
- fprintf(f, " goto cancel;\n");
- fprintf(f, " }\n");
- fprintf(f, " }\n");
-% else
- fprintf(f, <%= to_cstr.call(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
- {
- struct case_dispatch_var arg;
- arg.f = f;
- arg.base_pos = pos + insn_len(insn);
- arg.last_value = Qundef;
-
- fprintf(f, " switch (<%= dest %>) {\n");
- st_foreach(RHASH_TBL_RAW(hash), compile_case_dispatch_each, (VALUE)&arg);
- fprintf(f, " case %lu:\n", else_offset);
- fprintf(f, " goto label_%lu;\n", arg.base_pos + else_offset);
- fprintf(f, " }\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) = ...
- fprintf(f, " stack[%d] = <%= ret.fetch(:name) %>;\n", b->stack_size + (int)<%= insn.call_attribute('sp_inc') %> - <%= i + 1 %>);
-% end
-%
- next_pos = pos + insn_len(insn) + (unsigned int)<%= dest %>;
- fprintf(f, " goto label_%d;\n", next_pos);
-% end
-% when /\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/
-% # For `opt_xxx`'s fallbacks.
- if (status->local_stack_p) {
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
- }
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", pos);
- fprintf(f, " RB_DEBUG_COUNTER_INC(mjit_cancel_opt_insn);\n");
- fprintf(f, " 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]
- fprintf(f, " <%= prefix.gsub(/\t/, ' ' * 8) %>INSN_LABEL(<%= name %>_%d)<%= suffix.sub(/\n/, '\n') %>", pos);
-% 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.
- fprintf(f, <%= to_cstr.call(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
- fprintf(f, <%= to_cstr.call(line.sub(/\bGET_SP\(\)/, '%s')) %>, (status->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]
- fprintf(f, <%= to_cstr.call(line.sub(/\bSTACK_ADDR_FROM_TOP\(([^)]+)\)/, '%s')) %>,
- (status->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]
- fprintf(f, <%= to_cstr.call(line.sub(/\bTOPN\(([^)]+)\)/, '%s')) %>,
- (status->local_stack_p ? "*(stack + (stack_size - (<%= num %>) - 1))" : "TOPN(<%= num %>)"));
-% else
- fprintf(f, <%= to_cstr.call(line) %>);
-% end
-% end
-% end
-% end
diff --git a/tool/ruby_vm/views/_mjit_compile_invokebuiltin.erb b/tool/ruby_vm/views/_mjit_compile_invokebuiltin.erb
deleted file mode 100644
index a3796ffc5e..0000000000
--- a/tool/ruby_vm/views/_mjit_compile_invokebuiltin.erb
+++ /dev/null
@@ -1,29 +0,0 @@
-% # -*- C -*-
-% # Copyright (c) 2020 Urabe, Shyouhei. All rights reserved.
-% #
-% # This file is a part of the programming language Ruby. Permission is hereby
-% # granted, to either redistribute and/or modify this file, provided that the
-% # conditions mentioned in the file COPYING are met. Consult the file for
-% # details.
-%
-% insn.opes.each_with_index do |ope, i|
- <%= ope.fetch(:decl) %> = (<%= ope.fetch(:type) %>)operands[<%= i %>];
-% end
- rb_snum_t sp_inc = <%= insn.call_attribute('sp_inc') %>;
- unsigned sp = b->stack_size + (unsigned)sp_inc;
- VM_ASSERT(b->stack_size > -sp_inc);
- VM_ASSERT(sp_inc < UINT_MAX - b->stack_size);
-
- if (bf->compiler) {
- fprintf(f, "{\n");
- fprintf(f, " VALUE val;\n");
- bf->compiler(f, <%=
- insn.name == 'invokebuiltin' ? '-1' : '(rb_num_t)operands[1]'
- %>, b->stack_size, body->builtin_inline_p);
- fprintf(f, " stack[%u] = val;\n", sp - 1);
- fprintf(f, "}\n");
-% if insn.name != 'opt_invokebuiltin_delegate_leave'
- b->stack_size = sp;
- break;
-% end
- }
diff --git a/tool/ruby_vm/views/_mjit_compile_ivar.erb b/tool/ruby_vm/views/_mjit_compile_ivar.erb
deleted file mode 100644
index 1425b3b055..0000000000
--- a/tool/ruby_vm/views/_mjit_compile_ivar.erb
+++ /dev/null
@@ -1,110 +0,0 @@
-% # -*- C -*-
-% # Copyright (c) 2018 Takashi Kokubun. All rights reserved.
-% #
-% # This file is a part of the programming language Ruby. Permission is hereby
-% # granted, to either redistribute and/or modify this file, provided that the
-% # conditions mentioned in the file COPYING are met. Consult the file for
-% # details.
-%
-% # Optimized case of get_instancevariable instruction.
-#if OPT_IC_FOR_IVAR
-{
-% # compiler: Prepare operands which may be used by `insn.call_attribute`
-% insn.opes.each_with_index do |ope, i|
- MAYBE_UNUSED(<%= ope.fetch(:decl) %>) = (<%= ope.fetch(:type) %>)operands[<%= i %>];
-% end
-% # compiler: Use copied IVC to avoid race condition
- IVC ic_copy = &(status->is_entries + ((union iseq_inline_storage_entry *)ic - body->is_entries))->iv_cache;
-%
- if (!status->compile_info->disable_ivar_cache && ic_copy->entry) { // Only ic_copy is enabled.
-% # 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`.
-% # <%= render 'mjit_compile_pc_and_sp', locals: { insn: insn } -%>
-%
-% # JIT: prepare vm_getivar/vm_setivar arguments and variables
- fprintf(f, "{\n");
- fprintf(f, " VALUE obj = GET_SELF();\n");
- fprintf(f, " const uint32_t index = %u;\n", (ic_copy->entry->index));
- 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`
- fprintf(f, " VM_ASSERT(RB_TYPE_P(obj, T_OBJECT));\n");
- fprintf(f, " VM_ASSERT((rb_serial_t)%"PRI_SERIALT_PREFIX"u == RCLASS_SERIAL(RBASIC(obj)->klass));\n", ic_copy->entry->class_serial);
- fprintf(f, " VM_ASSERT(index < ROBJECT_NUMIV(obj));\n");
-% if insn.name == 'setinstancevariable'
-#if USE_RVARGC
- fprintf(f, " if (LIKELY(!RB_OBJ_FROZEN_RAW(obj) && index < ROBJECT_NUMIV(obj))) {\n");
- fprintf(f, " RB_OBJ_WRITE(obj, &ROBJECT_IVPTR(obj)[index], stack[%d]);\n", b->stack_size - 1);
-#else
- fprintf(f, " if (LIKELY(!RB_OBJ_FROZEN_RAW(obj) && %s)) {\n", status->max_ivar_index >= ROBJECT_EMBED_LEN_MAX ? "true" : "RB_FL_ANY_RAW(obj, ROBJECT_EMBED)");
- fprintf(f, " RB_OBJ_WRITE(obj, &ROBJECT(obj)->as.%s, stack[%d]);\n",
- status->max_ivar_index >= ROBJECT_EMBED_LEN_MAX ? "heap.ivptr[index]" : "ary[index]", b->stack_size - 1);
-#endif
- fprintf(f, " }\n");
-% else
- fprintf(f, " VALUE val;\n");
-#if USE_RVARGC
- fprintf(f, " if (LIKELY(index < ROBJECT_NUMIV(obj) && (val = ROBJECT_IVPTR(obj)[index]) != Qundef)) {\n");
-#else
- fprintf(f, " if (LIKELY(%s && (val = ROBJECT(obj)->as.%s) != Qundef)) {\n",
- status->max_ivar_index >= ROBJECT_EMBED_LEN_MAX ? "true" : "RB_FL_ANY_RAW(obj, ROBJECT_EMBED)",
- status->max_ivar_index >= ROBJECT_EMBED_LEN_MAX ? "heap.ivptr[index]" : "ary[index]");
-#endif
- fprintf(f, " stack[%d] = val;\n", b->stack_size);
- fprintf(f, " }\n");
-%end
- }
- else {
- fprintf(f, " const rb_serial_t ic_serial = (rb_serial_t)%"PRI_SERIALT_PREFIX"u;\n", ic_copy->entry->class_serial);
-% # JIT: cache hit path of vm_getivar/vm_setivar, or cancel JIT (recompile it with exivar)
-% if insn.name == 'setinstancevariable'
- fprintf(f, " 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");
- fprintf(f, " VALUE *ptr = ROBJECT_IVPTR(obj);\n");
- fprintf(f, " RB_OBJ_WRITE(obj, &ptr[index], stack[%d]);\n", b->stack_size - 1);
- fprintf(f, " }\n");
-% else
- fprintf(f, " VALUE val;\n");
- fprintf(f, " 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");
- fprintf(f, " stack[%d] = val;\n", b->stack_size);
- fprintf(f, " }\n");
-% end
- }
- fprintf(f, " else {\n");
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", pos);
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
- fprintf(f, " goto ivar_cancel;\n");
- fprintf(f, " }\n");
-
-% # compiler: Move JIT compiler's internal stack pointer
- b->stack_size += <%= insn.call_attribute('sp_inc') %>;
- fprintf(f, "}\n");
- break;
- }
-% if insn.name == 'getinstancevariable'
- else if (!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`.
-% # <%= render 'mjit_compile_pc_and_sp', locals: { insn: insn } -%>
-%
-% # JIT: prepare vm_getivar's arguments and variables
- fprintf(f, "{\n");
- fprintf(f, " VALUE obj = GET_SELF();\n");
- fprintf(f, " const rb_serial_t ic_serial = (rb_serial_t)%"PRI_SERIALT_PREFIX"u;\n", ic_copy->entry->class_serial);
- fprintf(f, " const uint32_t index = %u;\n", ic_copy->entry->index);
-% # JIT: cache hit path of vm_getivar, or cancel JIT (recompile it without any ivar optimization)
- fprintf(f, " struct gen_ivtbl *ivtbl;\n");
- fprintf(f, " VALUE val;\n");
- fprintf(f, " 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");
- fprintf(f, " stack[%d] = val;\n", b->stack_size);
- fprintf(f, " }\n");
- fprintf(f, " else {\n");
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", pos);
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
- fprintf(f, " goto exivar_cancel;\n");
- fprintf(f, " }\n");
-
-% # compiler: Move JIT compiler's internal stack pointer
- b->stack_size += <%= insn.call_attribute('sp_inc') %>;
- fprintf(f, "}\n");
- break;
- }
-% end
-}
-#endif // OPT_IC_FOR_IVAR
diff --git a/tool/ruby_vm/views/_mjit_compile_pc_and_sp.erb b/tool/ruby_vm/views/_mjit_compile_pc_and_sp.erb
deleted file mode 100644
index 390b3ce525..0000000000
--- a/tool/ruby_vm/views/_mjit_compile_pc_and_sp.erb
+++ /dev/null
@@ -1,38 +0,0 @@
-% # Copyright (c) 2018 Takashi Kokubun. All rights reserved.
-% #
-% # This file is a part of the programming language Ruby. Permission is hereby
-% # granted, to either redistribute and/or modify this file, provided that the
-% # conditions mentioned in the file COPYING are met. Consult the file for
-% # details.
-%
-% # 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.
- MAYBE_UNUSED(bool pc_moved_p) = false;
- if (<%= !(insn.always_leaf? || insn.leaf_without_check_ints?) %>) {
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", next_pos); /* ADD_PC(INSN_ATTR(width)); */
- pc_moved_p = true;
- }
-%
-% # JIT: move sp to use or preserve stack variables
- if (status->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):
- {
- rb_snum_t i, push_size;
- push_size = -<%= insn.call_attribute('sp_inc') %> + <%= insn.rets.size %> - <%= insn.pops.size %>;
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %ld;\n", push_size); /* POPN(INSN_ATTR(popn)); */
- for (i = 0; i < push_size; i++) {
- fprintf(f, " *(reg_cfp->sp + %ld) = stack[%ld];\n", i - push_size, (rb_snum_t)b->stack_size - push_size + i);
- }
- }
-% end
- }
- else {
-% if insn.handles_sp?
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size - <%= insn.pops.size %>); /* POPN(INSN_ATTR(popn)); */
-% else
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
-% end
- }
diff --git a/tool/ruby_vm/views/_mjit_compile_send.erb b/tool/ruby_vm/views/_mjit_compile_send.erb
deleted file mode 100644
index 316974a7e6..0000000000
--- a/tool/ruby_vm/views/_mjit_compile_send.erb
+++ /dev/null
@@ -1,119 +0,0 @@
-% # -*- C -*-
-% # Copyright (c) 2018 Takashi Kokubun. All rights reserved.
-% #
-% # This file is a part of the programming language Ruby. Permission is hereby
-% # granted, to either redistribute and/or modify this file, provided that the
-% # conditions mentioned in the file COPYING are met. Consult the file for
-% # details.
-%
-% # Optimized case of send / opt_send_without_block instructions.
-{
-% # compiler: Prepare operands which may be used by `insn.call_attribute`
-% insn.opes.each_with_index do |ope, i|
- MAYBE_UNUSED(<%= ope.fetch(:decl) %>) = (<%= ope.fetch(:type) %>)operands[<%= i %>];
-% end
-% # compiler: Use captured cc to avoid race condition
- size_t cd_index = call_data_index(cd, body);
- const struct rb_callcache **cc_entries = captured_cc_entries(status);
- const struct rb_callcache *captured_cc = cc_entries[cd_index];
-%
-% # compiler: Inline send insn where some supported fastpath is used.
- const rb_iseq_t *iseq = NULL;
- const CALL_INFO ci = cd->ci;
- int kw_splat = IS_ARGS_KW_SPLAT(ci) > 0;
- extern bool rb_splat_or_kwargs_p(const struct rb_callinfo *restrict ci);
- 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 == VM_METHOD_TYPE_CFUNC
- && !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 == VM_METHOD_TYPE_ISEQ
- && fastpath_applied_iseq_p(ci, captured_cc, iseq = def_iseq_ptr(vm_cc_cme(captured_cc)->def))
- && !(vm_ci_flag(ci) & VM_CALL_TAILCALL))
- )) {
- const bool cfunc_debug = false; // Set true when you want to see inlined cfunc
- if (cfunc_debug && vm_cc_cme(captured_cc)->def->type == VM_METHOD_TYPE_CFUNC)
- fprintf(stderr, " * %s\n", rb_id2name(vm_ci_mid(ci)));
-
- int sp_inc = (int)sp_inc_of_sendish(ci);
- fprintf(f, "{\n");
-
-% # JIT: Invalidate call cache if it requires vm_search_method. This allows to inline some of following things.
- bool opt_class_of = !maybe_special_const_class_p(captured_cc->klass); // If true, use RBASIC_CLASS instead of CLASS_OF to reduce code size
- fprintf(f, " const struct rb_callcache *cc = (const struct rb_callcache *)0x%"PRIxVALUE";\n", (VALUE)captured_cc);
- fprintf(f, " const rb_callable_method_entry_t *cc_cme = (const rb_callable_method_entry_t *)0x%"PRIxVALUE";\n", (VALUE)vm_cc_cme(captured_cc));
- fprintf(f, " const VALUE recv = stack[%d];\n", b->stack_size + sp_inc - 1);
- fprintf(f, " if (UNLIKELY(%s || !vm_cc_valid_p(cc, cc_cme, %s(recv)))) {\n", opt_class_of ? "RB_SPECIAL_CONST_P(recv)" : "false", opt_class_of ? "RBASIC_CLASS" : "CLASS_OF");
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", pos);
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
- fprintf(f, " goto send_cancel;\n");
- fprintf(f, " }\n");
-
-% # JIT: move sp and pc if necessary
-<%= render 'mjit_compile_pc_and_sp', locals: { insn: insn } -%>
-
-% # JIT: If ISeq is inlinable, call the inlined method without pushing a frame.
- if (iseq && status->inlined_iseqs != NULL && ISEQ_BODY(iseq) == status->inlined_iseqs[pos]) {
- fprintf(f, " {\n");
- fprintf(f, " VALUE orig_self = reg_cfp->self;\n");
- fprintf(f, " reg_cfp->self = stack[%d];\n", b->stack_size + sp_inc - 1);
- fprintf(f, " stack[%d] = _mjit%d_inlined_%d(ec, reg_cfp, orig_self, original_iseq);\n", b->stack_size + sp_inc - 1, status->compiled_id, pos);
- fprintf(f, " reg_cfp->self = orig_self;\n");
- fprintf(f, " }\n");
- }
- else {
-% # JIT: Forked `vm_sendish` (except method_explorer = vm_search_method_wrap) to inline various things
- fprintf(f, " {\n");
- fprintf(f, " VALUE val;\n");
- fprintf(f, " struct rb_calling_info calling;\n");
-% if insn.name == 'send'
- fprintf(f, " calling.block_handler = vm_caller_setup_arg_block(ec, reg_cfp, (const struct rb_callinfo *)0x%"PRIxVALUE", (rb_iseq_t *)0x%"PRIxVALUE", FALSE);\n", (VALUE)ci, (VALUE)blockiseq);
-% else
- fprintf(f, " calling.block_handler = VM_BLOCK_HANDLER_NONE;\n");
-% end
- fprintf(f, " calling.kw_splat = %d;\n", kw_splat);
- fprintf(f, " calling.recv = stack[%d];\n", b->stack_size + sp_inc - 1);
- fprintf(f, " calling.argc = %d;\n", vm_ci_argc(ci));
-
- if (vm_cc_cme(captured_cc)->def->type == VM_METHOD_TYPE_CFUNC) {
-% # TODO: optimize this more
- fprintf(f, " calling.ci = (CALL_INFO)0x%"PRIxVALUE";\n", (VALUE)ci); // creating local cd here because operand's cd->cc may not be the same as inlined cc.
- fprintf(f, " calling.cc = cc;");
- fprintf(f, " val = vm_call_cfunc_with_frame(ec, reg_cfp, &calling);\n");
- }
- else { // VM_METHOD_TYPE_ISEQ
-% # fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
- fprintf(f, " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, cc_cme, 0, %d, %d);\n", ISEQ_BODY(iseq)->param.size, ISEQ_BODY(iseq)->local_table_size);
- if (ISEQ_BODY(iseq)->catch_except_p) {
- fprintf(f, " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n");
- fprintf(f, " val = vm_exec(ec, true);\n");
- }
- else {
- fprintf(f, " if ((val = jit_exec(ec)) == Qundef) {\n");
- fprintf(f, " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n"); // This is vm_call0_body's code after vm_call_iseq_setup
- fprintf(f, " val = vm_exec(ec, false);\n");
- fprintf(f, " }\n");
- }
- }
- fprintf(f, " stack[%d] = val;\n", b->stack_size + sp_inc - 1);
- fprintf(f, " }\n");
-
-% # JIT: We should evaluate ISeq modified for TracePoint if it's enabled. Note: This is slow.
- fprintf(f, " if (UNLIKELY(!mjit_call_p)) {\n");
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size + (int)<%= insn.call_attribute('sp_inc') %>);
- if (!pc_moved_p) {
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", next_pos);
- }
- fprintf(f, " RB_DEBUG_COUNTER_INC(mjit_cancel_invalidate_all);\n");
- fprintf(f, " goto cancel;\n");
- fprintf(f, " }\n");
- }
-
-% # compiler: Move JIT compiler's internal stack pointer
- b->stack_size += <%= insn.call_attribute('sp_inc') %>;
-
- fprintf(f, "}\n");
- break;
- }
-}
diff --git a/tool/ruby_vm/views/mjit_compile.inc.erb b/tool/ruby_vm/views/mjit_compile.inc.erb
deleted file mode 100644
index 00808b21ff..0000000000
--- a/tool/ruby_vm/views/mjit_compile.inc.erb
+++ /dev/null
@@ -1,110 +0,0 @@
-/* -*- C -*- */
-
-% # Copyright (c) 2018 Takashi Kokubun. All rights reserved.
-% #
-% # This file is a part of the programming language Ruby. Permission is hereby
-% # granted, to either redistribute and/or modify this file, provided that the
-% # conditions mentioned in the file COPYING are met. Consult the file for
-% # details.
-<%= render 'copyright' %>
-%
-% # This is an ERB template that generates Ruby code that generates C code that
-% # generates JIT-ed C code.
-<%= render 'notice', locals: {
- this_file: 'is the main part of compile_insn() in mjit_compiler.c',
- edit: __FILE__,
-} -%>
-%
-% unsupported_insns = [
-% 'defineclass', # low priority
-% ]
-%
-% opt_send_without_block = RubyVM::Instructions.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 = RubyVM::BareInstructions.to_a.select do |insn|
-% insn.name.start_with?('opt_') && opt_send_without_block.opes == insn.opes &&
-% insn.expr.expr.lines.any? { |l| l.match(/\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/) }
-% end.map(&:name)
-%
-% # 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`
-
-switch (insn) {
-% (RubyVM::BareInstructions.to_a + RubyVM::OperandsUnifications.to_a).each do |insn|
-% next if unsupported_insns.include?(insn.name)
- case BIN(<%= insn.name %>): {
-% # Instruction-specific behavior in JIT
-% case insn.name
-% when 'opt_send_without_block', 'send'
-<%= render 'mjit_compile_send', locals: { insn: insn } -%>
-% when *send_compatible_opt_insns
-% # To avoid cancel, just emit `opt_send_without_block` instead of `opt_*` insn if call cache is populated.
-% cd_index = insn.opes.index { |o| o.fetch(:type) == 'CALL_DATA' }
- if (has_cache_for_send(captured_cc_entries(status)[call_data_index((CALL_DATA)operands[<%= cd_index %>], body)], BIN(<%= insn.name %>))) {
-<%= render 'mjit_compile_send', locals: { insn: opt_send_without_block } -%>
-<%= render 'mjit_compile_insn', locals: { insn: opt_send_without_block } -%>
- break;
- }
-% when 'getinstancevariable', 'setinstancevariable'
-<%= render 'mjit_compile_ivar', locals: { insn: insn } -%>
-% when 'invokebuiltin', 'opt_invokebuiltin_delegate'
-<%= render 'mjit_compile_invokebuiltin', locals: { insn: insn } -%>
-% when 'opt_getconstant_path'
-<%= render 'mjit_compile_getconstant_path', locals: { insn: insn } -%>
-% when 'leave', 'opt_invokebuiltin_delegate_leave'
-% # opt_invokebuiltin_delegate_leave also implements leave insn. We need to handle it here for inlining.
-% if insn.name == 'opt_invokebuiltin_delegate_leave'
-<%= render 'mjit_compile_invokebuiltin', locals: { insn: insn } -%>
-% else
- if (b->stack_size != 1) {
- if (mjit_opts.warnings || mjit_opts.verbose)
- fprintf(stderr, "MJIT warning: Unexpected JIT stack_size on leave: %d\n", b->stack_size);
- status->success = false;
- }
-% end
-% # Skip vm_pop_frame for inlined call
- if (status->inlined_iseqs != NULL) { // the current ISeq is NOT being inlined
-% # Cancel on interrupts to make leave insn leaf
- fprintf(f, " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n");
- fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
- fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", pos);
- fprintf(f, " rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);\n");
- fprintf(f, " }\n");
- fprintf(f, " ec->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(reg_cfp);\n"); // vm_pop_frame
- }
- fprintf(f, " return stack[0];\n");
- b->stack_size += <%= insn.call_attribute('sp_inc') %>;
- b->finish_p = TRUE;
- break;
-% end
-%
-% # Main insn implementation generated by insns.def
-<%= render 'mjit_compile_insn', locals: { insn: insn } -%>
- break;
- }
-% end
-%
-% # We don't support InstructionsUnifications yet because it's not used for now.
-% # We don't support TraceInstructions yet. There is no blocker for it but it's just not implemented.
- default:
- if (mjit_opts.warnings || mjit_opts.verbose)
- fprintf(stderr, "MJIT warning: Skipped to compile unsupported instruction: %s\n", insn_name(insn));
- status->success = false;
- break;
-}
diff --git a/tool/ruby_vm/views/mjit_compile_attr.inc.erb b/tool/ruby_vm/views/mjit_compile_attr.inc.erb
new file mode 100644
index 0000000000..7b925420dd
--- /dev/null
+++ b/tool/ruby_vm/views/mjit_compile_attr.inc.erb
@@ -0,0 +1,17 @@
+static rb_snum_t
+mjit_call_attribute_sp_inc(const int insn, const VALUE *operands)
+{
+ switch (insn) {
+% (RubyVM::BareInstructions.to_a + RubyVM::OperandsUnifications.to_a).each do |insn|
+ case BIN(<%= insn.name %>): {
+% # compiler: Prepare operands which may be used by `insn.call_attribute`
+% insn.opes.each_with_index do |ope, i|
+ MAYBE_UNUSED(<%= ope.fetch(:decl) %>) = (<%= ope.fetch(:type) %>)operands[<%= i %>];
+% end
+ return <%= insn.call_attribute('sp_inc') %>;
+ }
+% end
+ default:
+ rb_bug("unexpected insn in mjit_call_attribute_sp_inc");
+ }
+}
diff --git a/tool/ruby_vm/views/mjit_instruction.rb.erb b/tool/ruby_vm/views/mjit_instruction.rb.erb
new file mode 100644
index 0000000000..1f49669fa5
--- /dev/null
+++ b/tool/ruby_vm/views/mjit_instruction.rb.erb
@@ -0,0 +1,40 @@
+module RubyVM::MJIT
+ Instruction = Struct.new(
+ :name,
+ :bin,
+ :len,
+ :expr,
+ :declarations,
+ :preamble,
+ :opes,
+ :pops,
+ :rets,
+ :always_leaf?,
+ :leaf_without_check_ints?,
+ :handles_sp?,
+ )
+
+ INSNS = {
+% RubyVM::Instructions.each_with_index do |insn, i|
+% next if insn.name.start_with?('trace_')
+ <%= i %> => Instruction.new(
+ name: :<%= insn.name %>,
+ bin: <%= i %>, # BIN(<%= insn.name %>)
+ len: <%= insn.width %>, # insn_len
+ expr: <<-EXPR,
+<%= insn.expr.expr.dump.sub(/\A"/, '').sub(/"\z/, '').gsub(/\\n/, "\n").gsub(/\\t/, ' ' * 8) %>
+ EXPR
+ declarations: <%= insn.declarations.inspect %>,
+ preamble: <%= insn.preamble.map(&:expr).inspect %>,
+ opes: <%= insn.opes.inspect %>,
+ pops: <%= insn.pops.inspect %>,
+ rets: <%= insn.rets.inspect %>,
+ always_leaf?: <%= insn.always_leaf? %>,
+ leaf_without_check_ints?: <%= insn.leaf_without_check_ints? %>,
+ handles_sp?: <%= insn.handles_sp? %>,
+ ),
+% end
+ }
+
+ private_constant *constants
+end if RubyVM::MJIT.enabled?
diff --git a/tool/update-deps b/tool/update-deps
index 27230c50e4..ba94840f65 100755
--- a/tool/update-deps
+++ b/tool/update-deps
@@ -120,7 +120,7 @@ FILES_NEED_VPATH = %w[
known_errors.inc
lex.c
miniprelude.c
- mjit_compile.inc
+ mjit_compile_attr.inc
newline.c
node_name.inc
opt_sc.inc