evanphx / rubinius

Rubinius, the Ruby VM

This URL has Read+Write access

rubinius / kernel / compiler / blocks.rb
100644 107 lines (87 sloc) 2.154 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
class Compiler
  class BlockExtractor
    class Block
      def initialize(ip, block_id)
        @ip = ip
        @block_id = block_id
        @instructions = []
        @next = []
      end
 
      attr_reader :ip, :block_id, :instructions
 
      def <<(insn)
        @instructions << insn
      end
 
      def add_next(block, reason=:goto)
        @next << [block, reason]
      end
 
      def next_blocks
        @next
      end
 
      def size
        @instructions.size
      end
    end
 
    def initialize(iseq)
      @iseq = iseq.opcodes
      @block_id = 0
      @blocks = Hash.new { |h,k| h[k] = Block.new(k, @block_id += 1) }
    end
 
    def run
      run_from(0)
    end
 
    def run_from(start_ip)
      if block = @blocks.key?(start_ip)
        return @blocks[start_ip]
      end
 
      block = @blocks[start_ip]
 
      ip = start_ip
      total = @iseq.size
 
      while ip < total
        opcode = Rubinius::InstructionSet[@iseq[ip]]
        size = opcode.size
 
        if size == 1
          block << opcode.opcode
        else
          ary = [opcode.opcode]
          case size
          when 2
            ary << @iseq[ip + 1]
          when 3
            ary << @iseq[ip + 1]
            ary << @iseq[ip + 2]
          end
          block << ary
        end
 
        case opcode.opcode
        when :setup_unwind, :goto_if_false, :goto_if_true, :goto_if_defined
          dest = @iseq[ip + 1]
          block.add_next run_from(dest), :condition
          block.add_next run_from(ip + 2)
          return block
        when :goto
          dest = @iseq[ip + 1]
          block.add_next run_from(dest)
          return block
        when :ret, :raise_exc
          return block
        end
 
        ip += size
      end
 
      raise "control flowed off end"
    end
 
    class BlockInspector
      def initialize(&block)
        @seen_blocks = {}
        @operation = block
      end
 
      def run(block)
        return if @seen_blocks[block.block_id]
        @seen_blocks[block.block_id] = true
 
        @operation.call block
 
        block.next_blocks.each do |sub, reason|
          run sub
        end
      end
    end
  end
end