seafoam 0.3 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/workflows.yml +40 -0
  3. data/.gitignore +0 -1
  4. data/.rubocop.yml +1 -1
  5. data/.ruby-version +1 -1
  6. data/Gemfile.lock +59 -0
  7. data/README.md +76 -3
  8. data/bin/bgv2isabelle +16 -45
  9. data/bin/bgv2json +18 -36
  10. data/bin/cfg2asm +24 -0
  11. data/bin/seafoam +1 -1
  12. data/demos/box-unbox-stats +65 -0
  13. data/docs/bgv.md +2 -1
  14. data/docs/getting-graphs.md +8 -0
  15. data/examples/Fib.java +1 -1
  16. data/examples/fib-java.bgv.gz +0 -0
  17. data/examples/fib.js +1 -1
  18. data/examples/java/JavaExamples.java +1 -1
  19. data/examples/ruby/clamps.rb +20 -0
  20. data/examples/ruby/graal.patch +15 -0
  21. data/examples/ruby/ruby_examples.rb +278 -0
  22. data/lib/seafoam.rb +5 -1
  23. data/lib/seafoam/annotators/graal.rb +1 -1
  24. data/lib/seafoam/bgv/bgv_parser.rb +10 -2
  25. data/lib/seafoam/cfg/cfg_parser.rb +93 -0
  26. data/lib/seafoam/cfg/disassembler.rb +70 -0
  27. data/lib/seafoam/commands.rb +190 -30
  28. data/lib/seafoam/graal/source.rb +23 -0
  29. data/lib/seafoam/graph.rb +25 -1
  30. data/lib/seafoam/graphviz_writer.rb +21 -2
  31. data/lib/seafoam/isabelle_writer.rb +46 -0
  32. data/lib/seafoam/json_writer.rb +58 -0
  33. data/lib/seafoam/version.rb +1 -1
  34. data/seafoam.gemspec +4 -2
  35. data/spec/seafoam/annotators/graal_spec.rb +7 -7
  36. data/spec/seafoam/bgv/bgv_parser_spec.rb +13 -3
  37. data/spec/seafoam/cfg/cfg_parser_spec.rb +21 -0
  38. data/spec/seafoam/cfg/disassembler_spec.rb +32 -0
  39. data/spec/seafoam/command_spec.rb +86 -40
  40. data/spec/seafoam/json_writer_spec.rb +14 -0
  41. data/spec/seafoam/spec_helpers.rb +4 -0
  42. data/spec/seafoam/spotlight_spec.rb +1 -1
  43. data/tools/render-all +2 -2
  44. metadata +33 -96
  45. data/.github/workflows/rubocop.yml +0 -10
  46. data/.github/workflows/specs.yml +0 -19
  47. data/examples/fib-java.bgv +0 -0
  48. data/examples/fib-js.bgv +0 -0
  49. data/examples/fib-ruby.bgv +0 -0
  50. data/examples/identity.bgv +0 -0
  51. data/examples/java/exampleArithOperator.bgv +0 -0
  52. data/examples/java/exampleArithOperator.cfg +0 -925
  53. data/examples/java/exampleArrayAllocation.bgv +0 -0
  54. data/examples/java/exampleArrayAllocation.cfg +0 -5268
  55. data/examples/java/exampleArrayRead.bgv +0 -0
  56. data/examples/java/exampleArrayRead.cfg +0 -2263
  57. data/examples/java/exampleArrayWrite.bgv +0 -0
  58. data/examples/java/exampleArrayWrite.cfg +0 -2315
  59. data/examples/java/exampleCatch.bgv +0 -0
  60. data/examples/java/exampleCatch.cfg +0 -4150
  61. data/examples/java/exampleCompareOperator.bgv +0 -0
  62. data/examples/java/exampleCompareOperator.cfg +0 -1109
  63. data/examples/java/exampleDoubleSynchronized.bgv +0 -0
  64. data/examples/java/exampleDoubleSynchronized.cfg +0 -26497
  65. data/examples/java/exampleExactArith.bgv +0 -0
  66. data/examples/java/exampleExactArith.cfg +0 -1888
  67. data/examples/java/exampleFieldRead.bgv +0 -0
  68. data/examples/java/exampleFieldRead.cfg +0 -1228
  69. data/examples/java/exampleFieldWrite.bgv +0 -0
  70. data/examples/java/exampleFieldWrite.cfg +0 -1102
  71. data/examples/java/exampleFor.bgv +0 -0
  72. data/examples/java/exampleFor.cfg +0 -3936
  73. data/examples/java/exampleFullEscape.bgv +0 -0
  74. data/examples/java/exampleFullEscape.cfg +0 -5893
  75. data/examples/java/exampleIf.bgv +0 -0
  76. data/examples/java/exampleIf.cfg +0 -2462
  77. data/examples/java/exampleIfNeverTaken.bgv +0 -0
  78. data/examples/java/exampleIfNeverTaken.cfg +0 -2476
  79. data/examples/java/exampleInstanceOfManyImpls.bgv +0 -0
  80. data/examples/java/exampleInstanceOfManyImpls.cfg +0 -6391
  81. data/examples/java/exampleInstanceOfOneImpl.bgv +0 -0
  82. data/examples/java/exampleInstanceOfOneImpl.cfg +0 -2604
  83. data/examples/java/exampleIntSwitch.bgv +0 -0
  84. data/examples/java/exampleIntSwitch.cfg +0 -3121
  85. data/examples/java/exampleInterfaceCallManyImpls.bgv +0 -0
  86. data/examples/java/exampleInterfaceCallManyImpls.cfg +0 -1358
  87. data/examples/java/exampleInterfaceCallOneImpl.bgv +0 -0
  88. data/examples/java/exampleInterfaceCallOneImpl.cfg +0 -3859
  89. data/examples/java/exampleLocalInstanceOf.bgv +0 -0
  90. data/examples/java/exampleLocalInstanceOf.cfg +0 -5276
  91. data/examples/java/exampleLocalSynchronized.bgv +0 -0
  92. data/examples/java/exampleLocalSynchronized.cfg +0 -1364
  93. data/examples/java/exampleLocalVariables.bgv +0 -0
  94. data/examples/java/exampleLocalVariables.cfg +0 -1195
  95. data/examples/java/exampleLocalVariablesState.bgv +0 -0
  96. data/examples/java/exampleLocalVariablesState.cfg +0 -1673
  97. data/examples/java/exampleNestedWhile.bgv +0 -0
  98. data/examples/java/exampleNestedWhile.cfg +0 -15499
  99. data/examples/java/exampleNestedWhileBreak.bgv +0 -0
  100. data/examples/java/exampleNestedWhileBreak.cfg +0 -11162
  101. data/examples/java/exampleNoEscape.bgv +0 -0
  102. data/examples/java/exampleNoEscape.cfg +0 -974
  103. data/examples/java/exampleObjectAllocation.bgv +0 -0
  104. data/examples/java/exampleObjectAllocation.cfg +0 -5287
  105. data/examples/java/examplePartialEscape.bgv +0 -0
  106. data/examples/java/examplePartialEscape.cfg +0 -7042
  107. data/examples/java/examplePhi.bgv +0 -0
  108. data/examples/java/examplePhi.cfg +0 -3227
  109. data/examples/java/exampleReducible.bgv +0 -0
  110. data/examples/java/exampleReducible.cfg +0 -5578
  111. data/examples/java/exampleSimpleCall.bgv +0 -0
  112. data/examples/java/exampleSimpleCall.cfg +0 -1435
  113. data/examples/java/exampleStamp.bgv +0 -0
  114. data/examples/java/exampleStamp.cfg +0 -913
  115. data/examples/java/exampleStaticCall.bgv +0 -0
  116. data/examples/java/exampleStaticCall.cfg +0 -1154
  117. data/examples/java/exampleStringSwitch.bgv +0 -0
  118. data/examples/java/exampleStringSwitch.cfg +0 -15377
  119. data/examples/java/exampleSynchronized.bgv +0 -0
  120. data/examples/java/exampleSynchronized.cfg +0 -26027
  121. data/examples/java/exampleThrow.bgv +0 -0
  122. data/examples/java/exampleThrow.cfg +0 -780
  123. data/examples/java/exampleThrowCatch.bgv +0 -0
  124. data/examples/java/exampleThrowCatch.cfg +0 -744
  125. data/examples/java/exampleUnsafeRead.bgv +0 -0
  126. data/examples/java/exampleUnsafeRead.cfg +0 -912
  127. data/examples/java/exampleUnsafeWrite.bgv +0 -0
  128. data/examples/java/exampleUnsafeWrite.cfg +0 -962
  129. data/examples/java/exampleWhile.bgv +0 -0
  130. data/examples/java/exampleWhile.cfg +0 -3936
  131. data/examples/java/exampleWhileBreak.bgv +0 -0
  132. data/examples/java/exampleWhileBreak.cfg +0 -5963
  133. data/examples/matmult-java.bgv +0 -0
  134. data/examples/matmult-ruby.bgv +0 -0
  135. data/examples/overflow.bgv +0 -0
  136. data/spec/seafoam/bgv/fixtures/not.bgv +0 -1
  137. data/spec/seafoam/bgv/fixtures/unsupported.bgv +0 -1
@@ -0,0 +1,70 @@
1
+ require 'stringio'
2
+
3
+ module Seafoam
4
+ module CFG
5
+ # Disassemble and print comments from cfg files
6
+ class Disassembler
7
+ def initialize(out)
8
+ @out = out
9
+ end
10
+
11
+ def disassemble(nmethod, print_comments)
12
+ require_crabstone
13
+
14
+ comments = nmethod.comments
15
+ comments_n = 0
16
+
17
+ case [nmethod.code.arch, nmethod.code.arch_width]
18
+ when %w[AMD64 64]
19
+ crabstone_arch = [Crabstone::ARCH_X86, Crabstone::MODE_64]
20
+ else
21
+ raise "Unknown architecture #{nmethod.code.arch} and bit width #{nmethod.code.arch_width}"
22
+ end
23
+
24
+ cs = Crabstone::Disassembler.new(*crabstone_arch)
25
+ begin
26
+ cs.disasm(nmethod.code.code, nmethod.code.base).each do |i|
27
+ if print_comments
28
+ # Print comments associated to the instruction.
29
+ last_comment = i.address + i.bytes.length - nmethod.code.base
30
+ while comments_n < comments.length && comments[comments_n].offset < last_comment
31
+ if comments[comments_n].offset == -1
32
+ @out.printf("\t\t\t\t;%<comment>s\n", comment: comments[comments_n].comment)
33
+ else
34
+ @out.printf(
35
+ "\t\t\t\t;Comment %<loc>i:\t%<comment>s\n",
36
+ loc: comments[comments_n].offset,
37
+ comment: comments[comments_n].comment
38
+ )
39
+ end
40
+ comments_n += 1
41
+ end
42
+ end
43
+
44
+ # Print the instruction.
45
+ @out.printf(
46
+ "\t0x%<address>x:\t%<instruction>s\t%<details>s\n",
47
+ address: i.address,
48
+ instruction: i.mnemonic,
49
+ details: i.op_str
50
+ )
51
+ end
52
+ rescue StandardError => e
53
+ raise "Disassembly error: #{e.message}"
54
+ ensure
55
+ cs.close
56
+ end
57
+ end
58
+
59
+ def require_crabstone
60
+ require 'crabstone'
61
+ rescue LoadError => e
62
+ if $DEBUG
63
+ raise e
64
+ else
65
+ raise 'Could not load Capstone - is it installed?'
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -8,12 +8,32 @@ module Seafoam
8
8
  @config = config
9
9
  end
10
10
 
11
- # Run any command.
12
- def run(*args)
11
+ # Run the general seafoam command.
12
+ def seafoam(*args)
13
13
  first, *args = args
14
14
  case first
15
15
  when nil, 'help', '-h', '--help', '-help'
16
- help(*args)
16
+ raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
17
+
18
+ @out.puts 'seafoam file.bgv info'
19
+ @out.puts ' file.bgv list'
20
+ @out.puts ' file.bgv[:graph][:node[-edge]] search term...'
21
+ @out.puts ' file.bgv[:graph][:node[-edge]] edges'
22
+ @out.puts ' file.bgv[:graph][:node[-edge]] props'
23
+ @out.puts ' file.bgv:graph:node source'
24
+ @out.puts ' file.bgv:graph render'
25
+ @out.puts ' --spotlight n,n,n...'
26
+ @out.puts ' --out graph.pdf'
27
+ @out.puts ' graph.svg'
28
+ @out.puts ' graph.png'
29
+ @out.puts ' graph.dot'
30
+ @out.puts ' --show-frame-state'
31
+ @out.puts ' --hide-floating'
32
+ @out.puts ' --no-reduce-edges'
33
+ @out.puts ' --draw-blocks'
34
+ @out.puts ' --option key value'
35
+ @out.puts ' --help'
36
+ @out.puts ' --version'
17
37
  when 'version', '-v', '-version', '--version'
18
38
  version(*args)
19
39
  else
@@ -32,6 +52,8 @@ module Seafoam
32
52
  edges name, *args
33
53
  when 'props'
34
54
  props name, *args
55
+ when 'source'
56
+ source name, *args
35
57
  when 'render'
36
58
  render name, *args
37
59
  when 'debug'
@@ -42,6 +64,141 @@ module Seafoam
42
64
  end
43
65
  end
44
66
 
67
+ # Run the bgv2isabelle command.
68
+ def bgv2isabelle(*args)
69
+ case args.first
70
+ when nil, 'help', '-h', '--help', '-help'
71
+ args = args.drop(1)
72
+ raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
73
+
74
+ @out.puts 'bgv2isabelle file.bgv...'
75
+ @out.puts ' --help'
76
+ @out.puts ' --version'
77
+ when 'version', '-v', '-version', '--version'
78
+ args = args.drop(1)
79
+ version(*args)
80
+ else
81
+ files = []
82
+
83
+ until args.empty?
84
+ arg = args.shift
85
+ if arg.start_with?('-')
86
+ raise ArgumentError, "unknown option #{arg}"
87
+ else
88
+ files.push arg
89
+ end
90
+ end
91
+
92
+ writer = IsabelleWriter.new(@out)
93
+
94
+ files.each do |file|
95
+ parser = Seafoam::BGV::BGVParser.new(file)
96
+ parser.read_file_header
97
+ parser.skip_document_props
98
+
99
+ loop do
100
+ index, = parser.read_graph_preheader
101
+ break unless index
102
+
103
+ graph_header = parser.read_graph_header
104
+ name = parser.graph_name(graph_header)
105
+ graph = parser.read_graph
106
+
107
+ writer.write index, name, graph
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def bgv2json(*args)
114
+ case args.first
115
+ when nil, 'help', '-h', '--help', '-help'
116
+ args = args.drop(1)
117
+ raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
118
+
119
+ @out.puts 'bgv2json file.bgv...'
120
+ @out.puts ' --help'
121
+ @out.puts ' --version'
122
+ when 'version', '-v', '-version', '--version'
123
+ args = args.drop(1)
124
+ version(*args)
125
+ else
126
+ files = []
127
+
128
+ until args.empty?
129
+ arg = args.shift
130
+ if arg.start_with?('-')
131
+ raise ArgumentError, "unknown option #{arg}"
132
+ else
133
+ files.push arg
134
+ end
135
+ end
136
+
137
+ writer = JSONWriter.new(@out)
138
+
139
+ files.each do |file|
140
+ parser = Seafoam::BGV::BGVParser.new(file)
141
+ parser.read_file_header
142
+ parser.skip_document_props
143
+
144
+ loop do
145
+ index, = parser.read_graph_preheader
146
+ break unless index
147
+
148
+ graph_header = parser.read_graph_header
149
+ name = parser.graph_name(graph_header)
150
+ graph = parser.read_graph
151
+
152
+ writer.write name, graph
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def cfg2asm(*args)
159
+ case args.first
160
+ when nil, 'help', '-h', '--help', '-help'
161
+ args = args.drop(1)
162
+ raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
163
+
164
+ @out.puts 'cfg2asm file.bgv...'
165
+ @out.puts ' --no-comments'
166
+ @out.puts ' --help'
167
+ @out.puts ' --version'
168
+ when 'version', '-v', '-version', '--version'
169
+ args = args.drop(1)
170
+ version(*args)
171
+ else
172
+ comments = true
173
+ files = []
174
+
175
+ until args.empty?
176
+ arg = args.shift
177
+ if arg.start_with?('-')
178
+ case arg
179
+ when '--no-comments'
180
+ comments = false
181
+ else
182
+ raise ArgumentError, "unknown option #{arg}"
183
+ end
184
+ else
185
+ files.push arg
186
+ end
187
+ end
188
+
189
+ files.each_with_index do |file, n|
190
+ parser = Seafoam::CFG::CFGParser.new(@out, file)
191
+ parser.skip_over_cfg 'After code installation'
192
+ nmethod = parser.read_nmethod
193
+
194
+ disassembler = Seafoam::CFG::Disassembler.new(@out)
195
+ @out.puts if n.positive?
196
+ @out.puts "[#{file}]"
197
+ disassembler.disassemble(nmethod, comments)
198
+ end
199
+ end
200
+ end
201
+
45
202
  private
46
203
 
47
204
  # seafoam file.bgv info
@@ -218,7 +375,24 @@ module Seafoam
218
375
  end
219
376
  end
220
377
 
221
- # seafoam file.bgv:0 render options...
378
+ # seafoam file.bgv:n:n source
379
+ def source(name, *args)
380
+ file, graph_index, node_id, edge_id = parse_name(name)
381
+ raise ArgumentError, 'source needs a node' unless node_id
382
+ raise ArgumentError, 'source only works with a node' if edge_id
383
+ raise ArgumentError, 'source does not take arguments' unless args.empty?
384
+
385
+ with_graph(file, graph_index) do |parser|
386
+ parser.read_graph_header
387
+ graph = parser.read_graph
388
+ node = graph.nodes[node_id]
389
+ raise ArgumentError, 'node not found' unless node
390
+
391
+ @out.puts Graal::Source.render(node.props['nodeSourcePosition'])
392
+ end
393
+ end
394
+
395
+ # seafoam file.bgv:n render options...
222
396
  def render(name, *args)
223
397
  file, graph_index, *rest = parse_name(name)
224
398
  raise ArgumentError, 'render needs at least a graph' unless graph_index
@@ -233,6 +407,7 @@ module Seafoam
233
407
  args = args.dup
234
408
  out_file = nil
235
409
  explicit_out_file = false
410
+ draw_blocks = false
236
411
  until args.empty?
237
412
  arg = args.shift
238
413
  case arg
@@ -251,6 +426,8 @@ module Seafoam
251
426
  annotator_options[:hide_floating] = true
252
427
  when '--no-reduce-edges'
253
428
  annotator_options[:reduce_edges] = false
429
+ when '--draw-blocks'
430
+ draw_blocks = true
254
431
  when '--option'
255
432
  key = args.shift
256
433
  raise ArgumentError, 'no key for --option' unless key
@@ -296,13 +473,17 @@ module Seafoam
296
473
  if out_format == :dot
297
474
  File.open(out_file, 'w') do |stream|
298
475
  writer = GraphvizWriter.new(stream)
299
- writer.write_graph graph
476
+ writer.write_graph graph, false, draw_blocks
300
477
  end
301
478
  else
302
- IO.popen(['dot', "-T#{out_format}", '-o', out_file], 'w') do |stream|
303
- writer = GraphvizWriter.new(stream)
304
- hidpi = out_format == :png
305
- writer.write_graph graph, hidpi
479
+ begin
480
+ IO.popen(['dot', "-T#{out_format}", '-o', out_file], 'w') do |stream|
481
+ writer = GraphvizWriter.new(stream)
482
+ hidpi = out_format == :png
483
+ writer.write_graph graph, hidpi, draw_blocks
484
+ end
485
+ rescue Errno::ENOENT
486
+ raise 'Could not run Graphviz - is it installed?'
306
487
  end
307
488
  autoopen out_file unless explicit_out_file
308
489
  end
@@ -388,27 +569,6 @@ module Seafoam
388
569
  raise ArgumentError, 'graph not found' unless graph_found
389
570
  end
390
571
 
391
- # Prints help.
392
- def help(*args)
393
- raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
394
-
395
- @out.puts 'seafoam file.bgv info'
396
- @out.puts ' file.bgv list'
397
- @out.puts ' file.bgv[:graph][:node[-edge]] search term...'
398
- @out.puts ' file.bgv[:graph][:node[-edge]] edges'
399
- @out.puts ' file.bgv[:graph][:node[-edge]] props'
400
- @out.puts ' file.bgv:graph render'
401
- @out.puts ' --spotlight n,n,n...'
402
- @out.puts ' --out graph.pdf'
403
- @out.puts ' graph.svg'
404
- @out.puts ' graph.png'
405
- @out.puts ' graph.dot'
406
- @out.puts ' --show-frame-state'
407
- @out.puts ' --hide-floating'
408
- @out.puts ' --no-reduce-edges'
409
- @out.puts ' --option key value'
410
- end
411
-
412
572
  # Prints the version.
413
573
  def version(*args)
414
574
  raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
@@ -0,0 +1,23 @@
1
+ module Seafoam
2
+ module Graal
3
+ # Routines for understanding source positions in Graal.
4
+ module Source
5
+ def self.render(source_position)
6
+ lines = []
7
+ caller = source_position
8
+ while caller
9
+ method = caller[:method]
10
+ lines.push render_method(method)
11
+ caller = caller[:caller]
12
+ end
13
+ lines.join("\n")
14
+ end
15
+
16
+ def self.render_method(method)
17
+ declaring_class = method[:declaring_class]
18
+ name = method[:method_name]
19
+ "#{declaring_class}##{name}"
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/seafoam/graph.rb CHANGED
@@ -2,12 +2,13 @@ module Seafoam
2
2
  # A graph, with properties, nodes, and edges. We don't encapsulate the graph
3
3
  # too much - be careful.
4
4
  class Graph
5
- attr_reader :props, :nodes, :edges
5
+ attr_reader :props, :nodes, :edges, :blocks
6
6
 
7
7
  def initialize(props = nil)
8
8
  @props = props || {}
9
9
  @nodes = {}
10
10
  @edges = []
11
+ @blocks = []
11
12
  end
12
13
 
13
14
  # Create a node.
@@ -27,6 +28,14 @@ module Seafoam
27
28
  to.inputs.push edge
28
29
  edge
29
30
  end
31
+
32
+ # Add a new basic block with given id and node id list.
33
+ def create_block(id, node_ids)
34
+ nodes = node_ids.select { |n| @nodes.key? n }.map { |n| @nodes[n] }
35
+ block = Block.new(id, nodes)
36
+ @blocks.push block
37
+ block
38
+ end
30
39
  end
31
40
 
32
41
  # A node, with properties, input edges, and output edges.
@@ -88,4 +97,19 @@ module Seafoam
88
97
  "<Edge #{from.id} -> #{to.id}>"
89
98
  end
90
99
  end
100
+
101
+ # A control-flow basic block
102
+ class Block
103
+ attr_reader :id, :nodes
104
+
105
+ def initialize(id, nodes)
106
+ @id = id
107
+ @nodes = nodes
108
+ end
109
+
110
+ # Inspect.
111
+ def inspect
112
+ "<Block #{id}>"
113
+ end
114
+ end
91
115
  end
@@ -7,15 +7,16 @@ module Seafoam
7
7
  end
8
8
 
9
9
  # Write a graph.
10
- def write_graph(graph, hidpi = false)
10
+ def write_graph(graph, hidpi = false, draw_blocks = false)
11
11
  inline_attrs = {}
12
12
  attrs = {}
13
13
  attrs[:dpi] = 200 if hidpi
14
- attrs[:bgcolor] = 'transparent'
14
+ attrs[:bgcolor] = 'white'
15
15
  @stream.puts 'digraph G {'
16
16
  @stream.puts " graph #{write_attrs(attrs)};"
17
17
  write_nodes inline_attrs, graph
18
18
  write_edges inline_attrs, graph
19
+ write_blocks graph if draw_blocks
19
20
  @stream.puts '}'
20
21
  end
21
22
 
@@ -166,6 +167,24 @@ module Seafoam
166
167
  end
167
168
  end
168
169
 
170
+ # Write basic block outlines.
171
+ def write_blocks(graph)
172
+ graph.blocks.each do |block|
173
+ @stream.puts " subgraph cluster_block#{block.id} {"
174
+ @stream.puts ' fontname = "Arial";'
175
+ @stream.puts " label = \"B#{block.id}\";"
176
+ @stream.puts ' style=dotted;'
177
+
178
+ block.nodes.each do |node|
179
+ next if node.props[:hidden] || node.props[:inlined]
180
+
181
+ @stream.puts " node#{node.id};"
182
+ end
183
+
184
+ @stream.puts ' }'
185
+ end
186
+ end
187
+
169
188
  # Return attributes for a node or edge modified to 'shade' them in terms
170
189
  # the spotlight functionality - so basically make them light grey.
171
190
  def shade(attrs)