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,1090 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "rbconfig"
5
+ require "set"
6
+
7
+ module Konpeito
8
+ module Codegen
9
+ # Generates CRuby extension (.so/.bundle) from LLVM module
10
+ class CRubyBackend
11
+ attr_reader :llvm_generator, :output_file, :module_name, :rbs_loader, :stdlib_requires, :debug, :profile
12
+
13
+ def initialize(llvm_generator, output_file:, module_name: nil, rbs_loader: nil, stdlib_requires: [], runtime_native_extensions: [], debug: false, profile: false, uses_json_parse_as: false)
14
+ @llvm_generator = llvm_generator
15
+ @output_file = output_file
16
+ @module_name = module_name || derive_module_name(output_file)
17
+ @rbs_loader = rbs_loader
18
+ @stdlib_requires = stdlib_requires
19
+ @runtime_native_extensions = runtime_native_extensions
20
+ @debug = debug
21
+ @profile = profile
22
+ @uses_json_parse_as = uses_json_parse_as
23
+ end
24
+
25
+ def generate
26
+ ir_file = "#{output_base}.ll"
27
+ obj_file = "#{output_base}.o"
28
+ init_c_file = "#{output_base}_init.c"
29
+ init_obj_file = "#{output_base}_init.o"
30
+ profile_c_file = nil
31
+ profile_obj_file = nil
32
+
33
+ begin
34
+ # Write LLVM IR to file
35
+ File.write(ir_file, llvm_generator.to_ir)
36
+
37
+ # Generate C wrapper for Init function
38
+ File.write(init_c_file, generate_init_c_code)
39
+
40
+ # Compile IR to object file
41
+ compile_ir_to_object(ir_file, obj_file)
42
+
43
+ # Compile C init wrapper
44
+ compile_c_to_object(init_c_file, init_obj_file)
45
+
46
+ obj_files = [obj_file, init_obj_file]
47
+
48
+ # Compile profile runtime if profiling enabled
49
+ if @profile
50
+ profile_c_file = "#{output_base}_profile_runtime.c"
51
+ profile_obj_file = "#{output_base}_profile_runtime.o"
52
+
53
+ # Write embedded C runtime
54
+ File.write(profile_c_file, profile_runtime_c_code)
55
+ compile_c_to_object(profile_c_file, profile_obj_file)
56
+
57
+ obj_files << profile_obj_file
58
+ end
59
+
60
+ # Link all object files to shared library
61
+ link_to_shared_library(obj_files, output_file)
62
+ ensure
63
+ # Cleanup temporary files (keep .ll for debugging)
64
+ # FileUtils.rm_f(ir_file)
65
+ unless @debug
66
+ FileUtils.rm_f(obj_file)
67
+ end
68
+ FileUtils.rm_f(init_c_file)
69
+ FileUtils.rm_f(init_obj_file)
70
+ FileUtils.rm_f(profile_c_file) if profile_c_file
71
+ FileUtils.rm_f(profile_obj_file) if profile_obj_file
72
+ end
73
+
74
+ output_file
75
+ end
76
+
77
+ private
78
+
79
+ def output_base
80
+ output_file.sub(/\.(so|bundle|dll)$/, "")
81
+ end
82
+
83
+ def derive_module_name(path)
84
+ File.basename(path).sub(/\.(so|bundle|dll)$/, "").gsub(/[^a-zA-Z0-9_]/, "_")
85
+ end
86
+
87
+ def generate_init_c_code
88
+ hir = llvm_generator.hir_program
89
+ lines = []
90
+
91
+ lines << "#include <ruby.h>"
92
+ lines << "#include <stddef.h>"
93
+ lines << "#include <string.h>"
94
+ lines << ""
95
+
96
+ # Collect NativeClasses from RBS loader
97
+ native_classes = @rbs_loader&.native_classes || {}
98
+
99
+ # Sort native classes by dependency order (embedded types first)
100
+ sorted_classes = topological_sort_native_classes(native_classes)
101
+
102
+ # Forward declare structs for vtable function signatures
103
+ sorted_classes.each do |class_name|
104
+ lines << "typedef struct Native_#{class_name}_s Native_#{class_name};"
105
+ end
106
+ lines << ""
107
+
108
+ # Generate struct definitions and TypedData for NativeClasses
109
+ sorted_classes.each do |class_name|
110
+ class_type = native_classes[class_name]
111
+ lines.concat(generate_native_class_struct_body(class_name, class_type))
112
+ end
113
+
114
+ # Declare external native functions from LLVM module (for NativeClass methods)
115
+ native_classes.each do |class_name, class_type|
116
+ class_type.methods.each do |method_name, method_sig|
117
+ lines.concat(generate_native_func_declaration(class_name, class_type, method_name, method_sig))
118
+ end
119
+ end
120
+
121
+ # Generate vtables for vtable classes (after function declarations)
122
+ sorted_classes.each do |class_name|
123
+ class_type = native_classes[class_name]
124
+ if class_type.uses_vtable?(native_classes)
125
+ lines.concat(generate_vtable(class_name, class_type, native_classes))
126
+ end
127
+ end
128
+
129
+ lines << ""
130
+
131
+ # Generate wrapper functions for NativeClass methods
132
+ native_classes.each do |class_name, class_type|
133
+ class_type.methods.each do |method_name, method_sig|
134
+ lines.concat(generate_native_method_wrapper(class_name, class_type, method_name, method_sig))
135
+ end
136
+ end
137
+
138
+ # Declare external functions from LLVM module (non-native classes)
139
+ hir.classes.each do |class_def|
140
+ next if native_classes.key?(class_def.name.to_sym)
141
+
142
+ (class_def.method_names + class_def.singleton_methods).each do |method_name|
143
+ mangled_name = mangle_method_name(class_def.name, method_name)
144
+ func = llvm_generator.mod.functions[mangled_name]
145
+ next unless func
146
+
147
+ # Use variadic signature for functions with **kwargs or *args
148
+ if llvm_generator.variadic_functions[mangled_name]
149
+ lines << "extern VALUE #{mangled_name}(int argc, VALUE *argv, VALUE self);"
150
+ else
151
+ arity = func.params.size - 1
152
+ lines << "extern VALUE #{mangled_name}(#{(['VALUE'] * (arity + 1)).join(', ')});"
153
+ end
154
+ end
155
+ end
156
+
157
+ # Declare external functions from LLVM module (modules)
158
+ hir.modules.each do |module_def|
159
+ (module_def.methods + module_def.singleton_methods).each do |method_name|
160
+ mangled_name = mangle_method_name(module_def.name, method_name)
161
+ func = llvm_generator.mod.functions[mangled_name]
162
+ next unless func
163
+
164
+ # Use variadic signature for functions with **kwargs or *args
165
+ if llvm_generator.variadic_functions[mangled_name]
166
+ lines << "extern VALUE #{mangled_name}(int argc, VALUE *argv, VALUE self);"
167
+ else
168
+ arity = func.params.size - 1
169
+ lines << "extern VALUE #{mangled_name}(#{(['VALUE'] * (arity + 1)).join(', ')});"
170
+ end
171
+ end
172
+ end
173
+
174
+ # Declare top-level functions
175
+ hir.functions.each do |func_def|
176
+ next if func_def.owner_class
177
+
178
+ mangled_name = "rn_#{func_def.name}".gsub(/[^a-zA-Z0-9_]/, "_")
179
+ func = llvm_generator.mod.functions[mangled_name]
180
+ next unless func
181
+
182
+ # Use variadic signature for functions with **kwargs or *args
183
+ if llvm_generator.variadic_functions[mangled_name]
184
+ lines << "extern VALUE #{mangled_name}(int argc, VALUE *argv, VALUE self);"
185
+ else
186
+ arity = func.params.size - 1
187
+ lines << "extern VALUE #{mangled_name}(#{(['VALUE'] * (arity + 1)).join(', ')});"
188
+ end
189
+ end
190
+
191
+ # Declare external C functions used by @cfunc annotations
192
+ cfunc_methods = @rbs_loader&.cfunc_methods || {}
193
+ unless cfunc_methods.empty?
194
+ lines << ""
195
+ lines << "/* External C functions (@cfunc) */"
196
+ declared_cfuncs = Set.new
197
+ cfunc_methods.each_value do |cfunc_type|
198
+ next if declared_cfuncs.include?(cfunc_type.c_func_name)
199
+
200
+ declared_cfuncs << cfunc_type.c_func_name
201
+ lines.concat(generate_cfunc_extern_declaration(cfunc_type))
202
+ end
203
+ end
204
+
205
+ # Profile runtime function declarations
206
+ if @profile
207
+ lines << "/* Profiling runtime functions */"
208
+ lines << "extern void konpeito_profile_init(int num_functions, const char* output_path);"
209
+ lines << "extern void konpeito_profile_finalize(void);"
210
+ lines << ""
211
+ end
212
+
213
+ lines << ""
214
+ lines << "void Init_#{module_name}(void) {"
215
+
216
+ # Initialize profiling if enabled
217
+ if @profile
218
+ num_funcs = llvm_generator.profiler&.num_functions || 0
219
+ profile_output = "#{module_name}_profile.json"
220
+ lines << " /* Initialize profiling */"
221
+ lines << " konpeito_profile_init(#{num_funcs}, \"#{profile_output}\");"
222
+ lines << ""
223
+ end
224
+
225
+ # Load stdlib dependencies first
226
+ unless @stdlib_requires.empty?
227
+ lines << " /* Load stdlib dependencies */"
228
+ @stdlib_requires.each do |lib_name|
229
+ lines << " rb_require(\"#{lib_name}\");"
230
+ end
231
+ lines << ""
232
+ end
233
+
234
+ # Define modules first (before classes that may include them)
235
+ hir.modules.each do |module_def|
236
+ module_var = "m#{module_def.name}"
237
+ lines << " VALUE #{module_var} = rb_define_module(\"#{module_def.name}\");"
238
+
239
+ # Register instance methods (for include/extend)
240
+ module_def.methods.each do |method_name|
241
+ # Skip @cfunc methods - they are direct C calls, not Ruby methods
242
+ next if @rbs_loader&.cfunc_method?(module_def.name, method_name, singleton: false)
243
+
244
+ mangled_name = mangle_method_name(module_def.name, method_name)
245
+ func = llvm_generator.mod.functions[mangled_name]
246
+ next unless func
247
+
248
+ # Use -1 arity for variadic functions
249
+ arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
250
+ lines << " rb_define_method(#{module_var}, \"#{method_name}\", #{mangled_name}, #{arity});"
251
+ end
252
+
253
+ # Register singleton methods (def self.method)
254
+ module_def.singleton_methods.each do |method_name|
255
+ # Skip @cfunc methods - they are direct C calls, not Ruby methods
256
+ next if @rbs_loader&.cfunc_method?(module_def.name, method_name, singleton: true)
257
+
258
+ mangled_name = mangle_method_name(module_def.name, method_name)
259
+ func = llvm_generator.mod.functions[mangled_name]
260
+ next unless func
261
+
262
+ # Use -1 arity for variadic functions
263
+ arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
264
+ lines << " rb_define_singleton_method(#{module_var}, \"#{method_name}\", #{mangled_name}, #{arity});"
265
+ end
266
+
267
+ # Register module constants
268
+ module_def.constants.each do |const_name, _value|
269
+ # Constants are typically registered at runtime via rb_const_set
270
+ # For compile-time literals, we could use rb_define_const here
271
+ # For now, mark them for runtime initialization
272
+ lines << " /* Module constant #{module_def.name}::#{const_name} registered at runtime */"
273
+ end
274
+ end
275
+
276
+ # Define NativeClasses with TypedData allocator
277
+ native_classes.each do |class_name, class_type|
278
+ lines.concat(generate_native_class_init(class_name, class_type))
279
+ end
280
+
281
+ # Define non-native classes (including @boxed classes)
282
+ # Native-first: Classes without field definitions or marked @boxed
283
+ # use standard VALUE-based class definition
284
+ # Sort classes topologically so superclasses are defined first
285
+ non_native_classes = hir.classes.reject { |cd| native_classes.key?(cd.name.to_sym) }
286
+ sorted_non_native = topological_sort_non_native_classes(non_native_classes)
287
+
288
+ sorted_non_native.each do |class_def|
289
+ class_var = "c#{class_def.name}"
290
+ if class_def.reopened
291
+ # Reopened class - get existing class instead of defining new one
292
+ lines << " VALUE #{class_var} = rb_const_get(rb_cObject, rb_intern(\"#{class_def.name}\"));"
293
+ else
294
+ superclass_expr = resolve_superclass_c_expr(class_def.superclass, non_native_classes)
295
+ lines << " VALUE #{class_var} = rb_define_class(\"#{class_def.name}\", #{superclass_expr});"
296
+ end
297
+
298
+ # Prepend modules (must come before include to maintain proper method resolution order)
299
+ class_def.prepended_modules.each do |module_name|
300
+ lines << " rb_prepend_module(#{class_var}, m#{module_name});"
301
+ end
302
+
303
+ # Include modules
304
+ class_def.included_modules.each do |module_name|
305
+ lines << " rb_include_module(#{class_var}, m#{module_name});"
306
+ end
307
+
308
+ # Extend modules (adds module methods as singleton methods on the class)
309
+ class_def.extended_modules.each do |module_name|
310
+ lines << " rb_extend_object(#{class_var}, m#{module_name});"
311
+ end
312
+
313
+ class_def.method_names.each do |method_name|
314
+ mangled_name = mangle_method_name(class_def.name, method_name)
315
+ func = llvm_generator.mod.functions[mangled_name]
316
+ next unless func
317
+
318
+ # Use -1 arity for variadic functions
319
+ arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
320
+ # Method visibility
321
+ define_func = if class_def.private_methods.include?(method_name)
322
+ "rb_define_private_method"
323
+ elsif class_def.protected_methods.include?(method_name)
324
+ "rb_define_protected_method"
325
+ else
326
+ "rb_define_method"
327
+ end
328
+ lines << " #{define_func}(#{class_var}, \"#{method_name}\", #{mangled_name}, #{arity});"
329
+ end
330
+
331
+ # Register singleton methods (class << self / def self.xxx)
332
+ class_def.singleton_methods.each do |method_name|
333
+ # Skip @cfunc methods - they are direct C calls, not Ruby methods
334
+ next if @rbs_loader&.cfunc_method?(class_def.name, method_name, singleton: true)
335
+
336
+ mangled_name = mangle_method_name(class_def.name, method_name)
337
+ func = llvm_generator.mod.functions[mangled_name]
338
+ next unless func
339
+
340
+ arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
341
+ lines << " rb_define_singleton_method(#{class_var}, \"#{method_name}\", #{mangled_name}, #{arity});"
342
+ end
343
+
344
+ # Register aliases
345
+ class_def.aliases.each do |new_name, old_name|
346
+ lines << " rb_define_alias(#{class_var}, \"#{new_name}\", \"#{old_name}\");"
347
+ end
348
+ end
349
+
350
+ # Define top-level methods on Object
351
+ hir.functions.each do |func_def|
352
+ next if func_def.owner_class
353
+ next if func_def.owner_module
354
+ next if func_def.name == "__main__"
355
+
356
+ mangled_name = "rn_#{func_def.name}".gsub(/[^a-zA-Z0-9_]/, "_")
357
+ func = llvm_generator.mod.functions[mangled_name]
358
+ next unless func
359
+
360
+ # Use -1 arity for variadic functions (**kwargs, *args)
361
+ if llvm_generator.variadic_functions[mangled_name]
362
+ arity = -1
363
+ else
364
+ arity = func.params.size - 1
365
+ end
366
+ lines << " rb_define_private_method(rb_cObject, \"#{func_def.name}\", #{mangled_name}, #{arity});"
367
+ end
368
+
369
+ lines << "}"
370
+ lines << ""
371
+
372
+ lines.join("\n")
373
+ end
374
+
375
+ # Generate C struct definition body and TypedData for a NativeClass
376
+ # Uses forward-declared struct name (Native_ClassName_s) to allow self-referential types
377
+ def generate_native_class_struct_body(class_name, class_type)
378
+ lines = []
379
+ struct_name = "Native_#{class_name}"
380
+ native_classes = @rbs_loader&.native_classes || {}
381
+ uses_vtable = class_type.uses_vtable?(native_classes)
382
+
383
+ # Struct definition with tag (for forward declaration)
384
+ lines << "struct Native_#{class_name}_s {"
385
+
386
+ # For vtable classes, add vptr as first field
387
+ if uses_vtable
388
+ lines << " void **vptr; /* Pointer to vtable */"
389
+ end
390
+
391
+ class_type.fields.each do |field_name, field_type|
392
+ c_type = case field_type
393
+ when :Int64 then "int64_t"
394
+ when :Float64 then "double"
395
+ when :Bool then "int8_t"
396
+ when :String, :Object, :Array, :Hash then "VALUE"
397
+ when Hash
398
+ # Reference to NativeClass - stored as VALUE (can be nil)
399
+ "VALUE"
400
+ else
401
+ # Embedded NativeClass - use the struct type
402
+ if @rbs_loader&.native_class?(field_type)
403
+ "Native_#{field_type}"
404
+ else
405
+ "VALUE"
406
+ end
407
+ end
408
+ lines << " #{c_type} #{field_name};"
409
+ end
410
+ lines << "};"
411
+ lines << ""
412
+
413
+ # GC mark function (if class has Ruby object fields)
414
+ ruby_fields = class_type.ruby_object_field_names(@rbs_loader&.native_classes || {})
415
+ unless ruby_fields.empty?
416
+ lines << "static void #{class_name}_mark(void *ptr) {"
417
+ lines << " #{struct_name} *obj = (#{struct_name} *)ptr;"
418
+ ruby_fields.each do |field_name|
419
+ lines << " rb_gc_mark(obj->#{field_name});"
420
+ end
421
+ lines << "}"
422
+ lines << ""
423
+ end
424
+
425
+ # TypedData type definition
426
+ ruby_fields = class_type.ruby_object_field_names(@rbs_loader&.native_classes || {})
427
+ dmark_func = ruby_fields.empty? ? "NULL" : "#{class_name}_mark"
428
+
429
+ lines << "static const rb_data_type_t #{class_name}_type = {"
430
+ lines << " .wrap_struct_name = \"#{class_name}\","
431
+ lines << " .function = {"
432
+ lines << " .dmark = #{dmark_func},"
433
+ lines << " .dfree = RUBY_DEFAULT_FREE,"
434
+ lines << " .dsize = NULL,"
435
+ lines << " },"
436
+ lines << " .flags = RUBY_TYPED_FREE_IMMEDIATELY,"
437
+ lines << "};"
438
+ lines << ""
439
+
440
+ # Allocator function
441
+ lines << "static VALUE #{class_name}_alloc(VALUE klass) {"
442
+ lines << " #{struct_name} *ptr;"
443
+ lines << " VALUE obj = TypedData_Make_Struct(klass, #{struct_name}, &#{class_name}_type, ptr);"
444
+
445
+ # Initialize vptr if using vtable
446
+ if uses_vtable
447
+ lines << " ptr->vptr = vtable_#{class_name};"
448
+ end
449
+
450
+ class_type.fields.each do |field_name, field_type|
451
+ case field_type
452
+ when :Int64
453
+ lines << " ptr->#{field_name} = 0;"
454
+ when :Float64
455
+ lines << " ptr->#{field_name} = 0.0;"
456
+ when :Bool
457
+ lines << " ptr->#{field_name} = 0;"
458
+ when :String, :Object, :Array, :Hash
459
+ lines << " ptr->#{field_name} = Qnil;"
460
+ when ::Hash
461
+ # Reference to NativeClass - initialize to nil
462
+ lines << " ptr->#{field_name} = Qnil;"
463
+ else
464
+ # Embedded NativeClass - initialize with memset or field-by-field
465
+ if @rbs_loader&.native_class?(field_type)
466
+ lines << " memset(&ptr->#{field_name}, 0, sizeof(Native_#{field_type}));"
467
+ else
468
+ lines << " ptr->#{field_name} = Qnil;"
469
+ end
470
+ end
471
+ end
472
+ lines << " return obj;"
473
+ lines << "}"
474
+ lines << ""
475
+
476
+ # Field accessor wrappers (getter)
477
+ class_type.fields.each do |field_name, field_type|
478
+ lines << "static VALUE #{class_name}_get_#{field_name}(VALUE self) {"
479
+ lines << " #{struct_name} *ptr;"
480
+ lines << " TypedData_Get_Struct(self, #{struct_name}, &#{class_name}_type, ptr);"
481
+ case field_type
482
+ when :Int64
483
+ lines << " return rb_int2inum(ptr->#{field_name});"
484
+ when :Float64
485
+ lines << " return rb_float_new(ptr->#{field_name});"
486
+ when :Bool
487
+ lines << " return ptr->#{field_name} ? Qtrue : Qfalse;"
488
+ when :String, :Object, :Array, :Hash
489
+ lines << " return ptr->#{field_name};"
490
+ when ::Hash
491
+ # Reference to NativeClass - return VALUE directly (can be nil)
492
+ lines << " return ptr->#{field_name};"
493
+ else
494
+ # Embedded NativeClass - return a new Ruby object with a copy
495
+ if @rbs_loader&.native_class?(field_type)
496
+ embedded_struct = "Native_#{field_type}"
497
+ lines << " VALUE klass = rb_const_get(rb_cObject, rb_intern(\"#{field_type}\"));"
498
+ lines << " VALUE result = #{field_type}_alloc(klass);"
499
+ lines << " #{embedded_struct} *result_ptr;"
500
+ lines << " TypedData_Get_Struct(result, #{embedded_struct}, &#{field_type}_type, result_ptr);"
501
+ lines << " *result_ptr = ptr->#{field_name};"
502
+ lines << " return result;"
503
+ else
504
+ lines << " return ptr->#{field_name};"
505
+ end
506
+ end
507
+ lines << "}"
508
+ lines << ""
509
+ end
510
+
511
+ # Field accessor wrappers (setter)
512
+ class_type.fields.each do |field_name, field_type|
513
+ lines << "static VALUE #{class_name}_set_#{field_name}(VALUE self, VALUE val) {"
514
+ lines << " #{struct_name} *ptr;"
515
+ lines << " TypedData_Get_Struct(self, #{struct_name}, &#{class_name}_type, ptr);"
516
+ case field_type
517
+ when :Int64
518
+ lines << " ptr->#{field_name} = NUM2LONG(val);"
519
+ when :Float64
520
+ lines << " ptr->#{field_name} = NUM2DBL(val);"
521
+ when :Bool
522
+ lines << " ptr->#{field_name} = RTEST(val) ? 1 : 0;"
523
+ when :String, :Object, :Array, :Hash
524
+ lines << " ptr->#{field_name} = val;"
525
+ when ::Hash
526
+ # Reference to NativeClass - store VALUE directly
527
+ lines << " ptr->#{field_name} = val;"
528
+ else
529
+ # Embedded NativeClass - copy from the passed object
530
+ if @rbs_loader&.native_class?(field_type)
531
+ embedded_struct = "Native_#{field_type}"
532
+ lines << " #{embedded_struct} *val_ptr;"
533
+ lines << " TypedData_Get_Struct(val, #{embedded_struct}, &#{field_type}_type, val_ptr);"
534
+ lines << " ptr->#{field_name} = *val_ptr;"
535
+ else
536
+ lines << " ptr->#{field_name} = val;"
537
+ end
538
+ end
539
+ lines << " return val;"
540
+ lines << "}"
541
+ lines << ""
542
+ end
543
+
544
+ lines
545
+ end
546
+
547
+ # Generate extern declaration for a native function
548
+ def generate_native_func_declaration(class_name, class_type, method_name, method_sig)
549
+ lines = []
550
+ struct_name = "Native_#{class_name}"
551
+ func_name = mangle_method_name(class_name, method_name)
552
+
553
+ # Build parameter list: first is struct pointer for self
554
+ params = ["#{struct_name}* self"]
555
+
556
+ method_sig.param_types.each_with_index do |param_type, i|
557
+ param_name = method_sig.param_names[i] || "arg#{i}"
558
+ c_type = native_type_to_c(param_type, class_name)
559
+ params << "#{c_type} #{param_name}"
560
+ end
561
+
562
+ # Return type
563
+ return_c_type = native_return_type_to_c(method_sig.return_type, class_name)
564
+
565
+ lines << "extern #{return_c_type} #{func_name}(#{params.join(', ')});"
566
+ lines
567
+ end
568
+
569
+ # Generate wrapper function for a NativeClass method
570
+ def generate_native_method_wrapper(class_name, class_type, method_name, method_sig)
571
+ lines = []
572
+ struct_name = "Native_#{class_name}"
573
+ native_func = mangle_method_name(class_name, method_name)
574
+ sanitized_method = sanitize_c_name(method_name.to_s)
575
+ wrapper_name = "rn_wrap_#{class_name}_#{sanitized_method}"
576
+
577
+ arity = method_sig.param_types.size
578
+
579
+ # Build parameter list for wrapper
580
+ params = ["VALUE self"]
581
+ arity.times { |i| params << "VALUE arg#{i}" }
582
+
583
+ lines << "static VALUE #{wrapper_name}(#{params.join(', ')}) {"
584
+ lines << " #{struct_name} *ptr;"
585
+ lines << " TypedData_Get_Struct(self, #{struct_name}, &#{class_name}_type, ptr);"
586
+
587
+ # Convert Ruby arguments to native types
588
+ call_args = ["ptr"]
589
+ method_sig.param_types.each_with_index do |param_type, i|
590
+ arg_name = "native_arg#{i}"
591
+ lines.concat(convert_ruby_to_native("arg#{i}", arg_name, param_type, class_name, class_type))
592
+ call_args << arg_name
593
+ end
594
+
595
+ # Call native function
596
+ return_type = method_sig.return_type
597
+ if return_type == :Void
598
+ lines << " #{native_func}(#{call_args.join(', ')});"
599
+ lines << " return Qnil;"
600
+ elsif return_type == :Self || @rbs_loader&.native_class?(return_type)
601
+ # Struct returned by value - allocate Ruby object and copy into it
602
+ result_class = return_type == :Self ? class_name : return_type
603
+ result_struct = "Native_#{result_class}"
604
+ lines << " #{result_struct} result = #{native_func}(#{call_args.join(', ')});"
605
+ lines << " VALUE result_klass = rb_const_get(rb_cObject, rb_intern(\"#{result_class}\"));"
606
+ lines << " VALUE result_obj = #{result_class}_alloc(result_klass);"
607
+ lines << " #{result_struct} *result_ptr;"
608
+ lines << " TypedData_Get_Struct(result_obj, #{result_struct}, &#{result_class}_type, result_ptr);"
609
+ lines << " *result_ptr = result;"
610
+ lines << " return result_obj;"
611
+ elsif return_type == :Int64
612
+ lines << " int64_t result = #{native_func}(#{call_args.join(', ')});"
613
+ lines << " return rb_int2inum(result);"
614
+ elsif return_type == :Float64
615
+ lines << " double result = #{native_func}(#{call_args.join(', ')});"
616
+ lines << " return rb_float_new(result);"
617
+ else
618
+ lines << " double result = #{native_func}(#{call_args.join(', ')});"
619
+ lines << " return rb_float_new(result);"
620
+ end
621
+
622
+ lines << "}"
623
+ lines << ""
624
+ lines
625
+ end
626
+
627
+ # Generate vtable for a NativeClass
628
+ def generate_vtable(class_name, class_type, native_classes)
629
+ lines = []
630
+
631
+ vtable_methods = class_type.vtable_methods(native_classes)
632
+ return lines if vtable_methods.empty?
633
+
634
+ # Generate vtable as array of function pointers
635
+ lines << "/* Vtable for #{class_name} */"
636
+ lines << "static void *vtable_#{class_name}[] = {"
637
+
638
+ vtable_methods.each_with_index do |(method_name, _method_sig, owner_name), idx|
639
+ # Each entry points to the implementing class's function
640
+ func_name = mangle_method_name(owner_name, method_name)
641
+ comma = idx < vtable_methods.size - 1 ? "," : ""
642
+ lines << " (void *)#{func_name}#{comma} /* #{idx}: #{method_name} */"
643
+ end
644
+
645
+ lines << "};"
646
+ lines << ""
647
+ lines
648
+ end
649
+
650
+ # Generate Init code for a NativeClass
651
+ def generate_native_class_init(class_name, class_type)
652
+ lines = []
653
+ class_var = "c#{class_name}"
654
+
655
+ # Define class with superclass
656
+ superclass = class_type.superclass
657
+ if superclass && @rbs_loader&.native_class?(superclass)
658
+ lines << " VALUE #{class_var} = rb_define_class(\"#{class_name}\", c#{superclass});"
659
+ else
660
+ lines << " VALUE #{class_var} = rb_define_class(\"#{class_name}\", rb_cObject);"
661
+ end
662
+
663
+ # Register allocator
664
+ lines << " rb_define_alloc_func(#{class_var}, #{class_name}_alloc);"
665
+
666
+ # Register field accessors
667
+ class_type.fields.each do |field_name, _field_type|
668
+ lines << " rb_define_method(#{class_var}, \"#{field_name}\", #{class_name}_get_#{field_name}, 0);"
669
+ lines << " rb_define_method(#{class_var}, \"#{field_name}=\", #{class_name}_set_#{field_name}, 1);"
670
+ end
671
+
672
+ # Register native methods
673
+ class_type.methods.each do |method_name, method_sig|
674
+ sanitized_method = sanitize_c_name(method_name.to_s)
675
+ wrapper_name = "rn_wrap_#{class_name}_#{sanitized_method}"
676
+ arity = method_sig.param_types.size
677
+ lines << " rb_define_method(#{class_var}, \"#{method_name}\", #{wrapper_name}, #{arity});"
678
+ end
679
+
680
+ lines
681
+ end
682
+
683
+ # Convert native type symbol to C type string
684
+ def native_type_to_c(type_sym, current_class)
685
+ case type_sym
686
+ when :Int64 then "int64_t"
687
+ when :Float64 then "double"
688
+ when :Self then "Native_#{current_class}*"
689
+ else
690
+ # Another NativeClass - use pointer
691
+ "Native_#{type_sym}*"
692
+ end
693
+ end
694
+
695
+ # Convert native return type to C type (structs are returned by value)
696
+ def native_return_type_to_c(type_sym, current_class)
697
+ case type_sym
698
+ when :Int64 then "int64_t"
699
+ when :Float64 then "double"
700
+ when :Void then "void"
701
+ when :Self then "Native_#{current_class}" # Return by value
702
+ else
703
+ # Another NativeClass - return by value
704
+ "Native_#{type_sym}"
705
+ end
706
+ end
707
+
708
+ # Generate code to convert Ruby VALUE to native type
709
+ def convert_ruby_to_native(ruby_var, native_var, type_sym, current_class, class_type)
710
+ lines = []
711
+ case type_sym
712
+ when :Int64
713
+ lines << " int64_t #{native_var} = NUM2LONG(#{ruby_var});"
714
+ when :Float64
715
+ lines << " double #{native_var} = NUM2DBL(#{ruby_var});"
716
+ when :Self
717
+ struct_name = "Native_#{current_class}"
718
+ lines << " #{struct_name} *#{native_var};"
719
+ lines << " TypedData_Get_Struct(#{ruby_var}, #{struct_name}, &#{current_class}_type, #{native_var});"
720
+ else
721
+ # Another NativeClass
722
+ struct_name = "Native_#{type_sym}"
723
+ lines << " #{struct_name} *#{native_var};"
724
+ lines << " TypedData_Get_Struct(#{ruby_var}, #{struct_name}, &#{type_sym}_type, #{native_var});"
725
+ end
726
+ lines
727
+ end
728
+
729
+ def mangle_method_name(class_name, method_name)
730
+ owner = class_name.to_s.gsub(/[^a-zA-Z0-9_]/, "_")
731
+ name = sanitize_c_name(method_name.to_s)
732
+ "rn_#{owner}_#{name}"
733
+ end
734
+
735
+ # Sanitize a Ruby method name to a valid C identifier
736
+ # Operators get distinctive names to avoid collisions
737
+ OPERATOR_NAME_MAP = {
738
+ "+" => "op_plus", "-" => "op_minus", "*" => "op_mul", "/" => "op_div",
739
+ "%" => "op_mod", "**" => "op_pow", "==" => "op_eq", "!=" => "op_neq",
740
+ "<" => "op_lt", ">" => "op_gt", "<=" => "op_le", ">=" => "op_ge",
741
+ "<=>" => "op_cmp", "<<" => "op_lshift", ">>" => "op_rshift",
742
+ "&" => "op_and", "|" => "op_or", "^" => "op_xor", "~" => "op_not",
743
+ "[]" => "op_aref", "[]=" => "op_aset", "+@" => "op_uplus", "-@" => "op_uminus",
744
+ }.freeze
745
+
746
+ def sanitize_c_name(name)
747
+ return OPERATOR_NAME_MAP[name] if OPERATOR_NAME_MAP.key?(name)
748
+ name.gsub(/[^a-zA-Z0-9_]/, "_")
749
+ end
750
+
751
+ def compile_ir_to_object(ir_file, obj_file)
752
+ llc = find_llvm_tool("llc")
753
+ optimized_ir = nil
754
+
755
+ # Run opt passes before llc for better optimization
756
+ # In debug mode, skip opt to preserve debug info
757
+ unless @debug
758
+ opt = find_llvm_tool("opt")
759
+ if opt
760
+ optimized_ir = "#{ir_file}.opt.ll"
761
+ opt_cmd = [
762
+ opt,
763
+ "--passes=default<O2>",
764
+ "-S", # Output as text IR (not bitcode)
765
+ "-o", optimized_ir,
766
+ ir_file
767
+ ]
768
+ if system(*opt_cmd)
769
+ ir_file = optimized_ir
770
+ else
771
+ optimized_ir = nil # Don't clean up if opt failed
772
+ end
773
+ end
774
+ end
775
+
776
+ # Use llc to compile IR to object file
777
+ # -O2 enables optimization passes including mem2reg which converts
778
+ # allocas to proper SSA form with Phi nodes for loop variables
779
+ # In debug mode, use -O0 to preserve debug info
780
+ opt_level = @debug ? "-O0" : "-O2"
781
+
782
+ cmd = [
783
+ llc,
784
+ opt_level,
785
+ "-filetype=obj",
786
+ "-relocation-model=pic" # Required for shared libraries
787
+ ]
788
+
789
+ # Add debug-specific options
790
+ if @debug
791
+ cmd << "--debugger-tune=#{Platform.debugger_tune}"
792
+ end
793
+
794
+ cmd += ["-o", obj_file, ir_file]
795
+
796
+ system(*cmd) or raise CodegenError, "Failed to compile LLVM IR to object file"
797
+ ensure
798
+ FileUtils.rm_f(optimized_ir) if optimized_ir
799
+ end
800
+
801
+ def compile_c_to_object(c_file, obj_file)
802
+ cc = find_llvm_tool("clang") || "cc"
803
+
804
+ cmd = [
805
+ cc,
806
+ "-c",
807
+ "-fPIC",
808
+ "-I#{RbConfig::CONFIG['rubyhdrdir']}",
809
+ "-I#{RbConfig::CONFIG['rubyarchhdrdir']}",
810
+ "-o", obj_file,
811
+ c_file
812
+ ]
813
+
814
+ system(*cmd) or raise CodegenError, "Failed to compile C init wrapper"
815
+ end
816
+
817
+ def link_to_shared_library(obj_files, output_file)
818
+ obj_files = Array(obj_files)
819
+
820
+ # Use clang or system linker
821
+ clang = find_llvm_tool("clang") || "cc"
822
+
823
+ # Get Ruby's library flags
824
+ ruby_libs = ruby_link_flags
825
+
826
+ # Get FFI library flags from RBS annotations
827
+ ffi_libs = ffi_link_flags
828
+
829
+ cmd = [
830
+ clang,
831
+ "-shared",
832
+ "-fPIC"
833
+ ]
834
+
835
+ # Add debug flag to preserve debug info during linking
836
+ if @debug
837
+ cmd << "-g"
838
+ end
839
+
840
+ cmd += [
841
+ "-o", output_file,
842
+ *obj_files,
843
+ *ruby_libs,
844
+ *ffi_libs
845
+ ]
846
+
847
+ # Add platform-specific flags
848
+ case RbConfig::CONFIG["host_os"]
849
+ when /darwin/
850
+ cmd << "-undefined"
851
+ cmd << "dynamic_lookup"
852
+ when /mingw|mswin|cygwin/
853
+ cmd << "-Wl,--export-all-symbols"
854
+ when /linux/
855
+ # Don't use --no-undefined for extensions
856
+ end
857
+
858
+ system(*cmd) or raise CodegenError, "Failed to link shared library"
859
+
860
+ # On macOS, generate dSYM bundle for debug info
861
+ if @debug && RbConfig::CONFIG["host_os"] =~ /darwin/
862
+ dsymutil = "dsymutil"
863
+ dsym_cmd = [dsymutil, output_file]
864
+ system(*dsym_cmd) # Don't fail if dsymutil is not available
865
+ end
866
+ end
867
+
868
+ # Get link flags for @ffi annotated libraries
869
+ def ffi_link_flags
870
+ flags = []
871
+
872
+ # Add yyjson object files if JSON parse_as is used
873
+ if @uses_json_parse_as
874
+ yyjson_objs = ensure_yyjson_compiled
875
+ flags.concat(yyjson_objs)
876
+ end
877
+
878
+ if @rbs_loader
879
+ @rbs_loader.all_ffi_libraries.each do |lib_name|
880
+ # Convert library name to linker flag
881
+ # "libm" -> "-lm", "libfoo" -> "-lfoo", "foo" -> "-lfoo"
882
+ link_name = lib_name.sub(/^lib/, "")
883
+ flags << "-l#{link_name}"
884
+ end
885
+ end
886
+
887
+ flags
888
+ end
889
+
890
+ # Compile yyjson.c and wrapper to object files if needed
891
+ # Returns array of object file paths
892
+ def ensure_yyjson_compiled
893
+ yyjson_dir = File.expand_path("../../../vendor/yyjson", __dir__)
894
+ yyjson_c = File.join(yyjson_dir, "yyjson.c")
895
+ yyjson_obj = File.join(yyjson_dir, "yyjson.o")
896
+ wrapper_c = File.join(yyjson_dir, "yyjson_wrapper.c")
897
+ wrapper_obj = File.join(yyjson_dir, "yyjson_wrapper.o")
898
+
899
+ return [] unless File.exist?(yyjson_c) && File.exist?(wrapper_c)
900
+
901
+ cc = find_llvm_tool("clang") || "cc"
902
+
903
+ # Compile yyjson.c
904
+ unless File.exist?(yyjson_obj) && File.mtime(yyjson_obj) > File.mtime(yyjson_c)
905
+ cmd = [cc, "-c", "-O3", "-fPIC", "-o", yyjson_obj, yyjson_c]
906
+ system(*cmd) or return []
907
+ end
908
+
909
+ # Compile wrapper
910
+ unless File.exist?(wrapper_obj) && File.mtime(wrapper_obj) > File.mtime(wrapper_c)
911
+ cmd = [cc, "-c", "-O3", "-fPIC", "-I#{yyjson_dir}", "-o", wrapper_obj, wrapper_c]
912
+ system(*cmd) or return []
913
+ end
914
+
915
+ [yyjson_obj, wrapper_obj]
916
+ end
917
+
918
+ def ruby_link_flags
919
+ # Get Ruby's linker flags for extensions
920
+ [
921
+ "-L#{RbConfig::CONFIG['libdir']}",
922
+ # Don't link against libruby for extensions - they're loaded by Ruby
923
+ ]
924
+ end
925
+
926
+ # Sort native classes in dependency order (embedded types first)
927
+ def topological_sort_native_classes(native_classes)
928
+ sorted = []
929
+ visited = {}
930
+ temp_mark = {}
931
+
932
+ visit = lambda do |name|
933
+ return if visited[name]
934
+ raise "Circular dependency detected in NativeClass #{name}" if temp_mark[name]
935
+
936
+ temp_mark[name] = true
937
+
938
+ class_type = native_classes[name]
939
+ if class_type
940
+ # Visit dependencies (embedded NativeClass fields only, not references)
941
+ class_type.fields.each_value do |field_type|
942
+ next if TypeChecker::Types::NativeClassType::ALLOWED_PRIMITIVE_TYPES.include?(field_type)
943
+ next if TypeChecker::Types::NativeClassType::RUBY_OBJECT_TYPES.include?(field_type)
944
+ next if field_type.is_a?(Hash) # Skip references (stored as VALUE)
945
+ visit.call(field_type) if native_classes.key?(field_type)
946
+ end
947
+
948
+ # Also visit superclass
949
+ visit.call(class_type.superclass) if class_type.superclass && native_classes.key?(class_type.superclass)
950
+ end
951
+
952
+ temp_mark.delete(name)
953
+ visited[name] = true
954
+ sorted << name
955
+ end
956
+
957
+ native_classes.each_key { |name| visit.call(name) }
958
+ sorted
959
+ end
960
+
961
+ # Topological sort for non-native classes (superclass before subclass)
962
+ def topological_sort_non_native_classes(class_defs)
963
+ by_name = class_defs.each_with_object({}) { |cd, h| h[cd.name] = cd }
964
+ sorted = []
965
+ visited = {}
966
+
967
+ visit = lambda do |cd|
968
+ return if visited[cd.name]
969
+
970
+ visited[cd.name] = true
971
+ if cd.superclass && by_name[cd.superclass]
972
+ visit.call(by_name[cd.superclass])
973
+ end
974
+ sorted << cd
975
+ end
976
+
977
+ class_defs.each { |cd| visit.call(cd) }
978
+ sorted
979
+ end
980
+
981
+ # Resolve superclass to C expression for rb_define_class
982
+ EXCEPTION_CLASS_MAP = {
983
+ "StandardError" => "rb_eStandardError",
984
+ "RuntimeError" => "rb_eRuntimeError",
985
+ "TypeError" => "rb_eTypeError",
986
+ "ArgumentError" => "rb_eArgError",
987
+ "NameError" => "rb_eNameError",
988
+ "NoMethodError" => "rb_eNoMethodError",
989
+ "RangeError" => "rb_eRangeError",
990
+ "IOError" => "rb_eIOError",
991
+ "EOFError" => "rb_eEOFError",
992
+ "IndexError" => "rb_eIndexError",
993
+ "KeyError" => "rb_eKeyError",
994
+ "StopIteration" => "rb_eStopIteration",
995
+ "ZeroDivisionError" => "rb_eZeroDivError",
996
+ "NotImplementedError" => "rb_eNotImpError",
997
+ "LoadError" => "rb_eLoadError",
998
+ "ScriptError" => "rb_eScriptError",
999
+ "SyntaxError" => "rb_eSyntaxError",
1000
+ "SecurityError" => "rb_eSecurityError",
1001
+ "RegexpError" => "rb_eRegexpError",
1002
+ "EncodingError" => "rb_eEncError",
1003
+ "Errno::ENOENT" => "rb_eSystemCallError",
1004
+ "Exception" => "rb_eException",
1005
+ }.freeze
1006
+
1007
+ KNOWN_SUPERCLASS_MAP = {
1008
+ "Numeric" => "rb_cNumeric",
1009
+ "Integer" => "rb_cInteger",
1010
+ "Float" => "rb_cFloat",
1011
+ "String" => "rb_cString",
1012
+ "Array" => "rb_cArray",
1013
+ "Hash" => "rb_cHash",
1014
+ "IO" => "rb_cIO",
1015
+ "Struct" => "rb_cStruct",
1016
+ "Comparable" => "rb_mComparable",
1017
+ }.freeze
1018
+
1019
+ def resolve_superclass_c_expr(superclass_name, non_native_classes)
1020
+ return "rb_cObject" unless superclass_name
1021
+
1022
+ # Check known exception classes
1023
+ if EXCEPTION_CLASS_MAP[superclass_name]
1024
+ return EXCEPTION_CLASS_MAP[superclass_name]
1025
+ end
1026
+
1027
+ # Check known standard classes
1028
+ if KNOWN_SUPERCLASS_MAP[superclass_name]
1029
+ return KNOWN_SUPERCLASS_MAP[superclass_name]
1030
+ end
1031
+
1032
+ # Check if it's a user-defined class in the same compilation unit
1033
+ if non_native_classes.any? { |cd| cd.name == superclass_name }
1034
+ return "c#{superclass_name}"
1035
+ end
1036
+
1037
+ # Fallback: runtime constant lookup
1038
+ "rb_const_get(rb_cObject, rb_intern(\"#{superclass_name}\"))"
1039
+ end
1040
+
1041
+ # Generate extern declaration for a @cfunc C function
1042
+ def generate_cfunc_extern_declaration(cfunc_type)
1043
+ lines = []
1044
+
1045
+ c_params = cfunc_type.param_types.map do |type|
1046
+ cfunc_type_to_c(type)
1047
+ end.join(", ")
1048
+ c_params = "void" if c_params.empty?
1049
+
1050
+ c_return = cfunc_type_to_c(cfunc_type.return_type)
1051
+
1052
+ lines << "extern #{c_return} #{cfunc_type.c_func_name}(#{c_params});"
1053
+ lines
1054
+ end
1055
+
1056
+ # Convert CFuncType type symbol to C type string
1057
+ def cfunc_type_to_c(type_sym)
1058
+ case type_sym
1059
+ when :Float then "double"
1060
+ when :Integer then "int64_t"
1061
+ when :String then "VALUE"
1062
+ when :Bool then "int"
1063
+ when :void then "void"
1064
+ else "VALUE"
1065
+ end
1066
+ end
1067
+
1068
+ def find_llvm_tool(name)
1069
+ path = Platform.find_llvm_tool(name)
1070
+ return path if path
1071
+
1072
+ raise CodegenError, "Could not find LLVM tool: #{name}. #{Platform.llvm_install_hint}"
1073
+ end
1074
+
1075
+ def ptr_type
1076
+ LLVM::Pointer(LLVM::Int8)
1077
+ end
1078
+
1079
+ def profile_runtime_c_code
1080
+ # Read the profile runtime C code from the installed location
1081
+ runtime_path = File.join(__dir__, "profile_runtime.c")
1082
+ if File.exist?(runtime_path)
1083
+ File.read(runtime_path)
1084
+ else
1085
+ raise CodegenError, "Profile runtime not found at #{runtime_path}"
1086
+ end
1087
+ end
1088
+ end
1089
+ end
1090
+ end