summaryrefslogtreecommitdiff
path: root/tool/ruby_vm/models/bare_instruction.rb
diff options
context:
space:
mode:
Diffstat (limited to 'tool/ruby_vm/models/bare_instruction.rb')
-rw-r--r--tool/ruby_vm/models/bare_instruction.rb236
1 files changed, 236 insertions, 0 deletions
diff --git a/tool/ruby_vm/models/bare_instruction.rb b/tool/ruby_vm/models/bare_instruction.rb
new file mode 100644
index 0000000000..f87dd74179
--- /dev/null
+++ b/tool/ruby_vm/models/bare_instruction.rb
@@ -0,0 +1,236 @@
+# -*- Ruby -*-
+# -*- frozen_string_literal: true; -*-
+# -*- warn_indent: true; -*-
+#
+# 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.
+
+require_relative '../loaders/insns_def'
+require_relative 'c_expr'
+require_relative 'typemap'
+require_relative 'attribute'
+
+class RubyVM::BareInstruction
+ attr_reader :template, :name, :operands, :pops, :rets, :decls, :expr
+
+ def initialize opts = {}
+ @template = opts[:template]
+ @name = opts[:name]
+ @loc = opts[:location]
+ @sig = opts[:signature]
+ @expr = RubyVM::CExpr.new opts[:expr]
+ @operands = typesplit @sig[:ope]
+ @pops = typesplit @sig[:pop].reject {|i| i == '...' }
+ @rets = typesplit @sig[:ret].reject {|i| i == '...' }
+ @attrs = opts[:attributes].map {|i|
+ RubyVM::Attribute.new i.merge(:insn => self)
+ }.each_with_object({}) {|a, h|
+ h[a.key] = a
+ }
+ @attrs_orig = @attrs.dup
+ check_attribute_consistency
+ predefine_attributes
+ end
+
+ def pretty_name
+ n = @sig[:name]
+ o = @sig[:ope].map{|i| i[/\S+$/] }.join ', '
+ p = @sig[:pop].map{|i| i[/\S+$/] }.join ', '
+ r = @sig[:ret].map{|i| i[/\S+$/] }.join ', '
+ return sprintf "%s(%s)(%s)(%s)", n, o, p, r
+ end
+
+ def bin
+ return "BIN(#{name})"
+ end
+
+ def call_attribute name
+ return sprintf 'attr_%s_%s(%s)', name, @name, \
+ @operands.map {|i| i[:name] }.compact.join(', ')
+ end
+
+ def has_attribute? k
+ @attrs_orig.has_key? k
+ end
+
+ def attributes
+ return @attrs \
+ . sort_by {|k, _| k } \
+ . map {|_, v| v }
+ end
+
+ def width
+ return 1 + operands.size
+ end
+
+ def declarations
+ return @variables \
+ . values \
+ . group_by {|h| h[:type] } \
+ . sort_by {|t, v| t } \
+ . map {|t, v| [t, v.map {|i| i[:name] }.sort ] } \
+ . map {|t, v|
+ sprintf("MAYBE_UNUSED(%s) %s", t, v.join(', '))
+ }
+ end
+
+ def preamble
+ # preamble makes sense for operand unifications
+ return []
+ end
+
+ def sc?
+ # sc stands for stack caching.
+ return false
+ end
+
+ def cast_to_VALUE var, expr = var[:name]
+ RubyVM::Typemap.typecast_to_VALUE var[:type], expr
+ end
+
+ def cast_from_VALUE var, expr = var[:name]
+ RubyVM::Typemap.typecast_from_VALUE var[:type], expr
+ end
+
+ def operands_info
+ operands.map {|o|
+ c, _ = RubyVM::Typemap.fetch o[:type]
+ next c
+ }.join
+ end
+
+ def handles_sp?
+ /\b(false|0)\b/ !~ @attrs.fetch('handles_sp').expr.expr
+ end
+
+ def handle_canary stmt
+ # Stack canary is basically a good thing that we want to add, however:
+ #
+ # - When the instruction returns variadic number of return values,
+ # it is not easy to tell where is the stack top. We can't but
+ # skip it.
+ #
+ # - When the instruction body is empty (like putobject), we can
+ # say for 100% sure that canary is a waste of time.
+ #
+ # So we skip canary for those cases.
+ return '' if @sig[:ret].any? {|i| i == '...' }
+ return '' if @expr.blank?
+ return " #{stmt};\n"
+ end
+
+ def inspect
+ sprintf "#<%s %s@%s:%d>", self.class.name, @name, @loc[0], @loc[1]
+ end
+
+ def has_ope? var
+ return @operands.any? {|i| i[:name] == var[:name] }
+ end
+
+ def has_pop? var
+ return @pops.any? {|i| i[:name] == var[:name] }
+ end
+
+ def use_call_data?
+ @use_call_data ||=
+ @variables.find { |_, var_info| var_info[:type] == 'CALL_DATA' }
+ end
+
+ def zjit_profile?
+ @attrs.fetch('zjit_profile').expr.expr != 'false;'
+ end
+
+ private
+
+ def check_attribute_consistency
+ if has_attribute?('sp_inc') \
+ && use_call_data? \
+ && !has_attribute?('comptime_sp_inc')
+ # As the call cache caches information that can only be obtained at
+ # runtime, we do not need it when compiling from AST to bytecode. This
+ # attribute defines an expression that computes the stack pointer
+ # increase based on just the call info to avoid reserving space for the
+ # call cache at compile time. In the expression, all call data operands
+ # are mapped to their call info counterpart. Additionally, all mentions
+ # of `cd` in the operand name are replaced with `ci`.
+ raise "Please define attribute `comptime_sp_inc` for `#{@name}`"
+ end
+ end
+
+ def generate_attribute t, k, v
+ @attrs[k] ||= RubyVM::Attribute.new \
+ insn: self, \
+ name: k, \
+ type: t, \
+ location: [], \
+ expr: v.to_s + ';'
+ return @attrs[k] ||= attr
+ end
+
+ def predefine_attributes
+ # 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', 'popn', pops.size
+ generate_attribute 'rb_num_t', 'retn', rets.size
+ generate_attribute 'rb_num_t', 'width', width
+ generate_attribute 'rb_snum_t', 'sp_inc', rets.size - pops.size
+ generate_attribute 'bool', 'handles_sp', default_definition_of_handles_sp
+ generate_attribute 'bool', 'leaf', default_definition_of_leaf
+ generate_attribute 'bool', 'zjit_profile', false
+ end
+
+ def default_definition_of_handles_sp
+ # Insn with ISEQ should yield it; can handle sp.
+ return operands.any? {|o| o[:type] == 'ISEQ' }
+ end
+
+ def default_definition_of_leaf
+ # Insn that handles SP can never be a leaf.
+ if not has_attribute? 'handles_sp' then
+ return ! default_definition_of_handles_sp
+ elsif handles_sp? then
+ return "! #{call_attribute 'handles_sp'}"
+ else
+ return true
+ end
+ end
+
+ def typesplit a
+ @variables ||= {}
+ a.map do |decl|
+ md = %r'
+ (?<comment> /[*] [^*]* [*]+ (?: [^*/] [^*]* [*]+ )* / ){0}
+ (?<ws> \g<comment> | \s ){0}
+ (?<ident> [_a-zA-Z] [0-9_a-zA-Z]* ){0}
+ (?<type> (?: \g<ident> \g<ws>+ )* \g<ident> ){0}
+ (?<var> \g<ident> ){0}
+ \G \g<ws>* \g<type> \g<ws>+ \g<var>
+ 'x.match(decl)
+ @variables[md['var']] ||= {
+ decl: decl,
+ type: md['type'],
+ name: md['var'],
+ }
+ end
+ end
+
+ @instances = RubyVM::InsnsDef.map {|h|
+ new h.merge(:template => h)
+ }
+
+ def self.find(name)
+ @instances.find do |insn|
+ insn.name == name
+ end or raise IndexError, "instruction not found: #{name}"
+ end
+
+ def self.all
+ @instances
+ end
+end