summaryrefslogtreecommitdiff
path: root/trunk/tool
diff options
context:
space:
mode:
authoryugui <yugui@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2008-08-25 15:02:05 +0000
committeryugui <yugui@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2008-08-25 15:02:05 +0000
commit0dc342de848a642ecce8db697b8fecd83a63e117 (patch)
tree2b7ed4724aff1f86073e4740134bda9c4aac1a39 /trunk/tool
parentef70cf7138ab8034b5b806f466e4b484b24f0f88 (diff)
added tag v1_9_0_4
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/tags/v1_9_0_4@18845 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'trunk/tool')
-rw-r--r--trunk/tool/asm_parse.rb51
-rwxr-xr-xtrunk/tool/build-transcode16
-rw-r--r--trunk/tool/compile_prelude.rb96
-rw-r--r--trunk/tool/eval.rb161
-rwxr-xr-xtrunk/tool/ifchange17
-rwxr-xr-xtrunk/tool/insns2vm.rb15
-rw-r--r--trunk/tool/instruction.rb1385
-rwxr-xr-xtrunk/tool/make-snapshot183
-rwxr-xr-xtrunk/tool/node_name.rb4
-rw-r--r--trunk/tool/parse.rb13
-rw-r--r--trunk/tool/transcode-tblgen.rb637
-rw-r--r--trunk/tool/vtlh.rb15
-rwxr-xr-xtrunk/tool/ytab.sed30
13 files changed, 2623 insertions, 0 deletions
diff --git a/trunk/tool/asm_parse.rb b/trunk/tool/asm_parse.rb
new file mode 100644
index 0000000000..f3d7f73c99
--- /dev/null
+++ b/trunk/tool/asm_parse.rb
@@ -0,0 +1,51 @@
+stat = {}
+
+while line = ARGF.gets
+ if /\[start\] (\w+)/ =~ line
+ name = $1
+ puts '--------------------------------------------------------------'
+ puts line
+ size = 0
+ len = 0
+
+ while line = ARGF.gets
+ if /\[start\] (\w+)/ =~ line
+ puts "\t; # length: #{len}, size: #{size}"
+ puts "\t; # !!"
+ stat[name] = [len, size]
+ #
+ name = $1
+ puts '--------------------------------------------------------------'
+ puts line
+ size = 0
+ len = 0
+ next
+ end
+
+ unless /(\ALM)|(\ALB)|(\A\.)|(\A\/)/ =~ line
+ puts line
+ if /\[length = (\d+)\]/ =~ line
+ len += $1.to_i
+ size += 1
+ end
+ end
+
+
+ if /__NEXT_INSN__/ !~ line && /\[end \] (\w+)/ =~ line
+ ename = $1
+ if name != ename
+ puts "!! start with #{name}, but end with #{ename}"
+ end
+ stat[ename] = [len, size]
+ puts "\t; # length: #{len}, size: #{size}"
+ break
+ end
+ end
+ end
+end
+
+stat.sort_by{|a, b| -b[0] * 1000 - a[0]}.each{|a, b|
+ puts "#{a}\t#{b.join("\t")}"
+}
+puts "total length :\t#{stat.inject(0){|r, e| r+e[1][0]}}"
+puts "total size :\t#{stat.inject(0){|r, e| r+e[1][1]}}"
diff --git a/trunk/tool/build-transcode b/trunk/tool/build-transcode
new file mode 100755
index 0000000000..fa71155530
--- /dev/null
+++ b/trunk/tool/build-transcode
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+[ "$1" -a -d "$1" ] && { cd "$1" || exit $?; } && shift
+[ "$#" = 0 ] && set enc/trans/*.trans
+for src; do
+ case "$src" in
+ *.trans)
+ c="`dirname $src`/`basename $src .trans`.c"
+ ${BASERUBY-ruby} tool/transcode-tblgen.rb -vo "$c" "$src"
+ ;;
+ *)
+ echo "$0: don't know how to deal with $src"
+ continue
+ ;;
+ esac
+done
diff --git a/trunk/tool/compile_prelude.rb b/trunk/tool/compile_prelude.rb
new file mode 100644
index 0000000000..70197aebf3
--- /dev/null
+++ b/trunk/tool/compile_prelude.rb
@@ -0,0 +1,96 @@
+# This file is interpreted by $(BASERUBY) and miniruby.
+# $(BASERUBY) is used for miniprelude.c.
+# miniruby is used for prelude.c.
+# Since $(BASERUBY) may be older than Ruby 1.9,
+# Ruby 1.9 feature should not be used.
+
+$:.unshift(File.expand_path("../..", __FILE__))
+
+preludes = ARGV.dup
+outfile = preludes.pop
+init_name = outfile[/\w+(?=_prelude.c\b)/] || 'prelude'
+
+C_ESC = {
+ "\\" => "\\\\",
+ '"' => '\"',
+ "\n" => '\n',
+}
+
+0x00.upto(0x1f) {|ch| C_ESC[[ch].pack("C")] ||= "\\%03o" % ch }
+0x7f.upto(0xff) {|ch| C_ESC[[ch].pack("C")] = "\\%03o" % ch }
+C_ESC_PAT = Regexp.union(*C_ESC.keys)
+
+def c_esc(str)
+ '"' + str.gsub(C_ESC_PAT) { C_ESC[$&] } + '"'
+end
+
+mkconf = nil
+setup_ruby_prefix = nil
+teardown_ruby_prefix = nil
+lines_list = preludes.map {|filename|
+ lines = []
+ need_ruby_prefix = false
+ File.readlines(filename).each {|line|
+ line.gsub!(/RbConfig::CONFIG\["(\w+)"\]/) {
+ key = $1
+ unless mkconf
+ require 'rbconfig'
+ mkconf = RbConfig::MAKEFILE_CONFIG.merge('prefix'=>'#{TMP_RUBY_PREFIX}')
+ setup_ruby_prefix = "TMP_RUBY_PREFIX = $:.reverse.find{|e|e!=\".\"}.sub(%r{(.*)/lib/.*}m, \"\\\\1\")\n"
+ teardown_ruby_prefix = 'Object.class_eval { remove_const "TMP_RUBY_PREFIX" }'
+ end
+ if RbConfig::MAKEFILE_CONFIG.has_key? key
+ val = RbConfig.expand("$(#{key})", mkconf)
+ need_ruby_prefix = true if /\A\#\{TMP_RUBY_PREFIX\}/ =~ val
+ c_esc(val)
+ else
+ "nil"
+ end
+ }
+ lines << c_esc(line)
+ }
+ setup_lines = []
+ if need_ruby_prefix
+ setup_lines << c_esc(setup_ruby_prefix)
+ lines << c_esc(teardown_ruby_prefix)
+ end
+ [setup_lines, lines]
+}
+
+require 'erb'
+
+tmp = ERB.new(<<'EOS', nil, '%').result(binding)
+#include "ruby/ruby.h"
+#include "vm_core.h"
+
+% preludes.zip(lines_list).each_with_index {|(prelude, (setup_lines, lines)), i|
+static const char prelude_name<%=i%>[] = <%=c_esc(File.basename(prelude))%>;
+static const char prelude_code<%=i%>[] =
+% (setup_lines+lines).each {|line|
+<%=line%>
+% }
+;
+% }
+
+void
+Init_<%=init_name%>(void)
+{
+% lines_list.each_with_index {|(setup_lines, lines), i|
+ rb_iseq_eval(rb_iseq_compile(
+ rb_str_new(prelude_code<%=i%>, sizeof(prelude_code<%=i%>) - 1),
+ rb_str_new(prelude_name<%=i%>, sizeof(prelude_name<%=i%>) - 1),
+ INT2FIX(<%=1-setup_lines.length%>)));
+
+% }
+#if 0
+% preludes.length.times {|i|
+ puts(prelude_code<%=i%>);
+% }
+#endif
+}
+EOS
+
+open(outfile, 'w'){|f|
+ f << tmp
+}
+
diff --git a/trunk/tool/eval.rb b/trunk/tool/eval.rb
new file mode 100644
index 0000000000..906ba9c23c
--- /dev/null
+++ b/trunk/tool/eval.rb
@@ -0,0 +1,161 @@
+
+require 'rbconfig'
+require 'fileutils'
+require 'pp'
+
+Ruby = ENV['RUBY'] ||
+ File.join(Config::CONFIG["bindir"],
+ Config::CONFIG["ruby_install_name"] + Config::CONFIG["EXEEXT"])
+#
+
+OPTIONS = %w{
+ opt-direct-threaded-code
+ opt-basic-operations
+ opt-operands-unification
+ opt-instructions-unification
+ opt-inline-method-cache
+ opt-stack-caching
+}.map{|opt|
+ '--disable-' + opt
+}
+
+opts = OPTIONS.dup
+Configs = OPTIONS.map{|opt|
+ o = opts.dup
+ opts.delete(opt)
+ o
+} + [[]]
+
+pp Configs if $DEBUG
+
+
+def exec_cmd(cmd)
+ puts cmd
+ unless system(cmd)
+ p cmd
+ raise "error"
+ end
+end
+
+def dirname idx
+ "ev-#{idx}"
+end
+
+def build
+ Configs.each_with_index{|config, idx|
+ dir = dirname(idx)
+ FileUtils.rm_rf(dir) if FileTest.exist?(dir)
+ Dir.mkdir(dir)
+ FileUtils.cd(dir){
+ exec_cmd("#{Ruby} ../extconf.rb " + config.join(" "))
+ exec_cmd("make clean test-all")
+ }
+ }
+end
+
+def check
+ Configs.each_with_index{|c, idx|
+ puts "= #{idx}"
+ system("#{Ruby} -r ev-#{idx}/yarvcore -e 'puts YARVCore::OPTS'")
+ }
+end
+
+def bench_each idx
+ puts "= #{idx}"
+ 5.times{|count|
+ print count
+ FileUtils.cd(dirname(idx)){
+ exec_cmd("make benchmark OPT=-y ITEMS=#{ENV['ITEMS']} > ../b#{idx}-#{count}")
+ }
+ }
+ puts
+end
+
+def bench
+ # return bench_each(6)
+ Configs.each_with_index{|c, idx|
+ bench_each idx
+ }
+end
+
+def parse_result data
+ flag = false
+ stat = []
+ data.each{|line|
+ if flag
+ if /(\w+)\t([\d\.]+)/ =~ line
+ stat << [$1, $2.to_f]
+ else
+ raise "not a data"
+ end
+
+ end
+ if /benchmark summary/ =~ line
+ flag = true
+ end
+ }
+ stat
+end
+
+def calc_each data
+ data.sort!
+ data.pop # remove max
+ data.shift # remove min
+
+ data.inject(0.0){|res, e|
+ res += e
+ } / data.size
+end
+
+def calc_stat stats
+ stat = []
+ stats[0].each_with_index{|e, idx|
+ bm = e[0]
+ vals = stats.map{|st|
+ st[idx][1]
+ }
+ [bm, calc_each(vals)]
+ }
+end
+
+def stat
+ total = []
+ Configs.each_with_index{|c, idx|
+ stats = []
+ 5.times{|count|
+ file = "b#{idx}-#{count}"
+ # p file
+ open(file){|f|
+ stats << parse_result(f.read)
+ }
+ }
+ # merge stats
+ total << calc_stat(stats)
+ total
+ }
+ # pp total
+ total[0].each_with_index{|e, idx|
+ bm = e[0]
+ # print "#{bm}\t"
+ total.each{|st|
+ print st[idx][1], "\t"
+ }
+ puts
+ }
+end
+
+ARGV.each{|cmd|
+ case cmd
+ when 'build'
+ build
+ when 'check'
+ check
+ when 'bench'
+ bench
+ when 'stat'
+ stat
+ else
+ raise
+ end
+}
+
diff --git a/trunk/tool/ifchange b/trunk/tool/ifchange
new file mode 100755
index 0000000000..544513ad15
--- /dev/null
+++ b/trunk/tool/ifchange
@@ -0,0 +1,17 @@
+#!/bin/sh
+# usage: ifchange target temporary
+
+target="$1"
+temp="$2"
+if [ "$temp" = - ]; then
+ temp="tmpdata$$.tmp~"
+ cat > "$temp" || exit $?
+ trap 'rm -f "$temp"' 0
+fi
+if cmp "$target" "$temp" >/dev/null 2>&1; then
+ echo "$target unchanged"
+ rm -f "$temp"
+else
+ echo "$target updated"
+ mv -f "$temp" "$target"
+fi
diff --git a/trunk/tool/insns2vm.rb b/trunk/tool/insns2vm.rb
new file mode 100755
index 0000000000..47aba50c1e
--- /dev/null
+++ b/trunk/tool/insns2vm.rb
@@ -0,0 +1,15 @@
+#!ruby -Kn
+
+require 'optparse'
+
+Version = %w$Revision: 11626 $[1..-1]
+
+require "#{File.join(File.dirname(__FILE__), 'instruction')}"
+
+if $0 == __FILE__
+ opts = ARGV.options
+ maker = RubyVM::SourceCodeGenerator.def_options(opts)
+ files = opts.parse!
+ generator = maker.call
+ generator.generate(files)
+end
diff --git a/trunk/tool/instruction.rb b/trunk/tool/instruction.rb
new file mode 100644
index 0000000000..5e5eb0b78a
--- /dev/null
+++ b/trunk/tool/instruction.rb
@@ -0,0 +1,1385 @@
+#
+#
+#
+
+require 'erb'
+
+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
+ 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' && ((re = /\b#{v}\b/n) =~ @sp_inc ||
+ @defopes.any?{|t, val| re =~ val})
+ ret << " #{t} #{v} = FIX2INT(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"] || '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
+ 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 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
+
+ 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
+ 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
+ comment " /* prepare stack status */"
+
+ push_ba = insn.pushsc
+ raise "unsupport" if push_ba[0].size > 0 && push_ba[1].size > 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
+
+ re = /\b#{var}\b/n
+ if re =~ insn.body or re =~ insn.sp_inc or insn.rets.any?{|t, v| re =~ v}
+ 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 " USAGE_ANALYSIS_INSN(BIN(#{insn.name}));"
+ insn.opes.each_with_index{|op, i|
+ commit " USAGE_ANALYSIS_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_hader_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 make_footer_stack_val insn
+ comment " /* push stack val */"
+
+ insn.rets.reverse_each{|v|
+ if v[1] == '...'
+ break
+ end
+ 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
+ commit "INSN_ENTRY(#{insn.name}){"
+ make_header_prepare_stack insn
+ commit "{"
+ 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_hader_debug insn
+ make_header_pc insn
+ make_header_popn insn
+ make_header_defines insn
+ make_header_analysis insn
+ commit "{"
+ end
+
+ def make_footer insn
+ make_footer_stack_val insn
+ make_footer_default_operands insn
+ make_footer_undefs insn
+ 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
+ insn.body
+ end
+ make_footer(insn)
+ end
+ end
+ end
+
+ ###################################################################
+ # vmtc.inc
+ class VmTCIncGenerator < SourceCodeGenerator
+ def generate
+
+ insns_table = build_string do
+ @insns.each{|insn|
+ commit " LABEL_PTR(#{insn.name}),"
+ }
+ end
+
+ insn_end_table = build_string do
+ @insns.each{|insn|
+ commit " ELABEL_PTR(#{insn.name}),\n"
+ }
+ end
+
+ 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"
+ 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_DINDEX' => 'D',
+ 'TS_VALUE' => 'V',
+ 'TS_ID' => 'I',
+ 'TS_GENTRY' => 'G',
+ 'TS_IC' => 'C',
+ 'TS_CDHASH' => 'H',
+ 'TS_ISEQ' => 'S',
+ 'TS_VARIABLE' => '.',
+ 'TS_FUNCPTR' => 'F',
+ }
+
+ # 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
+ i=0
+ insns = build_string do
+ @insns.each{|insn|
+ commit " %-30s = %d,\n" % ["BIN(#{insn.name})", i]
+ i+=1
+ }
+ end
+
+ ERB.new(vpath.read('template/insns.inc.tmpl')).result(binding)
+ end
+ end
+
+ ###################################################################
+ # minsns.inc
+ class MInsnsIncGenerator < SourceCodeGenerator
+ def generate
+ i=0
+ defs = build_string do
+ @insns.each{|insn|
+ commit " rb_define_const(mYarvInsns, %-30s, INT2FIX(%d));\n" %
+ ["\"I#{insn.name}\"", i]
+ i+=1
+ }
+ end
+ 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/, /^rb_insn_func_t/
+ 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
+ }
+
+ rule = build_string do
+ opt_insns_map.each{|originsn, optinsns|
+ commit "case BIN(#{originsn.name}):"
+
+ optinsns.sort_by{|opti|
+ opti.defopes.find_all{|e| e[1] == '*'}.size
+ }.each{|opti|
+ commit " if("
+ i = 0
+ commit " " + opti.defopes.map{|opinfo|
+ i += 1
+ next if opinfo[1] == '*'
+ "insnobj->operands[#{i-1}] == #{val_as_type(opinfo)}"
+ }.compact.join('&& ')
+ commit " ){"
+ idx = 0
+ n = 0
+ opti.defopes.each{|opinfo|
+ if opinfo[1] == '*'
+ if idx != n
+ commit " insnobj->operands[#{idx}] = insnobj->operands[#{n}];"
+ end
+ idx += 1
+ else
+ # skip
+ end
+ n += 1
+ }
+ commit " insnobj->insn_id = BIN(#{opti.name});"
+ commit " insnobj->operand_size = #{idx};"
+ commit " break;\n }\n"
+ }
+ commit " break;";
+ }
+ end
+
+ 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 const 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 const int *const 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 const int *const *const 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(output_path(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("-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.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/trunk/tool/make-snapshot b/trunk/tool/make-snapshot
new file mode 100755
index 0000000000..65f5bd024d
--- /dev/null
+++ b/trunk/tool/make-snapshot
@@ -0,0 +1,183 @@
+#!/usr/bin/ruby -s
+require 'uri'
+require 'digest/md5'
+require 'digest/sha2'
+require 'fileutils'
+require 'tmpdir'
+STDOUT.sync = true
+
+ENV["LC_ALL"] = ENV["LANG"] = "C"
+SVNURL = URI.parse("http://svn.ruby-lang.org/repos/ruby/")
+RUBY_VERSION_PATTERN = /^\#define\s+RUBY_VERSION\s+"([\d.]+)"/
+
+ENV["VPATH"] ||= "include/ruby"
+YACC = ENV["YACC"] ||= "bison"
+ENV["BASERUBY"] ||= "ruby"
+ENV["RUBY"] ||= "ruby"
+ENV["MV"] ||= "mv"
+ENV["MINIRUBY"] ||= "ruby"
+
+$patch_file &&= File.expand_path($patch_file)
+path = ENV["PATH"].split(File::PATH_SEPARATOR)
+%w[YACC BASERUBY RUBY MV MINIRUBY].each do |var|
+ cmd = ENV[var]
+ unless path.any? {|dir|
+ file = File.join(dir, cmd)
+ File.file?(file) and File.executable?(file)
+ }
+ abort "#{File.basename $0}: #{var} command not found - #{cmd}"
+ end
+end
+
+unless destdir = ARGV.shift
+ abort "usage: #{File.basename $0} new-directory-to-save [version ...]"
+end
+revisions = ARGV.empty? ? ["trunk"] : ARGV
+unless tmp = $exported
+ FileUtils.mkpath(destdir)
+ destdir = File.expand_path(destdir)
+ tmp = Dir.mktmpdir("ruby-snapshot")
+ FileUtils.mkpath(tmp)
+ at_exit {
+ Dir.chdir "/"
+ FileUtils.rm_rf(tmp)
+ } unless $keep_temp
+end
+Dir.chdir tmp
+
+def package(rev, destdir)
+ patchlevel = false
+ case rev
+ when /\Atrunk\z/, /\Abranches\//, /\Atags\//
+ url = SVNURL + rev
+ when /\Astable\z/
+ url = SVNURL + "branches/"
+ url = url + `svn ls #{url}`[/.*^(ruby_\d+_\d+)\//m, 1]
+ when /\A\(.*\..*\..*\)-/
+ patchlevel = true
+ url = SVNURL + "tags/v#{rev.sub(/-p?/, '_').tr('.', '_')}"
+ when /\./
+ url = SVNURL + "branches/ruby_#{rev.tr('.', '_')}"
+ else
+ warn "#{$0}: unknown version - #{rev}"
+ return
+ end
+ revision = `svn info #{url} 2>&1`[/Last Changed Rev: (\d+)/, 1]
+ version = nil
+ unless revision
+ url = SVNURL + "trunk"
+ version = `svn cat #{url + "version.h"}`[RUBY_VERSION_PATTERN, 1]
+ unless rev == version
+ warn "#{$0}: #{rev} not found"
+ return
+ end
+ revision = `svn info #{url}`[/Last Changed Rev: (\d+)/, 1]
+ end
+ unless $exported
+ puts "Exporting #{rev}@#{revision}"
+ IO.popen("svn export #{url} ruby") do |pipe|
+ pipe.each {|line| /^A/ =~ line or print line}
+ end
+ unless $?.success?
+ warn("Export failed")
+ return
+ end
+ end
+
+ if !File.directory?(v = "ruby")
+ v = Dir.glob("ruby-*").select(&File.method(:directory?))
+ v.size == 1 or abort "not exported"
+ v = v[0]
+ end
+ open("#{v}/revision.h", "wb") {|f| f.puts "#define RUBY_REVISION #{revision}"}
+ version ||= (versionhdr = IO.read("#{v}/version.h"))[RUBY_VERSION_PATTERN, 1]
+ version or return
+ if patchlevel
+ versionhdr ||= IO.read("#{v}/version.h")
+ patchlevel = versionhdr[/^\#define\s+RUBY_PATCHLEVEL\s+(\d+)/, 1]
+ tag = (patchlevel ? "p#{patchlevel}" : "r#{revision}")
+ else
+ tag = "r#{revision}"
+ end
+ v = "ruby-#{version}-#{tag}"
+ File.directory?(v) or File.rename "ruby", v
+ system("patch -d #{v} -p0 -i #{$patch_file}") if $patch_file
+ def (clean = []).add(n) push(n); n end
+ Dir.chdir(v) do
+ File.open(clean.add("cross.rb"), "w") {|f| f.puts "CROSS_COMPILING=true"}
+ unless File.exist?("configure")
+ print "creating configure..."
+ unless system("autoconf")
+ puts " failed"
+ return
+ end
+ puts " done"
+ end
+ clean.add("autom4te.cache")
+ print "creating prerequisites..."
+ if File.file?("common.mk") && /^prereq/ =~ commonmk = IO.read("common.mk")
+ puts
+ extout = clean.add('tmp')
+ File.open(clean.add("config.status"), "w") {|f|
+ f.puts "s,@configure_args@,|#_!!_#|,g"
+ f.puts "s,@EXTOUT@,|#_!!_#|#{extout},g"
+ f.puts "s,@bindir@,|#_!!_#|,g"
+ f.puts "s,@ruby_install_name@,|#_!!_#|,g"
+ f.puts "s,@ARCH_FLAG@,|#_!!_#|,g"
+ f.puts "s,@CFLAGS@,|#_!!_#|,g"
+ f.puts "s,@CPPFLAGS@,|#_!!_#|,g"
+ f.puts "s,@LDFLAGS@,|#_!!_#|,g"
+ f.puts "s,@DLDFLAGS@,|#_!!_#|,g"
+ f.puts "s,@LIBEXT@,|#_!!_#|a,g"
+ f.puts "s,@OBJEXT@,|#_!!_#|o,g"
+ f.puts "s,@LIBRUBY@,|#_!!_#|liburyb.a,g"
+ f.puts "s,@LIBRUBY_A@,|#_!!_#|liburyb.a,g"
+ }
+ FileUtils.mkpath(hdrdir = "#{extout}/include/ruby")
+ File.open("#{hdrdir}/config.h", "w") {}
+ miniruby = ENV['MINIRUBY'] + " -rcross"
+ IO.popen("make -f - prereq srcdir=. IFCHANGE=tool/ifchange 'MINIRUBY=#{miniruby}'", "w") do |f|
+ f.puts(IO.read("Makefile.in")[/^lex\.c.*?^$/m])
+ f.puts(commonmk.gsub(/\{[^{}]*\}/, ""))
+ end
+ clean.push("rbconfig.rb", ".rbconfig.time")
+ print "prerequisites"
+ else
+ system("#{YACC} -o parse.c parse.y")
+ end
+ FileUtils.rm_rf(clean)
+ unless $?.success?
+ puts " failed"
+ return
+ end
+ puts " done"
+ end
+
+ return [["bzip tarball", ".tar.bz2", %w"tar cjf"],
+ ["gzip tarball", ".tar.gz", %w"tar czf"],
+ ["zip archive", ".zip", %w"zip -qr"]
+ ].collect do |mesg, ext, cmd|
+ file = "#{destdir}/#{v}#{ext}"
+ print "creating #{mesg}... #{file}"
+ if system(*(cmd + [file, v]))
+ puts " done"
+ file
+ else
+ puts " failed"
+ nil
+ end
+ end.compact
+ensure
+ FileUtils.rm_rf(v) if v and !$exported and !$keep_temp
+end
+
+revisions.collect {|rev| package(rev, destdir)}.flatten.each do |name|
+ name or next
+ str = open(name, "rb") {|f| f.read}
+ md5 = Digest::MD5.hexdigest str
+ sha = Digest::SHA256.hexdigest str
+ puts "MD5(#{name})= #{md5}"
+ puts "SHA256(#{name})= #{sha}"
+ puts "SIZE(name)= #{str.size}"
+ puts
+end
diff --git a/trunk/tool/node_name.rb b/trunk/tool/node_name.rb
new file mode 100755
index 0000000000..5d39e9f5cc
--- /dev/null
+++ b/trunk/tool/node_name.rb
@@ -0,0 +1,4 @@
+#! ./miniruby -n
+if ~/enum node_type \{/..~/^\};/
+ ~/(NODE_.+),/ and puts(" case #{$1}:\n\treturn \"#{$1}\";")
+end
diff --git a/trunk/tool/parse.rb b/trunk/tool/parse.rb
new file mode 100644
index 0000000000..6243d7aa8e
--- /dev/null
+++ b/trunk/tool/parse.rb
@@ -0,0 +1,13 @@
+$file = ARGV[0]
+$str = ARGF.read.sub(/^__END__.*\z/m, '')
+puts '# ' + '-' * 70
+puts "# target program: "
+puts '# ' + '-' * 70
+puts $str
+puts '# ' + '-' * 70
+
+$parsed = RubyVM::InstructionSequence.compile_file($file)
+puts "# disasm result: "
+puts '# ' + '-' * 70
+puts $parsed.disasm
+puts '# ' + '-' * 70
diff --git a/trunk/tool/transcode-tblgen.rb b/trunk/tool/transcode-tblgen.rb
new file mode 100644
index 0000000000..f79fc551ec
--- /dev/null
+++ b/trunk/tool/transcode-tblgen.rb
@@ -0,0 +1,637 @@
+require 'optparse'
+require 'erb'
+require 'fileutils'
+
+C_ESC = {
+ "\\" => "\\\\",
+ '"' => '\"',
+ "\n" => '\n',
+}
+
+0x00.upto(0x1f) {|ch| C_ESC[[ch].pack("C")] ||= "\\%03o" % ch }
+0x7f.upto(0xff) {|ch| C_ESC[[ch].pack("C")] = "\\%03o" % ch }
+C_ESC_PAT = Regexp.union(*C_ESC.keys)
+
+def c_esc(str)
+ '"' + str.gsub(C_ESC_PAT) { C_ESC[$&] } + '"'
+end
+
+class StrSet
+ def self.parse(pattern)
+ if /\A\s*(([0-9a-f][0-9a-f]|\{([0-9a-f][0-9a-f]|[0-9a-f][0-9a-f]-[0-9a-f][0-9a-f])(,([0-9a-f][0-9a-f]|[0-9a-f][0-9a-f]-[0-9a-f][0-9a-f]))*\})+(\s+|\z))*\z/i !~ pattern
+ raise ArgumentError, "invalid pattern: #{pattern.inspect}"
+ end
+ result = []
+ pattern.scan(/\S+/) {|seq|
+ seq_result = []
+ while !seq.empty?
+ if /\A([0-9a-f][0-9a-f])/i =~ seq
+ byte = $1.to_i(16)
+ seq_result << [byte..byte]
+ seq = $'
+ elsif /\A\{([^\}]+)\}/ =~ seq
+ set = $1
+ seq = $'
+ set_result = []
+ set.scan(/[^,]+/) {|range|
+ if /\A([0-9a-f][0-9a-f])-([0-9a-f][0-9a-f])\z/ =~ range
+ b = $1.to_i(16)
+ e = $2.to_i(16)
+ set_result << (b..e)
+ elsif /\A([0-9a-f][0-9a-f])\z/ =~ range
+ byte = $1.to_i(16)
+ set_result << (byte..byte)
+ else
+ raise "invalid range: #{range.inspect}"
+ end
+ }
+ seq_result << set_result
+ else
+ raise "invalid sequence: #{seq.inspect}"
+ end
+ end
+ result << seq_result
+ }
+ self.new(result)
+ end
+
+ def initialize(pat)
+ @pat = pat
+ end
+
+ def hash
+ @pat.hash
+ end
+
+ def eql?(other)
+ self.class == other.class &&
+ @pat == other.instance_eval { @pat }
+ end
+
+ alias == eql?
+
+ def to_s
+ if @pat.empty?
+ "(empset)"
+ else
+ @pat.map {|seq|
+ if seq.empty?
+ "(empstr)"
+ else
+ seq.map {|byteset|
+ if byteset.length == 1 && byteset[0].begin == byteset[0].end
+ "%02x" % byteset[0].begin
+ else
+ "{" +
+ byteset.map {|range|
+ if range.begin == range.end
+ "%02x" % range.begin
+ else
+ "%02x-%02x" % [range.begin, range.end]
+ end
+ }.join(',') +
+ "}"
+ end
+ }.join('')
+ end
+ }.join(' ')
+ end
+ end
+
+ def inspect
+ "\#<#{self.class}: #{self.to_s}>"
+ end
+
+ def min_length
+ if @pat.empty?
+ nil
+ else
+ @pat.map {|seq| seq.length }.min
+ end
+ end
+
+ def max_length
+ if @pat.empty?
+ nil
+ else
+ @pat.map {|seq| seq.length }.max
+ end
+ end
+
+ def emptyable?
+ @pat.any? {|seq|
+ seq.empty?
+ }
+ end
+
+ def first_bytes
+ result = {}
+ @pat.each {|seq|
+ next if seq.empty?
+ seq.first.each {|range|
+ range.each {|byte|
+ result[byte] = true
+ }
+ }
+ }
+ result.keys.sort
+ end
+
+ def each_firstbyte
+ h = {}
+ @pat.each {|seq|
+ next if seq.empty?
+ seq.first.each {|range|
+ range.each {|byte|
+ (h[byte] ||= []) << seq[1..-1]
+ }
+ }
+ }
+ h.keys.sort.each {|byte|
+ yield byte, StrSet.new(h[byte])
+ }
+ end
+end
+
+class ActionMap
+ def self.parse(hash)
+ h = {}
+ hash.each {|pat, action|
+ h[StrSet.parse(pat)] = action
+ }
+ self.new(h)
+ end
+
+ def initialize(h)
+ @map = h
+ end
+
+ def hash
+ hash = 0
+ @map.each {|k,v|
+ hash ^= k.hash ^ v.hash
+ }
+ hash
+ end
+
+ def eql?(other)
+ self.class == other.class &&
+ @map == other.instance_eval { @map }
+ end
+
+ alias == eql?
+
+ def inspect
+ "\#<#{self.class}:" +
+ @map.map {|k, v| " [" + k.to_s + "]=>" + v.inspect }.join('') +
+ ">"
+ end
+
+ def max_input_length
+ @map.keys.map {|k| k.max_length }.max
+ end
+
+ def empty_action
+ @map.each {|ss, action|
+ return action if ss.emptyable?
+ }
+ nil
+ end
+
+ def each_firstbyte(valid_encoding=nil)
+ h = {}
+ @map.each {|ss, action|
+ if ss.emptyable?
+ raise "emptyable pattern"
+ else
+ ss.each_firstbyte {|byte, rest|
+ h[byte] ||= {}
+ if h[byte][rest]
+ raise "ambiguous"
+ end
+ h[byte][rest] = action
+ }
+ end
+ }
+ if valid_encoding
+ valid_encoding.each_firstbyte {|byte, rest|
+ if h[byte]
+ am = ActionMap.new(h[byte])
+ yield byte, am, rest
+ else
+ am = ActionMap.new(rest => :undef)
+ yield byte, am, nil
+ end
+ }
+ else
+ h.keys.sort.each {|byte|
+ am = ActionMap.new(h[byte])
+ yield byte, am, nil
+ }
+ end
+ end
+
+ OffsetsMemo = {}
+ InfosMemo = {}
+
+ def format_offsets(min, max, offsets)
+ offsets = offsets[min..max]
+ code = "{ %d, %d,\n" % [min, max]
+ 0.step(offsets.length-1,16) {|i|
+ code << " "
+ code << offsets[i,8].map {|off| "%3d," % off.to_s }.join('')
+ if i+8 < offsets.length
+ code << " "
+ code << offsets[i+8,8].map {|off| "%3d," % off.to_s }.join('')
+ end
+ code << "\n"
+ }
+ code << '}'
+ code
+ end
+
+ def generate_info(info)
+ case info
+ when :nomap
+ "NOMAP"
+ when :undef
+ "UNDEF"
+ when :invalid
+ "INVALID"
+ when :func_ii
+ "FUNii"
+ when :func_si
+ "FUNsi"
+ when :func_io
+ "FUNio"
+ when :func_so
+ "FUNso"
+ when /\A([0-9a-f][0-9a-f])\z/i
+ "o1(0x#$1)"
+ when /\A([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])\z/i
+ "o2(0x#$1,0x#$2)"
+ when /\A([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])\z/i
+ "o3(0x#$1,0x#$2,0x#$3)"
+ when /\A([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])\z/i
+ "o4(0x#$1,0x#$2,0x#$3,0x#$4)"
+ when /\A&/ # pointer to BYTE_LOOKUP structure
+ info.to_s
+ else
+ raise "unexpected action: #{info.inspect}"
+ end
+ end
+
+ def format_infos(infos)
+ infos = infos.map {|info| generate_info(info) }
+ maxlen = infos.map {|info| info.length }.max
+ columns = maxlen <= 16 ? 4 : 2
+ code = "{\n"
+ 0.step(infos.length-1, columns) {|i|
+ code << " "
+ is = infos[i,columns]
+ is.each {|info|
+ code << sprintf(" %#{maxlen}s,", info)
+ }
+ code << "\n"
+ }
+ code << "}"
+ code
+ end
+
+ def generate_lookup_node(name, table)
+ offsets = []
+ infos = []
+ infomap = {}
+ min = max = nil
+ table.each_with_index {|action, byte|
+ action ||= :invalid
+ if action != :invalid
+ min = byte if !min
+ max = byte
+ end
+ unless o = infomap[action]
+ infomap[action] = o = infos.length
+ infos[o] = action
+ end
+ offsets[byte] = o
+ }
+ if !min
+ min = max = 0
+ end
+
+ offsets_key = [min, max, offsets[min..max]]
+ if n = OffsetsMemo[offsets_key]
+ offsets_name = n
+ offsets_code = ''
+ else
+ offsets_name = "#{name}_offsets"
+ offsets_code = <<"End"
+static const unsigned char
+#{offsets_name}[#{2+max-min+1}] = #{format_offsets(min,max,offsets)};
+End
+ OffsetsMemo[offsets_key] = offsets_name
+ end
+
+ if n = InfosMemo[infos]
+ infos_name = n
+ infos_code = ''
+ else
+ infos_name = "#{name}_infos"
+ infos_code = <<"End"
+static const struct byte_lookup* const
+#{infos_name}[#{infos.length}] = #{format_infos(infos)};
+End
+ InfosMemo[infos] = infos_name
+ end
+
+ r = offsets_code + infos_code + <<"End"
+static const BYTE_LOOKUP
+#{name} = {
+ #{offsets_name},
+ #{infos_name}
+};
+
+End
+ r
+ end
+
+ PreMemo = {}
+ PostMemo = {}
+ NextName = "a"
+
+ def generate_node(code, name_hint=nil, valid_encoding=nil)
+ if n = PreMemo[[self,valid_encoding]]
+ return n
+ end
+
+ table = Array.new(0x100, :invalid)
+ each_firstbyte(valid_encoding) {|byte, rest, rest_valid_encoding|
+ if a = rest.empty_action
+ table[byte] = a
+ else
+ name_hint2 = nil
+ name_hint2 = "#{name_hint}_#{'%02X' % byte}" if name_hint
+ table[byte] = "&" + rest.generate_node(code, name_hint2, rest_valid_encoding)
+ end
+ }
+
+ if n = PostMemo[table]
+ return n
+ end
+
+ if !name_hint
+ name_hint = "fun_" + NextName.dup
+ NextName.succ!
+ end
+
+ PreMemo[[self,valid_encoding]] = PostMemo[table] = name_hint
+
+ code << generate_lookup_node(name_hint, table)
+ name_hint
+ end
+end
+
+def encode_utf8(map)
+ r = []
+ map.each {|k, v|
+ # integer means UTF-8 encoded sequence.
+ k = [k].pack("U").unpack("H*")[0].upcase if Integer === k
+ v = [v].pack("U").unpack("H*")[0].upcase if Integer === v
+ r << [k,v]
+ }
+ r
+end
+
+def transcode_compile_tree(name, from, map)
+ map = encode_utf8(map)
+ h = {}
+ map.each {|k, v|
+ h[k] = v
+ }
+ am = ActionMap.parse(h)
+
+ max_input = am.max_input_length
+
+ if ValidEncoding[from]
+ valid_encoding = StrSet.parse(ValidEncoding[from])
+ else
+ valid_encoding = nil
+ end
+
+ code = ''
+ defined_name = am.generate_node(code, name, valid_encoding)
+ return defined_name, code, max_input
+end
+
+TRANSCODERS = []
+
+def transcode_tblgen(from, to, map)
+ STDERR.puts "converter from #{from} to #{to}" if VERBOSE_MODE
+ id_from = from.tr('^0-9A-Za-z', '_')
+ id_to = to.tr('^0-9A-Za-z', '_')
+ if from == "UTF-8"
+ tree_name = "to_#{id_to}"
+ elsif to == "UTF-8"
+ tree_name = "from_#{id_from}"
+ else
+ tree_name = "from_#{id_from}_to_#{id_to}"
+ end
+ map = encode_utf8(map)
+ real_tree_name, tree_code, max_input = transcode_compile_tree(tree_name, from, map)
+ transcoder_name = "rb_#{tree_name}"
+ TRANSCODERS << transcoder_name
+ input_unit_length = UnitLength[from]
+ max_output = map.map {|k,v| String === v ? v.length/2 : 1 }.max
+ transcoder_code = <<"End"
+static const rb_transcoder
+#{transcoder_name} = {
+ #{c_esc from}, #{c_esc to}, &#{real_tree_name},
+ #{input_unit_length}, /* input_unit_length */
+ #{max_input}, /* max_input */
+ #{max_output}, /* max_output */
+ stateless_converter, /* stateful_type */
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL
+};
+End
+ tree_code + "\n" + transcoder_code
+end
+
+def transcode_generate_node(am, name_hint=nil)
+ STDERR.puts "converter for #{name_hint}" if VERBOSE_MODE
+ code = ''
+ am.generate_node(code, name_hint)
+ code
+end
+
+def transcode_register_code
+ code = ''
+ TRANSCODERS.each {|transcoder_name|
+ code << " rb_register_transcoder(&#{transcoder_name});\n"
+ }
+ code
+end
+
+UnitLength = {
+ 'UTF-16BE' => 2,
+ 'UTF-16LE' => 2,
+ 'UTF-32BE' => 4,
+ 'UTF-32LE' => 4,
+}
+UnitLength.default = 1
+
+ValidEncoding = {
+ '1byte' => '{00-ff}',
+ '2byte' => '{00-ff}{00-ff}',
+ '4byte' => '{00-ff}{00-ff}{00-ff}{00-ff}',
+ 'US-ASCII' => '{00-7f}',
+ 'UTF-8' => '{00-7f}
+ {c2-df}{80-bf}
+ e0{a0-bf}{80-bf}
+ {e1-ec}{80-bf}{80-bf}
+ ed{80-9f}{80-bf}
+ {ee-ef}{80-bf}{80-bf}
+ f0{90-bf}{80-bf}{80-bf}
+ {f1-f3}{80-bf}{80-bf}{80-bf}
+ f4{80-8f}{80-bf}{80-bf}',
+ 'UTF-16BE' => '{00-d7,e0-ff}{00-ff}
+ {d8-db}{00-ff}{dc-df}{00-ff}',
+ 'UTF-16LE' => '{00-ff}{00-d7,e0-ff}
+ {00-ff}{d8-db}{00-ff}{dc-df}',
+ 'UTF-32BE' => '0000{00-d7,e0-ff}{00-ff}
+ 00{01-10}{00-ff}{00-ff}',
+ 'UTF-32LE' => '{00-ff}{00-d7,e0-ff}0000
+ {00-ff}{00-ff}{01-10}00',
+ 'EUC-JP' => '{00-7f}
+ {a1-fe}{a1-fe}
+ 8e{a1-fe}
+ 8f{a1-fe}{a1-fe}',
+ 'CP51932' => '{00-7f}
+ {a1-fe}{a1-fe}
+ 8e{a1-fe}',
+ 'Shift_JIS' => '{00-7f}
+ {81-9f,e0-fc}{40-7e,80-fc}
+ {a1-df}',
+ 'EUC-KR' => '{00-7f}
+ {a1-fe}{a1-fe}',
+ 'CP949' => '{00-7f}
+ {81-fe}{41-5a,61-7a,81-fe}',
+ 'Big5' => '{00-7f}
+ {81-fe}{40-7e,a1-fe}',
+ 'EUC-TW' => '{00-7f}
+ {a1-fe}{a1-fe}
+ 8e{a1-b0}{a1-fe}{a1-fe}',
+ 'GBK' => '{00-80}
+ {81-fe}{40-7e,80-fe}',
+ 'GB18030' => '{00-7f}
+ {81-fe}{40-7e,80-fe}
+ {81-fe}{30-39}{81-fe}{30-39}',
+}
+
+{
+ 'ASCII-8BIT' => '1byte',
+ 'ISO-8859-1' => '1byte',
+ 'ISO-8859-2' => '1byte',
+ 'ISO-8859-3' => '1byte',
+ 'ISO-8859-4' => '1byte',
+ 'ISO-8859-5' => '1byte',
+ 'ISO-8859-6' => '1byte',
+ 'ISO-8859-7' => '1byte',
+ 'ISO-8859-8' => '1byte',
+ 'ISO-8859-9' => '1byte',
+ 'ISO-8859-10' => '1byte',
+ 'ISO-8859-11' => '1byte',
+ 'ISO-8859-13' => '1byte',
+ 'ISO-8859-14' => '1byte',
+ 'ISO-8859-15' => '1byte',
+ 'Windows-31J' => 'Shift_JIS',
+}.each {|k, v|
+ ValidEncoding[k] = ValidEncoding.fetch(v)
+}
+
+def make_signature(filename, src)
+ "src=#{filename.dump}, len=#{src.length}, checksum=#{src.sum}"
+end
+
+output_filename = nil
+verbose_mode = false
+force_mode = false
+
+op = OptionParser.new
+op.def_option("--help", "show help message") { puts op; exit 0 }
+op.def_option("--verbose", "verbose mode") { verbose_mode = true }
+op.def_option("--force", "force table generation") { force_mode = true }
+op.def_option("--output=FILE", "specify output file") {|arg| output_filename = arg }
+op.parse!
+
+VERBOSE_MODE = verbose_mode
+
+arg = ARGV.shift
+dir = File.dirname(arg)
+$:.unshift dir unless $:.include? dir
+src = File.read(arg)
+src.force_encoding("ascii-8bit") if src.respond_to? :force_encoding
+this_script = File.read(__FILE__)
+this_script.force_encoding("ascii-8bit") if this_script.respond_to? :force_encoding
+
+base_signature = "/* autogenerated. */\n"
+base_signature << "/* #{make_signature(File.basename(__FILE__), this_script)} */\n"
+base_signature << "/* #{make_signature(File.basename(arg), src)} */\n"
+
+if !force_mode && output_filename && File.readable?(output_filename)
+ old_signature = File.open(output_filename) {|f| f.gets("").chomp }
+ chk_signature = base_signature.dup
+ old_signature.each_line {|line|
+ if %r{/\* src="([0-9a-z_.-]+)",} =~ line
+ name = $1
+ next if name == File.basename(arg) || name == File.basename(__FILE__)
+ path = File.join(dir, name)
+ if File.readable? path
+ chk_signature << "/* #{make_signature(name, File.read(path))} */\n"
+ end
+ end
+ }
+ if old_signature == chk_signature
+ now = Time.now
+ File.utime(now, now, output_filename)
+ STDERR.puts "already up-to-date: #{output_filename}" if VERBOSE_MODE
+ exit
+ end
+end
+
+if VERBOSE_MODE
+ if output_filename
+ STDERR.puts "generating #{output_filename} ..."
+ end
+end
+
+libs1 = $".dup
+erb_result = ERB.new(src, nil, '%').result(binding)
+libs2 = $".dup
+
+libs = libs2 - libs1
+lib_sigs = ''
+libs.each {|lib|
+ lib = File.basename(lib)
+ path = File.join(dir, lib)
+ if File.readable? path
+ lib_sigs << "/* #{make_signature(lib, File.read(path))} */\n"
+ end
+}
+
+result = ''
+result << base_signature
+result << lib_sigs
+result << "\n"
+result << erb_result
+result << "\n"
+
+if output_filename
+ new_filename = output_filename + ".new"
+ FileUtils.mkdir_p(File.dirname(output_filename))
+ File.open(new_filename, "wb") {|f| f << result }
+ File.rename(new_filename, output_filename)
+ STDERR.puts "done." if VERBOSE_MODE
+else
+ print result
+end
diff --git a/trunk/tool/vtlh.rb b/trunk/tool/vtlh.rb
new file mode 100644
index 0000000000..fcd3630821
--- /dev/null
+++ b/trunk/tool/vtlh.rb
@@ -0,0 +1,15 @@
+# ARGF = open('ha')
+cd = `pwd`.chomp + '/'
+ARGF.each{|line|
+ if /^0x([a-z0-9]+),/ =~ line
+ stat = line.split(',')
+ addr = stat[0].hex + 0x00400000
+ retired = stat[2].to_i
+ ticks = stat[3].to_i
+
+ src = `addr2line -e miniruby.exe #{addr.to_s(16)}`.chomp
+ src.sub!(cd, '')
+ puts '%-40s 0x%08x %8d %8d' % [src, addr, retired, ticks]
+ end
+}
+
diff --git a/trunk/tool/ytab.sed b/trunk/tool/ytab.sed
new file mode 100755
index 0000000000..17a57fe494
--- /dev/null
+++ b/trunk/tool/ytab.sed
@@ -0,0 +1,30 @@
+#!/bin/sed -f
+/^int yydebug;/{
+i\
+#ifndef yydebug
+a\
+#endif
+}
+/^yydestruct.*yymsg/,/#endif/{
+ /^yydestruct/{
+ /parser/!{
+ h
+ s/^/ruby_parser_&/
+ s/)$/, parser)/
+ /\*/s/parser)$/struct parser_params *&/
+ }
+ }
+ /^#endif/{
+ x
+ /^./{
+ i\
+ struct parser_params *parser;
+ a\
+#define yydestruct(m, t, v) ruby_parser_yydestruct(m, t, v, parser)
+ }
+ x
+ }
+}
+s/^\([ ]*\)\(yyerror[ ]*([ ]*parser,\)/\1parser_\2/
+s!^ *extern char \*getenv();!/* & */!
+s/^\(#.*\)".*\.tab\.c"/\1"parse.c"/