summaryrefslogtreecommitdiff
path: root/tool/ruby_vm
diff options
context:
space:
mode:
Diffstat (limited to 'tool/ruby_vm')
-rw-r--r--tool/ruby_vm/controllers/application_controller.rb5
-rw-r--r--tool/ruby_vm/helpers/c_escape.rb5
-rw-r--r--tool/ruby_vm/helpers/dumper.rb7
-rw-r--r--tool/ruby_vm/models/attribute.rb2
-rwxr-xr-xtool/ruby_vm/models/bare_instructions.rb16
-rw-r--r--tool/ruby_vm/models/c_expr.rb6
-rw-r--r--tool/ruby_vm/models/operands_unifications.rb8
-rw-r--r--tool/ruby_vm/models/typemap.rb1
-rw-r--r--tool/ruby_vm/scripts/insns2vm.rb12
-rw-r--r--tool/ruby_vm/views/_comptime_insn_stack_increase.erb2
-rw-r--r--tool/ruby_vm/views/_insn_entry.erb9
-rw-r--r--tool/ruby_vm/views/_insn_type_chars.erb19
-rw-r--r--tool/ruby_vm/views/_leaf_helpers.erb2
-rw-r--r--tool/ruby_vm/views/_mjit_compile_getinlinecache.erb31
-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.erb101
-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/lib/ruby_vm/rjit/instruction.rb.erb14
-rw-r--r--tool/ruby_vm/views/mjit_compile.inc.erb110
-rw-r--r--tool/ruby_vm/views/opt_sc.inc.erb40
-rw-r--r--tool/ruby_vm/views/optinsn.inc.erb8
24 files changed, 722 insertions, 83 deletions
diff --git a/tool/ruby_vm/controllers/application_controller.rb b/tool/ruby_vm/controllers/application_controller.rb
index e03e54e397..25c10947ed 100644
--- a/tool/ruby_vm/controllers/application_controller.rb
+++ b/tool/ruby_vm/controllers/application_controller.rb
@@ -16,11 +16,10 @@ require_relative '../models/typemap'
require_relative '../loaders/vm_opts_h'
class ApplicationController
- def generate i, destdir, basedir
+ def generate i, destdir
path = Pathname.new i
dst = destdir ? Pathname.new(destdir).join(i) : Pathname.new(i)
- base = basedir ? Pathname.new(basedir) : Pathname.pwd
- dumper = RubyVM::Dumper.new dst, base.expand_path
+ dumper = RubyVM::Dumper.new dst
return [path, dumper]
end
end
diff --git a/tool/ruby_vm/helpers/c_escape.rb b/tool/ruby_vm/helpers/c_escape.rb
index 2a99e408da..34fafd1e34 100644
--- a/tool/ruby_vm/helpers/c_escape.rb
+++ b/tool/ruby_vm/helpers/c_escape.rb
@@ -17,10 +17,7 @@ module RubyVM::CEscape
# generate comment, with escaps.
def commentify str
- unless str = str.dump[/\A"\K.*(?="\z)/]
- raise Encoding::CompatibilityError, "must be ASCII-compatible (#{str.encoding})"
- end
- return "/* #{str.gsub('*/', '*\\/').gsub('/*', '/\\*')} */"
+ return "/* #{str.b.gsub('*/', '*\\/').gsub('/*', '/\\*')} */"
end
# Mimic gensym of CL.
diff --git a/tool/ruby_vm/helpers/dumper.rb b/tool/ruby_vm/helpers/dumper.rb
index c083dffa7a..98104f4b92 100644
--- a/tool/ruby_vm/helpers/dumper.rb
+++ b/tool/ruby_vm/helpers/dumper.rb
@@ -28,8 +28,8 @@ class RubyVM::Dumper
path = Pathname.new(__FILE__)
path = (path.relative_path_from(Pathname.pwd) rescue path).dirname
path += '../views'
- path += Pathname.pwd.join(spec).expand_path.to_s.sub("#{@base}/", '')
- src = path.expand_path.read mode: 'rt:utf-8:utf-8'
+ path += spec
+ src = path.read mode: 'rt:utf-8:utf-8'
rescue Errno::ENOENT
raise "don't know how to generate #{path}"
else
@@ -85,11 +85,10 @@ class RubyVM::Dumper
. join
end
- def initialize dst, base
+ def initialize dst
@erb = {}
@empty = new_binding
@file = cstr dst.to_path
- @base = base
end
def render partial, opts = { :locals => {} }
diff --git a/tool/ruby_vm/models/attribute.rb b/tool/ruby_vm/models/attribute.rb
index ac4122f3ac..de35e7234a 100644
--- a/tool/ruby_vm/models/attribute.rb
+++ b/tool/ruby_vm/models/attribute.rb
@@ -21,7 +21,7 @@ class RubyVM::Attribute
@key = opts[:name]
@expr = RubyVM::CExpr.new location: opts[:location], expr: opts[:expr]
@type = opts[:type]
- @ope_decls = @insn.operands.map do |operand|
+ @ope_decls = @insn.opes.map do |operand|
decl = operand[:decl]
if @key == 'comptime_sp_inc' && operand[:type] == 'CALL_DATA'
decl = decl.gsub('CALL_DATA', 'CALL_INFO').gsub('cd', 'ci')
diff --git a/tool/ruby_vm/models/bare_instructions.rb b/tool/ruby_vm/models/bare_instructions.rb
index a810d89f3c..6b5f1f6cf8 100755
--- a/tool/ruby_vm/models/bare_instructions.rb
+++ b/tool/ruby_vm/models/bare_instructions.rb
@@ -16,7 +16,7 @@ require_relative 'typemap'
require_relative 'attribute'
class RubyVM::BareInstructions
- attr_reader :template, :name, :operands, :pops, :rets, :decls, :expr
+ attr_reader :template, :name, :opes, :pops, :rets, :decls, :expr
def initialize opts = {}
@template = opts[:template]
@@ -24,7 +24,7 @@ class RubyVM::BareInstructions
@loc = opts[:location]
@sig = opts[:signature]
@expr = RubyVM::CExpr.new opts[:expr]
- @operands = typesplit @sig[:ope]
+ @opes = typesplit @sig[:ope]
@pops = typesplit @sig[:pop].reject {|i| i == '...' }
@rets = typesplit @sig[:ret].reject {|i| i == '...' }
@attrs = opts[:attributes].map {|i|
@@ -51,7 +51,7 @@ class RubyVM::BareInstructions
def call_attribute name
return sprintf 'attr_%s_%s(%s)', name, @name, \
- @operands.map {|i| i[:name] }.compact.join(', ')
+ @opes.map {|i| i[:name] }.compact.join(', ')
end
def has_attribute? k
@@ -65,7 +65,7 @@ class RubyVM::BareInstructions
end
def width
- return 1 + operands.size
+ return 1 + opes.size
end
def declarations
@@ -98,7 +98,7 @@ class RubyVM::BareInstructions
end
def operands_info
- operands.map {|o|
+ opes.map {|o|
c, _ = RubyVM::Typemap.fetch o[:type]
next c
}.join
@@ -137,7 +137,7 @@ class RubyVM::BareInstructions
end
def has_ope? var
- return @operands.any? {|i| i[:name] == var[:name] }
+ return @opes.any? {|i| i[:name] == var[:name] }
end
def has_pop? var
@@ -180,7 +180,7 @@ class RubyVM::BareInstructions
# Beware: order matters here because some attribute depends another.
generate_attribute 'const char*', 'name', "insn_name(#{bin})"
generate_attribute 'enum ruby_vminsn_type', 'bin', bin
- generate_attribute 'rb_num_t', 'open', operands.size
+ generate_attribute 'rb_num_t', 'open', opes.size
generate_attribute 'rb_num_t', 'popn', pops.size
generate_attribute 'rb_num_t', 'retn', rets.size
generate_attribute 'rb_num_t', 'width', width
@@ -191,7 +191,7 @@ class RubyVM::BareInstructions
def default_definition_of_handles_sp
# Insn with ISEQ should yield it; can handle sp.
- return operands.any? {|o| o[:type] == 'ISEQ' }
+ return opes.any? {|o| o[:type] == 'ISEQ' }
end
def default_definition_of_leaf
diff --git a/tool/ruby_vm/models/c_expr.rb b/tool/ruby_vm/models/c_expr.rb
index 4b5aec58dd..073112f545 100644
--- a/tool/ruby_vm/models/c_expr.rb
+++ b/tool/ruby_vm/models/c_expr.rb
@@ -36,10 +36,6 @@ class RubyVM::CExpr
end
def inspect
- if @__LINE__
- sprintf "#<%s:%d %s>", @__FILE__, @__LINE__, @expr
- else
- sprintf "#<%s %s>", @__FILE__, @expr
- end
+ sprintf "#<%s:%d %s>", @__FILE__, @__LINE__, @expr
end
end
diff --git a/tool/ruby_vm/models/operands_unifications.rb b/tool/ruby_vm/models/operands_unifications.rb
index 10e5742897..ee4e3a695d 100644
--- a/tool/ruby_vm/models/operands_unifications.rb
+++ b/tool/ruby_vm/models/operands_unifications.rb
@@ -38,8 +38,8 @@ class RubyVM::OperandsUnifications < RubyVM::BareInstructions
end
def operand_shift_of var
- before = @original.operands.find_index var
- after = @operands.find_index var
+ before = @original.opes.find_index var
+ after = @opes.find_index var
raise "no #{var} for #{@name}" unless before and after
return before - after
end
@@ -50,7 +50,7 @@ class RubyVM::OperandsUnifications < RubyVM::BareInstructions
case val when '*' then
next nil
else
- type = @original.operands[i][:type]
+ type = @original.opes[i][:type]
expr = RubyVM::Typemap.typecast_to_VALUE type, val
next "#{ptr}[#{i}] == #{expr}"
end
@@ -85,7 +85,7 @@ class RubyVM::OperandsUnifications < RubyVM::BareInstructions
def compose location, spec, template
name = namegen spec
*, argv = *spec
- opes = @original.operands
+ opes = @original.opes
if opes.size != argv.size
raise sprintf("operand size mismatch for %s (%s's: %d, given: %d)",
name, template[:name], opes.size, argv.size)
diff --git a/tool/ruby_vm/models/typemap.rb b/tool/ruby_vm/models/typemap.rb
index d762dd3321..ed3aea7d2e 100644
--- a/tool/ruby_vm/models/typemap.rb
+++ b/tool/ruby_vm/models/typemap.rb
@@ -16,7 +16,6 @@ RubyVM::Typemap = {
"CDHASH" => %w[H TS_CDHASH],
"IC" => %w[K TS_IC],
"IVC" => %w[A TS_IVC],
- "ICVARC" => %w[J TS_ICVARC],
"ID" => %w[I TS_ID],
"ISE" => %w[T TS_ISE],
"ISEQ" => %w[S TS_ISEQ],
diff --git a/tool/ruby_vm/scripts/insns2vm.rb b/tool/ruby_vm/scripts/insns2vm.rb
index 47d8da5513..8325dd364f 100644
--- a/tool/ruby_vm/scripts/insns2vm.rb
+++ b/tool/ruby_vm/scripts/insns2vm.rb
@@ -15,10 +15,10 @@ require_relative '../controllers/application_controller.rb'
module RubyVM::Insns2VM
def self.router argv
- options = { destdir: nil, basedir: nil }
+ options = { destdir: nil }
targets = generate_parser(options).parse argv
return targets.map do |i|
- next ApplicationController.new.generate i, options[:destdir], options[:basedir]
+ next ApplicationController.new.generate i, options[:destdir]
end
end
@@ -84,14 +84,6 @@ module RubyVM::Insns2VM
options[:destdir] = dir
end
- this.on "--basedir=DIR", <<-'begin' do |dir|
- Change the base directory from the current working directory
- to the given path. Used for searching the source template.
- begin
- raise "directory was not found in '#{dir}'" unless Dir.exist?(dir)
- options[:basedir] = dir
- end
-
this.on "-V", "--[no-]verbose", <<-'end'
Please let us ignore this and be modest.
end
diff --git a/tool/ruby_vm/views/_comptime_insn_stack_increase.erb b/tool/ruby_vm/views/_comptime_insn_stack_increase.erb
index cb895815ce..b633ab4d32 100644
--- a/tool/ruby_vm/views/_comptime_insn_stack_increase.erb
+++ b/tool/ruby_vm/views/_comptime_insn_stack_increase.erb
@@ -42,7 +42,7 @@ comptime_insn_stack_increase_dispatch(enum ruby_vminsn_type insn, const VALUE *o
% end
case <%= i.bin %>:
return <%= attr_function %>(<%=
- i.operands.map.with_index do |v, j|
+ i.opes.map.with_index do |v, j|
if v[:type] == 'CALL_DATA' && i.has_attribute?('comptime_sp_inc')
v = v.dup
v[:type] = 'CALL_INFO'
diff --git a/tool/ruby_vm/views/_insn_entry.erb b/tool/ruby_vm/views/_insn_entry.erb
index 6ec33461c4..f34afddb1f 100644
--- a/tool/ruby_vm/views/_insn_entry.erb
+++ b/tool/ruby_vm/views/_insn_entry.erb
@@ -20,11 +20,11 @@ INSN_ENTRY(<%= insn.name %>)
<%= render_c_expr konst -%>
% end
%
-% insn.operands.each_with_index do |ope, i|
+% insn.opes.each_with_index do |ope, i|
<%= ope[:decl] %> = (<%= ope[:type] %>)GET_OPERAND(<%= i + 1 %>);
% end
# define INSN_ATTR(x) <%= insn.call_attribute(' ## x ## ') %>
- const bool MAYBE_UNUSED(leaf) = INSN_ATTR(leaf);
+ const bool leaf = INSN_ATTR(leaf);
% insn.pops.reverse_each.with_index.reverse_each do |pop, i|
<%= pop[:decl] %> = <%= insn.cast_from_VALUE pop, "TOPN(#{i})"%>;
% end
@@ -35,13 +35,13 @@ INSN_ENTRY(<%= insn.name %>)
% end
/* ### Instruction preambles. ### */
- ADD_PC(INSN_ATTR(width));
+ if (! leaf) ADD_PC(INSN_ATTR(width));
% if insn.handles_sp?
POPN(INSN_ATTR(popn));
% end
<%= insn.handle_canary "SETUP_CANARY(leaf)" -%>
COLLECT_USAGE_INSN(INSN_ATTR(bin));
-% insn.operands.each_with_index do |ope, i|
+% insn.opes.each_with_index do |ope, i|
COLLECT_USAGE_OPERAND(INSN_ATTR(bin), <%= i %>, <%= ope[:name] %>);
% end
% unless body.empty?
@@ -68,6 +68,7 @@ INSN_ENTRY(<%= insn.name %>)
VM_ASSERT(!RB_TYPE_P(TOPN(<%= i %>), T_MOVED));
% end
% end
+ if (leaf) ADD_PC(INSN_ATTR(width));
# undef INSN_ATTR
/* ### Leave the instruction. ### */
diff --git a/tool/ruby_vm/views/_insn_type_chars.erb b/tool/ruby_vm/views/_insn_type_chars.erb
index 27daec6c6d..4e1f63e660 100644
--- a/tool/ruby_vm/views/_insn_type_chars.erb
+++ b/tool/ruby_vm/views/_insn_type_chars.erb
@@ -11,22 +11,3 @@ enum ruby_insn_type_chars {
<%= t %> = '<%= c %>',
% end
};
-
-static inline union iseq_inline_storage_entry *
-ISEQ_IS_ENTRY_START(const struct rb_iseq_constant_body *body, char op_type)
-{
- unsigned int relative_ic_offset = 0;
- switch (op_type) {
- case TS_IC:
- relative_ic_offset += body->ise_size;
- case TS_ISE:
- relative_ic_offset += body->icvarc_size;
- case TS_ICVARC:
- relative_ic_offset += body->ivc_size;
- case TS_IVC:
- break;
- default:
- rb_bug("Wrong op type");
- }
- return &body->is_entries[relative_ic_offset];
-}
diff --git a/tool/ruby_vm/views/_leaf_helpers.erb b/tool/ruby_vm/views/_leaf_helpers.erb
index f740107a0a..1735db2196 100644
--- a/tool/ruby_vm/views/_leaf_helpers.erb
+++ b/tool/ruby_vm/views/_leaf_helpers.erb
@@ -10,7 +10,7 @@
#include "iseq.h"
-// This is used to tell RJIT that this insn would be leaf if CHECK_INTS didn't exist.
+// This is used to tell MJIT that this insn would be leaf if CHECK_INTS didn't exist.
// It should be used only when RUBY_VM_CHECK_INTS is directly written in insns.def.
static bool leafness_of_check_ints = false;
diff --git a/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb b/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb
new file mode 100644
index 0000000000..d4eb4977a4
--- /dev/null
+++ b/tool/ruby_vm/views/_mjit_compile_getinlinecache.erb
@@ -0,0 +1,31 @@
+% # -*- 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 && GET_IC_SERIAL(ice) && !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", %"PRI_SERIALT_PREFIX"u, reg_cfp->ep)) {", ice->flags, ice->value, (VALUE)ice->ic_cref, GET_IC_SERIAL(ice));
+ fprintf(f, " stack[%d] = 0x%"PRIxVALUE";\n", b->stack_size, ice->value);
+ fprintf(f, " goto label_%d;\n", pos + insn_len(insn) + (int)dst);
+ 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
new file mode 100644
index 0000000000..f54d1b0e0e
--- /dev/null
+++ b/tool/ruby_vm/views/_mjit_compile_insn.erb
@@ -0,0 +1,92 @@
+% # -*- 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
new file mode 100644
index 0000000000..187e043837
--- /dev/null
+++ b/tool/ruby_vm/views/_mjit_compile_insn_body.erb
@@ -0,0 +1,129 @@
+% # -*- 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
new file mode 100644
index 0000000000..a3796ffc5e
--- /dev/null
+++ b/tool/ruby_vm/views/_mjit_compile_invokebuiltin.erb
@@ -0,0 +1,29 @@
+% # -*- 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
new file mode 100644
index 0000000000..5105584ba3
--- /dev/null
+++ b/tool/ruby_vm/views/_mjit_compile_ivar.erb
@@ -0,0 +1,101 @@
+% # -*- 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'
+ 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);
+ fprintf(f, " }\n");
+% else
+ fprintf(f, " VALUE val;\n");
+ 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]");
+ 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
new file mode 100644
index 0000000000..390b3ce525
--- /dev/null
+++ b/tool/ruby_vm/views/_mjit_compile_pc_and_sp.erb
@@ -0,0 +1,38 @@
+% # 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
new file mode 100644
index 0000000000..28e316a1ef
--- /dev/null
+++ b/tool/ruby_vm/views/_mjit_compile_send.erb
@@ -0,0 +1,119 @@
+% # -*- 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 == 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->param.size, iseq->body->local_table_size);
+ if (iseq->body->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 = mjit_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/lib/ruby_vm/rjit/instruction.rb.erb b/tool/ruby_vm/views/lib/ruby_vm/rjit/instruction.rb.erb
deleted file mode 100644
index 4f08524a77..0000000000
--- a/tool/ruby_vm/views/lib/ruby_vm/rjit/instruction.rb.erb
+++ /dev/null
@@ -1,14 +0,0 @@
-module RubyVM::RJIT # :nodoc: all
- Instruction = Data.define(:name, :bin, :len, :operands)
-
- INSNS = {
-% RubyVM::Instructions.each_with_index do |insn, i|
- <%= i %> => Instruction.new(
- name: :<%= insn.name %>,
- bin: <%= i %>, # BIN(<%= insn.name %>)
- len: <%= insn.width %>, # insn_len
- operands: <%= (insn.operands unless insn.name.start_with?('trace_')).inspect %>,
- ),
-% end
- }
-end
diff --git a/tool/ruby_vm/views/mjit_compile.inc.erb b/tool/ruby_vm/views/mjit_compile.inc.erb
new file mode 100644
index 0000000000..5820f81770
--- /dev/null
+++ b/tool/ruby_vm/views/mjit_compile.inc.erb
@@ -0,0 +1,110 @@
+/* -*- 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_compile.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_getinlinecache'
+<%= render 'mjit_compile_getinlinecache', 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/opt_sc.inc.erb b/tool/ruby_vm/views/opt_sc.inc.erb
new file mode 100644
index 0000000000..e58c81989f
--- /dev/null
+++ b/tool/ruby_vm/views/opt_sc.inc.erb
@@ -0,0 +1,40 @@
+/* -*- C -*- */
+
+%# Copyright (c) 2017 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.
+% raise ':FIXME:TBW' if RubyVM::VmOptsH['STACK_CACHING']
+<%= render 'copyright' %>
+<%= render 'notice', locals: {
+ this_file: 'is for threaded code',
+ edit: __FILE__,
+} -%>
+
+#define SC_STATE_SIZE 6
+
+#define SCS_XX 1
+#define SCS_AX 2
+#define SCS_BX 3
+#define SCS_AB 4
+#define SCS_BA 5
+
+#define SC_ERROR 0xffffffff
+
+static const VALUE sc_insn_info[][SC_STATE_SIZE] = {
+#define NO_SC { SC_ERROR, SC_ERROR, SC_ERROR, SC_ERROR, SC_ERROR, SC_ERROR }
+% RubyVM::Instructions.each_slice 8 do |a|
+ <%= a.map{|i| 'NO_SC' }.join(', ') %>,
+% end
+#undef NO_SC
+};
+
+static const VALUE sc_insn_next[] = {
+% RubyVM::Instructions.each_slice 8 do |a|
+ <%= a.map{|i| 'SCS_XX' }.join(', ') %>,
+% end
+};
+
+ASSERT_VM_INSTRUCTION_SIZE(sc_insn_next);
diff --git a/tool/ruby_vm/views/optinsn.inc.erb b/tool/ruby_vm/views/optinsn.inc.erb
index de7bb210ea..676f1edaba 100644
--- a/tool/ruby_vm/views/optinsn.inc.erb
+++ b/tool/ruby_vm/views/optinsn.inc.erb
@@ -29,14 +29,14 @@ insn_operands_unification(INSN *iobj)
/* <%= insn.pretty_name %> */
if ( <%= insn.condition('op') %> ) {
-% insn.operands.each_with_index do |o, x|
+% insn.opes.each_with_index do |o, x|
% n = insn.operand_shift_of(o)
% if n != 0 then
op[<%= x %>] = op[<%= x + n %>];
% end
% end
iobj->insn_id = <%= insn.bin %>;
- iobj->operand_size = <%= insn.operands.size %>;
+ iobj->operand_size = <%= insn.opes.size %>;
break;
}
% end
@@ -55,12 +55,12 @@ rb_insn_unified_local_var_level(VALUE insn)
/* optimize rule */
switch (insn) {
default:
- return -1; /* do nothing */;
+ return -1; /* do nothing */;
% RubyVM::OperandsUnifications.each_group do |orig, unifs|
% unifs.each do|insn|
case <%= insn.bin %>:
% insn.spec.map{|(var,val)|val}.reject{|i| i == '*' }.each do |val|
- return <%= val %>;
+ return <%= val %>;
% break
% end
% end