diff --git a/ChangeLog b/ChangeLog index 2d6addca06..00f37a1954 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +Fri Jun 29 14:48:18 2007 Koichi Sasada + + * tool/insns2vm.rb, lib/vm/instruction.rb: move process body + to lib/vm/instruction.rb. + + * common.mk: fix aotc rule. + experimental. bin/ruby2cext is not added yet. + Fri Jun 29 11:23:09 2007 Nobuyoshi Nakada * parse.y (dsym): return non-null NODE even if yyerror(). based on a diff --git a/common.mk b/common.mk index d9b0f15c75..1c99013072 100644 --- a/common.mk +++ b/common.mk @@ -650,8 +650,8 @@ benchmark-each: $(PROGRAM) PHONY tbench: $(PROGRAM) PHONY $(RUNRUBY) $(srcdir)/benchmark/run.rb bmx $(OPT) --ruby=./$(PROGRAM) --matzruby=$(MATZRUBY) --opts=-I$(srcdir)/lib -aotc: - $(RUBY) -I$(srcdir) -I. $(srcdir)/tool/aotcompile.rb $(INSNS2VMOPT) +aotc: $(PROGRAM) PHONY + ./$(PROGRAM) -I$(srcdir)/lib $(srcdir)/bin/ruby2cext $(srcdir)/test.rb vmasm: vm.$(ASMEXT) diff --git a/lib/vm/instruction.rb b/lib/vm/instruction.rb new file mode 100644 index 0000000000..4a9c23f82d --- /dev/null +++ b/lib/vm/instruction.rb @@ -0,0 +1,1331 @@ +# +# +# + +require 'erb' + +module 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 + 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 + + 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 definision + 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' + ret << " unsigned long #{v} = FIX2INT(opes[#{i}]);\n" + end + } + @defopes.each_with_index{|((t, var), val), i| + if t == 'rb_num_t' && val != '*' + ret << " unsigned long #{var} = #{val};\n" + end + } + + ret << " #{@sp_inc};\n" + ret << " return depth + inc;" + ret + else + "return depth + #{rets.size - pops.size};" + end + end + + def inspect + "#" + end + end + + class InstructionsLoader + def initialize opts = {} + @insns = [] + @insn_map = {} + + @vpath = opts[:VPATH] || File + @use_const = opts[:use_const] + @verbose = opts[:verbose] + + (@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"] || 'opt_operand.def' + load_insn_unification_def opts[:"unif.def"] || 'opt_insn_unif.def' + make_stackcaching_insns if vm_opt?('STACK_CACHING') + end + + attr_reader :vpath + + %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 = '' + + if /\/\/(.+)/ =~ rets_str + sp_inc = $1 + else + sp_inc = nil + end + + 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 mismatcvh 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_optimiized(insn, ninsn, nopes, defv) + end + + def make_insn_operand_optimiized 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| + "#define #{v} #{v}_#{i}" + }.join("\n") + "\n" + + insn.body + + passed_vars.map{|rpvars| + "#undef #{rpvars[0][1]}" + }.join("\n") + + "\n" + + redef_vars.keys.map{|v| + "#undef #{v}" + }.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 + + after = nil + SPECIAL_INSN_FOR_SC_AFTER.any?{|k, v| + if k =~ insn.name + after = v + break + end + } + + 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_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 + end + + ################################################################### + # vm.inc + class VmBodyGenerator < SourceCodeGenerator + # vm.inc + def generate + vm_body = '' + @insns.each{|insn| + vm_body << "\n" + vm_body << make_insn_def(insn) + } + src = vpath.read('template/vm.inc.tmpl') + ERB.new(src).result(binding) + end + + def generate_from_insnname insnname + make_insn_def @insns[insnname.to_s] + end + + ####### + private + + def make_header_prepare_stack insn + ret = [] + push_ba = insn.pushsc + raise "unsupport" if push_ba[0].size > 0 && push_ba[1].size > 0 + + push_ba.each{|pushs| + pushs.each{|r| + ret << " PUSH(SCREG(#{r}));" + } + } + ret.join("\n") + "\n" + end + + def make_header_operands insn + vars = insn.opes + n = 0 + ops = [] + + vars.each_with_index{|(type, var), i| + if type == '...' + break + end + + ops << " #{type} #{var} = (#{type})GET_OPERAND(#{i+1});" + n += 1 + } + @opn = n + + # reverse or not? + # ops.join + ops.reverse.join("\n") + "\n" + end + + def make_header_default_operands insn + ret = [] + vars = insn.defopes + + vars.each{|e| + next if e[1] == '*' + if use_const? + ret << " const #{e[0][0]} #{e[0][1]} = #{e[1]};" + else + ret << " #define #{e[0][1]} #{e[1]}" + end + } + ret.join("\n") + "\n" + end + + def make_footer_default_operands insn + if use_const? + "\n" + else + ret = [] + vars = insn.defopes + + vars.each{|e| + next if e[1] == '*' + ret << "#undef #{e[0][1]}\n" + } + ret.join("\n") + "\n" + end + end + + def make_header_stack_pops insn + 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? + pops.reverse.join("\n") + "\n" + end + + def make_header_temporary_vars insn + ret = [] + insn.tvars.each{|var| + ret << " #{var[0]} #{var[1]};" + } + ret.join("\n") + "\n" + end + + def make_header_stack_val insn + ret = [] + 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] != '...' + ret << " #{var[0]} #{var[1]};" + end + } + ret.join("\n") + "\n" + end + + def make_footer_stack_val insn + ret = [] + insn.rets.reverse_each{|v| + if v[1] == '...' + break + end + if v[2] + ret << " SCREG(#{v[2]}) = #{v[1]};" + else + ret << " PUSH(#{v[1]});" + end + } + ret.join("\n") + "\n" + end + + def make_header insn + ret = "\nINSN_ENTRY(#{insn.name}){\n" + ret += " /* prepare stack status */\n" if verbose? + ret += make_header_prepare_stack insn + ret += "{\n" + ret += " /* declare stack push val */\n" if verbose? + ret += make_header_stack_val insn + ret += " /* declare and initialize default opes */\n" if verbose? + ret += make_header_default_operands insn + ret += " /* declare and get from iseq */\n" if verbose? + ret += make_header_operands insn + ret += " /* declare and pop from stack */\n" if verbose? + ret += make_header_stack_pops insn + ret += " /* declare temporary vars */\n" if verbose? + ret += make_header_temporary_vars insn + + ret += " /* for debug */\n" if verbose? + ret += " DEBUG_ENTER_INSN(\"#{insn.name}\");\n" + ret += " /* management */\n" if verbose? + ret += " ADD_PC(1+#{@opn});\n" + ret += " PREFETCH(GET_PC());\n" + ret += " POPN(#{@popn});\n" if @popn > 0 + ret += " #define CURRENT_INSN_#{insn.name} 1\n" + ret += " #define INSN_IS_SC() #{insn.sc ? 0 : 1}\n" + ret += " #define INSN_LABEL(lab) LABEL_#{insn.name}_##lab\n" + + ret += " #define LABEL_IS_SC(lab) LABEL_##lab##_###{insn.sc.size == 0 ? 't' : 'f'}\n" + + ret += " USAGE_ANALYSIS_INSN(BIN(#{insn.name}));\n" + insn.opes.each_with_index{|op, i| + ret += " USAGE_ANALYSIS_OPERAND(BIN(#{insn.name}), #{i}, #{op[1]});\n" + } + ret += "{\n" + end + + def make_footer insn + ret = '' + ret = " /* push stack val */\n" if verbose? + ret += make_footer_stack_val insn + # debug info + + # epilogue + ret += make_footer_default_operands insn + ret += "#undef CURRENT_INSN_#{insn.name}\n" + ret += "#undef INSN_IS_SC\n" + ret += "#undef INSN_LABEL\n" + ret += "#undef LABEL_IS_SC\n" + ret += " END_INSN(#{insn.name});\n}}\n" + ret += "}\n" + end + + def make_insn_def insn + ret = make_header insn + if line = insn.body.instance_variable_get(:@line_no) + file = insn.body.instance_variable_get(:@file) + ret << "#line #{line+1} \"#{file}\"" << "\n" + ret << insn.body + ret << '#line __CURRENT_LINE__ "vm.inc"' << "\n" + else + ret << insn.body + end + ret << make_footer(insn) + end + end + + ################################################################### + # vmtc.inc + class VmTCIncGenerator < SourceCodeGenerator + def generate + insns_table = '' + insns_end_table = '' + + @insns.each{|insn| + insns_table << " LABEL_PTR(#{insn.name}),\n" + } + @insns.each{|insn| + insns_end_table << " ELABEL_PTR(#{insn.name}),\n" + } + ERB.new(vpath.read('template/vmtc.inc.tmpl')).result(binding) + end + end + + ################################################################### + # insns_info.inc + class InsnsInfoIncGenerator < SourceCodeGenerator + def generate + insns_info_inc + end + + ### + private + + def op2typesig op + case op + when /^OFFSET/ + "TS_OFFSET" + when /^rb_num_t/ + "TS_NUM" + when /^lindex_t/ + "TS_LINDEX" + when /^dindex_t/ + "TS_DINDEX" + when /^VALUE/ + "TS_VALUE" + when /^ID/ + "TS_ID" + when /GENTRY/ + "TS_GENTRY" + when /^IC/ + "TS_IC" + when /^\.\.\./ + "TS_VARIABLE" + when /^CDHASH/ + "TS_CDHASH" + when /^ISEQ/ + "TS_ISEQ" + else + raise "unknown op type: #{op}" + end + end + + TYPE_CHARS = { + 'TS_OFFSET' => 'O', + 'TS_NUM' => 'N', + 'TS_LINDEX' => 'L', + 'TS_DINDEX' => 'D', + 'TS_VALUE' => 'V', + 'TS_ID' => 'I', + 'TS_GENTRY' => 'G', + 'TS_IC' => 'C', + 'TS_CDHASH' => 'H', + 'TS_ISEQ' => 'S', + 'TS_VARIABLE' => '.', + } + + # insns_info.inc + def insns_info_inc + # insn_type_chars + insn_type_chars = TYPE_CHARS.map{|t, c| + "#define #{t} '#{c}'" + }.join("\n") + + # insn_names + insn_names = '' + @insns.each{|insn| + insn_names << " \"#{insn.name}\",\n" + } + + # operands info + operands_info = '' + operands_num_info = '' + @insns.each{|insn| + opes = insn.opes + operands_info << ' ' + ot = opes.map{|type, var| + TYPE_CHARS.fetch(op2typesig(type)) + } + operands_info << "\"#{ot.join}\"" << ", \n" + + num = opes.size + 1 + operands_num_info << " #{num},\n" + } + + # stack num + stack_num_info = '' + @insns.each{|insn| + num = insn.rets.size + stack_num_info << " #{num},\n" + } + + # stack increase + stack_increase = '' + @insns.each{|insn| + stack_increase << <<-EOS + case BIN(#{insn.name}):{ + #{insn.sp_increase_c_expr} + } + EOS + } + ERB.new(vpath.read('template/insns_info.inc.tmpl')).result(binding) + end + end + + ################################################################### + # insns.inc + class InsnsIncGenerator < SourceCodeGenerator + def generate + insns = '' + i=0 + @insns.each{|insn| + insns << " %-30s = %d,\n" % ["BIN(#{insn.name})", i] + i+=1 + } + ERB.new(vpath.read('template/insns.inc.tmpl')).result(binding) + end + end + + ################################################################### + # minsns.inc + class MInsnsIncGenerator < SourceCodeGenerator + def generate + defs = '' + i=0 + @insns.each{|insn| + defs << " rb_define_const(mYarvInsns, %-30s, INT2FIX(%d));\n" % + ["\"I#{insn.name}\"", i] + i+=1 + } + ERB.new(vpath.read('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/, /^dindex_t/ + "INT2FIX(#{val})" + when /^VALUE/ + val + when /^ID/ + "INT2FIX(#{val})" + when /^ISEQ/ + val + when /GENTRY/ + raise + when /^\.\.\./ + raise + else + raise "type: #{type}" + end + end + + # optinsn.inc + def optinsn_inc + rule = '' + opt_insns_map = Hash.new{|h, k| h[k] = []} + + @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{|originsn, optinsns| + rule += "case BIN(#{originsn.name}):\n" + + optinsns.sort_by{|opti| + opti.defopes.find_all{|e| e[1] == '*'}.size + }.each{|opti| + rule += " if(\n" + i = 0 + rule += ' ' + opti.defopes.map{|opinfo| + i += 1 + next if opinfo[1] == '*' + "insnobj->operands[#{i-1}] == #{val_as_type(opinfo)}\n" + }.compact.join('&& ') + rule += " ){\n" + idx = 0 + n = 0 + opti.defopes.each{|opinfo| + if opinfo[1] == '*' + if idx != n + rule += " insnobj->operands[#{idx}] = insnobj->operands[#{n}];\n" + end + idx += 1 + else + # skip + end + n += 1 + } + rule += " insnobj->insn_id = BIN(#{opti.name});\n" + rule += " insnobj->operand_size = #{idx};\n" + rule += " break;\n }\n" + } + rule += " break;\n"; + } + ERB.new(vpath.read('template/optinsn.inc.tmpl')).result(binding) + end + end + + ################################################################### + # optunifs.inc + class OptUnifsIncGenerator < SourceCodeGenerator + def generate + unif_insns_each = '' + unif_insns = '' + unif_insns_data = [] + + insns = @insns.find_all{|insn| !insn.is_sc} + insns.each{|insn| + size = insn.unifs.size + if size > 0 + insn.unifs.sort_by{|unif| -unif[1].size}.each_with_index{|unif, i| + + uni_insn, uni_insns = *unif + uni_insns = uni_insns[1..-1] + unif_insns_each << "static int UNIFIED_#{insn.name}_#{i}[] = {" + + " BIN(#{uni_insn.name}), #{uni_insns.size + 2}, \n " + + uni_insns.map{|e| "BIN(#{e.name})"}.join(", ") + "};\n" + } + else + + end + if size > 0 + unif_insns << "static int *UNIFIED_#{insn.name}[] = {(int *)#{size+1}, \n" + unif_insns << (0...size).map{|e| " UNIFIED_#{insn.name}_#{e}"}.join(",\n") + "};\n" + unif_insns_data << " UNIFIED_#{insn.name}" + else + unif_insns_data << " 0" + end + } + unif_insns_data = "static int **unified_insns_data[] = {\n" + + unif_insns_data.join(",\n") + "};\n" + ERB.new(vpath.read('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") + ERB.new(vpath.read('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" + } + ERB.new(vpath.read('template/yasmdata.rb.tmpl')).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 #{seq}\n" % i + d << ":stack: #{before} => #{after}\n\n" + i+=1 + } + d + end + + def desc_ja + d = desc :j + ERB.new(vpath.read('template/yarvarch.ja')).result(binding) + end + + def desc_en + d = desc :e + ERB.new(vpath.read('template/yarvarch.en')).result(binding) + end + end + + module VPATH + def search(meth, base, *rest) + begin + meth.call(base, *rest) + rescue Errno::ENOENT => error + each do |dir| + return meth.call(File.join(dir, base), *rest) rescue nil + end + raise error + end + end + + def process(*args, &block) + search(File.method(__callee__), *args, &block) + end + + alias stat process + alias lstat process + + def open(*args) + f = search(File.method(:open), *args) + if block_given? + begin + yield f + ensure + f.close unless f.closed? + end + else + f + end + end + + def read(*args) + open(*args) {|f| f.read} + end + + def foreach(file, *args, &block) + open(file) {|f| f.each(*args, &block)} + end + + def self.def_options(opt) + vpath = [] + path_sep = ':' + + opt.on("-I", "--srcdir=DIR", "add a directory to search path") {|dir| + vpath |= [dir] + } + opt.on("-L", "--vpath=PATH LIST", "add directories to search path") {|dirs| + vpath |= dirs.split(path_sep) + } + opt.on("--path-separator=SEP", /\A\W\z/, "separator for vpath") {|sep| + path_sep = sep + } + + proc { + vpath.extend(self) unless vpath.empty? + } + 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(fn, 'w') {|f| f.puts(s)} + } + end + + def self.def_options(opt) + opts = { + :"insns.def" => 'insns.def', + :"opope.def" => 'opt_operand.def', + :"unif.def" => '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("-V", "--[no-]verbose") {|v| + opts[:verbose] = v + } + + vpath = VPATH.def_options(opt) + + proc { + opts[:VPATH] = vpath.call + build opts + } + end + + def self.build opts, vpath = ['./'] + opts[:VPATH] = vpath.extend(VPATH) unless opts[:VPATH] + self.new InstructionsLoader.new(opts) + end + end +end + diff --git a/tool/insns2vm.rb b/tool/insns2vm.rb index 3eb2cc507e..82a5e7c88c 100644 --- a/tool/insns2vm.rb +++ b/tool/insns2vm.rb @@ -1,1304 +1,15 @@ #!/usr/bin/env ruby -# -# -# -# $verbose = true -# $use_const = true + +require 'optparse' Version = %w$Revision: 11626 $[1..-1] -require 'pp' -require 'erb' -require 'optparse' - -module VPATH - def search(meth, base, *rest) - begin - meth.call(base, *rest) - rescue Errno::ENOENT => error - each do |dir| - return meth.call(File.join(dir, base), *rest) rescue nil - end - raise error - end - end - - def process(*args, &block) - search(File.method(__callee__), *args, &block) - end - - alias stat process - alias lstat process - - def open(*args) - f = search(File.method(:open), *args) - if block_given? - begin - yield f - ensure - f.close unless f.closed? - end - else - f - end - end - - def read(*args) - open(*args) {|f| f.read} - end - - def foreach(file, *args, &block) - open(file) {|f| f.each(*args, &block)} - end - - def self.def_options(opt) - vpath = [] - path_sep = ':' - - opt.on("-I", "--srcdir=DIR", "add a directory to search path") {|dir| - vpath |= [dir] - } - opt.on("-L", "--vpath=PATH LIST", "add directories to search path") {|dirs| - vpath |= dirs.split(path_sep) - } - opt.on("--path-separator=SEP", /\A\W\z/, "separator for vpath") {|sep| - path_sep = sep - } - - proc { - vpath.extend(self) unless vpath.empty? - } - end -end - -class InsnsDef - class InsnInfo - - 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 - 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 - - 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 definision - 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' - ret << " unsigned long #{v} = FIX2INT(opes[#{i}]);\n" - end - } - @defopes.each_with_index{|((t, var), val), i| - if t == 'rb_num_t' && val != '*' - ret << " unsigned long #{var} = #{val};\n" - end - } - - ret << " #{@sp_inc};\n" - ret << " return depth + inc;" - ret - else - "return depth + #{rets.size - pops.size};" - end - end - - def inspect - "#" - end - end - - def initialize file, optopfile, uniffile, opts = {} - @insns = [] - @insn_map = {} - - @vpath = opts['VPATH'] || File - @use_const = opts['use-const'] - @verbose = opts['verbose'] - - (@vm_opts = load_vm_opts).each {|k, v| - @vm_opts[k] = opts[k] if opts.key?(k) - } - - load_insns_def file - - load_opt_operand_def optopfile - load_insn_unification_def uniffile - make_stackcaching_insns if vm_opt?('STACK_CACHING') - end - - attr_reader :insns - attr_reader :insn_map - - attr_reader :vpath - - %w[use_const verbose].each do |attr| - attr_reader attr - alias_method "#{attr}?", attr - remove_method attr - end - - 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 each - @insns.each{|insn| - yield insn - } - end - - 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 InsnInfo.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 = '' - - if /\/\/(.+)/ =~ rets_str - sp_inc = $1 - else - sp_inc = nil - end - - 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} - } - 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 mismatcvh 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_optimiized(insn, ninsn, nopes, defv) - end - - def make_insn_operand_optimiized orig_insn, name, opes, defopes - comm = orig_insn.comm.dup - comm[:c] = 'optimize' - add_insn insn = InsnInfo.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] - } - } - 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| - "#define #{v} #{v}_#{i}" - }.join("\n") + "\n" + - insn.body + - passed_vars.map{|rpvars| - "#undef #{rpvars[0][1]}" - }.join("\n") + - "\n" + - redef_vars.keys.map{|v| - "#undef #{v}" - }.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 = InsnInfo.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 - - after = nil - SPECIAL_INSN_FOR_SC_AFTER.any?{|k, v| - if k =~ insn.name - after = v - break - end - } - - 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) - } - } - # exit! 1 - end - - def make_insn_sc orig_insn, name, opes, pops, rets, pushs, nextsc - comm = orig_insn.comm.dup - comm[:c] = 'optimize(sc)' - - scinsn = InsnInfo.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 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_#{complement_name(ofrom)}_#{complement_name(from)}", - pops, rets, pushs_before, pushs, from] - #p ret - ret - end - - ################################################################### - # vm.inc - def make_header_prepare_stack insn - ret = [] - push_ba = insn.pushsc - raise "unsupport" if push_ba[0].size > 0 && push_ba[1].size > 0 - - push_ba.each{|pushs| - pushs.each{|r| - ret << " PUSH(SCREG(#{r}));" - } - } - ret.join("\n") + "\n" - end - - def make_header_operands insn - vars = insn.opes - n = 0 - ops = [] - - vars.each_with_index{|(type, var), i| - if type == '...' - break - end - - ops << " #{type} #{var} = (#{type})GET_OPERAND(#{i+1});" - n += 1 - } - @opn = n - - # reverse or not? - # ops.join - ops.reverse.join("\n") + "\n" - end - - def make_header_default_operands insn - ret = [] - vars = insn.defopes - - vars.each{|e| - next if e[1] == '*' - if use_const? - ret << " const #{e[0][0]} #{e[0][1]} = #{e[1]};" - else - ret << " #define #{e[0][1]} #{e[1]}" - end - } - ret.join("\n") + "\n" - end - - def make_footer_default_operands insn - if use_const? - "\n" - else - ret = [] - vars = insn.defopes - - vars.each{|e| - next if e[1] == '*' - ret << "#undef #{e[0][1]}\n" - } - ret.join("\n") + "\n" - end - end - - def make_header_stack_pops insn - 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? - pops.reverse.join("\n") + "\n" - end - - def make_header_temporary_vars insn - ret = [] - insn.tvars.each{|var| - ret << " #{var[0]} #{var[1]};" - } - ret.join("\n") + "\n" - end - - def make_header_stack_val insn - ret = [] - - 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] != '...' - ret << " #{var[0]} #{var[1]};" - end - } - ret.join("\n") + "\n" - end - - def make_footer_stack_val insn - ret = [] - insn.rets.reverse_each{|v| - if v[1] == '...' - break - end - if v[2] - ret << " SCREG(#{v[2]}) = #{v[1]};" - else - ret << " PUSH(#{v[1]});" - end - } - ret.join("\n") + "\n" - end - - def make_header insn - ret = "\nINSN_ENTRY(#{insn.name}){\n" - ret += " /* prepare stack status */\n" if verbose? - ret += make_header_prepare_stack insn - ret += "{\n" - ret += " /* declare stack push val */\n" if verbose? - ret += make_header_stack_val insn - ret += " /* declare and initialize default opes */\n" if verbose? - ret += make_header_default_operands insn - ret += " /* declare and get from iseq */\n" if verbose? - ret += make_header_operands insn - ret += " /* declare and pop from stack */\n" if verbose? - ret += make_header_stack_pops insn - ret += " /* declare temporary vars */\n" if verbose? - ret += make_header_temporary_vars insn - - ret += " /* for debug */\n" if verbose? - ret += " DEBUG_ENTER_INSN(\"#{insn.name}\");\n" - ret += " /* management */\n" if verbose? - ret += " ADD_PC(1+#{@opn});\n" - ret += " PREFETCH(GET_PC());\n" - ret += " POPN(#{@popn});\n" if @popn > 0 - ret += " #define CURRENT_INSN_#{insn.name} 1\n" - ret += " #define INSN_IS_SC() #{insn.sc ? 0 : 1}\n" - ret += " #define INSN_LABEL(lab) LABEL_#{insn.name}_##lab\n" - - ret += " #define LABEL_IS_SC(lab) LABEL_##lab##_###{insn.sc.size == 0 ? 't' : 'f'}\n" - - ret += " USAGE_ANALYSIS_INSN(BIN(#{insn.name}));\n" - insn.opes.each_with_index{|op, i| - ret += " USAGE_ANALYSIS_OPERAND(BIN(#{insn.name}), #{i}, #{op[1]});\n" - } - ret += "{\n" - - end - - def make_footer insn - ret = '' - ret = " /* push stack val */\n" if verbose? - ret += make_footer_stack_val insn - # debug info - - # epilogue - ret += make_footer_default_operands insn - ret += "#undef CURRENT_INSN_#{insn.name}\n" - ret += "#undef INSN_IS_SC\n" - ret += "#undef INSN_LABEL\n" - ret += "#undef LABEL_IS_SC\n" - ret += " END_INSN(#{insn.name});\n}}\n" - ret += "}\n" - end - - def make_insn_def insn - ret = make_header insn - if line = insn.body.instance_variable_get(:@line_no) - file = insn.body.instance_variable_get(:@file) - ret << "#line #{line+1} \"#{file}\"" << "\n" - ret << insn.body - ret << '#line __CURRENT_LINE__ "vm.inc"' << "\n" - else - ret << insn.body - end - ret << make_footer(insn) - end - - # vm.inc - def vm_inc - vm_body = '' - - @insns.each{|insn| - vm_body << "\n" - vm_body << make_insn_def(insn) - } - src = vpath.read('template/vm.inc.tmpl') - ERB.new(src).result(binding) - end - - - ################################################################### - # vmtc.inc - - def vmtc_inc - insns_table = '' - insns_end_table = '' - - @insns.each{|insn| - insns_table << " LABEL_PTR(#{insn.name}),\n" - } - @insns.each{|insn| - insns_end_table << " ELABEL_PTR(#{insn.name}),\n" - } - - ERB.new(vpath.read('template/vmtc.inc.tmpl')).result(binding) - end - - - ################################################################### - # insns_info.inc - - def op2typesig op - case op - when /^OFFSET/ - "TS_OFFSET" - when /^rb_num_t/ - "TS_NUM" - when /^lindex_t/ - "TS_LINDEX" - when /^dindex_t/ - "TS_DINDEX" - when /^VALUE/ - "TS_VALUE" - when /^ID/ - "TS_ID" - when /GENTRY/ - "TS_GENTRY" - when /^IC/ - "TS_IC" - when /^\.\.\./ - "TS_VARIABLE" - when /^CDHASH/ - "TS_CDHASH" - when /^ISEQ/ - "TS_ISEQ" - else - raise "unknown op type: #{op}" - end - end - - TYPE_CHARS = { - 'TS_OFFSET' => 'O', - 'TS_NUM' => 'N', - 'TS_LINDEX' => 'L', - 'TS_DINDEX' => 'D', - 'TS_VALUE' => 'V', - 'TS_ID' => 'I', - 'TS_GENTRY' => 'G', - 'TS_IC' => 'C', - 'TS_CDHASH' => 'H', - 'TS_ISEQ' => 'S', - 'TS_VARIABLE' => '.', - } - - # insns_info.inc - def insns_info_inc - # insn_type_chars - insn_type_chars = TYPE_CHARS.map{|t, c| - "#define #{t} '#{c}'" - }.join("\n") - - # insn_names - insn_names = '' - @insns.each{|insn| - insn_names << " \"#{insn.name}\",\n" - } - - # operands info - operands_info = '' - operands_num_info = '' - @insns.each{|insn| - opes = insn.opes - operands_info << ' ' - ot = opes.map{|type, var| - TYPE_CHARS.fetch(op2typesig(type)) - } - operands_info << "\"#{ot.join}\"" << ", \n" - - num = opes.size + 1 - operands_num_info << " #{num},\n" - } - - # stack num - stack_num_info = '' - @insns.each{|insn| - num = insn.rets.size - stack_num_info << " #{num},\n" - } - - # stack increase - stack_increase = '' - @insns.each{|insn| - stack_increase << <<-EOS - case BIN(#{insn.name}):{ - #{insn.sp_increase_c_expr} - } - EOS - } - ERB.new(vpath.read('template/insns_info.inc.tmpl')).result(binding) - end - - - ################################################################### - # insns.inc - def insns_inc - insns = '' - i=0 - @insns.each{|insn| - insns << " %-30s = %d,\n" % ["BIN(#{insn.name})", i] - i+=1 - } - ERB.new(vpath.read('template/insns.inc.tmpl')).result(binding) - end - - - ################################################################### - # minsns.inc - def minsns_inc - defs = '' - i=0 - @insns.each{|insn| - defs << " rb_define_const(mYarvInsns, %-30s, INT2FIX(%d));\n" % - ["\"I#{insn.name}\"", i] - i+=1 - } - ERB.new(vpath.read('template/minsns.inc.tmpl')).result(binding) - end - - - ################################################################### - # optinsn.inc - def val_as_type op - type = op[0][0] - val = op[1] - - case type - when /^long/, /^rb_num_t/, /^lindex_t/, /^dindex_t/ - "INT2FIX(#{val})" - when /^VALUE/ - val - when /^ID/ - "INT2FIX(#{val})" - when /^ISEQ/ - val - when /GENTRY/ - raise - when /^\.\.\./ - raise - else - raise "type: #{type}" - end - end - - # optinsn.inc - def optinsn_inc - rule = '' - opt_insns_map = Hash.new{|h, k| h[k] = []} - - @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{|originsn, optinsns| - rule += "case BIN(#{originsn.name}):\n" - - optinsns.sort_by{|opti| - opti.defopes.find_all{|e| e[1] == '*'}.size - }.each{|opti| - rule += " if(\n" - i = 0 - rule += ' ' + opti.defopes.map{|opinfo| - i += 1 - next if opinfo[1] == '*' - "insnobj->operands[#{i-1}] == #{val_as_type(opinfo)}\n" - }.compact.join('&& ') - rule += " ){\n" - idx = 0 - n = 0 - opti.defopes.each{|opinfo| - if opinfo[1] == '*' - if idx != n - rule += " insnobj->operands[#{idx}] = insnobj->operands[#{n}];\n" - end - idx += 1 - else - # skip - end - n += 1 - } - rule += " insnobj->insn_id = BIN(#{opti.name});\n" - rule += " insnobj->operand_size = #{idx};\n" - rule += " break;\n }\n" - } - rule += " break;\n"; - } - ERB.new(vpath.read('template/optinsn.inc.tmpl')).result(binding) - end - - - ################################################################### - # optunifs.inc - def optunifs_unc - unif_insns_each = '' - unif_insns = '' - unif_insns_data = [] - - insns = find_all{|insn| !insn.is_sc} - insns.each{|insn| - size = insn.unifs.size - if size > 0 - insn.unifs.sort_by{|unif| -unif[1].size}.each_with_index{|unif, i| - - uni_insn, uni_insns = *unif - uni_insns = uni_insns[1..-1] - unif_insns_each << "static int UNIFIED_#{insn.name}_#{i}[] = {" + - " BIN(#{uni_insn.name}), #{uni_insns.size + 2}, \n " + - uni_insns.map{|e| "BIN(#{e.name})"}.join(", ") + "};\n" - } - else - - end - if size > 0 - unif_insns << "static int *UNIFIED_#{insn.name}[] = {(int *)#{size+1}, \n" - unif_insns << (0...size).map{|e| " UNIFIED_#{insn.name}_#{e}"}.join(",\n") + "};\n" - unif_insns_data << " UNIFIED_#{insn.name}" - else - unif_insns_data << " 0" - end - } - unif_insns_data = "static int **unified_insns_data[] = {\n" + - unif_insns_data.join(",\n") + "};\n" - ERB.new(vpath.read('template/optunifs.inc.tmpl')).result(binding) - end - - ################################################################### - # opt_sc.inc - def opt_sc_inc - 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_#{complement_name(insn.nextsc).upcase}" + - (verbose? ? " /* #{insn.name} */" : '') - }.join(",\n") - ERB.new(vpath.read('template/opt_sc.inc.tmpl')).result(binding) - end - - ################################################################### - # yasmdata.rb - def yasmdata_rb - insn_id2no = '' - @insns.each_with_index{|insn, i| - insn_id2no << " :#{insn.name} => #{i},\n" - } - ERB.new(vpath.read('template/yasmdata.rb.tmpl')).result(binding) - end - - ################################################################### - # yarvarch.* - 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 #{seq}\n" % i - d << ":stack: #{before} => #{after}\n\n" - i+=1 - } - d - end - - def desc_ja - d = desc :j - ERB.new(vpath.read('template/yarvarch.ja')).result(binding) - end - - def desc_en - d = desc :e - ERB.new(vpath.read('template/yarvarch.en')).result(binding) - end - - Files = { # codes - 'vm.inc' => :vm_inc, - 'vmtc.inc' => :vmtc_inc, - 'insns.inc' => :insns_inc, - 'insns_info.inc' => :insns_info_inc, - # 'minsns.inc' => :minsns_inc, - 'optinsn.inc' => :optinsn_inc, - 'optunifs.inc' => :optunifs_unc, - 'opt_sc.inc' => :opt_sc_inc, - 'yasmdata.rb' => :yasmdata_rb, - } - - def make_sources args = [] - args = Files.keys if args.empty? - - args.each{|fn| - s = __send__(Files[fn]) - open(fn, 'w') {|f| f.puts(s)} - } - end - - def self.make_sources insns_def, opopt_def, unif_def, args = [], opts = {} - insns = InsnsDef.new(insns_def, opopt_def, unif_def, opts) - insns.make_sources(args) - end - - def self.def_options(opt) - opts = {} - insns_def = 'insns.def' - opope_def = 'opt_operand.def' - unif_def = '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", /\A\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| - insns_def = n - } - opt.on("-o", "--opt-operanddef=FILE", "--opt-operand-def", - "vm option: operand definition file") {|n| - opope_def = n - } - opt.on("-u", "--opt-insnunifdef=FILE", "--opt-insn-unif-def", - "vm option: instruction unification file") {|n| - unif_def = n - } - opt.on("-C", "--[no-]use-const", - "use consts for default operands instead of macros") {|v| - opts['use-const'] = v - } - opt.on("-V", "--[no-]verbose") {|v| - opts['verbose'] = v - } - - vpath = VPATH.def_options(opt) - - proc { - opts['VPATH'] = vpath[] - new insns_def, opope_def, unif_def, opts - } - end -end - - - - -############################################## -def insns_def_new(argv = ARGV) - opt = OptionParser.new - insn_maker = InsnsDef.def_options(opt) - opt.parse(argv) - insn_maker[] -end +require "#{File.join(File.dirname(__FILE__), '../lib/vm/instruction')}" if $0 == __FILE__ opts = ARGV.options - insn_maker = InsnsDef.def_options(opts) + maker = RubyVM::SourceCodeGenerator.def_options(opts) files = opts.parse! - insns = insn_maker[] - insns.make_sources(files) + generator = maker.call + generator.generate(files) end -