/
laser_proc.rb
155 lines (138 loc) · 5.07 KB
/
laser_proc.rb
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
module Laser
module Analysis
class LaserProc < LaserObject
attr_accessor :ast_node, :arguments, :cfg, :exit_block, :lexical_self
attr_accessor :annotations, :line_number
def initialize(arguments, ast_node, cfg = nil, callsite_block = nil)
@ast_node = ast_node
@arguments = arguments
@cfg = cfg
@callsite_block = callsite_block
@lexical_self = @exit_block = nil
@annotations = Hash.new { |h, k| h[k] = [] }
end
def start_block
@callsite_block && @callsite_block.block_taken_successors.first
end
def inspect
desc_part = "Proc:0x#{object_id.to_s(16)}@#{ast_node.file_name}"
arg_part = "(#{arguments.map(&:name).join(', ')})"
if ast_node.source_begin
"#<#{desc_part}:#{ast_node.line_number}*#{arg_part}>"
else
"#<#{desc_part}*#{arg_part}>"
end
end
alias name inspect
def compiled_cfg
return @cfg if @cfg
# since this is lazily compiling, we should update cref to reflect the runtime
# value before compiling. hackish.
if @ast_node.scope.self_ptr != Scope::GlobalScope.self_ptr
@ast_node.scope.lexical_target = @ast_node.scope.self_ptr.value.binding
end
builder = ControlFlow::GraphBuilder.new(@ast_node, @arguments, @ast_node.scope)
@cfg = builder.build
end
def simulate(args, block, opts={})
update_cfg_edges(opts) if opts[:invocation_sites]
self_to_use = opts[:self] || @lexical_self
cfg.simulate(args, opts.merge({self: self_to_use, block: block, start_block: start_block}))
end
def update_cfg_edges(opts)
opts[:invocation_sites][self].each do |callsite|
opts[:invocation_counts][self][callsite] += 1
callsite_succ = callsite.successors.find do |b|
b.name == start_block.name || b.name.include?("SSA-FIX-#{start_block.name}")
end
if !callsite.has_flag?(callsite_succ, RGL::ControlFlowGraph::EDGE_EXECUTABLE)
callsite.add_flag(callsite_succ, RGL::ControlFlowGraph::EDGE_EXECUTABLE)
else
exit_succ = exit_block.successors.find do |b|
b.name == start_block.name || b.name.include?("SSA-FIX-#{start_block.name}")
end
exit_block.add_flag(exit_succ, RGL::ControlFlowGraph::EDGE_EXECUTABLE)
end
end
end
def to_proc
self
end
def call(*args, &blk)
end
def ssa_cfg
@ssa_cfg ||= compiled_cfg.tap do |cfg|
cfg.perform_dead_code_discovery(true)
Laser.debug_puts('>>> Starting SSA Transformation <<<')
cfg.static_single_assignment_form
Laser.debug_puts('>>> Finished SSA Transformation <<<')
end
end
def klass
ClassRegistry['Proc']
end
%w(special pure builtin predictable mutation).each do |attr|
define_method(attr) do
default = attr == 'predictable'
note = self.annotations[attr]
which_value = note && note.first(&:literal?)
which_value ? which_value.literal : default
end
end
def annotated_return
notes = annotations['returns']
if notes.any?
if notes.size > 1
raise ArgumentError.new("Cannot have more than one 'returns' annotation")
end
return_type = notes.first
if return_type.type?
return_type.type
else
raise NotImplementedError.new('Literal annotated return types not implemented')
end
end
end
def annotated_yield_usage
notes = annotations['yield_usage']
if notes.any?
if notes.size > 1
raise ArgumentError.new("Cannot have more than one 'yield_usage' annotation")
end
yield_usage = notes.first
if yield_usage.type?
raise ArgumentError.new('yield_usage requires a literal yield usage category')
else
yield_usage.literal
end
end
end
def overloads
Hash[*(annotations['overload'].map do |overload|
raise ArgumentError.new('overload must be a type') unless overload.type?
proc_type = overload.type
unless Types::GenericType === proc_type && proc_type.base_type == Types::PROC
raise ArgumentError.new('overload must be a function type')
end
[proc_type.subtypes[0].element_types, proc_type]
end.flatten(1))]
end
def annotated_raise_frequency
if annotations['raises'].any?
annotations['raises'].select(&:literal?).map(&:literal).each do |literal|
return Frequency[literal] if !literal || Symbol === literal
end
nil
end
end
def raises
if annotations['raises'].any?
types = annotations['raises'].select(&:type?)
if types.any?
Types::UnionType.new(types.map { |note| note.type })
end
end
end
end
end
end