konpeito 0.1.0
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.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -0
- data/CONTRIBUTING.md +123 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/Rakefile +11 -0
- data/bin/konpeito +6 -0
- data/konpeito.gemspec +43 -0
- data/lib/konpeito/ast/typed_ast.rb +620 -0
- data/lib/konpeito/ast/visitor.rb +78 -0
- data/lib/konpeito/cache/cache_manager.rb +230 -0
- data/lib/konpeito/cache/dependency_graph.rb +192 -0
- data/lib/konpeito/cache.rb +8 -0
- data/lib/konpeito/cli/base_command.rb +187 -0
- data/lib/konpeito/cli/build_command.rb +220 -0
- data/lib/konpeito/cli/check_command.rb +104 -0
- data/lib/konpeito/cli/config.rb +231 -0
- data/lib/konpeito/cli/deps_command.rb +128 -0
- data/lib/konpeito/cli/doctor_command.rb +340 -0
- data/lib/konpeito/cli/fmt_command.rb +199 -0
- data/lib/konpeito/cli/init_command.rb +312 -0
- data/lib/konpeito/cli/lsp_command.rb +40 -0
- data/lib/konpeito/cli/run_command.rb +150 -0
- data/lib/konpeito/cli/test_command.rb +248 -0
- data/lib/konpeito/cli/watch_command.rb +212 -0
- data/lib/konpeito/cli.rb +301 -0
- data/lib/konpeito/codegen/builtin_methods.rb +229 -0
- data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
- data/lib/konpeito/codegen/debug_info.rb +352 -0
- data/lib/konpeito/codegen/inliner.rb +486 -0
- data/lib/konpeito/codegen/jvm_backend.rb +197 -0
- data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
- data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
- data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
- data/lib/konpeito/codegen/monomorphizer.rb +359 -0
- data/lib/konpeito/codegen/profile_runtime.c +341 -0
- data/lib/konpeito/codegen/profiler.rb +99 -0
- data/lib/konpeito/compiler.rb +592 -0
- data/lib/konpeito/dependency_resolver.rb +296 -0
- data/lib/konpeito/diagnostics/collector.rb +127 -0
- data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
- data/lib/konpeito/diagnostics/renderer.rb +144 -0
- data/lib/konpeito/formatter/formatter.rb +1214 -0
- data/lib/konpeito/hir/builder.rb +7167 -0
- data/lib/konpeito/hir/nodes.rb +2465 -0
- data/lib/konpeito/lsp/document_manager.rb +820 -0
- data/lib/konpeito/lsp/server.rb +183 -0
- data/lib/konpeito/lsp/transport.rb +38 -0
- data/lib/konpeito/parser/prism_adapter.rb +65 -0
- data/lib/konpeito/platform.rb +103 -0
- data/lib/konpeito/profile/report.rb +136 -0
- data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
- data/lib/konpeito/stdlib/compression/compression.rb +72 -0
- data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
- data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
- data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
- data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
- data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
- data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
- data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
- data/lib/konpeito/stdlib/http/extconf.rb +19 -0
- data/lib/konpeito/stdlib/http/http.rb +125 -0
- data/lib/konpeito/stdlib/http/http.rbs +57 -0
- data/lib/konpeito/stdlib/http/http_native.c +440 -0
- data/lib/konpeito/stdlib/json/extconf.rb +17 -0
- data/lib/konpeito/stdlib/json/json.rb +44 -0
- data/lib/konpeito/stdlib/json/json.rbs +33 -0
- data/lib/konpeito/stdlib/json/json_native.c +286 -0
- data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
- data/lib/konpeito/stdlib/ui/ui.rb +318 -0
- data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
- data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
- data/lib/konpeito/type_checker/inferrer.rb +565 -0
- data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
- data/lib/konpeito/type_checker/type_resolver.rb +276 -0
- data/lib/konpeito/type_checker/types.rb +1434 -0
- data/lib/konpeito/type_checker/unification.rb +323 -0
- data/lib/konpeito/ui/animation/animated_state.rb +80 -0
- data/lib/konpeito/ui/animation/easing.rb +59 -0
- data/lib/konpeito/ui/animation/value_tween.rb +66 -0
- data/lib/konpeito/ui/app.rb +379 -0
- data/lib/konpeito/ui/box.rb +38 -0
- data/lib/konpeito/ui/castella.rb +70 -0
- data/lib/konpeito/ui/castella_native.rb +76 -0
- data/lib/konpeito/ui/chart/area_chart.rb +305 -0
- data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
- data/lib/konpeito/ui/chart/base_chart.rb +210 -0
- data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
- data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
- data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
- data/lib/konpeito/ui/chart/line_chart.rb +289 -0
- data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
- data/lib/konpeito/ui/chart/scales.rb +77 -0
- data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
- data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
- data/lib/konpeito/ui/column.rb +271 -0
- data/lib/konpeito/ui/core.rb +2199 -0
- data/lib/konpeito/ui/dsl.rb +443 -0
- data/lib/konpeito/ui/frame.rb +171 -0
- data/lib/konpeito/ui/frame_native.rb +494 -0
- data/lib/konpeito/ui/markdown/ast.rb +124 -0
- data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
- data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
- data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
- data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
- data/lib/konpeito/ui/markdown/parser.rb +805 -0
- data/lib/konpeito/ui/markdown/renderer.rb +639 -0
- data/lib/konpeito/ui/markdown/theme.rb +165 -0
- data/lib/konpeito/ui/render_node.rb +260 -0
- data/lib/konpeito/ui/row.rb +207 -0
- data/lib/konpeito/ui/spacer.rb +18 -0
- data/lib/konpeito/ui/style.rb +799 -0
- data/lib/konpeito/ui/theme.rb +563 -0
- data/lib/konpeito/ui/themes/material.rb +35 -0
- data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
- data/lib/konpeito/ui/widgets/button.rb +103 -0
- data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
- data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
- data/lib/konpeito/ui/widgets/container.rb +91 -0
- data/lib/konpeito/ui/widgets/data_table.rb +667 -0
- data/lib/konpeito/ui/widgets/divider.rb +29 -0
- data/lib/konpeito/ui/widgets/image.rb +105 -0
- data/lib/konpeito/ui/widgets/input.rb +485 -0
- data/lib/konpeito/ui/widgets/markdown.rb +57 -0
- data/lib/konpeito/ui/widgets/modal.rb +163 -0
- data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
- data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
- data/lib/konpeito/ui/widgets/net_image.rb +100 -0
- data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
- data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
- data/lib/konpeito/ui/widgets/slider.rb +133 -0
- data/lib/konpeito/ui/widgets/switch.rb +84 -0
- data/lib/konpeito/ui/widgets/tabs.rb +157 -0
- data/lib/konpeito/ui/widgets/text.rb +110 -0
- data/lib/konpeito/ui/widgets/tree.rb +426 -0
- data/lib/konpeito/version.rb +5 -0
- data/lib/konpeito.rb +109 -0
- data/test_native_array.rb +172 -0
- data/test_native_array_class.rb +197 -0
- data/test_native_class.rb +151 -0
- data/tools/konpeito-asm/build.sh +65 -0
- data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
- metadata +267 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Konpeito
|
|
4
|
+
module Codegen
|
|
5
|
+
# Optimizes loops in HIR by:
|
|
6
|
+
# 1. Detecting natural loop structures (while/until)
|
|
7
|
+
# 2. Hoisting loop-invariant instructions to a preheader block
|
|
8
|
+
# 3. Recognizing known-pure method calls that can be safely moved
|
|
9
|
+
#
|
|
10
|
+
# This complements LLVM's opt passes which cannot optimize opaque
|
|
11
|
+
# rb_funcallv calls. HIR-level LICM can hoist calls like .length/.size
|
|
12
|
+
# that we know are side-effect-free.
|
|
13
|
+
class LoopOptimizer
|
|
14
|
+
# Methods known to be pure (no side effects, same result for same args)
|
|
15
|
+
PURE_METHODS = Set.new(%i[
|
|
16
|
+
length size count frozen? nil? empty?
|
|
17
|
+
first last class is_a? kind_of? instance_of?
|
|
18
|
+
equal? respond_to? object_id hash
|
|
19
|
+
abs ceil floor round truncate
|
|
20
|
+
even? odd? zero? positive? negative?
|
|
21
|
+
integer? float? finite? infinite? nan?
|
|
22
|
+
to_i to_f to_r to_c
|
|
23
|
+
min max minmax
|
|
24
|
+
ascii_only? encoding bytesize
|
|
25
|
+
key? has_key? include? member?
|
|
26
|
+
keys values
|
|
27
|
+
]).freeze
|
|
28
|
+
|
|
29
|
+
attr_reader :hoisted_count
|
|
30
|
+
|
|
31
|
+
def initialize(hir_program)
|
|
32
|
+
@hir_program = hir_program
|
|
33
|
+
@hoisted_count = 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def optimize
|
|
37
|
+
@hir_program.functions.each do |func|
|
|
38
|
+
optimize_function(func)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# Represents a natural loop in the CFG
|
|
45
|
+
LoopInfo = Struct.new(:header, :body_blocks, :exit_block, :preheader_source, keyword_init: true)
|
|
46
|
+
|
|
47
|
+
def optimize_function(func)
|
|
48
|
+
block_map = build_block_map(func)
|
|
49
|
+
loops = detect_loops(func, block_map)
|
|
50
|
+
|
|
51
|
+
loops.each do |loop_info|
|
|
52
|
+
hoist_invariants(func, loop_info, block_map)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Build a map from block label to BasicBlock
|
|
57
|
+
def build_block_map(func)
|
|
58
|
+
map = {}
|
|
59
|
+
func.body.each { |block| map[block.label] = block }
|
|
60
|
+
map
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Detect natural loops by finding back edges (Jump from body → cond)
|
|
64
|
+
def detect_loops(func, block_map)
|
|
65
|
+
loops = []
|
|
66
|
+
|
|
67
|
+
func.body.each_with_index do |block, idx|
|
|
68
|
+
# Look for the pattern:
|
|
69
|
+
# block_before → Jump(while_cond)
|
|
70
|
+
# while_cond → Branch(while_body, while_exit)
|
|
71
|
+
# while_body → Jump(while_cond) [back edge]
|
|
72
|
+
next unless block.label =~ /^(while|until)_cond/
|
|
73
|
+
|
|
74
|
+
cond_block = block
|
|
75
|
+
term = cond_block.terminator
|
|
76
|
+
next unless term.is_a?(HIR::Branch)
|
|
77
|
+
|
|
78
|
+
# Determine body and exit based on loop type
|
|
79
|
+
if cond_block.label.start_with?("while_cond")
|
|
80
|
+
body_label = term.then_block
|
|
81
|
+
exit_label = term.else_block
|
|
82
|
+
else # until_cond - condition is inverted
|
|
83
|
+
body_label = term.else_block
|
|
84
|
+
exit_label = term.then_block
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
body_block = block_map[body_label]
|
|
88
|
+
next unless body_block
|
|
89
|
+
|
|
90
|
+
# Verify back edge exists (body → cond)
|
|
91
|
+
back_edge = has_back_edge?(body_block, cond_block.label, block_map)
|
|
92
|
+
next unless back_edge
|
|
93
|
+
|
|
94
|
+
# Find the block that jumps to cond (preheader source)
|
|
95
|
+
preheader = find_preheader_source(func, cond_block.label, body_label, block_map)
|
|
96
|
+
|
|
97
|
+
# Collect all blocks that form the loop body
|
|
98
|
+
body_blocks = collect_loop_body_blocks(cond_block.label, body_label, block_map)
|
|
99
|
+
|
|
100
|
+
loops << LoopInfo.new(
|
|
101
|
+
header: cond_block,
|
|
102
|
+
body_blocks: body_blocks,
|
|
103
|
+
exit_block: block_map[exit_label],
|
|
104
|
+
preheader_source: preheader
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
loops
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Check if a block (or any successor within the loop) has a back edge to header
|
|
112
|
+
def has_back_edge?(block, header_label, block_map)
|
|
113
|
+
return true if block.terminator.is_a?(HIR::Jump) && block.terminator.target == header_label
|
|
114
|
+
if block.terminator.is_a?(HIR::Branch)
|
|
115
|
+
return true if block.terminator.then_block == header_label
|
|
116
|
+
return true if block.terminator.else_block == header_label
|
|
117
|
+
end
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Find the block that jumps to the loop header (not from within the loop)
|
|
122
|
+
def find_preheader_source(func, header_label, body_label, block_map)
|
|
123
|
+
func.body.each do |block|
|
|
124
|
+
next if block.label == body_label
|
|
125
|
+
next if block.label == header_label
|
|
126
|
+
next if block.label =~ /^(while|until)_body/ && block.terminator.is_a?(HIR::Jump) && block.terminator.target == header_label
|
|
127
|
+
|
|
128
|
+
if block.terminator.is_a?(HIR::Jump) && block.terminator.target == header_label
|
|
129
|
+
return block
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Collect all basic blocks that are part of the loop body
|
|
136
|
+
def collect_loop_body_blocks(header_label, body_label, block_map)
|
|
137
|
+
body_block = block_map[body_label]
|
|
138
|
+
return [body_block] unless body_block
|
|
139
|
+
|
|
140
|
+
# Simple: just return the body block for now
|
|
141
|
+
# For nested structures (if/else inside loop), we'd need to trace all reachable blocks
|
|
142
|
+
# that eventually jump back to the header
|
|
143
|
+
blocks = [body_block]
|
|
144
|
+
visited = Set.new([header_label, body_label])
|
|
145
|
+
|
|
146
|
+
# BFS to find all blocks within the loop
|
|
147
|
+
queue = []
|
|
148
|
+
term = body_block.terminator
|
|
149
|
+
if term.is_a?(HIR::Branch)
|
|
150
|
+
queue << term.then_block unless visited.include?(term.then_block)
|
|
151
|
+
queue << term.else_block unless visited.include?(term.else_block)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
while (label = queue.shift)
|
|
155
|
+
next if visited.include?(label)
|
|
156
|
+
visited << label
|
|
157
|
+
|
|
158
|
+
block = block_map[label]
|
|
159
|
+
next unless block
|
|
160
|
+
|
|
161
|
+
# Check if this block is within the loop (eventually reaches header)
|
|
162
|
+
if block_reaches_header?(block, header_label, block_map, Set.new)
|
|
163
|
+
blocks << block
|
|
164
|
+
term = block.terminator
|
|
165
|
+
if term.is_a?(HIR::Jump)
|
|
166
|
+
queue << term.target unless visited.include?(term.target)
|
|
167
|
+
elsif term.is_a?(HIR::Branch)
|
|
168
|
+
queue << term.then_block unless visited.include?(term.then_block)
|
|
169
|
+
queue << term.else_block unless visited.include?(term.else_block)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
blocks
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Check if a block can reach the loop header (part of the loop)
|
|
178
|
+
def block_reaches_header?(block, header_label, block_map, visited)
|
|
179
|
+
return false if visited.include?(block.label)
|
|
180
|
+
visited << block.label
|
|
181
|
+
|
|
182
|
+
term = block.terminator
|
|
183
|
+
return false unless term
|
|
184
|
+
|
|
185
|
+
if term.is_a?(HIR::Jump)
|
|
186
|
+
return true if term.target == header_label
|
|
187
|
+
next_block = block_map[term.target]
|
|
188
|
+
return next_block && block_reaches_header?(next_block, header_label, block_map, visited)
|
|
189
|
+
elsif term.is_a?(HIR::Branch)
|
|
190
|
+
return true if term.then_block == header_label || term.else_block == header_label
|
|
191
|
+
then_block = block_map[term.then_block]
|
|
192
|
+
else_block = block_map[term.else_block]
|
|
193
|
+
(then_block && block_reaches_header?(then_block, header_label, block_map, visited)) ||
|
|
194
|
+
(else_block && block_reaches_header?(else_block, header_label, block_map, visited))
|
|
195
|
+
else
|
|
196
|
+
false
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Hoist loop-invariant instructions from loop body to preheader
|
|
201
|
+
def hoist_invariants(func, loop_info, block_map)
|
|
202
|
+
return unless loop_info.preheader_source
|
|
203
|
+
|
|
204
|
+
# Collect variables modified inside the loop (cond + body blocks)
|
|
205
|
+
modified_vars = collect_modified_vars(loop_info)
|
|
206
|
+
|
|
207
|
+
# Collect variables used in the loop condition
|
|
208
|
+
cond_vars = collect_referenced_vars_in_block(loop_info.header)
|
|
209
|
+
|
|
210
|
+
# Find invariant instructions in the loop condition block
|
|
211
|
+
# (most common: arr.length in `while i < arr.length`)
|
|
212
|
+
invariants_from_cond = find_invariant_instructions(
|
|
213
|
+
loop_info.header, modified_vars, loop_info
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Find invariant instructions in body blocks
|
|
217
|
+
invariants_from_body = []
|
|
218
|
+
loop_info.body_blocks.each do |body_block|
|
|
219
|
+
invariants_from_body.concat(
|
|
220
|
+
find_invariant_instructions(body_block, modified_vars, loop_info)
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
all_invariants = invariants_from_cond + invariants_from_body
|
|
225
|
+
return if all_invariants.empty?
|
|
226
|
+
|
|
227
|
+
# Move invariant instructions to the preheader
|
|
228
|
+
preheader = loop_info.preheader_source
|
|
229
|
+
all_invariants.each do |inv|
|
|
230
|
+
source_block = inv[:block]
|
|
231
|
+
inst = inv[:instruction]
|
|
232
|
+
|
|
233
|
+
# Remove from source block
|
|
234
|
+
source_block.instructions.delete(inst)
|
|
235
|
+
|
|
236
|
+
# Add to preheader (before terminator, which is conceptually at the end)
|
|
237
|
+
preheader.instructions << inst
|
|
238
|
+
|
|
239
|
+
@hoisted_count += 1
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Collect all variables that are written to inside the loop
|
|
244
|
+
def collect_modified_vars(loop_info)
|
|
245
|
+
modified = Set.new
|
|
246
|
+
|
|
247
|
+
all_blocks = [loop_info.header] + loop_info.body_blocks
|
|
248
|
+
all_blocks.each do |block|
|
|
249
|
+
block.instructions.each do |inst|
|
|
250
|
+
case inst
|
|
251
|
+
when HIR::StoreLocal
|
|
252
|
+
modified << inst.var.to_s
|
|
253
|
+
when HIR::StoreInstanceVar
|
|
254
|
+
modified << inst.name.to_s
|
|
255
|
+
when HIR::StoreClassVar
|
|
256
|
+
modified << inst.name.to_s
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Instructions that produce results are also "modified"
|
|
260
|
+
if inst.respond_to?(:result_var) && inst.result_var
|
|
261
|
+
modified << inst.result_var.to_s
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
modified
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Collect variables referenced in a block's instructions
|
|
270
|
+
def collect_referenced_vars_in_block(block)
|
|
271
|
+
vars = Set.new
|
|
272
|
+
block.instructions.each do |inst|
|
|
273
|
+
collect_vars_from_instruction(inst, vars)
|
|
274
|
+
end
|
|
275
|
+
# Also check terminator condition
|
|
276
|
+
if block.terminator.is_a?(HIR::Branch) && block.terminator.condition
|
|
277
|
+
collect_vars_from_instruction(block.terminator.condition, vars)
|
|
278
|
+
end
|
|
279
|
+
vars
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Collect variable references from an instruction
|
|
283
|
+
def collect_vars_from_instruction(inst, vars)
|
|
284
|
+
case inst
|
|
285
|
+
when HIR::LoadLocal
|
|
286
|
+
vars << inst.var.to_s
|
|
287
|
+
when HIR::LoadInstanceVar
|
|
288
|
+
vars << inst.name.to_s
|
|
289
|
+
when HIR::Call
|
|
290
|
+
collect_vars_from_instruction(inst.receiver, vars) if inst.receiver
|
|
291
|
+
inst.args.each { |arg| collect_vars_from_instruction(arg, vars) }
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Find instructions that are invariant (don't depend on loop-modified vars)
|
|
296
|
+
def find_invariant_instructions(block, modified_vars, loop_info)
|
|
297
|
+
invariants = []
|
|
298
|
+
|
|
299
|
+
block.instructions.each do |inst|
|
|
300
|
+
next unless invariant_instruction?(inst, modified_vars, loop_info)
|
|
301
|
+
|
|
302
|
+
invariants << { block: block, instruction: inst }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
invariants
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Check if an instruction is loop-invariant
|
|
309
|
+
def invariant_instruction?(inst, modified_vars, loop_info)
|
|
310
|
+
case inst
|
|
311
|
+
when HIR::Call
|
|
312
|
+
# Only hoist calls to known-pure methods
|
|
313
|
+
return false unless PURE_METHODS.include?(inst.method_name.to_sym)
|
|
314
|
+
|
|
315
|
+
# Check that the receiver doesn't depend on loop-modified vars
|
|
316
|
+
return false unless operand_invariant?(inst.receiver, modified_vars)
|
|
317
|
+
|
|
318
|
+
# Check that all args don't depend on loop-modified vars
|
|
319
|
+
return false unless inst.args.all? { |arg| operand_invariant?(arg, modified_vars) }
|
|
320
|
+
|
|
321
|
+
# Don't hoist if the result_var is used as a store target in the loop
|
|
322
|
+
# (it shouldn't be, but safety check)
|
|
323
|
+
if inst.result_var
|
|
324
|
+
return false if modified_vars.include?(inst.result_var.to_s)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
true
|
|
328
|
+
|
|
329
|
+
when HIR::Literal, HIR::IntegerLit, HIR::FloatLit, HIR::StringLit,
|
|
330
|
+
HIR::SymbolLit, HIR::NilLit, HIR::BoolLit
|
|
331
|
+
# Literals are always invariant, but they're cheap to compute
|
|
332
|
+
# Only hoist if they have a result_var that's used in the loop
|
|
333
|
+
false # Not worth hoisting literals
|
|
334
|
+
|
|
335
|
+
else
|
|
336
|
+
false
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Check if an operand (receiver/arg) doesn't depend on loop-modified vars
|
|
341
|
+
def operand_invariant?(operand, modified_vars)
|
|
342
|
+
case operand
|
|
343
|
+
when HIR::LoadLocal
|
|
344
|
+
!modified_vars.include?(operand.var.to_s)
|
|
345
|
+
when HIR::LoadInstanceVar
|
|
346
|
+
!modified_vars.include?(operand.name.to_s)
|
|
347
|
+
when HIR::SelfRef
|
|
348
|
+
true # self doesn't change
|
|
349
|
+
when HIR::Literal, HIR::IntegerLit, HIR::FloatLit,
|
|
350
|
+
HIR::StringLit, HIR::SymbolLit, HIR::NilLit, HIR::BoolLit
|
|
351
|
+
true # Literals are invariant
|
|
352
|
+
when HIR::Call
|
|
353
|
+
# Nested call: check recursively
|
|
354
|
+
PURE_METHODS.include?(operand.method_name.to_sym) &&
|
|
355
|
+
operand_invariant?(operand.receiver, modified_vars) &&
|
|
356
|
+
operand.args.all? { |arg| operand_invariant?(arg, modified_vars) }
|
|
357
|
+
else
|
|
358
|
+
false # Unknown operand types are assumed variant
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|