Skip to content

Commit d66c977

Browse files
committed
Use length for offsets to make it more readable
1 parent f600b06 commit d66c977

File tree

4 files changed

+90
-66
lines changed

4 files changed

+90
-66
lines changed

lib/syntax_tree/yarv/basic_block.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,14 @@ def initialize(block_start, insns)
3232

3333
# Yield each instruction in this basic block along with its index from the
3434
# original instruction sequence.
35-
def each_with_index(&block)
36-
insns.each.with_index(block_start, &block)
35+
def each_with_length
36+
return enum_for(:each_with_length) unless block_given?
37+
38+
length = block_start
39+
insns.each do |insn|
40+
yield insn, length
41+
length += insn.length
42+
end
3743
end
3844

3945
# This method is used to verify that the basic block is well formed. It

lib/syntax_tree/yarv/control_flow_graph.rb

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,23 @@ def initialize(iseq, insns, blocks)
3434

3535
def disasm
3636
fmt = Disassembler.new(iseq)
37-
fmt.output.puts "== cfg #{iseq.name}"
37+
fmt.output.print("== cfg: #<ISeq:#{iseq.name}@<compiled>:1 ")
38+
fmt.output.puts("(#{iseq.line},0)-(#{iseq.line},0)>")
3839

3940
blocks.each do |block|
40-
fmt.output.print(block.id)
41-
42-
unless block.incoming_blocks.empty?
43-
fmt.output.print(" # from: #{block.incoming_blocks.map(&:id).join(", ")}")
44-
end
45-
46-
fmt.output.puts
47-
48-
fmt.with_prefix(" ") { fmt.format_insns!(block.insns) }
41+
fmt.output.puts(block.id)
42+
fmt.with_prefix(" ") do
43+
unless block.incoming_blocks.empty?
44+
from = block.incoming_blocks.map(&:id).join(", ")
45+
fmt.output.puts("#{fmt.current_prefix}== from: #{from}")
46+
end
4947

50-
dests = block.outgoing_blocks.map(&:id)
51-
dests << "leaves" if block.insns.last.leaves?
52-
fmt.output.print(" # to: #{dests.join(", ")}") unless dests.empty?
48+
fmt.format_insns!(block.insns, block.block_start)
5349

54-
fmt.output.puts
50+
to = block.outgoing_blocks.map(&:id)
51+
to << "leaves" if block.insns.last.leaves?
52+
fmt.output.puts("#{fmt.current_prefix}== to: #{to.join(", ")}")
53+
end
5554
end
5655

5756
fmt.string
@@ -71,23 +70,34 @@ def self.compile(iseq)
7170
# This class is responsible for creating a control flow graph from the
7271
# given instruction sequence.
7372
class Compiler
74-
attr_reader :iseq, :labels, :insns
73+
# This is the instruction sequence that is being compiled.
74+
attr_reader :iseq
75+
76+
# This is a hash of indices in the YARV instruction sequence that point
77+
# to their corresponding instruction.
78+
attr_reader :insns
79+
80+
# This is a hash of labels that point to their corresponding index into
81+
# the YARV instruction sequence. Note that this is not the same as the
82+
# index into the list of instructions on the instruction sequence
83+
# object. Instead, this is the index into the C array, so it includes
84+
# operands.
85+
attr_reader :labels
7586

7687
def initialize(iseq)
7788
@iseq = iseq
7889

79-
# We need to find all of the instructions that immediately follow
80-
# labels so that when we are looking at instructions that branch we
81-
# know where they branch to.
90+
@insns = {}
8291
@labels = {}
83-
@insns = []
8492

93+
length = 0
8594
iseq.insns.each do |insn|
8695
case insn
8796
when Instruction
88-
@insns << insn
97+
@insns[length] = insn
98+
length += insn.length
8999
when InstructionSequence::Label
90-
@labels[insn] = @insns.length
100+
@labels[insn] = length
91101
end
92102
end
93103
end
@@ -111,15 +121,15 @@ def compile
111121
def find_basic_block_starts
112122
block_starts = Set.new([0])
113123

114-
insns.each_with_index do |insn, index|
124+
insns.each do |index, insn|
115125
branch_targets = insn.branch_targets
116126

117127
if branch_targets.any?
118128
branch_targets.each do |branch_target|
119129
block_starts.add(labels[branch_target])
120130
end
121131

122-
block_starts.add(index + 1) if insn.falls_through?
132+
block_starts.add(index + insn.length) if insn.falls_through?
123133
end
124134
end
125135

@@ -131,10 +141,14 @@ def find_basic_block_starts
131141
def build_basic_blocks
132142
block_starts = find_basic_block_starts
133143

134-
block_starts.each_with_index.to_h do |block_start, block_index|
135-
block_end = (block_starts[(block_index + 1)..] + [insns.length]).min
136-
block_insns = insns[block_start...block_end]
144+
length = 0
145+
blocks =
146+
iseq.insns.grep(Instruction).slice_after do |insn|
147+
length += insn.length
148+
block_starts.include?(length)
149+
end
137150

151+
block_starts.zip(blocks).to_h do |block_start, block_insns|
138152
[block_start, BasicBlock.new(block_start, block_insns)]
139153
end
140154
end
@@ -150,7 +164,8 @@ def connect_basic_blocks(blocks)
150164
end
151165

152166
if (insn.branch_targets.empty? && !insn.leaves?) || insn.falls_through?
153-
block.outgoing_blocks << blocks.fetch(block_start + block.insns.length)
167+
fall_through_start = block_start + block.insns.sum(&:length)
168+
block.outgoing_blocks << blocks.fetch(fall_through_start)
154169
end
155170

156171
block.outgoing_blocks.each do |outgoing_block|

lib/syntax_tree/yarv/data_flow_graph.rb

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ def disasm
4444
output.puts " # in: #{block_flow.in.join(", ")}"
4545
end
4646

47-
block.each_with_index do |insn, index|
47+
block.each_with_length do |insn, length|
4848
output.print(" ")
4949
output.print(insn.disasm(fmt))
5050

51-
insn_flow = insn_flows[index]
51+
insn_flow = insn_flows[length]
5252
if insn_flow.in.empty? && insn_flow.out.empty?
5353
output.puts
5454
next
@@ -120,8 +120,8 @@ def initialize(cfg)
120120
# This data structure will hold the data flow between instructions
121121
# within individual basic blocks.
122122
@insn_flows = {}
123-
cfg.insns.each_with_index do |insn, index|
124-
@insn_flows[index] = DataFlow.new
123+
cfg.insns.each_key do |length|
124+
@insn_flows[length] = DataFlow.new
125125
end
126126

127127
# This data structure will hold the data flow between basic blocks.
@@ -147,8 +147,8 @@ def find_local_flow
147147
stack = []
148148

149149
# Go through each instruction in the block...
150-
block.each_with_index do |insn, index|
151-
insn_flow = insn_flows[index]
150+
block.each_with_length do |insn, length|
151+
insn_flow = insn_flows[length]
152152

153153
# How many values will be missing from the local stack to run this
154154
# instruction?
@@ -172,7 +172,7 @@ def find_local_flow
172172

173173
# Record on our abstract stack that this instruction pushed
174174
# this value onto the stack.
175-
insn.pushes.times { stack << index }
175+
insn.pushes.times { stack << length }
176176
end
177177

178178
# Values that are left on the stack after going through all
@@ -184,16 +184,16 @@ def find_local_flow
184184
end
185185

186186
# Go backwards and connect from producers to consumers.
187-
cfg.insns.each_with_index do |insn, index|
187+
cfg.insns.each_key do |length|
188188
# For every instruction that produced a value used in this
189189
# instruction...
190-
insn_flows[index].in.each do |producer|
190+
insn_flows[length].in.each do |producer|
191191
# If it's actually another instruction and not a basic block
192192
# argument...
193193
if producer.is_a?(Integer)
194194
# Record in the producing instruction that it produces a value
195195
# used by this construction.
196-
insn_flows[producer].out << index
196+
insn_flows[producer].out << length
197197
end
198198
end
199199
end

test/yarv_test.rb

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -303,25 +303,28 @@ def test_cfg
303303
cfg = SyntaxTree::YARV::ControlFlowGraph.compile(iseq)
304304

305305
assert_equal(<<~CFG, cfg.disasm)
306-
== cfg <compiled>
306+
== cfg: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,0)>
307307
block_0
308308
0000 putobject 100
309309
0002 putobject 14
310310
0004 putobject_INT2FIX_0_
311311
0005 opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE>
312312
0007 branchunless 13
313-
# to: block_7, block_5
314-
block_5 # from: block_0
315-
0000 putobject -1
316-
0002 jump 14
317-
# to: block_8
318-
block_7 # from: block_0
319-
0000 putobject_INT2FIX_1_
320-
# to: block_8
321-
block_8 # from: block_5, block_7
322-
0000 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>
323-
0002 leave
324-
# to: leaves
313+
== to: block_13, block_9
314+
block_9
315+
== from: block_0
316+
0009 putobject -1
317+
0011 jump 14
318+
== to: block_14
319+
block_13
320+
== from: block_0
321+
0013 putobject_INT2FIX_1_
322+
== to: block_14
323+
block_14
324+
== from: block_9, block_13
325+
0014 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>
326+
0016 leave
327+
== to: leaves
325328
CFG
326329
end
327330

@@ -335,27 +338,27 @@ def test_dfg
335338
== dfg <compiled>
336339
block_0
337340
putobject 100 # out: out_0
338-
putobject 14 # out: 3
339-
putobject_INT2FIX_0_ # out: 3
340-
opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE> # in: 1, 2; out: 4
341-
branchunless 13 # in: 3
342-
# to: block_7, block_5
341+
putobject 14 # out: 5
342+
putobject_INT2FIX_0_ # out: 5
343+
opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE> # in: 2, 4; out: 7
344+
branchunless 13 # in: 5
345+
# to: block_13, block_9
343346
# out: 0
344-
block_5 # from: block_0
347+
block_9 # from: block_0
345348
# in: pass_0
346349
putobject -1 # out: out_0
347350
jump 14
348-
# to: block_8
349-
# out: pass_0, 5
350-
block_7 # from: block_0
351+
# to: block_14
352+
# out: pass_0, 9
353+
block_13 # from: block_0
351354
# in: pass_0
352355
putobject_INT2FIX_1_ # out: out_0
353-
# to: block_8
354-
# out: pass_0, 7
355-
block_8 # from: block_5, block_7
356+
# to: block_14
357+
# out: pass_0, 13
358+
block_14 # from: block_9, block_13
356359
# in: in_0, in_1
357-
opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE> # in: in_0, in_1; out: 9
358-
leave # in: 8
360+
opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE> # in: in_0, in_1; out: 16
361+
leave # in: 14
359362
# to: leaves
360363
DFG
361364
end

0 commit comments

Comments
 (0)