evoasm 0.0.2.pre7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gemrelease +2 -0
  3. data/.gitignore +16 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.rake +8 -0
  6. data/Gemfile.rake.lock +51 -0
  7. data/LICENSE.txt +373 -0
  8. data/Makefile +6 -0
  9. data/README.md +43 -0
  10. data/Rakefile +128 -0
  11. data/bin/gdb +2 -0
  12. data/data/tables/README.md +19 -0
  13. data/data/tables/x64.csv +1684 -0
  14. data/data/templates/evoasm-x64.c.erb +319 -0
  15. data/data/templates/evoasm-x64.h.erb +126 -0
  16. data/evoasm.gemspec +30 -0
  17. data/examples/abs.yml +20 -0
  18. data/examples/popcnt.yml +17 -0
  19. data/examples/sym_reg.yml +26 -0
  20. data/exe/evoasm-search +13 -0
  21. data/ext/evoasm_ext/evoasm-alloc.c +145 -0
  22. data/ext/evoasm_ext/evoasm-alloc.h +59 -0
  23. data/ext/evoasm_ext/evoasm-arch.c +44 -0
  24. data/ext/evoasm_ext/evoasm-arch.h +161 -0
  25. data/ext/evoasm_ext/evoasm-bitmap.h +114 -0
  26. data/ext/evoasm_ext/evoasm-buf.c +130 -0
  27. data/ext/evoasm_ext/evoasm-buf.h +47 -0
  28. data/ext/evoasm_ext/evoasm-error.c +31 -0
  29. data/ext/evoasm_ext/evoasm-error.h +75 -0
  30. data/ext/evoasm_ext/evoasm-free-list.c.tmpl +121 -0
  31. data/ext/evoasm_ext/evoasm-free-list.h.tmpl +86 -0
  32. data/ext/evoasm_ext/evoasm-log.c +108 -0
  33. data/ext/evoasm_ext/evoasm-log.h +69 -0
  34. data/ext/evoasm_ext/evoasm-misc.c +23 -0
  35. data/ext/evoasm_ext/evoasm-misc.h +282 -0
  36. data/ext/evoasm_ext/evoasm-param.h +37 -0
  37. data/ext/evoasm_ext/evoasm-search.c +2145 -0
  38. data/ext/evoasm_ext/evoasm-search.h +214 -0
  39. data/ext/evoasm_ext/evoasm-util.h +40 -0
  40. data/ext/evoasm_ext/evoasm-x64.c +275624 -0
  41. data/ext/evoasm_ext/evoasm-x64.h +5436 -0
  42. data/ext/evoasm_ext/evoasm.c +7 -0
  43. data/ext/evoasm_ext/evoasm.h +23 -0
  44. data/ext/evoasm_ext/evoasm_ext.c +1757 -0
  45. data/ext/evoasm_ext/extconf.rb +31 -0
  46. data/lib/evoasm/cli/search.rb +127 -0
  47. data/lib/evoasm/cli.rb +6 -0
  48. data/lib/evoasm/core_ext/array.rb +9 -0
  49. data/lib/evoasm/core_ext/integer.rb +10 -0
  50. data/lib/evoasm/core_ext/kwstruct.rb +13 -0
  51. data/lib/evoasm/core_ext/range.rb +5 -0
  52. data/lib/evoasm/core_ext.rb +1 -0
  53. data/lib/evoasm/error.rb +20 -0
  54. data/lib/evoasm/examples.rb +27 -0
  55. data/lib/evoasm/gen/enum.rb +169 -0
  56. data/lib/evoasm/gen/name_util.rb +80 -0
  57. data/lib/evoasm/gen/state.rb +176 -0
  58. data/lib/evoasm/gen/state_dsl.rb +152 -0
  59. data/lib/evoasm/gen/strio.rb +27 -0
  60. data/lib/evoasm/gen/translator.rb +1102 -0
  61. data/lib/evoasm/gen/version.rb +5 -0
  62. data/lib/evoasm/gen/x64/funcs.rb +495 -0
  63. data/lib/evoasm/gen/x64/inst.rb +781 -0
  64. data/lib/evoasm/gen/x64.rb +237 -0
  65. data/lib/evoasm/gen.rb +8 -0
  66. data/lib/evoasm/program.rb +23 -0
  67. data/lib/evoasm/search.rb +40 -0
  68. data/lib/evoasm/tasks/gen_task.rb +86 -0
  69. data/lib/evoasm/tasks/template_task.rb +52 -0
  70. data/lib/evoasm/version.rb +3 -0
  71. data/lib/evoasm.rb +22 -0
  72. data/test/test_helper.rb +1 -0
  73. data/test/x64/test_helper.rb +19 -0
  74. data/test/x64/x64_test.rb +87 -0
  75. metadata +221 -0
@@ -0,0 +1,31 @@
1
+ require 'mkmf'
2
+
3
+ RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
4
+
5
+ if have_header('capstone/capstone.h')
6
+ $LDFLAGS << ' -lcapstone'
7
+ end
8
+
9
+ $warnflags.gsub! '-Wdeclaration-after-statement', ''
10
+
11
+ $CFLAGS << ' -std=c11 -pedantic -fstrict-aliasing'
12
+ $warnflags << ' -Wextra -Wall -Wno-unused-label -Wuninitialized'\
13
+ ' -Wswitch-default -Wstrict-aliasing=3 -Wunreachable-code'\
14
+ ' -Wundef -Wpointer-arith -Wwrite-strings -Wconversion -Winit-self -Wno-unused-parameter'
15
+
16
+ $LDFLAGS << ''
17
+
18
+ if RbConfig::MAKEFILE_CONFIG['CC'] =~ /clang/
19
+ $warnflags << ' -Wno-unknown-warning-option -Wno-parentheses-equality -Wno-error=ignored-attributes'\
20
+ ' -Wno-missing-field-initializers -Wno-missing-braces'
21
+ end
22
+
23
+ if enable_config('debug')
24
+ $warnflags << ' -Werror -Wno-error=unused-function -Wno-error=pedantic'\
25
+ ' -Wno-error=implicit-function-declaration'
26
+ $defs.push('-DEVOASM_MIN_LOG_LEVEL=EVOASM_LOG_LEVEL_DEBUG')
27
+ $CFLAGS.gsub!(/-O\d/, '')
28
+ $CFLAGS << ' -O0 -g3 -fno-omit-frame-pointer'
29
+ end
30
+
31
+ create_makefile('evoasm_ext')
@@ -0,0 +1,127 @@
1
+ require 'yaml'
2
+ require 'pastel'
3
+ require 'pry'
4
+
5
+ module Evoasm
6
+ module Cli
7
+ class Search
8
+ attr_reader :filename
9
+
10
+ def initialize(filename, options)
11
+ @filename = filename
12
+ raise ArgumentError, 'filename is nil' if filename.nil?
13
+
14
+ if options.any?{|o| o =~ /\--log-level=(\d)/}
15
+ Evoasm.log_level = $1.to_i
16
+ end
17
+ end
18
+
19
+ def start!
20
+ x64 = X64.new
21
+ params = YAML.load(File.read filename)
22
+ insts = filter_insts x64.instructions, params['instructions']
23
+
24
+ p insts.map(&:name)
25
+ pastel = Pastel.new
26
+
27
+ program_size = parse_range params['program_size']
28
+ kernel_size = parse_range params['kernel_size']
29
+ program_counter = 0
30
+ max_programs = params['max_programs']
31
+ recur_limit = params['recur_limit'] || 0
32
+
33
+ domains = convert_domains_hash params['domains']
34
+ parameters = (params['parameters'] || %i(reg0 reg1 reg2 imm0 imm1)).map(&:to_sym)
35
+
36
+ start_ts = Time.now
37
+
38
+ search = Evoasm::Search.new x64,
39
+ examples: params['examples'],
40
+ instructions: insts,
41
+ kernel_size: kernel_size,
42
+ program_size: program_size,
43
+ population_size: params['population_size'],
44
+ parameters: parameters,
45
+ domains: domains,
46
+ recur_limit: recur_limit
47
+
48
+ search.start!(params['max_loss'] || 0.0) do |program, loss|
49
+ ts = Time.now
50
+ puts pastel.bold "Program #{program_counter}, #{ts.strftime '%H:%M:%S'} (found after #{(ts - start_ts).to_i} seconds)"
51
+
52
+ if program.buffer.respond_to? :disassemble
53
+ puts program.buffer.disassemble.join "\n"
54
+ else
55
+ puts program.instructions.map(&:name)
56
+ end
57
+
58
+ puts
59
+
60
+ if params['console'] != false
61
+ binding.pry
62
+ end
63
+
64
+ program_counter += 1
65
+
66
+ if program_counter == max_programs
67
+ # stops search
68
+ return false
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+ def filter_insts(insts, params)
75
+ op_types = %i(rm reg imm)
76
+ reg_types = %i(rflags)
77
+ bad_regs = %i(SP IP)
78
+
79
+ grep_regexp = params && Regexp.new(params['grep']) rescue nil
80
+ grep_v_regexp = params && Regexp.new(params['grep_v']) rescue nil
81
+
82
+ reg_types.concat params['reg_types'].map(&:to_sym)
83
+
84
+ insts.select do |inst|
85
+ next false if grep_regexp && inst.name !~ grep_regexp
86
+ next false if grep_v_regexp && inst.name =~ grep_v_regexp
87
+ next false if inst.operands.size == 0
88
+
89
+ inst.operands.all? do |op|
90
+ next false unless op_types.include? op.type
91
+ if op.register
92
+ next false unless reg_types.include?(op.register.type)
93
+ next false if bad_regs.include? op.register.name
94
+ end
95
+
96
+ true
97
+ end
98
+ end
99
+ end
100
+
101
+ def convert_domains_hash(hash)
102
+ Hash(hash).map do |k, v|
103
+ new_k = k.to_sym
104
+ new_v =
105
+ case v
106
+ when Array
107
+ v.map {|e| e.is_a?(String) ? e.to_sym : e }
108
+ else
109
+ v
110
+ end
111
+ [new_k, new_v]
112
+ end.to_h
113
+ end
114
+
115
+ def parse_range(str)
116
+ case str
117
+ when Integer
118
+ str
119
+ when /^\(?(\d+)\.\.(\.?)(\d+)\)?$/
120
+ Range.new($1.to_i, $3.to_i, !$2.empty?)
121
+ else
122
+ raise ArgumentError, "invalid range '#{str}'"
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
data/lib/evoasm/cli.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Evoasm
2
+ module Cli
3
+ end
4
+ end
5
+
6
+ require 'evoasm/cli/search'
@@ -0,0 +1,9 @@
1
+ class Array
2
+ def keys
3
+ map { |k, _v| k }
4
+ end
5
+
6
+ def values
7
+ map { |_k, v| v }
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class Integer
2
+ INT8_MAX = 0x7f
3
+ INT8_MIN = -INT8_MAX - 1
4
+ INT16_MAX = 0x7fff
5
+ INT16_MIN = -INT16_MAX - 1
6
+ INT32_MAX = 0x7fffffff
7
+ INT32_MIN = -INT32_MAX - 1
8
+ INT64_MAX = 0x7fffffffffffffff
9
+ INT64_MIN = -INT64_MAX - 1
10
+ end
@@ -0,0 +1,13 @@
1
+ # The MIT License (MIT)
2
+ # Copyright (c) 2015 Maxim Chernyak
3
+ class KwStruct < Struct
4
+ def self.new(*members, &block)
5
+ super.tap do |struct_class|
6
+ struct_class.class_eval <<-RUBY
7
+ def initialize(#{members.map { |m| "#{m}: nil" }.join(', ')})
8
+ super(#{members.join(', ')})
9
+ end
10
+ RUBY
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class Range
2
+ def sample
3
+ rand self
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'evoasm/core_ext/range'
@@ -0,0 +1,20 @@
1
+ module Evoasm
2
+ class Error
3
+ def message
4
+ msg = __message
5
+
6
+ case code
7
+ when :not_encodable
8
+ "#{msg} #{parameter}"
9
+ when :missing_param
10
+ "#{msg} (#{parameter})"
11
+ when :missing_feature
12
+ "missing features #{features.join ', '}"
13
+ when :invalid_access
14
+ "#{msg} (#{instruction}/#{register})"
15
+ else
16
+ msg || code
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ module Evoasm
2
+ module Examples
3
+
4
+ class << self
5
+ def convert(examples)
6
+ examples.inject(nil) do |(in_arity, out_arity), example|
7
+ inputs, outputs = example
8
+ example_in_arity = inputs.size
9
+ example_out_arity = outputs.size
10
+
11
+ validate_example inputs, example_in_arity, in_arity
12
+ validate_example outputs, example_out_arity, out_arity
13
+
14
+ [example_in_arity, example_out_arity]
15
+ end.unshift(examples.flatten)
16
+ end
17
+
18
+ private
19
+ def validate_example(example, example_arity, arity)
20
+ if arity && arity != example_arity
21
+ raise ArgumentError, "invalid arity for example '#{example}'"\
22
+ " (#{example_arity} for #{arity})"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,169 @@
1
+ require 'evoasm/gen/strio'
2
+ require 'evoasm/gen/name_util'
3
+
4
+ module Evoasm
5
+ module Gen
6
+ class Enum
7
+ include NameUtil
8
+
9
+ attr_reader :name, :flags
10
+ alias_method :flags?, :flags
11
+
12
+ def initialize(name = nil, elems = [], prefix: nil, flags: false)
13
+ @name = name
14
+ @prefix = prefix
15
+ @map = {}
16
+ @counter = 0
17
+ @flags = flags
18
+ add_all elems
19
+ end
20
+
21
+ def n
22
+ @counter
23
+ end
24
+
25
+ def to_c(io = StrIO.new, typedef: true)
26
+ raise 'name missing' if !name
27
+
28
+ type_name = c_type_name
29
+
30
+ io.puts "#{typedef ? 'typedef ' : ''}enum #{type_name} {"
31
+ io.indent do
32
+ each do |elem, value|
33
+ elem_name = elem_name_to_c elem
34
+ c_value =
35
+ if valid_elem?(value)
36
+ elem_name_to_c value
37
+ else
38
+ if flags?
39
+ "1 << #{value}"
40
+ else
41
+ "#{value}"
42
+ end
43
+ end
44
+ io.puts "#{elem_name} = #{c_value},"
45
+ end
46
+ if !flags?
47
+ io.puts n_elem_to_c
48
+ end
49
+ end
50
+ io.write '}'
51
+ io.write " #{type_name}" if typedef
52
+ io.puts ';'
53
+ io.puts "#define #{bitsize_to_c} #{bitsize}"
54
+ unless flags?
55
+ io.puts "#define #{bitsize_to_c true} #{bitsize true}"
56
+ else
57
+ io.puts "#define #{all_to_c} #{all_value}"
58
+ end
59
+
60
+ io.string
61
+ end
62
+
63
+ def bitsize(with_n = false)
64
+ if flags?
65
+ @map.size
66
+ else
67
+ Math.log2(max + 1 + (with_n ? 1 : 0)).ceil.to_i
68
+ end
69
+ end
70
+
71
+ def max
72
+ @map.each_with_index.inject(0) do |acc, (index, (k, v))|
73
+ if v
74
+ [v + 1, acc + 1].max
75
+ else
76
+ acc + 1
77
+ end
78
+ end - 1
79
+ end
80
+
81
+ def c_type(typedef = false)
82
+ "#{typedef ? '' : 'enum '}#{c_type_name}"
83
+ end
84
+
85
+ def c_type_name
86
+ name_to_c name, @prefix
87
+ end
88
+
89
+ def all_to_c
90
+ name_to_c "#{prefix_name}_all", @prefix, const: true
91
+ end
92
+
93
+ def all_value
94
+ (2**@map.size) - 1
95
+ end
96
+
97
+ def bitsize_to_c(with_n = false)
98
+ name_to_c "#{prefix_name}_bitsize#{with_n ? '_WITH_N' : ''}", @prefix, const: true
99
+ end
100
+
101
+ def n_elem_to_c
102
+ name_to_c "n_#{prefix_name}s", @prefix, const: true
103
+ end
104
+
105
+ def keys
106
+ @map.keys
107
+ end
108
+
109
+ def add(elem, alias_elem = nil)
110
+ fail ArgumentError, 'can only add symbols or strings' \
111
+ unless valid_elem?(elem) && (!alias_elem || valid_elem?(alias_elem))
112
+
113
+ return if @map.key? elem
114
+
115
+ value = alias_elem || @counter
116
+ @counter += 1 if alias_elem.nil?
117
+
118
+ @map[elem] = value
119
+ end
120
+
121
+ def add_all(elems)
122
+ elems.each do |elem|
123
+ add elem
124
+ end
125
+ end
126
+
127
+ def each(&block)
128
+ return to_enum(:each) if block.nil?
129
+ @map.each_key do |k|
130
+ block[k, self[k]]
131
+ end
132
+ end
133
+
134
+ def alias(key)
135
+ key = @map[key]
136
+ case key
137
+ when Symbol, String
138
+ key
139
+ else
140
+ nil
141
+ end
142
+ end
143
+
144
+ def [](elem)
145
+ value = @map[elem]
146
+
147
+ if @map.key? value
148
+ @map.fetch value
149
+ else
150
+ value
151
+ end
152
+ end
153
+
154
+ private
155
+ def prefix_name
156
+ name.to_s.sub(/_id$/, '')
157
+ end
158
+
159
+ def elem_name_to_c(elem_name)
160
+ # convention: _id does not appear in element's name
161
+ name_to_c elem_name, Array(@prefix) + [prefix_name], const: true
162
+ end
163
+
164
+ def valid_elem?(elem)
165
+ elem.is_a?(Symbol) || elem.is_a?(String)
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,80 @@
1
+ module Evoasm
2
+ module Gen
3
+ module NameUtil
4
+ def namespace
5
+ 'evoasm'
6
+ end
7
+
8
+ def const_name_to_c(name, prefix)
9
+ name_to_c name, prefix, const: true
10
+ end
11
+
12
+ def name_to_c(name, prefix = nil, const: false)
13
+ c_name = [namespace, *prefix, name.to_s.sub(/\?$/, '')].compact.join '_'
14
+ if const
15
+ c_name.upcase
16
+ else
17
+ c_name
18
+ end
19
+ end
20
+
21
+ def indep_arch_prefix(name = nil)
22
+ ['arch', name]
23
+ end
24
+
25
+ def arch_prefix(name = nil)
26
+ [arch, name]
27
+ end
28
+
29
+ def error_code_to_c(name)
30
+ prefix = name == :ok ? :error_code : indep_arch_prefix(:error_code)
31
+ const_name_to_c name, prefix
32
+ end
33
+
34
+ def reg_name_to_c(name)
35
+ const_name_to_c name, arch_prefix(:reg)
36
+ end
37
+
38
+ def exception_to_c(name)
39
+ const_name_to_c name, arch_prefix(:exception)
40
+ end
41
+
42
+ def reg_type_to_c(name)
43
+ const_name_to_c name, arch_prefix(:reg_type)
44
+ end
45
+
46
+ def operand_type_to_c(name)
47
+ const_name_to_c name, arch_prefix(:operand_type)
48
+ end
49
+
50
+ def inst_name_to_c(inst)
51
+ const_name_to_c inst.name, arch_prefix(:inst)
52
+ end
53
+
54
+ def operand_size_to_c(size)
55
+ const_name_to_c size, :operand_size
56
+ end
57
+
58
+ def bit_mask_to_c(mask)
59
+ name =
60
+ case mask
61
+ when Range then"#{mask.min}_#{mask.max}"
62
+ else mask.to_s
63
+ end
64
+ const_name_to_c name, arch_prefix(:bit_mask)
65
+ end
66
+
67
+ def feature_name_to_c(name)
68
+ const_name_to_c name, arch_prefix(:feature)
69
+ end
70
+
71
+ def inst_flag_to_c(flag)
72
+ const_name_to_c flag, arch_prefix(:inst_flag)
73
+ end
74
+
75
+ def param_name_to_c(name)
76
+ const_name_to_c name, arch_prefix(:param)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,176 @@
1
+ require 'set'
2
+
3
+ module Evoasm::Gen
4
+ State = Struct.new(:children, :actions, :ret, :_local_params) do
5
+ attr_accessor :id, :comment, :parents
6
+
7
+ def initialize
8
+ self.children = []
9
+ self.parents = []
10
+ self.actions = []
11
+ self._local_params = []
12
+ end
13
+
14
+ def local_params
15
+ child_local_params = children.map do |child, _, _|
16
+ child.local_params
17
+ end
18
+ all_local_params = (_local_params + child_local_params)
19
+ all_local_params.flatten!
20
+ all_local_params.uniq!
21
+
22
+ all_local_params
23
+ end
24
+
25
+ def add_local_param(param)
26
+ if param.to_s[0] != '_'
27
+ fail ArgumentError, 'params must start with underscore'
28
+ end
29
+
30
+ _local_params << param unless _local_params.include? param
31
+ end
32
+
33
+ protected def add_parent(parent)
34
+ parents << parent unless parents.include? parent
35
+ end
36
+
37
+ def add_child(child, cond = nil, priority)
38
+ child.add_parent self
39
+ children << [child, cond, priority]
40
+ end
41
+
42
+ %i(sets asserts calls writes debugs).each do |name|
43
+ action_name = name.to_s[0..-2].to_sym
44
+ define_method name do
45
+ actions.select { |action, _| action == action_name }
46
+ .map { |_, args| args }
47
+ end
48
+ end
49
+
50
+ private def roots
51
+ return [self] if parents.empty?
52
+ parents.flat_map(&:roots)
53
+ end
54
+
55
+ def root
56
+ roots = roots()
57
+ fail 'multiple roots' if roots.size > 1
58
+ roots.first
59
+ end
60
+
61
+ def empty?
62
+ actions.empty?
63
+ end
64
+
65
+ def terminal?
66
+ children.empty?
67
+ end
68
+
69
+ def ret?
70
+ ret != nil
71
+ end
72
+
73
+ def to_gv
74
+ require 'gv'
75
+
76
+ graph = GV::Graph.open 'ast'
77
+ graph[:ranksep] = 1.5
78
+ graph[:statesep] = 0.8
79
+ __to_gv__ graph
80
+ graph
81
+ end
82
+
83
+ def __to_gv__(graph, gv_parent = nil, cond = nil, attrs = {}, index = nil, seen = {})
84
+ if seen.key?(self)
85
+ # return
86
+ else
87
+ seen[self] = true
88
+ end
89
+
90
+ edge_label = ''
91
+ state_label = ''
92
+
93
+ if cond
94
+ if cond.first == :else
95
+ edge_label << "<b> else</b><br></br>\n"
96
+ else
97
+ edge_label << "<b> if</b> #{expr_to_s cond}<br></br>\n"
98
+ end
99
+ end
100
+
101
+ if attrs
102
+ attrs.each do |name, value|
103
+ edge_label << "<b> #{name}</b>: #{value}<br></br>\n"
104
+ end
105
+ end
106
+
107
+ actions.each do |name, args|
108
+ state_label << send(:"label_#{name}", *args)
109
+ end
110
+
111
+ state_label << "<i>#{comment}</i>\n" if comment
112
+
113
+ gv_state = graph.node object_id.to_s,
114
+ shape: (self.ret? ? :house : (state_label.empty? ? :point : :box)),
115
+ label: graph.html(state_label)
116
+
117
+ children.each_with_index do |(child, cond, attrs), index|
118
+ child.__to_gv__(graph, gv_state, cond, attrs, index, seen)
119
+ end
120
+
121
+ if gv_parent
122
+ graph.edge gv_parent.name + '.' + gv_state.name + index.to_s,
123
+ gv_parent, gv_state,
124
+ label: graph.html(edge_label)
125
+ end
126
+
127
+ graph
128
+ end
129
+
130
+ private
131
+
132
+ def label_set(name, value, _options = {})
133
+ "<b>set</b> #{name} := #{expr_to_s value}<br></br>"
134
+ end
135
+
136
+ def label_assert(cond)
137
+ "<b>assert</b> #{expr_to_s cond}<br></br>"
138
+ end
139
+
140
+ def label_call(name)
141
+ "<b>call</b> #{name}<br></br>"
142
+ end
143
+
144
+ def label_debug(_format, *_args)
145
+ ''
146
+ end
147
+
148
+ def label_write(value, size)
149
+ label =
150
+ if value.is_a?(Integer) && size.is_a?(Integer)
151
+ if size == 8
152
+ 'x%x' % value
153
+ else
154
+ "b%0#{size}b" % value
155
+ end
156
+ elsif size.is_a? Array
157
+ Array(value).zip(Array(size)).map do |v, s|
158
+ "#{expr_to_s v} [#{expr_to_s s}]"
159
+ end.join ', '
160
+ else
161
+ "#{expr_to_s value} [#{expr_to_s size}]"
162
+ end
163
+ "<b>output</b> #{label}<br></br>"
164
+ end
165
+
166
+ def expr_to_s(pred)
167
+ case pred
168
+ when Array
169
+ pred, *args = *pred
170
+ "#{pred}(#{args.map { |a| expr_to_s(a) }.join(', ')})"
171
+ else
172
+ pred
173
+ end
174
+ end
175
+ end
176
+ end