summaryrefslogtreecommitdiff
path: root/tool/instruction.rb
diff options
context:
space:
mode:
authorshyouhei <shyouhei@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-01-10 01:53:24 +0000
committershyouhei <shyouhei@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-01-10 01:53:24 +0000
commit5ad95486e63675b2bb3ad665bb2b84eb260c6f29 (patch)
tree9ed7d02028ba1edd4a74e090fb4c7155b55aafd6 /tool/instruction.rb
parent7c7d47d2369881f8ea22da34077459771276786a (diff)
merge revisions 61753:61750 61747:61740 61737:61728
Revert all the VM generator rewrites; requested by naruse git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@61755 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'tool/instruction.rb')
-rwxr-xr-xtool/instruction.rb1249
1 files changed, 1249 insertions, 0 deletions
diff --git a/tool/instruction.rb b/tool/instruction.rb
new file mode 100755
index 0000000000..447fed5948
--- /dev/null
+++ b/tool/instruction.rb
@@ -0,0 +1,1249 @@
+#!./miniruby
+# -*- coding: us-ascii -*-
+#
+# This library is used by insns2vm.rb as part of autogenerating
+# instruction files with .inc extensions like insns.inc and vm.inc.
+
+require 'erb'
+$:.unshift(File.dirname(__FILE__))
+require 'vpath'
+
+class RubyVM
+ class Instruction
+ def initialize name, opes, pops, rets, comm, body, tvars, sp_inc,
+ orig = self, defopes = [], type = nil,
+ nsc = [], psc = [[], []]
+
+ @name = name
+ @opes = opes # [[type, name], ...]
+ @pops = pops # [[type, name], ...]
+ @rets = rets # [[type, name], ...]
+ @comm = comm # {:c => category, :e => en desc, :j => ja desc}
+ @body = body # '...'
+
+ @orig = orig
+ @defopes = defopes
+ @type = type
+ @tvars = tvars
+
+ @nextsc = nsc
+ @pushsc = psc
+ @sc = []
+ @unifs = []
+ @optimized = []
+ @is_sc = false
+ @sp_inc = sp_inc
+ @trace = trace
+ end
+
+ def add_sc sci
+ @sc << sci
+ sci.set_sc
+ end
+
+ attr_reader :name, :opes, :pops, :rets
+ attr_reader :body, :comm
+ attr_reader :nextsc, :pushsc
+ attr_reader :orig, :defopes, :type
+ attr_reader :sc
+ attr_reader :unifs, :optimized
+ attr_reader :is_sc
+ attr_reader :tvars
+ attr_reader :sp_inc
+ attr_accessor :trace
+
+ def set_sc
+ @is_sc = true
+ end
+
+ def add_unif insns
+ @unifs << insns
+ end
+
+ def add_optimized insn
+ @optimized << insn
+ end
+
+ def sp_increase_c_expr
+ if(pops.any?{|t, v| v == '...'} ||
+ rets.any?{|t, v| v == '...'})
+ # user definition
+ raise "no sp increase definition" if @sp_inc.nil?
+ ret = "int inc = 0;\n"
+
+ @opes.each_with_index{|(t, v), i|
+ if (t == 'rb_num_t' && ((re = /\b#{v}\b/n) =~ @sp_inc)) ||
+ (@defopes.any?{|t, val| re =~ val})
+ ret << " int #{v} = FIX2INT(opes[#{i}]);\n"
+ elsif (t == 'CALL_INFO' && ((re = /\b#{v}\b/n) =~ @sp_inc))
+ ret << " CALL_INFO #{v} = (CALL_INFO)(opes[#{i}]);\n"
+ end
+ }
+
+ @defopes.each_with_index{|((t, var), val), i|
+ if t == 'rb_num_t' && val != '*' && /\b#{var}\b/ =~ @sp_inc
+ ret << " #{t} #{var} = #{val};\n"
+ end
+ }
+
+ ret << " #{@sp_inc};\n"
+ ret << " return depth + inc;"
+ ret
+ else
+ "return depth + #{rets.size - pops.size};"
+ end
+ end
+
+ def inspect
+ "#<Instruction:#{@name}>"
+ end
+ end
+
+ class InstructionsLoader
+ def initialize opts = {}
+ @insns = []
+ @insn_map = {}
+
+ @vpath = opts[:VPATH] || File
+ @use_const = opts[:use_const]
+ @verbose = opts[:verbose]
+ @destdir = opts[:destdir]
+
+ (@vm_opts = load_vm_opts).each {|k, v|
+ @vm_opts[k] = opts[k] if opts.key?(k)
+ }
+
+ load_insns_def opts[:"insns.def"] || 'insns.def'
+
+ load_opt_operand_def opts[:"opope.def"] || 'defs/opt_operand.def'
+ load_insn_unification_def opts[:"unif.def"] || 'defs/opt_insn_unif.def'
+ make_stackcaching_insns if vm_opt?('STACK_CACHING')
+ make_trace_insns
+ end
+
+ attr_reader :vpath
+ attr_reader :destdir
+
+ %w[use_const verbose].each do |attr|
+ attr_reader attr
+ alias_method "#{attr}?", attr
+ remove_method attr
+ end
+
+ def [](s)
+ @insn_map[s.to_s]
+ end
+
+ def each
+ @insns.each{|insn|
+ yield insn
+ }
+ end
+
+ def size
+ @insns.size
+ end
+
+ ###
+ private
+
+ def vm_opt? name
+ @vm_opts[name]
+ end
+
+ def load_vm_opts file = nil
+ file ||= 'vm_opts.h'
+ opts = {}
+ vpath.open(file) do |f|
+ f.grep(/^\#define\s+OPT_([A-Z_]+)\s+(\d+)/) do
+ opts[$1] = !$2.to_i.zero?
+ end
+ end
+ opts
+ end
+
+ SKIP_COMMENT_PATTERN = Regexp.compile(Regexp.escape('/** ##skip'))
+
+ include Enumerable
+
+ def add_insn insn
+ @insns << insn
+ @insn_map[insn.name] = insn
+ end
+
+ def make_insn name, opes, pops, rets, comm, body, sp_inc
+ add_insn Instruction.new(name, opes, pops, rets, comm, body, [], sp_inc)
+ end
+
+ # str -> [[type, var], ...]
+ def parse_vars line
+ raise unless /\((.*?)\)/ =~ line
+ vars = $1.split(',')
+ vars.map!{|v|
+ if /\s*(\S+)\s+(\S+)\s*/ =~ v
+ type = $1
+ var = $2
+ elsif /\s*\.\.\.\s*/ =~ v
+ type = var = '...'
+ else
+ raise
+ end
+ [type, var]
+ }
+ vars
+ end
+
+ def parse_comment comm
+ c = 'others'
+ j = ''
+ e = ''
+ comm.each_line{|line|
+ case line
+ when /@c (.+)/
+ c = $1
+ when /@e (.+)/
+ e = $1
+ when /@e\s*$/
+ e = ''
+ when /@j (.+)$/
+ j = $1
+ when /@j\s*$/
+ j = ''
+ end
+ }
+ { :c => c,
+ :e => e,
+ :j => j,
+ }
+ end
+
+ def load_insns_def file
+ body = insn = opes = pops = rets = nil
+ comment = ''
+
+ vpath.open(file) {|f|
+ f.instance_variable_set(:@line_no, 0)
+ class << f
+ def line_no
+ @line_no
+ end
+ def gets
+ @line_no += 1
+ super
+ end
+ end
+
+ while line = f.gets
+ line.chomp!
+ case line
+
+ when SKIP_COMMENT_PATTERN
+ while line = f.gets.chomp
+ if /\s+\*\/$/ =~ line
+ break
+ end
+ end
+
+ # collect instruction comment
+ when /^\/\*\*$/
+ while line = f.gets
+ if /\s+\*\/\s*$/ =~ line
+ break
+ else
+ comment << line
+ end
+ end
+
+ # start instruction body
+ when /^DEFINE_INSN$/
+ insn = f.gets.chomp
+ opes = parse_vars(f.gets.chomp)
+ pops = parse_vars(f.gets.chomp).reverse
+ rets_str = f.gets.chomp
+ rets = parse_vars(rets_str).reverse
+ comment = parse_comment(comment)
+ insn_in = true
+ body = ''
+
+ sp_inc = rets_str[%r"//\s*(.+)", 1]
+
+ raise unless /^\{$/ =~ f.gets.chomp
+ line_no = f.line_no
+
+ # end instruction body
+ when /^\}/
+ if insn_in
+ body.instance_variable_set(:@line_no, line_no)
+ body.instance_variable_set(:@file, f.path)
+ insn = make_insn(insn, opes, pops, rets, comment, body, sp_inc)
+ insn_in = false
+ comment = ''
+ end
+
+ else
+ if insn_in
+ body << line + "\n"
+ end
+ end
+ end
+ }
+ end
+
+ ## opt op
+ def load_opt_operand_def file
+ vpath.foreach(file) {|line|
+ line = line.gsub(/\#.*/, '').strip
+ next if line.length == 0
+ break if /__END__/ =~ line
+ /(\S+)\s+(.+)/ =~ line
+ insn = $1
+ opts = $2
+ add_opt_operand insn, opts.split(/,/).map{|e| e.strip}
+ } if file
+ end
+
+ def label_escape label
+ label.gsub(/\(/, '_O_').
+ gsub(/\)/, '_C_').
+ gsub(/\*/, '_WC_')
+ end
+
+ def add_opt_operand insn_name, opts
+ insn = @insn_map[insn_name]
+ opes = insn.opes
+
+ if opes.size != opts.size
+ raise "operand size mismatch for #{insn.name} (opes: #{opes.size}, opts: #{opts.size})"
+ end
+
+ ninsn = insn.name + '_OP_' + opts.map{|e| label_escape(e)}.join('_')
+ nopes = []
+ defv = []
+
+ opts.each_with_index{|e, i|
+ if e == '*'
+ nopes << opes[i]
+ end
+ defv << [opes[i], e]
+ }
+
+ make_insn_operand_optimized(insn, ninsn, nopes, defv)
+ end
+
+ def make_insn_operand_optimized orig_insn, name, opes, defopes
+ comm = orig_insn.comm.dup
+ comm[:c] = 'optimize'
+ add_insn insn = Instruction.new(
+ name, opes, orig_insn.pops, orig_insn.rets, comm,
+ orig_insn.body, orig_insn.tvars, orig_insn.sp_inc,
+ orig_insn, defopes)
+ orig_insn.add_optimized insn
+ end
+
+ ## insn unif
+ def load_insn_unification_def file
+ vpath.foreach(file) {|line|
+ line = line.gsub(/\#.*/, '').strip
+ next if line.length == 0
+ break if /__END__/ =~ line
+ make_unified_insns line.split.map{|e|
+ raise "unknown insn: #{e}" unless @insn_map[e]
+ @insn_map[e]
+ }
+ } if file
+ end
+
+ def all_combination sets
+ ret = sets.shift.map{|e| [e]}
+
+ sets.each{|set|
+ prev = ret
+ ret = []
+ prev.each{|ary|
+ set.each{|e|
+ eary = ary.dup
+ eary << e
+ ret << eary
+ }
+ }
+ }
+ ret
+ end
+
+ def make_unified_insns insns
+ if vm_opt?('UNIFY_ALL_COMBINATION')
+ insn_sets = insns.map{|insn|
+ [insn] + insn.optimized
+ }
+
+ all_combination(insn_sets).each{|insns_set|
+ make_unified_insn_each insns_set
+ }
+ else
+ make_unified_insn_each insns
+ end
+ end
+
+ def mk_private_val vals, i, redef
+ vals.dup.map{|v|
+ # v[0] : type
+ # v[1] : var name
+
+ v = v.dup
+ if v[0] != '...'
+ redef[v[1]] = v[0]
+ v[1] = "#{v[1]}_#{i}"
+ end
+ v
+ }
+ end
+
+ def mk_private_val2 vals, i, redef
+ vals.dup.map{|v|
+ # v[0][0] : type
+ # v[0][1] : var name
+ # v[1] : default val
+
+ pv = v.dup
+ v = pv[0] = pv[0].dup
+ if v[0] != '...'
+ redef[v[1]] = v[0]
+ v[1] = "#{v[1]}_#{i}"
+ end
+ pv
+ }
+ end
+
+ def make_unified_insn_each insns
+ names = []
+ opes = []
+ pops = []
+ rets = []
+ comm = {
+ :c => 'optimize',
+ :e => 'unified insn',
+ :j => 'unified insn',
+ }
+ body = ''
+ passed = []
+ tvars = []
+ defopes = []
+ sp_inc = ''
+
+ insns.each_with_index{|insn, i|
+ names << insn.name
+ redef_vars = {}
+
+ e_opes = mk_private_val(insn.opes, i, redef_vars)
+ e_pops = mk_private_val(insn.pops, i, redef_vars)
+ e_rets = mk_private_val(insn.rets, i, redef_vars)
+ # ToDo: fix it
+ e_defs = mk_private_val2(insn.defopes, i, redef_vars)
+
+ passed_vars = []
+ while pvar = e_pops.pop
+ rvar = rets.pop
+ if rvar
+ raise "unsupported unif insn: #{insns.inspect}" if rvar[0] == '...'
+ passed_vars << [pvar, rvar]
+ tvars << rvar
+ else
+ e_pops.push pvar
+ break
+ end
+ end
+
+ opes.concat e_opes
+ pops.concat e_pops
+ rets.concat e_rets
+ defopes.concat e_defs
+ sp_inc << "#{insn.sp_inc}"
+
+ body << "{ /* unif: #{i} */\n" +
+ passed_vars.map{|rpvars|
+ pv = rpvars[0]
+ rv = rpvars[1]
+ "#define #{pv[1]} #{rv[1]}"
+ }.join("\n") +
+ "\n" +
+ redef_vars.map{|v, type|
+ "#{type} #{v} = #{v}_#{i};"
+ }.join("\n") + "\n"
+ if line = insn.body.instance_variable_get(:@line_no)
+ file = insn.body.instance_variable_get(:@file)
+ body << "#line #{line+1} \"#{file}\"\n"
+ body << insn.body
+ body << "\n#line __CURRENT_LINE__ \"__CURRENT_FILE__\"\n"
+ else
+ body << insn.body
+ end
+ body << redef_vars.keys.map{|v|
+ "#{v}_#{i} = #{v};"
+ }.join("\n") +
+ "\n" +
+ passed_vars.map{|rpvars|
+ "#undef #{rpvars[0][1]}"
+ }.join("\n") +
+ "\n}\n"
+ }
+
+ tvars_ary = []
+ tvars.each{|tvar|
+ unless opes.any?{|var|
+ var[1] == tvar[1]
+ } || defopes.any?{|pvar|
+ pvar[0][1] == tvar[1]
+ }
+ tvars_ary << tvar
+ end
+ }
+ add_insn insn = Instruction.new("UNIFIED_" + names.join('_'),
+ opes, pops, rets.reverse, comm, body,
+ tvars_ary, sp_inc)
+ insn.defopes.replace defopes
+ insns[0].add_unif [insn, insns]
+ end
+
+ ## sc
+ SPECIAL_INSN_FOR_SC_AFTER = {
+ /\Asend/ => [:a],
+ /\Aend/ => [:a],
+ /\Ayield/ => [:a],
+ /\Aclassdef/ => [:a],
+ /\Amoduledef/ => [:a],
+ }
+ FROM_SC = [[], [:a], [:b], [:a, :b], [:b, :a]]
+
+ def make_stackcaching_insns
+ pops = rets = nil
+
+ @insns.dup.each{|insn|
+ opops = insn.pops
+ orets = insn.rets
+ oopes = insn.opes
+ ocomm = insn.comm
+ oname = insn.name
+
+ after = SPECIAL_INSN_FOR_SC_AFTER.find {|k, v| k =~ oname}
+
+ insns = []
+ FROM_SC.each{|from|
+ name, pops, rets, pushs1, pushs2, nextsc =
+ *calc_stack(insn, from, after, opops, orets)
+
+ make_insn_sc(insn, name, oopes, pops, rets, [pushs1, pushs2], nextsc)
+ }
+ }
+ end
+
+ def make_trace_insns
+ @insns.dup.each{|insn|
+ body = <<-EOS
+ vm_trace(ec, GET_CFP(), GET_PC());
+ DISPATCH_ORIGINAL_INSN(#{insn.name});
+ EOS
+
+ trace_insn = Instruction.new(name = "trace_#{insn.name}",
+ insn.opes, insn.pops, insn.rets, insn.comm,
+ body, insn.tvars, insn.sp_inc)
+ trace_insn.trace = true
+ add_insn trace_insn
+ }
+ end
+
+ def make_insn_sc orig_insn, name, opes, pops, rets, pushs, nextsc
+ comm = orig_insn.comm.dup
+ comm[:c] = 'optimize(sc)'
+
+ scinsn = Instruction.new(
+ name, opes, pops, rets, comm,
+ orig_insn.body, orig_insn.tvars, orig_insn.sp_inc,
+ orig_insn, orig_insn.defopes, :sc, nextsc, pushs)
+
+ add_insn scinsn
+ orig_insn.add_sc scinsn
+ end
+
+ def self.complement_name st
+ "#{st[0] ? st[0] : 'x'}#{st[1] ? st[1] : 'x'}"
+ end
+
+ def add_stack_value st
+ len = st.length
+ if len == 0
+ st[0] = :a
+ [nil, :a]
+ elsif len == 1
+ if st[0] == :a
+ st[1] = :b
+ else
+ st[1] = :a
+ end
+ [nil, st[1]]
+ else
+ st[0], st[1] = st[1], st[0]
+ [st[1], st[1]]
+ end
+ end
+
+ def calc_stack insn, ofrom, oafter, opops, orets
+ from = ofrom.dup
+ pops = opops.dup
+ rets = orets.dup
+ rest_scr = ofrom.dup
+
+ pushs_before = []
+ pushs= []
+
+ pops.each_with_index{|e, i|
+ if e[0] == '...'
+ pushs_before = from
+ from = []
+ end
+ r = from.pop
+ break unless r
+ pops[i] = pops[i].dup << r
+ }
+
+ if oafter
+ from = oafter
+ from.each_with_index{|r, i|
+ rets[i] = rets[i].dup << r if rets[i]
+ }
+ else
+ rets = rets.reverse
+ rets.each_with_index{|e, i|
+ break if e[0] == '...'
+ pushed, r = add_stack_value from
+ rets[i] = rets[i].dup << r
+ if pushed
+ if rest_scr.pop
+ pushs << pushed
+ end
+
+ if i - 2 >= 0
+ rets[i-2].pop
+ end
+ end
+ }
+ end
+
+ if false #|| insn.name =~ /test3/
+ p ofrom
+ p pops
+ p rets
+ p pushs_before
+ p pushs
+ p from
+ exit
+ end
+
+ ret = ["#{insn.name}_SC_#{InstructionsLoader.complement_name(ofrom)}_#{complement_name(from)}",
+ pops, rets, pushs_before, pushs, from]
+ end
+ end
+
+ class SourceCodeGenerator
+ def initialize insns
+ @insns = insns
+ end
+
+ attr_reader :insns
+
+ def generate
+ raise "should not reach here"
+ end
+
+ def vpath
+ @insns.vpath
+ end
+
+ def verbose?
+ @insns.verbose?
+ end
+
+ def use_const?
+ @insns.use_const?
+ end
+
+ def template(name)
+ ERB.new(vpath.read("template/#{name}"), nil, '%-')
+ end
+
+ def build_string
+ @lines = []
+ yield
+ @lines.join("\n")
+ end
+
+ EMPTY_STRING = ''.freeze
+
+ def commit str = EMPTY_STRING
+ @lines << str
+ end
+
+ def comment str
+ @lines << str if verbose?
+ end
+
+ def output_path(fn)
+ d = @insns.destdir
+ fn = File.join(d, fn) if d
+ fn
+ end
+ end
+
+ ###################################################################
+ # vm.inc
+ class VmBodyGenerator < SourceCodeGenerator
+ # vm.inc
+ def generate
+ template('vm.inc.tmpl').result(binding)
+ end
+
+ def generate_from_insnname insnname
+ make_insn_def @insns[insnname.to_s]
+ end
+
+ #######
+ private
+
+ def make_header_prepare_stack insn
+ comment " /* prepare stack status */"
+
+ push_ba = insn.pushsc
+ raise "unsupport" if push_ba[0].size > 0 && push_ba[1].size > 0
+
+ n = 0
+ push_ba.each {|pushs| n += pushs.length}
+ commit " CHECK_VM_STACK_OVERFLOW_FOR_INSN(VM_REG_CFP, #{n});" if n > 0
+ push_ba.each{|pushs|
+ pushs.each{|r|
+ commit " PUSH(SCREG(#{r}));"
+ }
+ }
+ end
+
+ def make_header_operands insn
+ comment " /* declare and get from iseq */"
+
+ vars = insn.opes
+ n = 0
+ ops = []
+
+ vars.each_with_index{|(type, var), i|
+ if type == '...'
+ break
+ end
+
+ # skip make operands when body has no reference to this operand
+ # TODO: really needed?
+ re = /\b#{var}\b/n
+ if re =~ insn.body or re =~ insn.sp_inc or insn.rets.any?{|t, v| re =~ v} or re =~ 'ic' or re =~ 'ci' or re =~ 'cc'
+ ops << " #{type} #{var} = (#{type})GET_OPERAND(#{i+1});"
+ end
+
+ n += 1
+ }
+ @opn = n
+
+ # reverse or not?
+ # ops.join
+ commit ops.reverse
+ end
+
+ def make_header_default_operands insn
+ vars = insn.defopes
+
+ vars.each{|e|
+ next if e[1] == '*'
+ if use_const?
+ commit " const #{e[0][0]} #{e[0][1]} = #{e[1]};"
+ else
+ commit " #define #{e[0][1]} #{e[1]}"
+ end
+ }
+ end
+
+ def make_footer_default_operands insn
+ comment " /* declare and initialize default opes */"
+ if use_const?
+ commit
+ else
+ vars = insn.defopes
+
+ vars.each{|e|
+ next if e[1] == '*'
+ commit "#undef #{e[0][1]}"
+ }
+ end
+ end
+
+ def make_header_stack_pops insn
+ comment " /* declare and pop from stack */"
+
+ n = 0
+ pops = []
+ vars = insn.pops
+ vars.each_with_index{|iter, i|
+ type, var, r = *iter
+ if type == '...'
+ break
+ end
+ if r
+ pops << " #{type} #{var} = SCREG(#{r});"
+ else
+ pops << " #{type} #{var} = TOPN(#{n});"
+ n += 1
+ end
+ }
+ @popn = n
+
+ # reverse or not?
+ commit pops.reverse
+ end
+
+ def make_header_temporary_vars insn
+ comment " /* declare temporary vars */"
+
+ insn.tvars.each{|var|
+ commit " #{var[0]} #{var[1]};"
+ }
+ end
+
+ def make_header_stack_val insn
+ comment "/* declare stack push val */"
+
+ vars = insn.opes + insn.pops + insn.defopes.map{|e| e[0]}
+
+ insn.rets.each{|var|
+ if vars.all?{|e| e[1] != var[1]} && var[1] != '...'
+ commit " #{var[0]} #{var[1]};"
+ end
+ }
+ end
+
+ def make_header_analysis insn
+ commit " COLLECT_USAGE_INSN(BIN(#{insn.name}));"
+ insn.opes.each_with_index{|op, i|
+ commit " COLLECT_USAGE_OPERAND(BIN(#{insn.name}), #{i}, #{op[1]});"
+ }
+ end
+
+ def make_header_pc insn
+ commit " ADD_PC(1+#{@opn});"
+ commit " PREFETCH(GET_PC());"
+ end
+
+ def make_header_popn insn
+ comment " /* management */"
+ commit " POPN(#{@popn});" if @popn > 0
+ end
+
+ def make_header_debug insn
+ comment " /* for debug */"
+ commit " DEBUG_ENTER_INSN(\"#{insn.name}\");"
+ end
+
+ def make_header_defines insn
+ commit " #define CURRENT_INSN_#{insn.name} 1"
+ commit " #define INSN_IS_SC() #{insn.sc ? 0 : 1}"
+ commit " #define INSN_LABEL(lab) LABEL_#{insn.name}_##lab"
+ commit " #define LABEL_IS_SC(lab) LABEL_##lab##_###{insn.sc.size == 0 ? 't' : 'f'}"
+ end
+
+ def each_footer_stack_val insn
+ insn.rets.reverse_each{|v|
+ break if v[1] == '...'
+ yield v
+ }
+ end
+
+ def make_footer_stack_val insn
+ comment " /* push stack val */"
+
+ n = 0
+ each_footer_stack_val(insn){|v|
+ n += 1 unless v[2]
+ }
+ commit " CHECK_VM_STACK_OVERFLOW_FOR_INSN(VM_REG_CFP, #{n});" if n > 0
+ each_footer_stack_val(insn){|v|
+ if v[2]
+ commit " SCREG(#{v[2]}) = #{v[1]};"
+ else
+ commit " PUSH(#{v[1]});"
+ end
+ }
+ end
+
+ def make_footer_undefs insn
+ commit "#undef CURRENT_INSN_#{insn.name}"
+ commit "#undef INSN_IS_SC"
+ commit "#undef INSN_LABEL"
+ commit "#undef LABEL_IS_SC"
+ end
+
+ def make_header insn
+ label = insn.trace ? '' : "START_OF_ORIGINAL_INSN(#{insn.name});"
+ commit "INSN_ENTRY(#{insn.name}){#{label}"
+ make_header_prepare_stack insn
+ commit "{"
+ unless insn.trace
+ make_header_stack_val insn
+ make_header_default_operands insn
+ make_header_operands insn
+ make_header_stack_pops insn
+ make_header_temporary_vars insn
+ #
+ make_header_debug insn
+ make_header_pc insn
+ make_header_popn insn
+ make_header_defines insn
+ make_header_analysis insn
+ end
+ commit "{"
+ end
+
+ def make_footer insn
+ unless insn.trace
+ make_footer_stack_val insn
+ make_footer_default_operands insn
+ make_footer_undefs insn
+ end
+ commit " END_INSN(#{insn.name});}}}"
+ end
+
+ def make_insn_def insn
+ build_string do
+ make_header insn
+ if line = insn.body.instance_variable_get(:@line_no)
+ file = insn.body.instance_variable_get(:@file)
+ commit "#line #{line+1} \"#{file}\""
+ commit insn.body
+ commit '#line __CURRENT_LINE__ "__CURRENT_FILE__"'
+ else
+ commit insn.body
+ end
+ make_footer(insn)
+ end
+ end
+ end
+
+ ###################################################################
+ # vmtc.inc
+ class VmTCIncGenerator < SourceCodeGenerator
+ def generate
+ template('vmtc.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # insns_info.inc
+ class InsnsInfoIncGenerator < SourceCodeGenerator
+ def generate
+ template('insns_info.inc.tmpl').result(binding)
+ end
+
+ ###
+ private
+
+ def op2typesig op
+ case op
+ when /^OFFSET/
+ "TS_OFFSET"
+ when /^rb_num_t/
+ "TS_NUM"
+ when /^lindex_t/
+ "TS_LINDEX"
+ when /^VALUE/
+ "TS_VALUE"
+ when /^ID/
+ "TS_ID"
+ when /GENTRY/
+ "TS_GENTRY"
+ when /^IC/
+ "TS_IC"
+ when /^CALL_INFO/
+ "TS_CALLINFO"
+ when /^CALL_CACHE/
+ "TS_CALLCACHE"
+ when /^\.\.\./
+ "TS_VARIABLE"
+ when /^CDHASH/
+ "TS_CDHASH"
+ when /^ISEQ/
+ "TS_ISEQ"
+ when /rb_insn_func_t/
+ "TS_FUNCPTR"
+ else
+ raise "unknown op type: #{op}"
+ end
+ end
+
+ TYPE_CHARS = {
+ 'TS_OFFSET' => 'O',
+ 'TS_NUM' => 'N',
+ 'TS_LINDEX' => 'L',
+ 'TS_VALUE' => 'V',
+ 'TS_ID' => 'I',
+ 'TS_GENTRY' => 'G',
+ 'TS_IC' => 'K',
+ 'TS_CALLINFO' => 'C',
+ 'TS_CALLCACHE' => 'E',
+ 'TS_CDHASH' => 'H',
+ 'TS_ISEQ' => 'S',
+ 'TS_VARIABLE' => '.',
+ 'TS_FUNCPTR' => 'F',
+ }
+
+ def max_length(array)
+ max = 0
+ array.each do |i|
+ if (n = i.length) > max
+ max = n
+ end
+ end
+ max
+ end
+ end
+
+ ###################################################################
+ # insns.inc
+ class InsnsIncGenerator < SourceCodeGenerator
+ def generate
+ template('insns.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # minsns.inc
+ class MInsnsIncGenerator < SourceCodeGenerator
+ def generate
+ template('minsns.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # optinsn.inc
+ class OptInsnIncGenerator < SourceCodeGenerator
+ def generate
+ optinsn_inc
+ end
+
+ ###
+ private
+
+ def val_as_type op
+ type = op[0][0]
+ val = op[1]
+
+ case type
+ when /^long/, /^rb_num_t/, /^lindex_t/
+ "INT2FIX(#{val})"
+ when /^VALUE/
+ val
+ when /^ID/
+ "INT2FIX(#{val})"
+ when /^ISEQ/, /^rb_insn_func_t/
+ val
+ when /GENTRY/
+ raise
+ when /^\.\.\./
+ raise
+ else
+ raise "type: #{type}"
+ end
+ end
+
+ # optinsn.inc
+ def optinsn_inc
+ opt_insns_map = Hash.new{[]}
+
+ @insns.each{|insn|
+ next if insn.defopes.size == 0
+ next if insn.type == :sc
+ next if /^UNIFIED/ =~ insn.name.to_s
+
+ originsn = insn.orig
+ opt_insns_map[originsn] <<= insn
+ }
+
+ opt_insns_map.each_value do |optinsns|
+ sorted = optinsns.sort_by {|opti|
+ opti.defopes.find_all{|e| e[1] == '*'}.size
+ }
+ optinsns.replace(sorted)
+ end
+
+ template('optinsn.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # optunifs.inc
+ class OptUnifsIncGenerator < SourceCodeGenerator
+ def generate
+ template('optunifs.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # opt_sc.inc
+ class OptSCIncGenerator < SourceCodeGenerator
+ def generate
+ sc_insn_info = []
+ @insns.each{|insn|
+ insns = insn.sc
+ if insns.size > 0
+ insns = ['SC_ERROR'] + insns.map{|e| " BIN(#{e.name})"}
+ else
+ insns = Array.new(6){'SC_ERROR'}
+ end
+ sc_insn_info << " {\n#{insns.join(",\n")}}"
+ }
+ sc_insn_info = sc_insn_info.join(",\n")
+
+ sc_insn_next = @insns.map{|insn|
+ " SCS_#{InstructionsLoader.complement_name(insn.nextsc).upcase}" +
+ (verbose? ? " /* #{insn.name} */" : '')
+ }.join(",\n")
+ template('opt_sc.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # yasmdata.rb
+ class YASMDataRbGenerator < SourceCodeGenerator
+ def generate
+ insn_id2no = ''
+ @insns.each_with_index{|insn, i|
+ insn_id2no << " :#{insn.name} => #{i},\n"
+ }
+ template('yasmdata.rb').result(binding)
+ end
+ end
+
+ ###################################################################
+ # yarvarch.*
+ class YARVDocGenerator < SourceCodeGenerator
+ def generate
+
+ end
+
+ def desc lang
+ d = ''
+ i = 0
+ cat = nil
+ @insns.each{|insn|
+ seq = insn.opes.map{|t,v| v}.join(' ')
+ before = insn.pops.reverse.map{|t,v| v}.join(' ')
+ after = insn.rets.reverse.map{|t,v| v}.join(' ')
+
+ if cat != insn.comm[:c]
+ d << "** #{insn.comm[:c]}\n\n"
+ cat = insn.comm[:c]
+ end
+
+ d << "*** #{insn.name}\n"
+ d << "\n"
+ d << insn.comm[lang] << "\n\n"
+ d << ":instruction sequence: 0x%02x " % i << seq << "\n"
+ d << ":stack: #{before} => #{after}\n\n"
+ i+=1
+ }
+ d
+ end
+
+ def desc_ja
+ d = desc :j
+ template('yarvarch.ja').result(binding)
+ end
+
+ def desc_en
+ d = desc :e
+ template('yarvarch.en').result(binding)
+ end
+ end
+
+ class SourceCodeGenerator
+ Files = { # codes
+ 'vm.inc' => VmBodyGenerator,
+ 'vmtc.inc' => VmTCIncGenerator,
+ 'insns.inc' => InsnsIncGenerator,
+ 'insns_info.inc' => InsnsInfoIncGenerator,
+ # 'minsns.inc' => MInsnsIncGenerator,
+ 'optinsn.inc' => OptInsnIncGenerator,
+ 'optunifs.inc' => OptUnifsIncGenerator,
+ 'opt_sc.inc' => OptSCIncGenerator,
+ 'yasmdata.rb' => YASMDataRbGenerator,
+ }
+
+ def generate args = []
+ args = Files.keys if args.empty?
+ args.each{|fn|
+ s = Files[fn].new(@insns).generate
+ open(output_path(fn), 'w') {|f| f.puts(s)}
+ }
+ end
+
+ def self.def_options(opt)
+ opts = {
+ :"insns.def" => 'insns.def',
+ :"opope.def" => 'defs/opt_operand.def',
+ :"unif.def" => 'defs/opt_insn_unif.def',
+ }
+
+ opt.on("-Dname", /\AOPT_(\w+)\z/, "enable VM option") {|s, v|
+ opts[v] = true
+ }
+ opt.on("--enable=name[,name...]", Array,
+ "enable VM options (without OPT_ prefix)") {|*a|
+ a.each {|v| opts[v] = true}
+ }
+ opt.on("-Uname", /\AOPT_(\w+)\z/, "disable VM option") {|s, v|
+ opts[v] = false
+ }
+ opt.on("--disable=name[,name...]", Array,
+ "disable VM options (without OPT_ prefix)") {|*a|
+ a.each {|v| opts[v] = false}
+ }
+ opt.on("-i", "--insnsdef=FILE", "--instructions-def",
+ "instructions definition file") {|n|
+ opts[:insns_def] = n
+ }
+ opt.on("-o", "--opt-operanddef=FILE", "--opt-operand-def",
+ "vm option: operand definition file") {|n|
+ opts[:opope_def] = n
+ }
+ opt.on("-u", "--opt-insnunifdef=FILE", "--opt-insn-unif-def",
+ "vm option: instruction unification file") {|n|
+ opts[:unif_def] = n
+ }
+ opt.on("-C", "--[no-]use-const",
+ "use consts for default operands instead of macros") {|v|
+ opts[:use_const] = v
+ }
+ opt.on("-d", "--destdir", "--output-directory=DIR",
+ "make output file underneath DIR") {|v|
+ opts[:destdir] = v
+ }
+ opt.on("-V", "--[no-]verbose") {|v|
+ opts[:verbose] = v
+ }
+
+ vpath = VPath.new
+ vpath.def_options(opt)
+
+ proc {
+ opts[:VPATH] = vpath
+ build opts
+ }
+ end
+
+ def self.build opts, vpath = ['./']
+ opts[:VPATH] ||= VPath.new(*vpath)
+ self.new InstructionsLoader.new(opts)
+ end
+ end
+end
+