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.
Files changed (180) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +75 -0
  4. data/CONTRIBUTING.md +123 -0
  5. data/LICENSE +21 -0
  6. data/README.md +257 -0
  7. data/Rakefile +11 -0
  8. data/bin/konpeito +6 -0
  9. data/konpeito.gemspec +43 -0
  10. data/lib/konpeito/ast/typed_ast.rb +620 -0
  11. data/lib/konpeito/ast/visitor.rb +78 -0
  12. data/lib/konpeito/cache/cache_manager.rb +230 -0
  13. data/lib/konpeito/cache/dependency_graph.rb +192 -0
  14. data/lib/konpeito/cache.rb +8 -0
  15. data/lib/konpeito/cli/base_command.rb +187 -0
  16. data/lib/konpeito/cli/build_command.rb +220 -0
  17. data/lib/konpeito/cli/check_command.rb +104 -0
  18. data/lib/konpeito/cli/config.rb +231 -0
  19. data/lib/konpeito/cli/deps_command.rb +128 -0
  20. data/lib/konpeito/cli/doctor_command.rb +340 -0
  21. data/lib/konpeito/cli/fmt_command.rb +199 -0
  22. data/lib/konpeito/cli/init_command.rb +312 -0
  23. data/lib/konpeito/cli/lsp_command.rb +40 -0
  24. data/lib/konpeito/cli/run_command.rb +150 -0
  25. data/lib/konpeito/cli/test_command.rb +248 -0
  26. data/lib/konpeito/cli/watch_command.rb +212 -0
  27. data/lib/konpeito/cli.rb +301 -0
  28. data/lib/konpeito/codegen/builtin_methods.rb +229 -0
  29. data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
  30. data/lib/konpeito/codegen/debug_info.rb +352 -0
  31. data/lib/konpeito/codegen/inliner.rb +486 -0
  32. data/lib/konpeito/codegen/jvm_backend.rb +197 -0
  33. data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
  34. data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
  35. data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
  36. data/lib/konpeito/codegen/monomorphizer.rb +359 -0
  37. data/lib/konpeito/codegen/profile_runtime.c +341 -0
  38. data/lib/konpeito/codegen/profiler.rb +99 -0
  39. data/lib/konpeito/compiler.rb +592 -0
  40. data/lib/konpeito/dependency_resolver.rb +296 -0
  41. data/lib/konpeito/diagnostics/collector.rb +127 -0
  42. data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
  43. data/lib/konpeito/diagnostics/renderer.rb +144 -0
  44. data/lib/konpeito/formatter/formatter.rb +1214 -0
  45. data/lib/konpeito/hir/builder.rb +7167 -0
  46. data/lib/konpeito/hir/nodes.rb +2465 -0
  47. data/lib/konpeito/lsp/document_manager.rb +820 -0
  48. data/lib/konpeito/lsp/server.rb +183 -0
  49. data/lib/konpeito/lsp/transport.rb +38 -0
  50. data/lib/konpeito/parser/prism_adapter.rb +65 -0
  51. data/lib/konpeito/platform.rb +103 -0
  52. data/lib/konpeito/profile/report.rb +136 -0
  53. data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
  54. data/lib/konpeito/stdlib/compression/compression.rb +72 -0
  55. data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
  56. data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
  57. data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
  58. data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
  59. data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
  60. data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
  61. data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
  62. data/lib/konpeito/stdlib/http/extconf.rb +19 -0
  63. data/lib/konpeito/stdlib/http/http.rb +125 -0
  64. data/lib/konpeito/stdlib/http/http.rbs +57 -0
  65. data/lib/konpeito/stdlib/http/http_native.c +440 -0
  66. data/lib/konpeito/stdlib/json/extconf.rb +17 -0
  67. data/lib/konpeito/stdlib/json/json.rb +44 -0
  68. data/lib/konpeito/stdlib/json/json.rbs +33 -0
  69. data/lib/konpeito/stdlib/json/json_native.c +286 -0
  70. data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
  71. data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
  72. data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
  73. data/lib/konpeito/stdlib/ui/ui.rb +318 -0
  74. data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
  75. data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
  76. data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
  77. data/lib/konpeito/type_checker/inferrer.rb +565 -0
  78. data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
  79. data/lib/konpeito/type_checker/type_resolver.rb +276 -0
  80. data/lib/konpeito/type_checker/types.rb +1434 -0
  81. data/lib/konpeito/type_checker/unification.rb +323 -0
  82. data/lib/konpeito/ui/animation/animated_state.rb +80 -0
  83. data/lib/konpeito/ui/animation/easing.rb +59 -0
  84. data/lib/konpeito/ui/animation/value_tween.rb +66 -0
  85. data/lib/konpeito/ui/app.rb +379 -0
  86. data/lib/konpeito/ui/box.rb +38 -0
  87. data/lib/konpeito/ui/castella.rb +70 -0
  88. data/lib/konpeito/ui/castella_native.rb +76 -0
  89. data/lib/konpeito/ui/chart/area_chart.rb +305 -0
  90. data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
  91. data/lib/konpeito/ui/chart/base_chart.rb +210 -0
  92. data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
  93. data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
  94. data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
  95. data/lib/konpeito/ui/chart/line_chart.rb +289 -0
  96. data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
  97. data/lib/konpeito/ui/chart/scales.rb +77 -0
  98. data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
  99. data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
  100. data/lib/konpeito/ui/column.rb +271 -0
  101. data/lib/konpeito/ui/core.rb +2199 -0
  102. data/lib/konpeito/ui/dsl.rb +443 -0
  103. data/lib/konpeito/ui/frame.rb +171 -0
  104. data/lib/konpeito/ui/frame_native.rb +494 -0
  105. data/lib/konpeito/ui/markdown/ast.rb +124 -0
  106. data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
  107. data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
  108. data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
  109. data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
  110. data/lib/konpeito/ui/markdown/parser.rb +805 -0
  111. data/lib/konpeito/ui/markdown/renderer.rb +639 -0
  112. data/lib/konpeito/ui/markdown/theme.rb +165 -0
  113. data/lib/konpeito/ui/render_node.rb +260 -0
  114. data/lib/konpeito/ui/row.rb +207 -0
  115. data/lib/konpeito/ui/spacer.rb +18 -0
  116. data/lib/konpeito/ui/style.rb +799 -0
  117. data/lib/konpeito/ui/theme.rb +563 -0
  118. data/lib/konpeito/ui/themes/material.rb +35 -0
  119. data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
  120. data/lib/konpeito/ui/widgets/button.rb +103 -0
  121. data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
  122. data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
  123. data/lib/konpeito/ui/widgets/container.rb +91 -0
  124. data/lib/konpeito/ui/widgets/data_table.rb +667 -0
  125. data/lib/konpeito/ui/widgets/divider.rb +29 -0
  126. data/lib/konpeito/ui/widgets/image.rb +105 -0
  127. data/lib/konpeito/ui/widgets/input.rb +485 -0
  128. data/lib/konpeito/ui/widgets/markdown.rb +57 -0
  129. data/lib/konpeito/ui/widgets/modal.rb +163 -0
  130. data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
  131. data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
  132. data/lib/konpeito/ui/widgets/net_image.rb +100 -0
  133. data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
  134. data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
  135. data/lib/konpeito/ui/widgets/slider.rb +133 -0
  136. data/lib/konpeito/ui/widgets/switch.rb +84 -0
  137. data/lib/konpeito/ui/widgets/tabs.rb +157 -0
  138. data/lib/konpeito/ui/widgets/text.rb +110 -0
  139. data/lib/konpeito/ui/widgets/tree.rb +426 -0
  140. data/lib/konpeito/version.rb +5 -0
  141. data/lib/konpeito.rb +109 -0
  142. data/test_native_array.rb +172 -0
  143. data/test_native_array_class.rb +197 -0
  144. data/test_native_class.rb +151 -0
  145. data/tools/konpeito-asm/build.sh +65 -0
  146. data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
  147. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  148. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
  149. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  150. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
  151. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
  152. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
  153. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  154. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
  155. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
  156. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
  157. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
  158. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
  159. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  160. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
  161. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
  162. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  163. data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
  164. data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
  165. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
  166. data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
  167. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
  168. data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
  169. data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
  170. data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
  171. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
  172. data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
  173. data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
  174. data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
  175. data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
  176. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
  177. data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
  178. data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
  179. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
  180. metadata +267 -0
@@ -0,0 +1,486 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Konpeito
4
+ module Codegen
5
+ # Inlines small functions at call sites to eliminate call overhead
6
+ # and enable further optimizations.
7
+ #
8
+ # Inlining criteria:
9
+ # - Function body has <= MAX_INLINE_INSTRUCTIONS instructions
10
+ # - No recursion (direct or indirect)
11
+ # - Not a class method (for simplicity)
12
+ #
13
+ class Inliner
14
+ MAX_INLINE_INSTRUCTIONS = 10
15
+ MAX_INLINE_DEPTH = 3
16
+
17
+ attr_reader :inlined_count
18
+
19
+ def initialize(hir_program)
20
+ @hir_program = hir_program
21
+ @functions = {} # name -> HIR::Function
22
+ @inline_candidates = {} # name -> true/false
23
+ @call_graph = {} # name -> Set of called function names
24
+ @inlined_count = 0
25
+ @current_depth = 0
26
+ end
27
+
28
+ # Analyze and transform the HIR program
29
+ def optimize
30
+ build_function_map
31
+ build_call_graph
32
+ identify_candidates
33
+ inline_functions
34
+ end
35
+
36
+ private
37
+
38
+ def build_function_map
39
+ @hir_program.functions.each do |func|
40
+ @functions[func.name.to_s] = func
41
+ end
42
+ end
43
+
44
+ def build_call_graph
45
+ @hir_program.functions.each do |func|
46
+ calls = Set.new
47
+ func.body.each do |block|
48
+ block.instructions.each do |inst|
49
+ if inst.is_a?(HIR::Call) && self_call?(inst)
50
+ calls << inst.method_name.to_s
51
+ end
52
+ end
53
+ end
54
+ @call_graph[func.name.to_s] = calls
55
+ end
56
+ end
57
+
58
+ def self_call?(inst)
59
+ inst.receiver.is_a?(HIR::SelfRef) ||
60
+ (inst.receiver.is_a?(HIR::Instruction) &&
61
+ inst.receiver.type.is_a?(TypeChecker::Types::Untyped))
62
+ end
63
+
64
+ def identify_candidates
65
+ @functions.each do |name, func|
66
+ @inline_candidates[name] = should_inline?(func)
67
+ end
68
+ end
69
+
70
+ def should_inline?(func)
71
+ # Skip main function
72
+ return false if func.name.to_s == "__main__"
73
+
74
+ # Skip class methods for now
75
+ return false if func.owner_class
76
+
77
+ # Skip functions with rest params (*args) or keyword_rest (**kwargs)
78
+ # The inliner doesn't support rest param array aggregation
79
+ return false if func.params.any? { |p| p.rest || p.keyword_rest }
80
+
81
+ # Skip functions with multiple blocks (contains if/while/etc.)
82
+ # These have complex control flow that can't be simply inlined
83
+ return false if func.body.size > 1
84
+
85
+ # Skip functions with Branch terminators (if statements)
86
+ # The inliner doesn't support control flow structures
87
+ func.body.each do |block|
88
+ return false if block.terminator.is_a?(HIR::Branch)
89
+ end
90
+
91
+ # Skip functions containing block calls (captures need complex transformation)
92
+ # Skip functions containing yield (need KBlock parameter handling)
93
+ # Skip functions containing BeginRescue (exception handling has complex sub-block structure)
94
+ func.body.each do |block|
95
+ block.instructions.each do |inst|
96
+ return false if inst.is_a?(HIR::Call) && inst.block
97
+ return false if inst.is_a?(HIR::Yield)
98
+ return false if inst.is_a?(HIR::BeginRescue)
99
+ return false if inst.is_a?(HIR::CaseStatement)
100
+ return false if inst.is_a?(HIR::CaseMatchStatement)
101
+ end
102
+ end
103
+
104
+ # Count instructions
105
+ instruction_count = func.body.sum { |block| block.instructions.size }
106
+ return false if instruction_count > MAX_INLINE_INSTRUCTIONS
107
+
108
+ # Check for recursion
109
+ return false if recursive?(func.name.to_s, Set.new)
110
+
111
+ true
112
+ end
113
+
114
+ def recursive?(func_name, visited)
115
+ return true if visited.include?(func_name)
116
+ visited = visited + [func_name]
117
+
118
+ calls = @call_graph[func_name] || Set.new
119
+ calls.any? { |callee| recursive?(callee, visited) }
120
+ end
121
+
122
+ def inline_functions
123
+ # Process each function
124
+ @hir_program.functions.each do |func|
125
+ @current_depth = 0
126
+ inline_in_function(func)
127
+ end
128
+ end
129
+
130
+ def inline_in_function(func)
131
+ return if @current_depth >= MAX_INLINE_DEPTH
132
+
133
+ changed = true
134
+ while changed
135
+ changed = false
136
+
137
+ func.body.each do |block|
138
+ new_instructions = []
139
+
140
+ block.instructions.each do |inst|
141
+ if inst.is_a?(HIR::Call) && can_inline_call?(inst)
142
+ # Inline this call
143
+ inlined = inline_call(inst, func)
144
+ new_instructions.concat(inlined)
145
+ changed = true
146
+ @inlined_count += 1
147
+ else
148
+ new_instructions << inst
149
+ end
150
+ end
151
+
152
+ # Replace instructions in block
153
+ block.instance_variable_set(:@instructions, new_instructions)
154
+ end
155
+ end
156
+ end
157
+
158
+ def can_inline_call?(inst)
159
+ return false unless self_call?(inst)
160
+
161
+ callee_name = inst.method_name.to_s
162
+ @inline_candidates[callee_name] == true
163
+ end
164
+
165
+ def inline_call(call_inst, caller_func)
166
+ callee = @functions[call_inst.method_name.to_s]
167
+ return [call_inst] unless callee
168
+
169
+ @current_depth += 1
170
+
171
+ # Create a unique prefix for inlined variables
172
+ prefix = "inline_#{@inlined_count}_"
173
+
174
+ # Map callee parameters to call arguments
175
+ param_map = {}
176
+ callee.params.each_with_index do |param, i|
177
+ if call_inst.args[i]
178
+ param_map[param.name] = call_inst.args[i]
179
+ end
180
+ end
181
+
182
+ # Clone and transform instructions from callee
183
+ result_instructions = []
184
+ final_value = nil
185
+
186
+ callee.body.each do |block|
187
+ block.instructions.each do |inst|
188
+ cloned = clone_and_rename(inst, prefix, param_map)
189
+ result_instructions << cloned if cloned
190
+ final_value = cloned
191
+ end
192
+
193
+ # Handle return in terminator
194
+ if block.terminator.is_a?(HIR::Return)
195
+ ret_value = block.terminator.value
196
+ if ret_value
197
+ final_value = transform_value(ret_value, prefix, param_map)
198
+ end
199
+ end
200
+ end
201
+
202
+ # Store result if needed
203
+ if call_inst.result_var && final_value
204
+ store = HIR::StoreLocal.new(
205
+ var: HIR::LocalVar.new(name: call_inst.result_var),
206
+ value: final_value,
207
+ type: call_inst.type
208
+ )
209
+ result_instructions << store
210
+ end
211
+
212
+ @current_depth -= 1
213
+ result_instructions
214
+ end
215
+
216
+ def clone_and_rename(inst, prefix, param_map)
217
+ case inst
218
+ when HIR::LoadLocal
219
+ # Check if this is a parameter reference
220
+ if param_map.key?(inst.var.name)
221
+ # Return the argument value directly
222
+ return nil if inst.result_var.nil?
223
+
224
+ # Store the argument value in the result variable
225
+ return HIR::StoreLocal.new(
226
+ var: HIR::LocalVar.new(name: prefix + inst.result_var),
227
+ value: param_map[inst.var.name],
228
+ type: inst.type
229
+ )
230
+ end
231
+
232
+ # Regular local variable
233
+ new_var = HIR::LocalVar.new(
234
+ name: prefix + inst.var.name,
235
+ type: inst.var.type
236
+ )
237
+ new_result = inst.result_var ? prefix + inst.result_var : nil
238
+ HIR::LoadLocal.new(var: new_var, type: inst.type, result_var: new_result)
239
+
240
+ when HIR::StoreLocal
241
+ new_var = HIR::LocalVar.new(
242
+ name: prefix + inst.var.name,
243
+ type: inst.var.type
244
+ )
245
+ new_value = transform_value(inst.value, prefix, param_map)
246
+ HIR::StoreLocal.new(var: new_var, value: new_value, type: inst.type)
247
+
248
+ when HIR::Call
249
+ new_receiver = transform_value(inst.receiver, prefix, param_map)
250
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
251
+ new_result = inst.result_var ? prefix + inst.result_var : nil
252
+
253
+ HIR::Call.new(
254
+ receiver: new_receiver,
255
+ method_name: inst.method_name,
256
+ args: new_args,
257
+ block: inst.block,
258
+ type: inst.type,
259
+ result_var: new_result
260
+ )
261
+
262
+ when HIR::IntegerLit, HIR::FloatLit, HIR::StringLit,
263
+ HIR::SymbolLit, HIR::BoolLit
264
+ new_result = inst.result_var ? prefix + inst.result_var : nil
265
+ inst.class.new(value: inst.value, result_var: new_result)
266
+
267
+ when HIR::NilLit
268
+ # NilLit doesn't have a value parameter
269
+ new_result = inst.result_var ? prefix + inst.result_var : nil
270
+ HIR::NilLit.new(result_var: new_result)
271
+
272
+ when HIR::SelfRef
273
+ new_result = inst.result_var ? prefix + inst.result_var : nil
274
+ HIR::SelfRef.new(type: inst.type, result_var: new_result)
275
+
276
+ when HIR::StringConcat
277
+ # Transform all parts to handle parameter substitution
278
+ new_parts = inst.parts.map { |part| transform_value(part, prefix, param_map) }
279
+ new_result = inst.result_var ? prefix + inst.result_var : nil
280
+ HIR::StringConcat.new(parts: new_parts, result_var: new_result)
281
+
282
+ when HIR::RangeLit
283
+ new_left = transform_value(inst.left, prefix, param_map)
284
+ new_right = transform_value(inst.right, prefix, param_map)
285
+ new_result = inst.result_var ? prefix + inst.result_var : nil
286
+ HIR::RangeLit.new(
287
+ left: new_left, right: new_right,
288
+ exclusive: inst.exclusive, type: inst.type, result_var: new_result
289
+ )
290
+
291
+ when HIR::ArrayLit
292
+ new_elements = inst.elements.map { |e| transform_value(e, prefix, param_map) }
293
+ new_result = inst.result_var ? prefix + inst.result_var : nil
294
+ HIR::ArrayLit.new(
295
+ elements: new_elements, result_var: new_result
296
+ )
297
+
298
+ when HIR::HashLit
299
+ new_pairs = inst.pairs.map { |k, v| [transform_value(k, prefix, param_map), transform_value(v, prefix, param_map)] }
300
+ new_result = inst.result_var ? prefix + inst.result_var : nil
301
+ HIR::HashLit.new(
302
+ pairs: new_pairs, result_var: new_result
303
+ )
304
+
305
+ when HIR::LoadGlobalVar
306
+ new_result = inst.result_var ? prefix + inst.result_var : nil
307
+ HIR::LoadGlobalVar.new(name: inst.name, type: inst.type, result_var: new_result)
308
+
309
+ when HIR::StoreGlobalVar
310
+ new_value = transform_value(inst.value, prefix, param_map)
311
+ HIR::StoreGlobalVar.new(name: inst.name, value: new_value, type: inst.type)
312
+
313
+ when HIR::LoadClassVar
314
+ new_result = inst.result_var ? prefix + inst.result_var : nil
315
+ HIR::LoadClassVar.new(name: inst.name, type: inst.type, result_var: new_result)
316
+
317
+ when HIR::StoreClassVar
318
+ new_value = transform_value(inst.value, prefix, param_map)
319
+ HIR::StoreClassVar.new(name: inst.name, value: new_value, type: inst.type)
320
+
321
+ when HIR::RegexpLit
322
+ new_result = inst.result_var ? prefix + inst.result_var : nil
323
+ HIR::RegexpLit.new(pattern: inst.pattern, options: inst.options, result_var: new_result)
324
+
325
+ when HIR::NativeArrayAlloc
326
+ new_result = inst.result_var ? prefix + inst.result_var : nil
327
+ new_size = transform_value(inst.size, prefix, param_map)
328
+ HIR::NativeArrayAlloc.new(
329
+ size: new_size,
330
+ element_type: inst.element_type,
331
+ result_var: new_result
332
+ )
333
+
334
+ when HIR::StaticArrayAlloc
335
+ new_result = inst.result_var ? prefix + inst.result_var : nil
336
+ new_initial = inst.initial_value ? transform_value(inst.initial_value, prefix, param_map) : nil
337
+ HIR::StaticArrayAlloc.new(
338
+ element_type: inst.element_type,
339
+ size: inst.size,
340
+ initial_value: new_initial,
341
+ result_var: new_result
342
+ )
343
+
344
+ when HIR::NativeArrayGet
345
+ new_result = inst.result_var ? prefix + inst.result_var : nil
346
+ new_array = transform_value(inst.array, prefix, param_map)
347
+ new_index = transform_value(inst.index, prefix, param_map)
348
+ HIR::NativeArrayGet.new(
349
+ array: new_array, index: new_index,
350
+ element_type: inst.element_type, result_var: new_result
351
+ )
352
+
353
+ when HIR::NativeArraySet
354
+ new_array = transform_value(inst.array, prefix, param_map)
355
+ new_index = transform_value(inst.index, prefix, param_map)
356
+ new_value = transform_value(inst.value, prefix, param_map)
357
+ HIR::NativeArraySet.new(
358
+ array: new_array, index: new_index, value: new_value,
359
+ element_type: inst.element_type
360
+ )
361
+
362
+ when HIR::NativeArrayLength
363
+ new_result = inst.result_var ? prefix + inst.result_var : nil
364
+ new_array = transform_value(inst.array, prefix, param_map)
365
+ HIR::NativeArrayLength.new(array: new_array, result_var: new_result)
366
+
367
+ when HIR::StaticArrayGet
368
+ new_result = inst.result_var ? prefix + inst.result_var : nil
369
+ new_array = transform_value(inst.array, prefix, param_map)
370
+ new_index = transform_value(inst.index, prefix, param_map)
371
+ HIR::StaticArrayGet.new(
372
+ array: new_array, index: new_index,
373
+ element_type: inst.element_type, size: inst.size, result_var: new_result
374
+ )
375
+
376
+ when HIR::StaticArraySet
377
+ new_array = transform_value(inst.array, prefix, param_map)
378
+ new_index = transform_value(inst.index, prefix, param_map)
379
+ new_value = transform_value(inst.value, prefix, param_map)
380
+ HIR::StaticArraySet.new(
381
+ array: new_array, index: new_index, value: new_value,
382
+ element_type: inst.element_type, size: inst.size
383
+ )
384
+
385
+ when HIR::StaticArraySize
386
+ new_result = inst.result_var ? prefix + inst.result_var : nil
387
+ new_array = transform_value(inst.array, prefix, param_map)
388
+ HIR::StaticArraySize.new(
389
+ array: new_array, size: inst.size, result_var: new_result
390
+ )
391
+
392
+ when HIR::ConstantLookup
393
+ new_result = inst.result_var ? prefix + inst.result_var : nil
394
+ HIR::ConstantLookup.new(name: inst.name, scope: inst.scope, type: inst.type, result_var: new_result)
395
+
396
+ when HIR::NativeNew
397
+ new_result = inst.result_var ? prefix + inst.result_var : nil
398
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
399
+ HIR::NativeNew.new(class_type: inst.class_type, result_var: new_result, args: new_args)
400
+
401
+ when HIR::NativeFieldGet
402
+ new_result = inst.result_var ? prefix + inst.result_var : nil
403
+ new_object = transform_value(inst.object, prefix, param_map)
404
+ HIR::NativeFieldGet.new(object: new_object, field_name: inst.field_name,
405
+ class_type: inst.class_type, result_var: new_result)
406
+
407
+ when HIR::NativeFieldSet
408
+ new_object = transform_value(inst.object, prefix, param_map)
409
+ new_value = transform_value(inst.value, prefix, param_map)
410
+ HIR::NativeFieldSet.new(object: new_object, field_name: inst.field_name,
411
+ value: new_value, class_type: inst.class_type)
412
+
413
+ when HIR::NativeMethodCall
414
+ new_result = inst.result_var ? prefix + inst.result_var : nil
415
+ new_receiver = transform_value(inst.receiver, prefix, param_map)
416
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
417
+ HIR::NativeMethodCall.new(
418
+ receiver: new_receiver, method_name: inst.method_name,
419
+ args: new_args, class_type: inst.class_type,
420
+ method_sig: inst.method_sig, owner_class: inst.owner_class,
421
+ result_var: new_result
422
+ )
423
+
424
+ when HIR::LoadInstanceVar
425
+ new_result = inst.result_var ? prefix + inst.result_var : nil
426
+ HIR::LoadInstanceVar.new(name: inst.name, type: inst.type, result_var: new_result)
427
+
428
+ when HIR::StoreInstanceVar
429
+ new_value = transform_value(inst.value, prefix, param_map)
430
+ HIR::StoreInstanceVar.new(name: inst.name, value: new_value, type: inst.type)
431
+
432
+ when HIR::StoreConstant
433
+ new_value = transform_value(inst.value, prefix, param_map)
434
+ HIR::StoreConstant.new(name: inst.name, value: new_value, scope: inst.scope, type: inst.type)
435
+
436
+ when HIR::SuperCall
437
+ new_result = inst.result_var ? prefix + inst.result_var : nil
438
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
439
+ HIR::SuperCall.new(args: new_args, type: inst.type, result_var: new_result)
440
+
441
+ else
442
+ # For other instructions, just return as-is with renamed result
443
+ inst
444
+ end
445
+ end
446
+
447
+ def transform_value(value, prefix, param_map)
448
+ case value
449
+ when HIR::LoadLocal
450
+ if param_map.key?(value.var.name)
451
+ param_map[value.var.name]
452
+ else
453
+ new_var = HIR::LocalVar.new(
454
+ name: prefix + value.var.name,
455
+ type: value.var.type
456
+ )
457
+ HIR::LoadLocal.new(var: new_var, type: value.type, result_var: nil)
458
+ end
459
+ when HIR::Instruction
460
+ if value.result_var
461
+ # Create a LoadLocal to reference the renamed variable
462
+ new_var = HIR::LocalVar.new(
463
+ name: prefix + value.result_var,
464
+ type: value.type
465
+ )
466
+ HIR::LoadLocal.new(var: new_var, type: value.type, result_var: nil)
467
+ else
468
+ value
469
+ end
470
+ when String
471
+ # Variable name reference - create a LoadLocal
472
+ var_name = if param_map.key?(value)
473
+ # If it's a parameter, use the argument directly
474
+ return param_map[value]
475
+ else
476
+ prefix + value
477
+ end
478
+ new_var = HIR::LocalVar.new(name: var_name, type: TypeChecker::Types::UNTYPED)
479
+ HIR::LoadLocal.new(var: new_var, type: TypeChecker::Types::UNTYPED, result_var: nil)
480
+ else
481
+ value
482
+ end
483
+ end
484
+ end
485
+ end
486
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "tmpdir"
5
+ require "fileutils"
6
+
7
+ module Konpeito
8
+ module Codegen
9
+ # JVM Backend: Orchestrates the compilation pipeline from HIR to .jar
10
+ #
11
+ # Pipeline:
12
+ # HIR → JVMGenerator → JSON IR → ASM tool (subprocess) → .class files → jar → .jar
13
+ class JVMBackend
14
+ ASM_TOOL_DIR = File.expand_path("../../../tools/konpeito-asm", __dir__)
15
+ ASM_TOOL_JAR = File.join(ASM_TOOL_DIR, "konpeito-asm.jar")
16
+ JAVA_HOME = ENV["JAVA_HOME"] || Platform.default_java_home
17
+
18
+ attr_reader :output_file
19
+
20
+ def initialize(jvm_generator, output_file:, module_name:, run_after: false, emit_ir: false, classpath: nil, library: false)
21
+ @jvm_generator = jvm_generator
22
+ @output_file = output_file
23
+ @module_name = module_name
24
+ @run_after = run_after
25
+ @emit_ir = emit_ir
26
+ @classpath = classpath
27
+ @library = library
28
+ end
29
+
30
+ def generate
31
+ ensure_asm_tool!
32
+
33
+ Dir.mktmpdir("konpeito-jvm") do |tmpdir|
34
+ classes_dir = File.join(tmpdir, "classes")
35
+ FileUtils.mkdir_p(classes_dir)
36
+
37
+ # Step 1: Generate JSON IR
38
+ json_ir = @jvm_generator.to_json
39
+
40
+ # Optionally save JSON IR for debugging
41
+ if @emit_ir
42
+ ir_path = @output_file.sub(/\.jar$/, ".json")
43
+ FileUtils.mkdir_p(File.dirname(File.expand_path(ir_path)))
44
+ File.write(ir_path, JSON.pretty_generate(@jvm_generator.to_json_ir))
45
+ puts "JSON IR written to: #{ir_path}"
46
+ end
47
+
48
+ # Step 2: Run ASM tool to generate .class files
49
+ run_asm_tool(json_ir, classes_dir)
50
+
51
+ # Step 2.5: Copy runtime classes (KArray, KHash, etc.)
52
+ copy_runtime_classes(classes_dir)
53
+
54
+ if @library
55
+ # Library mode: no Main-Class manifest
56
+ create_jar_without_manifest(classes_dir)
57
+ else
58
+ # Step 3: Create manifest
59
+ manifest_path = File.join(tmpdir, "MANIFEST.MF")
60
+ main_class = @jvm_generator.send(:main_class_name).gsub("/", ".")
61
+ File.write(manifest_path, "Main-Class: #{main_class}\n")
62
+
63
+ # Step 4: Package into .jar
64
+ create_jar(classes_dir, manifest_path)
65
+
66
+ # Step 5: Optionally run
67
+ run_jar if @run_after
68
+ end
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def ensure_asm_tool!
75
+ unless File.exist?(ASM_TOOL_JAR)
76
+ puts "Building ASM tool (first-time setup)..."
77
+ build_script = File.join(ASM_TOOL_DIR, "build.sh")
78
+ unless File.exist?(build_script)
79
+ raise CodegenError, "ASM tool build script not found: #{build_script}"
80
+ end
81
+
82
+ output = `bash #{build_script} 2>&1`
83
+ unless $?.success?
84
+ $stderr.puts output
85
+ raise CodegenError, "Failed to build ASM tool"
86
+ end
87
+ puts "ASM tool ready."
88
+ end
89
+ end
90
+
91
+ def run_asm_tool(json_ir, output_dir)
92
+ java_cmd = find_java
93
+
94
+ # Run the ASM tool, passing JSON IR via stdin
95
+ cmd = [java_cmd, "-jar", ASM_TOOL_JAR, output_dir]
96
+
97
+ IO.popen(cmd, "r+", err: [:child, :out]) do |io|
98
+ io.write(json_ir)
99
+ io.close_write
100
+ output = io.read
101
+ unless output.strip.empty?
102
+ # Check for errors
103
+ if output.include?("Error") || output.include?("Exception")
104
+ raise CodegenError, "ASM tool error:\n#{output}"
105
+ end
106
+ end
107
+ end
108
+
109
+ unless $?.success?
110
+ raise CodegenError, "ASM tool failed with exit code #{$?.exitstatus}"
111
+ end
112
+ end
113
+
114
+ def create_jar_without_manifest(classes_dir)
115
+ jar_cmd = find_jar
116
+
117
+ FileUtils.mkdir_p(File.dirname(File.expand_path(@output_file)))
118
+
119
+ output_path = File.expand_path(@output_file)
120
+ cmd = [jar_cmd, "cf", output_path, "-C", classes_dir, "."]
121
+
122
+ result = system(*cmd)
123
+ unless result
124
+ raise CodegenError, "jar command failed"
125
+ end
126
+ end
127
+
128
+ def create_jar(classes_dir, manifest_path)
129
+ jar_cmd = find_jar
130
+
131
+ # Ensure output directory exists
132
+ FileUtils.mkdir_p(File.dirname(File.expand_path(@output_file)))
133
+
134
+ # Create JAR with manifest
135
+ output_path = File.expand_path(@output_file)
136
+ cmd = [jar_cmd, "cfm", output_path, manifest_path, "-C", classes_dir, "."]
137
+
138
+ result = system(*cmd)
139
+ unless result
140
+ raise CodegenError, "jar command failed"
141
+ end
142
+ end
143
+
144
+ def run_jar
145
+ java_cmd = find_java
146
+ if @classpath
147
+ # Use -cp mode so external JARs are on the classpath
148
+ cp = "#{File.expand_path(@output_file)}#{Platform.classpath_separator}#{@classpath}"
149
+ main_class = @jvm_generator.send(:main_class_name).gsub("/", ".")
150
+ cmd = [java_cmd]
151
+ cmd << "-XstartOnFirstThread" if RUBY_PLATFORM.include?("darwin")
152
+ cmd += ["-cp", cp, main_class]
153
+ puts "Running: #{cmd.join(' ')}"
154
+ system(*cmd)
155
+ else
156
+ puts "Running: java -jar #{@output_file}"
157
+ system(java_cmd, "-jar", @output_file)
158
+ end
159
+ end
160
+
161
+ def find_java
162
+ # Try JAVA_HOME first, then PATH
163
+ java_path = File.join(JAVA_HOME, "bin", "java")
164
+ return java_path if File.exist?(java_path)
165
+
166
+ # Try PATH
167
+ java_in_path = Platform.find_executable("java")
168
+ return java_in_path if java_in_path
169
+
170
+ raise CodegenError, "Java not found. Install Java 21+: #{Platform.java_install_hint}"
171
+ end
172
+
173
+ def copy_runtime_classes(classes_dir)
174
+ runtime_dir = File.join(ASM_TOOL_DIR, "runtime-classes")
175
+ return unless File.directory?(runtime_dir)
176
+
177
+ # Copy runtime .class files preserving directory structure
178
+ Dir.glob(File.join(runtime_dir, "**", "*.class")).each do |class_file|
179
+ relative_path = class_file.sub("#{runtime_dir}/", "")
180
+ dest = File.join(classes_dir, relative_path)
181
+ FileUtils.mkdir_p(File.dirname(dest))
182
+ FileUtils.cp(class_file, dest)
183
+ end
184
+ end
185
+
186
+ def find_jar
187
+ jar_path = File.join(JAVA_HOME, "bin", "jar")
188
+ return jar_path if File.exist?(jar_path)
189
+
190
+ jar_in_path = Platform.find_executable("jar")
191
+ return jar_in_path if jar_in_path
192
+
193
+ raise CodegenError, "jar command not found. Install Java 21+: #{Platform.java_install_hint}"
194
+ end
195
+ end
196
+ end
197
+ end