diff options
Diffstat (limited to 'tool/ruby_vm/models')
| -rw-r--r-- | tool/ruby_vm/models/attribute.rb | 58 | ||||
| -rw-r--r-- | tool/ruby_vm/models/bare_instruction.rb | 236 | ||||
| -rw-r--r-- | tool/ruby_vm/models/c_expr.rb | 44 | ||||
| -rw-r--r-- | tool/ruby_vm/models/instructions.rb | 23 | ||||
| -rw-r--r-- | tool/ruby_vm/models/instructions_unification.rb | 42 | ||||
| -rw-r--r-- | tool/ruby_vm/models/operands_unification.rb | 141 | ||||
| -rw-r--r-- | tool/ruby_vm/models/trace_instruction.rb | 70 | ||||
| -rw-r--r-- | tool/ruby_vm/models/typemap.rb | 62 | ||||
| -rw-r--r-- | tool/ruby_vm/models/zjit_instruction.rb | 56 |
9 files changed, 732 insertions, 0 deletions
diff --git a/tool/ruby_vm/models/attribute.rb b/tool/ruby_vm/models/attribute.rb new file mode 100644 index 0000000000..177b701b92 --- /dev/null +++ b/tool/ruby_vm/models/attribute.rb @@ -0,0 +1,58 @@ +# -*- 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 'c_expr' + +class RubyVM::Attribute + include RubyVM::CEscape + attr_reader :insn, :key, :type, :expr + + def initialize opts = {} + @insn = opts[:insn] + @key = opts[:name] + @expr = RubyVM::CExpr.new location: opts[:location], expr: opts[:expr] + @type = opts[:type] + @ope_decls = @insn.operands.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') + end + decl + end + end + + def name + as_tr_cpp "attr #{@key} @ #{@insn.name}" + end + + def pretty_name + "attr #{type} #{key} @ #{insn.pretty_name}" + end + + def declaration + if @ope_decls.empty? + argv = "void" + else + argv = @ope_decls.join(', ') + end + sprintf '%s %s(%s)', @type, name, argv + end + + def definition + if @ope_decls.empty? + argv = "void" + else + argv = @ope_decls.map {|decl| "MAYBE_UNUSED(#{decl})" }.join(",\n ") + argv = "\n #{argv}\n" if @ope_decls.size > 1 + end + sprintf "%s\n%s(%s)", @type, name, argv + end +end 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 diff --git a/tool/ruby_vm/models/c_expr.rb b/tool/ruby_vm/models/c_expr.rb new file mode 100644 index 0000000000..095ff4f1d9 --- /dev/null +++ b/tool/ruby_vm/models/c_expr.rb @@ -0,0 +1,44 @@ +# -*- 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 '../helpers/c_escape.rb' + +class RubyVM::CExpr + include RubyVM::CEscape + + attr_reader :__FILE__, :__LINE__, :expr + + def initialize opts = {} + @__FILE__ = opts[:location][0] + @__LINE__ = opts[:location][1] + @expr = opts[:expr] + end + + # blank, in sense of C program. + RE = %r'\A{\g<s>*}\z|\A(?<s>\s|/[*][^*]*[*]+([^*/][^*]*[*]+)*/)*\z' + if RUBY_VERSION > '2.4' then + def blank? + RE.match? @expr + end + else + def blank? + RE =~ @expr + end + end + + def inspect + if @__LINE__ + sprintf "#<%s:%d %s>", @__FILE__, @__LINE__, @expr + else + sprintf "#<%s %s>", @__FILE__, @expr + end + end +end diff --git a/tool/ruby_vm/models/instructions.rb b/tool/ruby_vm/models/instructions.rb new file mode 100644 index 0000000000..7be7064b14 --- /dev/null +++ b/tool/ruby_vm/models/instructions.rb @@ -0,0 +1,23 @@ +# -*- 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 'bare_instruction' +require_relative 'operands_unification' +require_relative 'instructions_unification' +require_relative 'trace_instruction' +require_relative 'zjit_instruction' + +RubyVM::Instructions = RubyVM::BareInstruction.all + + RubyVM::OperandsUnification.all + + RubyVM::InstructionsUnification.all + + RubyVM::TraceInstruction.all + + RubyVM::ZJITInstruction.all +RubyVM::Instructions.freeze diff --git a/tool/ruby_vm/models/instructions_unification.rb b/tool/ruby_vm/models/instructions_unification.rb new file mode 100644 index 0000000000..5c798e6d54 --- /dev/null +++ b/tool/ruby_vm/models/instructions_unification.rb @@ -0,0 +1,42 @@ +# -*- 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 '../helpers/c_escape' +require_relative '../loaders/opt_insn_unif_def' +require_relative 'bare_instruction' + +class RubyVM::InstructionsUnification + include RubyVM::CEscape + + attr_reader :name + + def initialize opts = {} + @location = opts[:location] + @name = namegen opts[:signature] + @series = opts[:signature].map do |i| + RubyVM::BareInstruction.find(i) # Misshit is fatal + end + end + + private + + def namegen signature + as_tr_cpp ['UNIFIED', *signature].join('_') + end + + @instances = RubyVM::OptInsnUnifDef.map do |h| + new h + end + + def self.all + @instances + end +end diff --git a/tool/ruby_vm/models/operands_unification.rb b/tool/ruby_vm/models/operands_unification.rb new file mode 100644 index 0000000000..ce118648ca --- /dev/null +++ b/tool/ruby_vm/models/operands_unification.rb @@ -0,0 +1,141 @@ +# -*- 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 '../helpers/c_escape' +require_relative '../loaders/opt_operand_def' +require_relative 'bare_instruction' + +class RubyVM::OperandsUnification < RubyVM::BareInstruction + include RubyVM::CEscape + + attr_reader :preamble, :original, :spec + + def initialize opts = {} + name = opts[:signature][0] + @original = RubyVM::BareInstruction.find(name) + template = @original.template + parts = compose opts[:location], opts[:signature], template[:signature] + json = template.dup + json[:location] = opts[:location] + json[:signature] = parts[:signature] + json[:name] = parts[:name] + @preamble = parts[:preamble] + @spec = parts[:spec] + super json.merge(:template => template) + @konsts = parts[:vars] + @konsts.each do |v| + @variables[v[:name]] ||= v + end + end + + def operand_shift_of var + before = @original.operands.find_index var + after = @operands.find_index var + raise "no #{var} for #{@name}" unless before and after + return before - after + end + + def condition ptr + # :FIXME: I'm not sure if this method should be in model? + exprs = @spec.each_with_index.map do |(var, val), i| + case val when '*' then + next nil + else + type = @original.operands[i][:type] + expr = RubyVM::Typemap.typecast_to_VALUE type, val + next "#{ptr}[#{i}] == #{expr}" + end + end + exprs.compact! + if exprs.size == 1 then + return exprs[0] + else + exprs.map! {|i| "(#{i})" } + return exprs.join ' && ' + end + end + + def has_ope? var + super or @konsts.any? {|i| i[:name] == var[:name] } + end + + private + + def namegen signature + insn, argv = *signature + wcary = argv.map do |i| + case i when '*' then + 'WC' + else + i + end + end + as_tr_cpp [insn, *wcary].join(', ') + end + + def compose location, spec, template + name = namegen spec + *, argv = *spec + opes = @original.operands + if opes.size != argv.size + raise sprintf("operand size mismatch for %s (%s's: %d, given: %d)", + name, template[:name], opes.size, argv.size) + else + src = [] + mod = [] + spec = [] + vars = [] + argv.each_index do |i| + j = argv[i] + k = opes[i] + spec[i] = [k, j] + case j when '*' then + # operand is from iseq + mod << k[:decl] + else + # operand is inside C + vars << k + src << { + location: location, + expr: " const #{k[:decl]} = #{j};" + } + end + end + src.map! {|i| RubyVM::CExpr.new i } + return { + name: name, + signature: { + name: name, + ope: mod, + pop: template[:pop], + ret: template[:ret], + }, + preamble: src, + vars: vars, + spec: spec + } + end + end + + @instances = RubyVM::OptOperandDef.map do |h| + new h + end + + def self.all + @instances + end + + def self.each_group + all.group_by(&:original).each_pair do |k, v| + yield k, v + end + end +end diff --git a/tool/ruby_vm/models/trace_instruction.rb b/tool/ruby_vm/models/trace_instruction.rb new file mode 100644 index 0000000000..6a3ad53c44 --- /dev/null +++ b/tool/ruby_vm/models/trace_instruction.rb @@ -0,0 +1,70 @@ +# -*- 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 '../helpers/c_escape' +require_relative 'bare_instruction' + +class RubyVM::TraceInstruction + include RubyVM::CEscape + + attr_reader :name + + def initialize orig + @orig = orig + @name = as_tr_cpp "trace @ #{@orig.name}" + end + + def pretty_name + return sprintf "%s(...)(...)(...)", @name + end + + def jump_destination + return @orig.name + end + + def bin + return sprintf "BIN(%s)", @name + end + + def width + return @orig.width + end + + def operands_info + return @orig.operands_info + end + + def rets + return ['...'] + end + + def pops + return ['...'] + end + + def attributes + return [] + end + + def has_attribute? *; + return false + end + + private + + @instances = (RubyVM::BareInstruction.all + + RubyVM::OperandsUnification.all + + RubyVM::InstructionsUnification.all).map {|i| new(i) } + + def self.all + @instances + end +end diff --git a/tool/ruby_vm/models/typemap.rb b/tool/ruby_vm/models/typemap.rb new file mode 100644 index 0000000000..68ef5a41a5 --- /dev/null +++ b/tool/ruby_vm/models/typemap.rb @@ -0,0 +1,62 @@ +# -*- 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. + +RubyVM::Typemap = { + "..." => %w[. TS_VARIABLE], + "CALL_DATA" => %w[C TS_CALLDATA], + "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], + "OFFSET" => %w[O TS_OFFSET], + "VALUE" => %w[V TS_VALUE], + "lindex_t" => %w[L TS_LINDEX], + "rb_insn_func_t" => %w[F TS_FUNCPTR], + "rb_num_t" => %w[N TS_NUM], + "RB_BUILTIN" => %w[R TS_BUILTIN], +} + +# :FIXME: should this method be here? +class << RubyVM::Typemap + def typecast_from_VALUE type, val + # see also iseq_set_sequence() + case type + when '...' + raise "cast not possible: #{val}" + when 'VALUE' then + return val + when 'rb_num_t', 'lindex_t' then + return "NUM2LONG(#{val})" + when 'ID' then + return "SYM2ID(#{val})" + else + return "(#{type})(#{val})" + end + end + + def typecast_to_VALUE type, val + case type + when 'VALUE' then + return val + when 'ISEQ', 'rb_insn_func_t' then + return "(VALUE)(#{val})" + when 'rb_num_t', 'lindex_t' + "LONG2NUM(#{val})" + when 'ID' then + return "ID2SYM(#{val})" + else + raise ":FIXME: TBW for #{type}" + end + end +end diff --git a/tool/ruby_vm/models/zjit_instruction.rb b/tool/ruby_vm/models/zjit_instruction.rb new file mode 100644 index 0000000000..04764e4c61 --- /dev/null +++ b/tool/ruby_vm/models/zjit_instruction.rb @@ -0,0 +1,56 @@ +require_relative '../helpers/c_escape' +require_relative 'bare_instruction' + +# Profile YARV instructions to optimize code generated by ZJIT +class RubyVM::ZJITInstruction + include RubyVM::CEscape + + attr_reader :name + + def initialize(orig) + @orig = orig + @name = as_tr_cpp "zjit @ #{@orig.name}" + end + + def pretty_name + return sprintf "%s(...)(...)(...)", @name + end + + def jump_destination + return @orig.name + end + + def bin + return sprintf "BIN(%s)", @name + end + + def width + return @orig.width + end + + def operands_info + return @orig.operands_info + end + + def rets + return ['...'] + end + + def pops + return ['...'] + end + + def attributes + return [] + end + + def has_attribute?(*) + return false + end + + @instances = RubyVM::BareInstruction.all.filter(&:zjit_profile?).map {|i| new(i) } + + def self.all + @instances + end +end |
