1#!./miniruby
2# -*- coding: us-ascii -*-
3#
4#
5
6require 'erb'
7$:.unshift(File.dirname(__FILE__))
8require 'vpath'
9
10class RubyVM
11  class Instruction
12    def initialize name, opes, pops, rets, comm, body, tvars, sp_inc,
13                   orig = self, defopes = [], type = nil,
14                   nsc = [], psc = [[], []]
15
16      @name = name
17      @opes = opes # [[type, name], ...]
18      @pops = pops # [[type, name], ...]
19      @rets = rets # [[type, name], ...]
20      @comm = comm # {:c => category, :e => en desc, :j => ja desc}
21      @body = body # '...'
22
23      @orig    = orig
24      @defopes = defopes
25      @type    = type
26      @tvars   = tvars
27
28      @nextsc = nsc
29      @pushsc = psc
30      @sc     = []
31      @unifs  = []
32      @optimized = []
33      @is_sc  = false
34      @sp_inc = sp_inc
35    end
36
37    def add_sc sci
38      @sc << sci
39      sci.set_sc
40    end
41
42    attr_reader :name, :opes, :pops, :rets
43    attr_reader :body, :comm
44    attr_reader :nextsc, :pushsc
45    attr_reader :orig, :defopes, :type
46    attr_reader :sc
47    attr_reader :unifs, :optimized
48    attr_reader :is_sc
49    attr_reader :tvars
50    attr_reader :sp_inc
51
52    def set_sc
53      @is_sc = true
54    end
55
56    def add_unif insns
57      @unifs << insns
58    end
59
60    def add_optimized insn
61      @optimized << insn
62    end
63
64    def sp_increase_c_expr
65      if(pops.any?{|t, v| v == '...'} ||
66         rets.any?{|t, v| v == '...'})
67        # user definision
68        raise "no sp increase definition" if @sp_inc.nil?
69        ret = "int inc = 0;\n"
70
71        @opes.each_with_index{|(t, v), i|
72          if (t == 'rb_num_t' && ((re = /\b#{v}\b/n) =~ @sp_inc)) ||
73             (@defopes.any?{|t, val| re =~ val})
74            ret << "        int #{v} = FIX2INT(opes[#{i}]);\n"
75          elsif (t == 'CALL_INFO' && ((re = /\b#{v}\b/n) =~ @sp_inc))
76            ret << "        CALL_INFO #{v} = (CALL_INFO)(opes[#{i}]);\n"
77          end
78        }
79
80        @defopes.each_with_index{|((t, var), val), i|
81          if t == 'rb_num_t' && val != '*' && /\b#{var}\b/ =~ @sp_inc
82            ret << "        #{t} #{var} = #{val};\n"
83          end
84        }
85
86        ret << "        #{@sp_inc};\n"
87        ret << "        return depth + inc;"
88        ret
89      else
90        "return depth + #{rets.size - pops.size};"
91      end
92    end
93
94    def inspect
95      "#<Instruction:#{@name}>"
96    end
97  end
98
99  class InstructionsLoader
100    def initialize opts = {}
101      @insns    = []
102      @insn_map = {}
103
104      @vpath = opts[:VPATH] || File
105      @use_const = opts[:use_const]
106      @verbose   = opts[:verbose]
107      @destdir   = opts[:destdir]
108
109      (@vm_opts = load_vm_opts).each {|k, v|
110        @vm_opts[k] = opts[k] if opts.key?(k)
111      }
112
113      load_insns_def opts[:"insns.def"] || 'insns.def'
114
115      load_opt_operand_def opts[:"opope.def"] || 'defs/opt_operand.def'
116      load_insn_unification_def opts[:"unif.def"] || 'defs/opt_insn_unif.def'
117      make_stackcaching_insns if vm_opt?('STACK_CACHING')
118    end
119
120    attr_reader :vpath
121    attr_reader :destdir
122
123    %w[use_const verbose].each do |attr|
124      attr_reader attr
125      alias_method "#{attr}?", attr
126      remove_method attr
127    end
128
129    def [](s)
130      @insn_map[s.to_s]
131    end
132
133    def each
134      @insns.each{|insn|
135        yield insn
136      }
137    end
138
139    def size
140      @insns.size
141    end
142
143    ###
144    private
145
146    def vm_opt? name
147      @vm_opts[name]
148    end
149
150    def load_vm_opts file = nil
151      file ||= 'vm_opts.h'
152      opts = {}
153      vpath.open(file) do |f|
154        f.grep(/^\#define\s+OPT_([A-Z_]+)\s+(\d+)/) do
155          opts[$1] = !$2.to_i.zero?
156        end
157      end
158      opts
159    end
160
161    SKIP_COMMENT_PATTERN = Regexp.compile(Regexp.escape('/** ##skip'))
162
163    include Enumerable
164
165    def add_insn insn
166      @insns << insn
167      @insn_map[insn.name] = insn
168    end
169
170    def make_insn name, opes, pops, rets, comm, body, sp_inc
171      add_insn Instruction.new(name, opes, pops, rets, comm, body, [], sp_inc)
172    end
173
174    # str -> [[type, var], ...]
175    def parse_vars line
176      raise unless /\((.*?)\)/ =~ line
177      vars = $1.split(',')
178      vars.map!{|v|
179        if /\s*(\S+)\s+(\S+)\s*/ =~ v
180          type = $1
181          var  = $2
182        elsif /\s*\.\.\.\s*/ =~ v
183          type = var  = '...'
184        else
185          raise
186        end
187        [type, var]
188      }
189      vars
190    end
191
192    def parse_comment comm
193      c = 'others'
194      j = ''
195      e = ''
196      comm.each_line{|line|
197        case line
198        when /@c (.+)/
199          c = $1
200        when /@e (.+)/
201          e = $1
202        when /@e\s*$/
203          e = ''
204        when /@j (.+)$/
205          j = $1
206        when /@j\s*$/
207          j = ''
208        end
209      }
210      { :c => c,
211        :e => e,
212        :j => j,
213      }
214    end
215
216    def load_insns_def file
217      body = insn = opes = pops = rets = nil
218      comment = ''
219
220      vpath.open(file) {|f|
221        f.instance_variable_set(:@line_no, 0)
222        class << f
223          def line_no
224            @line_no
225          end
226          def gets
227            @line_no += 1
228            super
229          end
230        end
231
232        while line = f.gets
233          line.chomp!
234          case line
235
236          when SKIP_COMMENT_PATTERN
237            while line = f.gets.chomp
238              if /\s+\*\/$/ =~ line
239                break
240              end
241            end
242
243            # collect instruction comment
244          when /^\/\*\*$/
245            while line = f.gets
246              if /\s+\*\/\s*$/ =~ line
247                break
248              else
249                comment << line
250              end
251            end
252
253            # start instruction body
254          when /^DEFINE_INSN$/
255            insn = f.gets.chomp
256            opes = parse_vars(f.gets.chomp)
257            pops = parse_vars(f.gets.chomp).reverse
258            rets_str = f.gets.chomp
259            rets = parse_vars(rets_str).reverse
260            comment = parse_comment(comment)
261            insn_in = true
262            body    = ''
263
264            sp_inc = rets_str[%r"//\s*(.+)", 1]
265
266            raise unless /^\{$/ =~ f.gets.chomp
267            line_no = f.line_no
268
269          # end instruction body
270          when /^\}/
271            if insn_in
272              body.instance_variable_set(:@line_no, line_no)
273              body.instance_variable_set(:@file, f.path)
274              insn = make_insn(insn, opes, pops, rets, comment, body, sp_inc)
275              insn_in = false
276              comment = ''
277            end
278
279          else
280            if insn_in
281              body << line + "\n"
282            end
283          end
284        end
285      }
286    end
287
288    ## opt op
289    def load_opt_operand_def file
290      vpath.foreach(file) {|line|
291        line = line.gsub(/\#.*/, '').strip
292        next  if line.length == 0
293        break if /__END__/ =~ line
294        /(\S+)\s+(.+)/ =~ line
295        insn = $1
296        opts = $2
297        add_opt_operand insn, opts.split(/,/).map{|e| e.strip}
298      } if file
299    end
300
301    def label_escape label
302      label.gsub(/\(/, '_O_').
303      gsub(/\)/, '_C_').
304      gsub(/\*/, '_WC_')
305    end
306
307    def add_opt_operand insn_name, opts
308      insn = @insn_map[insn_name]
309      opes = insn.opes
310
311      if opes.size != opts.size
312        raise "operand size mismatcvh for #{insn.name} (opes: #{opes.size}, opts: #{opts.size})"
313      end
314
315      ninsn = insn.name + '_OP_' + opts.map{|e| label_escape(e)}.join('_')
316      nopes = []
317      defv  = []
318
319      opts.each_with_index{|e, i|
320        if e == '*'
321          nopes << opes[i]
322        end
323        defv  << [opes[i], e]
324      }
325
326      make_insn_operand_optimized(insn, ninsn, nopes, defv)
327    end
328
329    def make_insn_operand_optimized orig_insn, name, opes, defopes
330      comm = orig_insn.comm.dup
331      comm[:c] = 'optimize'
332      add_insn insn = Instruction.new(
333        name, opes, orig_insn.pops, orig_insn.rets, comm,
334        orig_insn.body, orig_insn.tvars, orig_insn.sp_inc,
335        orig_insn, defopes)
336      orig_insn.add_optimized insn
337    end
338
339    ## insn unif
340    def load_insn_unification_def file
341      vpath.foreach(file) {|line|
342        line = line.gsub(/\#.*/, '').strip
343        next  if line.length == 0
344        break if /__END__/ =~ line
345        make_unified_insns line.split.map{|e|
346          raise "unknown insn: #{e}" unless @insn_map[e]
347          @insn_map[e]
348        }
349      } if file
350    end
351
352    def all_combination sets
353      ret = sets.shift.map{|e| [e]}
354
355      sets.each{|set|
356        prev = ret
357        ret  = []
358        prev.each{|ary|
359          set.each{|e|
360            eary = ary.dup
361            eary << e
362            ret  << eary
363          }
364        }
365      }
366      ret
367    end
368
369    def make_unified_insns insns
370      if vm_opt?('UNIFY_ALL_COMBINATION')
371        insn_sets = insns.map{|insn|
372          [insn] + insn.optimized
373        }
374
375        all_combination(insn_sets).each{|insns_set|
376          make_unified_insn_each insns_set
377        }
378      else
379        make_unified_insn_each insns
380      end
381    end
382
383    def mk_private_val vals, i, redef
384      vals.dup.map{|v|
385        # v[0] : type
386        # v[1] : var name
387
388        v = v.dup
389        if v[0] != '...'
390          redef[v[1]] = v[0]
391          v[1] = "#{v[1]}_#{i}"
392        end
393        v
394      }
395    end
396
397    def mk_private_val2 vals, i, redef
398      vals.dup.map{|v|
399        # v[0][0] : type
400        # v[0][1] : var name
401        # v[1] : default val
402
403        pv = v.dup
404        v = pv[0] = pv[0].dup
405        if v[0] != '...'
406          redef[v[1]] = v[0]
407          v[1] = "#{v[1]}_#{i}"
408        end
409        pv
410      }
411    end
412
413    def make_unified_insn_each insns
414      names = []
415      opes = []
416      pops = []
417      rets = []
418      comm = {
419        :c => 'optimize',
420        :e => 'unified insn',
421        :j => 'unified insn',
422      }
423      body = ''
424      passed = []
425      tvars = []
426      defopes = []
427      sp_inc = ''
428
429      insns.each_with_index{|insn, i|
430        names << insn.name
431        redef_vars = {}
432
433        e_opes = mk_private_val(insn.opes, i, redef_vars)
434        e_pops = mk_private_val(insn.pops, i, redef_vars)
435        e_rets = mk_private_val(insn.rets, i, redef_vars)
436        # ToDo: fix it
437        e_defs = mk_private_val2(insn.defopes, i, redef_vars)
438
439        passed_vars = []
440        while pvar = e_pops.pop
441          rvar = rets.pop
442          if rvar
443            raise "unsupported unif insn: #{insns.inspect}" if rvar[0] == '...'
444            passed_vars << [pvar, rvar]
445            tvars << rvar
446          else
447            e_pops.push pvar
448            break
449          end
450        end
451
452        opes.concat e_opes
453        pops.concat e_pops
454        rets.concat e_rets
455        defopes.concat e_defs
456        sp_inc += "#{insn.sp_inc}"
457
458        body += "{ /* unif: #{i} */\n" +
459                passed_vars.map{|rpvars|
460                  pv = rpvars[0]
461                  rv = rpvars[1]
462                  "#define #{pv[1]} #{rv[1]}"
463                }.join("\n") +
464                "\n" +
465                redef_vars.map{|v, type|
466                  "#define #{v} #{v}_#{i}"
467                }.join("\n") + "\n" +
468                insn.body +
469                passed_vars.map{|rpvars|
470                  "#undef #{rpvars[0][1]}"
471                }.join("\n") +
472                "\n" +
473                redef_vars.keys.map{|v|
474                  "#undef  #{v}"
475                }.join("\n") +
476                "\n}\n"
477      }
478
479      tvars_ary = []
480      tvars.each{|tvar|
481        unless opes.any?{|var|
482          var[1] == tvar[1]
483        } || defopes.any?{|pvar|
484          pvar[0][1] == tvar[1]
485        }
486          tvars_ary << tvar
487        end
488      }
489      add_insn insn = Instruction.new("UNIFIED_" + names.join('_'),
490                                   opes, pops, rets.reverse, comm, body,
491                                   tvars_ary, sp_inc)
492      insn.defopes.replace defopes
493      insns[0].add_unif [insn, insns]
494    end
495
496    ## sc
497    SPECIAL_INSN_FOR_SC_AFTER = {
498      /\Asend/      => [:a],
499      /\Aend/       => [:a],
500      /\Ayield/     => [:a],
501      /\Aclassdef/  => [:a],
502      /\Amoduledef/ => [:a],
503    }
504    FROM_SC = [[], [:a], [:b], [:a, :b], [:b, :a]]
505
506    def make_stackcaching_insns
507      pops = rets = nil
508
509      @insns.dup.each{|insn|
510        opops = insn.pops
511        orets = insn.rets
512        oopes = insn.opes
513        ocomm = insn.comm
514        oname = insn.name
515
516        after = SPECIAL_INSN_FOR_SC_AFTER.find {|k, v| k =~ oname}
517
518        insns = []
519        FROM_SC.each{|from|
520          name, pops, rets, pushs1, pushs2, nextsc =
521          *calc_stack(insn, from, after, opops, orets)
522
523          make_insn_sc(insn, name, oopes, pops, rets, [pushs1, pushs2], nextsc)
524        }
525      }
526    end
527
528    def make_insn_sc orig_insn, name, opes, pops, rets, pushs, nextsc
529      comm = orig_insn.comm.dup
530      comm[:c] = 'optimize(sc)'
531
532      scinsn = Instruction.new(
533        name, opes, pops, rets, comm,
534        orig_insn.body, orig_insn.tvars, orig_insn.sp_inc,
535        orig_insn, orig_insn.defopes, :sc, nextsc, pushs)
536
537      add_insn scinsn
538      orig_insn.add_sc scinsn
539    end
540
541    def self.complement_name st
542      "#{st[0] ? st[0] : 'x'}#{st[1] ? st[1] : 'x'}"
543    end
544
545    def add_stack_value st
546      len = st.length
547      if len == 0
548        st[0] = :a
549        [nil, :a]
550      elsif len == 1
551        if st[0] == :a
552          st[1] = :b
553        else
554          st[1] = :a
555        end
556        [nil, st[1]]
557      else
558        st[0], st[1] = st[1], st[0]
559        [st[1], st[1]]
560      end
561    end
562
563    def calc_stack insn, ofrom, oafter, opops, orets
564      from = ofrom.dup
565      pops = opops.dup
566      rets = orets.dup
567      rest_scr = ofrom.dup
568
569      pushs_before = []
570      pushs= []
571
572      pops.each_with_index{|e, i|
573        if e[0] == '...'
574          pushs_before = from
575          from = []
576        end
577        r = from.pop
578        break unless r
579        pops[i] = pops[i].dup << r
580      }
581
582      if oafter
583        from = oafter
584        from.each_with_index{|r, i|
585          rets[i] = rets[i].dup << r if rets[i]
586        }
587      else
588        rets = rets.reverse
589        rets.each_with_index{|e, i|
590          break if e[0] == '...'
591          pushed, r = add_stack_value from
592          rets[i] = rets[i].dup << r
593          if pushed
594            if rest_scr.pop
595              pushs << pushed
596            end
597
598            if i - 2 >= 0
599              rets[i-2].pop
600            end
601          end
602        }
603      end
604
605      if false #|| insn.name =~ /test3/
606        p ofrom
607        p pops
608        p rets
609        p pushs_before
610        p pushs
611        p from
612        exit
613      end
614
615      ret = ["#{insn.name}_SC_#{InstructionsLoader.complement_name(ofrom)}_#{complement_name(from)}",
616            pops, rets, pushs_before, pushs, from]
617    end
618  end
619
620  class SourceCodeGenerator
621    def initialize insns
622      @insns = insns
623    end
624
625    attr_reader :insns
626
627    def generate
628      raise "should not reach here"
629    end
630
631    def vpath
632      @insns.vpath
633    end
634
635    def verbose?
636      @insns.verbose?
637    end
638
639    def use_const?
640      @insns.use_const?
641    end
642
643    def build_string
644      @lines = []
645      yield
646      @lines.join("\n")
647    end
648
649    EMPTY_STRING = ''.freeze
650
651    def commit str = EMPTY_STRING
652      @lines << str
653    end
654
655    def comment str
656      @lines << str if verbose?
657    end
658
659    def output_path(fn)
660      d = @insns.destdir
661      fn = File.join(d, fn) if d
662      fn
663    end
664  end
665
666  ###################################################################
667  # vm.inc
668  class VmBodyGenerator < SourceCodeGenerator
669    # vm.inc
670    def generate
671      vm_body = ''
672      @insns.each{|insn|
673        vm_body << "\n"
674        vm_body << make_insn_def(insn)
675      }
676      src = vpath.read('template/vm.inc.tmpl')
677      ERB.new(src).result(binding)
678    end
679
680    def generate_from_insnname insnname
681      make_insn_def @insns[insnname.to_s]
682    end
683
684    #######
685    private
686
687    def make_header_prepare_stack insn
688      comment "  /* prepare stack status */"
689
690      push_ba = insn.pushsc
691      raise "unsupport" if push_ba[0].size > 0 && push_ba[1].size > 0
692
693      n = 0
694      push_ba.each {|pushs| n += pushs.length}
695      commit "  CHECK_VM_STACK_OVERFLOW(REG_CFP, #{n});" if n > 0
696      push_ba.each{|pushs|
697        pushs.each{|r|
698          commit "  PUSH(SCREG(#{r}));"
699        }
700      }
701    end
702
703    def make_header_operands insn
704      comment "  /* declare and get from iseq */"
705
706      vars = insn.opes
707      n = 0
708      ops = []
709
710      vars.each_with_index{|(type, var), i|
711        if type == '...'
712          break
713        end
714
715        # skip make operands when body has no reference to this operand
716        # TODO: really needed?
717        re = /\b#{var}\b/n
718        if re =~ insn.body or re =~ insn.sp_inc or insn.rets.any?{|t, v| re =~ v} or re =~ 'ic' or re =~ 'ci'
719          ops << "  #{type} #{var} = (#{type})GET_OPERAND(#{i+1});"
720        end
721
722        n   += 1
723      }
724      @opn = n
725
726      # reverse or not?
727      # ops.join
728      commit ops.reverse
729    end
730
731    def make_header_default_operands insn
732      vars = insn.defopes
733
734      vars.each{|e|
735        next if e[1] == '*'
736        if use_const?
737          commit "  const #{e[0][0]} #{e[0][1]} = #{e[1]};"
738        else
739          commit "  #define #{e[0][1]} #{e[1]}"
740        end
741      }
742    end
743
744    def make_footer_default_operands insn
745      comment " /* declare and initialize default opes */"
746      if use_const?
747        commit
748      else
749        vars = insn.defopes
750
751        vars.each{|e|
752          next if e[1] == '*'
753          commit "#undef #{e[0][1]}"
754        }
755      end
756    end
757
758    def make_header_stack_pops insn
759      comment "  /* declare and pop from stack */"
760
761      n = 0
762      pops = []
763      vars = insn.pops
764      vars.each_with_index{|iter, i|
765        type, var, r = *iter
766        if type == '...'
767          break
768        end
769        if r
770          pops << "  #{type} #{var} = SCREG(#{r});"
771        else
772          pops << "  #{type} #{var} = TOPN(#{n});"
773          n += 1
774        end
775      }
776      @popn = n
777
778      # reverse or not?
779      commit pops.reverse
780    end
781
782    def make_header_temporary_vars insn
783      comment "  /* declare temporary vars */"
784
785      insn.tvars.each{|var|
786        commit "  #{var[0]} #{var[1]};"
787      }
788    end
789
790    def make_header_stack_val insn
791      comment "/* declare stack push val */"
792
793      vars = insn.opes + insn.pops + insn.defopes.map{|e| e[0]}
794
795      insn.rets.each{|var|
796        if vars.all?{|e| e[1] != var[1]} && var[1] != '...'
797          commit "  #{var[0]} #{var[1]};"
798        end
799      }
800    end
801
802    def make_header_analysis insn
803      commit "  COLLECT_USAGE_INSN(BIN(#{insn.name}));"
804      insn.opes.each_with_index{|op, i|
805        commit "  COLLECT_USAGE_OPERAND(BIN(#{insn.name}), #{i}, #{op[1]});"
806      }
807    end
808
809    def make_header_pc insn
810      commit  "  ADD_PC(1+#{@opn});"
811      commit  "  PREFETCH(GET_PC());"
812    end
813
814    def make_header_popn insn
815      comment "  /* management */"
816      commit  "  POPN(#{@popn});" if @popn > 0
817    end
818
819    def make_hader_debug insn
820      comment "  /* for debug */"
821      commit  "  DEBUG_ENTER_INSN(\"#{insn.name}\");"
822    end
823
824    def make_header_defines insn
825      commit  "  #define CURRENT_INSN_#{insn.name} 1"
826      commit  "  #define INSN_IS_SC()     #{insn.sc ? 0 : 1}"
827      commit  "  #define INSN_LABEL(lab)  LABEL_#{insn.name}_##lab"
828      commit  "  #define LABEL_IS_SC(lab) LABEL_##lab##_###{insn.sc.size == 0 ? 't' : 'f'}"
829    end
830
831    def each_footer_stack_val insn
832      insn.rets.reverse_each{|v|
833        break if v[1] == '...'
834        yield v
835      }
836    end
837
838    def make_footer_stack_val insn
839      comment "  /* push stack val */"
840
841      n = 0
842      each_footer_stack_val(insn){|v|
843        n += 1 unless v[2]
844      }
845      commit "  CHECK_VM_STACK_OVERFLOW(REG_CFP, #{n});" if n > 0
846      each_footer_stack_val(insn){|v|
847        if v[2]
848          commit "  SCREG(#{v[2]}) = #{v[1]};"
849        else
850          commit "  PUSH(#{v[1]});"
851        end
852      }
853    end
854
855    def make_footer_undefs insn
856      commit "#undef CURRENT_INSN_#{insn.name}"
857      commit "#undef INSN_IS_SC"
858      commit "#undef INSN_LABEL"
859      commit "#undef LABEL_IS_SC"
860    end
861
862    def make_header insn
863      commit "INSN_ENTRY(#{insn.name}){"
864      make_header_prepare_stack insn
865      commit "{"
866      make_header_stack_val  insn
867      make_header_default_operands insn
868      make_header_operands   insn
869      make_header_stack_pops insn
870      make_header_temporary_vars insn
871      #
872      make_hader_debug insn
873      make_header_pc insn
874      make_header_popn insn
875      make_header_defines insn
876      make_header_analysis insn
877      commit  "{"
878    end
879
880    def make_footer insn
881      make_footer_stack_val insn
882      make_footer_default_operands insn
883      make_footer_undefs insn
884      commit "  END_INSN(#{insn.name});}}}"
885    end
886
887    def make_insn_def insn
888      build_string do
889        make_header insn
890        if line = insn.body.instance_variable_get(:@line_no)
891          file = insn.body.instance_variable_get(:@file)
892          commit "#line #{line+1} \"#{file}\""
893          commit insn.body
894          commit '#line __CURRENT_LINE__ "__CURRENT_FILE__"'
895        else
896          insn.body
897        end
898        make_footer(insn)
899      end
900    end
901  end
902
903  ###################################################################
904  # vmtc.inc
905  class VmTCIncGenerator < SourceCodeGenerator
906    def generate
907
908      insns_table = build_string do
909        @insns.each{|insn|
910          commit "  LABEL_PTR(#{insn.name}),"
911        }
912      end
913
914      insn_end_table = build_string do
915        @insns.each{|insn|
916          commit "  ELABEL_PTR(#{insn.name}),\n"
917        }
918      end
919
920      ERB.new(vpath.read('template/vmtc.inc.tmpl')).result(binding)
921    end
922  end
923
924  ###################################################################
925  # insns_info.inc
926  class InsnsInfoIncGenerator < SourceCodeGenerator
927    def generate
928      insns_info_inc
929    end
930
931    ###
932    private
933
934    def op2typesig op
935      case op
936      when /^OFFSET/
937        "TS_OFFSET"
938      when /^rb_num_t/
939        "TS_NUM"
940      when /^lindex_t/
941        "TS_LINDEX"
942      when /^VALUE/
943        "TS_VALUE"
944      when /^ID/
945        "TS_ID"
946      when /GENTRY/
947        "TS_GENTRY"
948      when /^IC/
949        "TS_IC"
950      when /^CALL_INFO/
951        "TS_CALLINFO"
952      when /^\.\.\./
953        "TS_VARIABLE"
954      when /^CDHASH/
955        "TS_CDHASH"
956      when /^ISEQ/
957        "TS_ISEQ"
958      when /rb_insn_func_t/
959        "TS_FUNCPTR"
960      else
961        raise "unknown op type: #{op}"
962      end
963    end
964
965    TYPE_CHARS = {
966      'TS_OFFSET'    => 'O',
967      'TS_NUM'       => 'N',
968      'TS_LINDEX'    => 'L',
969      'TS_VALUE'     => 'V',
970      'TS_ID'        => 'I',
971      'TS_GENTRY'    => 'G',
972      'TS_IC'        => 'K',
973      'TS_CALLINFO'  => 'C',
974      'TS_CDHASH'    => 'H',
975      'TS_ISEQ'      => 'S',
976      'TS_VARIABLE'  => '.',
977      'TS_FUNCPTR'   => 'F',
978    }
979
980    # insns_info.inc
981    def insns_info_inc
982      # insn_type_chars
983      insn_type_chars = TYPE_CHARS.map{|t, c|
984        "#define #{t} '#{c}'"
985      }.join("\n")
986
987      # insn_names
988      insn_names = ''
989      @insns.each{|insn|
990        insn_names << "  \"#{insn.name}\",\n"
991      }
992
993      # operands info
994      operands_info = ''
995      operands_num_info = ''
996
997      @insns.each{|insn|
998        opes = insn.opes
999        operands_info << '  '
1000        ot = opes.map{|type, var|
1001          TYPE_CHARS.fetch(op2typesig(type))
1002        }
1003        operands_info << "\"#{ot.join}\"" << ", \n"
1004
1005        num = opes.size + 1
1006        operands_num_info << "  #{num},\n"
1007      }
1008
1009      # stack num
1010      stack_num_info = ''
1011      @insns.each{|insn|
1012        num = insn.rets.size
1013        stack_num_info << "  #{num},\n"
1014      }
1015
1016      # stack increase
1017      stack_increase = ''
1018      @insns.each{|insn|
1019        stack_increase << <<-EOS
1020        case BIN(#{insn.name}):{
1021          #{insn.sp_increase_c_expr}
1022        }
1023        EOS
1024      }
1025      ERB.new(vpath.read('template/insns_info.inc.tmpl')).result(binding)
1026    end
1027  end
1028
1029  ###################################################################
1030  # insns.inc
1031  class InsnsIncGenerator < SourceCodeGenerator
1032    def generate
1033      i=0
1034      insns = build_string do
1035        @insns.each{|insn|
1036          commit "  %-30s = %d,\n" % ["BIN(#{insn.name})", i]
1037          i+=1
1038        }
1039      end
1040
1041      ERB.new(vpath.read('template/insns.inc.tmpl')).result(binding)
1042    end
1043  end
1044
1045  ###################################################################
1046  # minsns.inc
1047  class MInsnsIncGenerator < SourceCodeGenerator
1048    def generate
1049      i=0
1050      defs = build_string do
1051        @insns.each{|insn|
1052          commit "  rb_define_const(mYarvInsns, %-30s, INT2FIX(%d));\n" %
1053                 ["\"I#{insn.name}\"", i]
1054          i+=1
1055        }
1056      end
1057      ERB.new(vpath.read('template/minsns.inc.tmpl')).result(binding)
1058    end
1059  end
1060
1061  ###################################################################
1062  # optinsn.inc
1063  class OptInsnIncGenerator < SourceCodeGenerator
1064    def generate
1065      optinsn_inc
1066    end
1067
1068    ###
1069    private
1070
1071    def val_as_type op
1072      type = op[0][0]
1073      val  = op[1]
1074
1075      case type
1076      when /^long/, /^rb_num_t/, /^lindex_t/
1077        "INT2FIX(#{val})"
1078      when /^VALUE/
1079        val
1080      when /^ID/
1081        "INT2FIX(#{val})"
1082      when /^ISEQ/, /^rb_insn_func_t/
1083        val
1084      when /GENTRY/
1085        raise
1086      when /^\.\.\./
1087        raise
1088      else
1089        raise "type: #{type}"
1090      end
1091    end
1092
1093    # optinsn.inc
1094    def optinsn_inc
1095      rule = ''
1096      opt_insns_map = Hash.new{|h, k| h[k] = []}
1097
1098      @insns.each{|insn|
1099        next if insn.defopes.size == 0
1100        next if insn.type         == :sc
1101        next if /^UNIFIED/ =~ insn.name.to_s
1102
1103        originsn = insn.orig
1104        opt_insns_map[originsn] << insn
1105      }
1106
1107      rule = build_string do
1108        opt_insns_map.each{|originsn, optinsns|
1109          commit "case BIN(#{originsn.name}):"
1110
1111          optinsns.sort_by{|opti|
1112            opti.defopes.find_all{|e| e[1] == '*'}.size
1113          }.each{|opti|
1114            commit "  if("
1115            i = 0
1116            commit "    " + opti.defopes.map{|opinfo|
1117              i += 1
1118              next if opinfo[1] == '*'
1119              "insnobj->operands[#{i-1}] == #{val_as_type(opinfo)}"
1120            }.compact.join('&&  ')
1121            commit "  ){"
1122            idx = 0
1123            n = 0
1124            opti.defopes.each{|opinfo|
1125              if opinfo[1] == '*'
1126                if idx != n
1127                  commit "    insnobj->operands[#{idx}] = insnobj->operands[#{n}];"
1128                end
1129                idx += 1
1130              else
1131                # skip
1132              end
1133              n += 1
1134            }
1135            commit "    insnobj->insn_id = BIN(#{opti.name});"
1136            commit "    insnobj->operand_size = #{idx};"
1137            commit "    break;\n  }\n"
1138          }
1139          commit "  break;";
1140        }
1141      end
1142
1143      ERB.new(vpath.read('template/optinsn.inc.tmpl')).result(binding)
1144    end
1145  end
1146
1147  ###################################################################
1148  # optunifs.inc
1149  class OptUnifsIncGenerator < SourceCodeGenerator
1150    def generate
1151      unif_insns_each = ''
1152      unif_insns      = ''
1153      unif_insns_data = []
1154
1155      insns = @insns.find_all{|insn| !insn.is_sc}
1156      insns.each{|insn|
1157        size = insn.unifs.size
1158        if size > 0
1159          insn.unifs.sort_by{|unif| -unif[1].size}.each_with_index{|unif, i|
1160
1161          uni_insn, uni_insns = *unif
1162          uni_insns = uni_insns[1..-1]
1163          unif_insns_each << "static const int UNIFIED_#{insn.name}_#{i}[] = {" +
1164                             "  BIN(#{uni_insn.name}), #{uni_insns.size + 2}, \n  " +
1165                             uni_insns.map{|e| "BIN(#{e.name})"}.join(", ") + "};\n"
1166          }
1167        else
1168
1169        end
1170        if size > 0
1171          unif_insns << "static const int *const UNIFIED_#{insn.name}[] = {(int *)#{size+1}, \n"
1172          unif_insns << (0...size).map{|e| "  UNIFIED_#{insn.name}_#{e}"}.join(",\n") + "};\n"
1173          unif_insns_data << "  UNIFIED_#{insn.name}"
1174        else
1175          unif_insns_data << "  0"
1176        end
1177      }
1178      unif_insns_data = "static const int *const *const unified_insns_data[] = {\n" +
1179                        unif_insns_data.join(",\n") + "};\n"
1180      ERB.new(vpath.read('template/optunifs.inc.tmpl')).result(binding)
1181    end
1182  end
1183
1184  ###################################################################
1185  # opt_sc.inc
1186  class OptSCIncGenerator < SourceCodeGenerator
1187    def generate
1188      sc_insn_info = []
1189      @insns.each{|insn|
1190        insns = insn.sc
1191        if insns.size > 0
1192          insns = ['SC_ERROR'] + insns.map{|e| "    BIN(#{e.name})"}
1193        else
1194          insns = Array.new(6){'SC_ERROR'}
1195        end
1196        sc_insn_info << "  {\n#{insns.join(",\n")}}"
1197      }
1198      sc_insn_info = sc_insn_info.join(",\n")
1199
1200      sc_insn_next = @insns.map{|insn|
1201        "  SCS_#{InstructionsLoader.complement_name(insn.nextsc).upcase}" +
1202        (verbose? ? " /* #{insn.name} */" : '')
1203      }.join(",\n")
1204      ERB.new(vpath.read('template/opt_sc.inc.tmpl')).result(binding)
1205    end
1206  end
1207
1208  ###################################################################
1209  # yasmdata.rb
1210  class YASMDataRbGenerator < SourceCodeGenerator
1211    def generate
1212      insn_id2no = ''
1213      @insns.each_with_index{|insn, i|
1214        insn_id2no << "        :#{insn.name} => #{i},\n"
1215      }
1216      ERB.new(vpath.read('template/yasmdata.rb.tmpl')).result(binding)
1217    end
1218  end
1219
1220  ###################################################################
1221  # yarvarch.*
1222  class YARVDocGenerator < SourceCodeGenerator
1223    def generate
1224
1225    end
1226
1227    def desc lang
1228      d = ''
1229      i = 0
1230      cat = nil
1231      @insns.each{|insn|
1232        seq    = insn.opes.map{|t,v| v}.join(' ')
1233        before = insn.pops.reverse.map{|t,v| v}.join(' ')
1234        after  = insn.rets.reverse.map{|t,v| v}.join(' ')
1235
1236        if cat != insn.comm[:c]
1237          d << "** #{insn.comm[:c]}\n\n"
1238          cat = insn.comm[:c]
1239        end
1240
1241        d << "*** #{insn.name}\n"
1242        d << "\n"
1243        d << insn.comm[lang] + "\n\n"
1244        d << ":instruction sequence: 0x%02x #{seq}\n" % i
1245        d << ":stack: #{before} => #{after}\n\n"
1246        i+=1
1247      }
1248      d
1249    end
1250
1251    def desc_ja
1252      d = desc :j
1253      ERB.new(vpath.read('template/yarvarch.ja')).result(binding)
1254    end
1255
1256    def desc_en
1257      d = desc :e
1258      ERB.new(vpath.read('template/yarvarch.en')).result(binding)
1259    end
1260  end
1261
1262  class SourceCodeGenerator
1263    Files = { # codes
1264      'vm.inc'         => VmBodyGenerator,
1265      'vmtc.inc'       => VmTCIncGenerator,
1266      'insns.inc'      => InsnsIncGenerator,
1267      'insns_info.inc' => InsnsInfoIncGenerator,
1268    # 'minsns.inc'     => MInsnsIncGenerator,
1269      'optinsn.inc'    => OptInsnIncGenerator,
1270      'optunifs.inc'   => OptUnifsIncGenerator,
1271      'opt_sc.inc'     => OptSCIncGenerator,
1272      'yasmdata.rb'    => YASMDataRbGenerator,
1273    }
1274
1275    def generate args = []
1276      args = Files.keys if args.empty?
1277      args.each{|fn|
1278        s = Files[fn].new(@insns).generate
1279        open(output_path(fn), 'w') {|f| f.puts(s)}
1280      }
1281    end
1282
1283    def self.def_options(opt)
1284      opts = {
1285        :"insns.def" => 'insns.def',
1286        :"opope.def" => 'defs/opt_operand.def',
1287        :"unif.def"  => 'defs/opt_insn_unif.def',
1288      }
1289
1290      opt.on("-Dname", /\AOPT_(\w+)\z/, "enable VM option") {|s, v|
1291        opts[v] = true
1292      }
1293      opt.on("--enable=name[,name...]", Array,
1294        "enable VM options (without OPT_ prefix)") {|*a|
1295        a.each {|v| opts[v] = true}
1296      }
1297      opt.on("-Uname", /\AOPT_(\w+)\z/, "disable VM option") {|s, v|
1298        opts[v] = false
1299      }
1300      opt.on("--disable=name[,name...]", Array,
1301        "disable VM options (without OPT_ prefix)") {|*a|
1302          a.each {|v| opts[v] = false}
1303      }
1304      opt.on("-i", "--insnsdef=FILE", "--instructions-def",
1305        "instructions definition file") {|n|
1306        opts[:insns_def] = n
1307      }
1308      opt.on("-o", "--opt-operanddef=FILE", "--opt-operand-def",
1309        "vm option: operand definition file") {|n|
1310        opts[:opope_def] = n
1311      }
1312      opt.on("-u", "--opt-insnunifdef=FILE", "--opt-insn-unif-def",
1313        "vm option: instruction unification file") {|n|
1314        opts[:unif_def] = n
1315      }
1316      opt.on("-C", "--[no-]use-const",
1317        "use consts for default operands instead of macros") {|v|
1318        opts[:use_const] = v
1319      }
1320      opt.on("-d", "--destdir", "--output-directory=DIR",
1321        "make output file underneath DIR") {|v|
1322        opts[:destdir] = v
1323      }
1324      opt.on("-V", "--[no-]verbose") {|v|
1325        opts[:verbose] = v
1326      }
1327
1328      vpath = VPath.new
1329      vpath.def_options(opt)
1330
1331      proc {
1332        opts[:VPATH] = vpath
1333        build opts
1334      }
1335    end
1336
1337    def self.build opts, vpath = ['./']
1338      opts[:VPATH] ||= VPath.new(*vpath)
1339      self.new InstructionsLoader.new(opts)
1340    end
1341  end
1342end
1343
1344