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,359 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Konpeito
4
+ module Codegen
5
+ # Monomorphizer generates specialized versions of polymorphic functions
6
+ # for specific type instantiations.
7
+ #
8
+ # Example:
9
+ # def identity(x)
10
+ # x
11
+ # end
12
+ #
13
+ # identity(42) # generates identity_Integer
14
+ # identity("hello") # generates identity_String
15
+ #
16
+ class Monomorphizer
17
+ attr_reader :specializations, :call_sites, :union_dispatches
18
+
19
+ def initialize(hir_program, type_info)
20
+ @hir_program = hir_program
21
+ @type_info = type_info # HMInferrer
22
+ @specializations = {} # { [func_name, type_args] => specialized_name }
23
+ @call_sites = [] # Array of { call: HIR::Call, types: [...] }
24
+ @union_call_sites = [] # Calls requiring runtime dispatch
25
+ @union_dispatches = {} # { [func_name, original_types] => dispatch_info }
26
+ @generated_functions = {}
27
+ end
28
+
29
+ # Analyze the HIR program to find monomorphization opportunities
30
+ def analyze
31
+ @hir_program.functions.each do |func|
32
+ analyze_function(func)
33
+ end
34
+
35
+ # Determine which specializations to generate
36
+ determine_specializations
37
+ end
38
+
39
+ # Apply monomorphization transformations
40
+ def transform
41
+ # Generate specialized function copies
42
+ generate_specialized_functions
43
+
44
+ # Rewrite call sites to use specialized functions
45
+ rewrite_call_sites
46
+ end
47
+
48
+ # Get the specialized function name for a call
49
+ def specialized_name(func_name, arg_types)
50
+ key = [func_name.to_s, arg_types.map(&:to_s)]
51
+ @specializations[key]
52
+ end
53
+
54
+ private
55
+
56
+ def analyze_function(func)
57
+ func.body.each do |block|
58
+ block.instructions.each do |inst|
59
+ analyze_instruction(inst, func)
60
+ end
61
+ end
62
+ end
63
+
64
+ def analyze_instruction(inst, context_func)
65
+ case inst
66
+ when HIR::Call
67
+ analyze_call(inst, context_func)
68
+ end
69
+ end
70
+
71
+ def analyze_call(call, context_func)
72
+ # Only specialize self-calls (calls on the same instance)
73
+ # Cross-class calls like h.add(1, 2) should not be specialized as direct calls
74
+ return unless self_receiver?(call.receiver)
75
+
76
+ # Check if this is a call to a local function that could be specialized
77
+ target_func = find_function(call.method_name)
78
+ return unless target_func
79
+ return if target_func.params.empty?
80
+
81
+ # Skip functions with rest params (*args) or keyword_rest (**kwargs)
82
+ # Rest params collect arguments into an array; specializing by element type is incorrect
83
+ return if target_func.params.any? { |p| p.rest || p.keyword_rest }
84
+
85
+ # Get parameter types from target function (may include Union types from RBS)
86
+ param_types = target_func.params.map(&:type)
87
+
88
+ # Get concrete argument types at this call site
89
+ arg_types = call.args.map { |arg| get_concrete_type(arg) }
90
+
91
+ # Check if any PARAMETER has a Union type (from RBS definition)
92
+ has_union_param = param_types.any? { |t| union_type?(t) }
93
+
94
+ # Also check if any argument has a Union type (from type inference)
95
+ has_union_arg = arg_types.any? { |t| union_type?(t) }
96
+
97
+ if has_union_param || has_union_arg
98
+ # Use parameter types for expansion (they contain the full Union info)
99
+ types_to_expand = param_types.each_with_index.map do |param_type, i|
100
+ if union_type?(param_type)
101
+ param_type
102
+ elsif i < arg_types.size && union_type?(arg_types[i])
103
+ arg_types[i]
104
+ else
105
+ arg_types[i] || param_type
106
+ end
107
+ end
108
+
109
+ # Expand union types into all possible combinations
110
+ expand_union_types(types_to_expand).each do |concrete_types|
111
+ @call_sites << {
112
+ call: call,
113
+ context: context_func,
114
+ target: target_func.name,
115
+ types: concrete_types,
116
+ union_dispatch: true,
117
+ original_types: types_to_expand
118
+ }
119
+ end
120
+ else
121
+ # Skip if any type is still polymorphic
122
+ return if arg_types.any? { |t| polymorphic_type?(t) }
123
+
124
+ @call_sites << {
125
+ call: call,
126
+ context: context_func,
127
+ target: target_func.name,
128
+ types: arg_types,
129
+ union_dispatch: false
130
+ }
131
+ end
132
+ end
133
+
134
+ # Check if a receiver is self (same instance)
135
+ # For monomorphization, we must be strict: only SelfRef is considered self
136
+ def self_receiver?(receiver)
137
+ receiver.is_a?(HIR::SelfRef)
138
+ end
139
+
140
+ def find_function(name)
141
+ @hir_program.functions.find { |f| f.name.to_s == name.to_s }
142
+ end
143
+
144
+ def get_concrete_type(hir_value)
145
+ case hir_value
146
+ when HIR::Instruction
147
+ hir_value.type
148
+ when HIR::Node
149
+ hir_value.type
150
+ else
151
+ TypeChecker::Types::UNTYPED
152
+ end
153
+ end
154
+
155
+ def polymorphic_type?(type)
156
+ return true if type.nil?
157
+ return true if type.is_a?(TypeChecker::Types::Untyped)
158
+ return true if type.respond_to?(:id) && type.class.name.include?("TypeVar")
159
+ false
160
+ end
161
+
162
+ # Check if a type is a Union type
163
+ def union_type?(type)
164
+ type.is_a?(TypeChecker::Types::Union)
165
+ end
166
+
167
+ # Expand union types into all possible combinations
168
+ # Example: [Integer, Union[String, Float]] => [[Integer, String], [Integer, Float]]
169
+ def expand_union_types(types)
170
+ combinations = [[]]
171
+ types.each do |type|
172
+ if union_type?(type)
173
+ new_combinations = []
174
+ combinations.each do |combo|
175
+ type.types.each { |member| new_combinations << (combo + [member]) }
176
+ end
177
+ combinations = new_combinations
178
+ else
179
+ combinations = combinations.map { |c| c + [type] }
180
+ end
181
+ end
182
+ combinations
183
+ end
184
+
185
+ def determine_specializations
186
+ # Group call sites by (function, types)
187
+ grouped = @call_sites.group_by { |cs| [cs[:target], cs[:types]] }
188
+
189
+ grouped.each do |(func_name, types), sites|
190
+ next if types.all? { |t| t == TypeChecker::Types::UNTYPED }
191
+
192
+ type_suffix = types.map { |t| type_to_suffix(t) }.join("_")
193
+ specialized = "#{func_name}_#{type_suffix}"
194
+
195
+ key = [func_name.to_s, types.map(&:to_s)]
196
+ @specializations[key] = specialized
197
+
198
+ # Track if this came from union expansion
199
+ if sites.any? { |s| s[:union_dispatch] }
200
+ @union_call_sites << {
201
+ call: sites.first[:call],
202
+ target: func_name,
203
+ original_types: sites.first[:original_types],
204
+ concrete_types: types,
205
+ specialized_name: specialized
206
+ }
207
+ end
208
+ end
209
+
210
+ # Group union call sites by original call
211
+ consolidate_union_dispatches
212
+ end
213
+
214
+ # Group union call sites by their original (pre-expansion) call
215
+ def consolidate_union_dispatches
216
+ @union_dispatches = {}
217
+
218
+ # Group by (target, original_types)
219
+ grouped = @union_call_sites.group_by { |s| [s[:target], s[:original_types].map(&:to_s)] }
220
+
221
+ grouped.each do |(target, original_type_strs), sites|
222
+ # Build a mapping from concrete types to specialized function names
223
+ specializations = {}
224
+ sites.each do |site|
225
+ concrete_key = site[:concrete_types].map(&:to_s)
226
+ specializations[concrete_key] = site[:specialized_name]
227
+ end
228
+
229
+ # Find union positions (which argument indices have Union types)
230
+ original_types = sites.first[:original_types]
231
+ union_positions = original_types.each_with_index
232
+ .select { |t, _i| union_type?(t) }
233
+ .map { |_, i| i }
234
+
235
+ key = [target.to_s, original_type_strs]
236
+ @union_dispatches[key] = {
237
+ call: sites.first[:call],
238
+ target: target,
239
+ original_types: original_types,
240
+ union_positions: union_positions,
241
+ specializations: specializations
242
+ }
243
+ end
244
+ end
245
+
246
+ def type_to_suffix(type)
247
+ case type
248
+ when TypeChecker::Types::ClassInstance
249
+ type.name.to_s
250
+ when TypeChecker::Types::NilType
251
+ "Nil"
252
+ when TypeChecker::Types::BoolType
253
+ "Bool"
254
+ else
255
+ type.to_s.gsub(/[^a-zA-Z0-9]/, "_")
256
+ end
257
+ end
258
+
259
+ def generate_specialized_functions
260
+ @specializations.each do |(func_name, type_strs), specialized_name|
261
+ original = find_function(func_name)
262
+ next unless original
263
+
264
+ # Parse type strings back to types (for comparison in call sites)
265
+ types = @call_sites
266
+ .find { |cs| cs[:target].to_s == func_name && cs[:types].map(&:to_s) == type_strs }
267
+ &.dig(:types)
268
+ next unless types
269
+
270
+ specialized = clone_function(original, specialized_name, types)
271
+ @generated_functions[specialized_name] = specialized
272
+ @hir_program.functions << specialized
273
+ end
274
+ end
275
+
276
+ def clone_function(original, new_name, param_types)
277
+ # Create specialized parameter list with concrete types
278
+ specialized_params = original.params.each_with_index.map do |param, i|
279
+ HIR::Param.new(
280
+ name: param.name,
281
+ type: param_types[i] || param.type,
282
+ default_value: param.default_value,
283
+ rest: param.rest,
284
+ keyword: param.keyword,
285
+ block: param.block
286
+ )
287
+ end
288
+
289
+ # Deep clone the body blocks
290
+ specialized_body = deep_clone_blocks(original.body)
291
+
292
+ HIR::Function.new(
293
+ name: new_name,
294
+ params: specialized_params,
295
+ body: specialized_body,
296
+ return_type: original.return_type,
297
+ is_instance_method: original.is_instance_method,
298
+ owner_class: original.owner_class
299
+ )
300
+ end
301
+
302
+ def deep_clone_blocks(blocks)
303
+ # Create a simple deep copy of blocks
304
+ # In a full implementation, we'd update type information throughout
305
+ blocks.map do |block|
306
+ new_block = HIR::BasicBlock.new(label: block.label)
307
+ block.instructions.each do |inst|
308
+ new_block.add_instruction(clone_instruction(inst))
309
+ end
310
+ new_block.set_terminator(clone_terminator(block.terminator)) if block.terminator
311
+ new_block
312
+ end
313
+ end
314
+
315
+ def clone_instruction(inst)
316
+ # Simple shallow clone for now
317
+ # A full implementation would update types based on specialization
318
+ inst.dup
319
+ rescue TypeError
320
+ # If dup fails, return the original
321
+ inst
322
+ end
323
+
324
+ def clone_terminator(term)
325
+ term.dup
326
+ rescue TypeError
327
+ term
328
+ end
329
+
330
+ def rewrite_call_sites
331
+ # Track which calls have already been processed (for Union calls that appear multiple times)
332
+ processed_calls = Set.new
333
+
334
+ @call_sites.each do |site|
335
+ call = site[:call]
336
+ next if processed_calls.include?(call.object_id)
337
+
338
+ if site[:union_dispatch]
339
+ # For union dispatch calls, set the dispatch info instead of a single specialized target
340
+ dispatch_key = [site[:target].to_s, site[:original_types].map(&:to_s)]
341
+ dispatch_info = @union_dispatches[dispatch_key]
342
+ if dispatch_info
343
+ call.instance_variable_set(:@union_dispatch_info, dispatch_info)
344
+ processed_calls.add(call.object_id)
345
+ end
346
+ else
347
+ # Regular monomorphized call
348
+ key = [site[:target].to_s, site[:types].map(&:to_s)]
349
+ specialized_name = @specializations[key]
350
+ next unless specialized_name
351
+
352
+ call.instance_variable_set(:@specialized_target, specialized_name)
353
+ processed_calls.add(call.object_id)
354
+ end
355
+ end
356
+ end
357
+ end
358
+ end
359
+ end
@@ -0,0 +1,341 @@
1
+ /* profile_runtime.c - Konpeito Profiling Runtime
2
+ *
3
+ * Thread-safe profiling with minimal overhead.
4
+ * Uses atomic counters and clock_gettime for timing.
5
+ */
6
+
7
+ #include <stdio.h>
8
+ #include <stdlib.h>
9
+ #include <string.h>
10
+ #include <stdint.h>
11
+ #include <time.h>
12
+
13
+ #ifdef __APPLE__
14
+ #include <mach/mach_time.h>
15
+ #endif
16
+
17
+ /* Maximum number of functions we can profile */
18
+ #define MAX_FUNCTIONS 4096
19
+
20
+ /* Maximum call stack depth for timing */
21
+ #define MAX_CALL_DEPTH 256
22
+
23
+ /* Maximum number of unique stack traces for flame graph */
24
+ #define MAX_STACK_SAMPLES 65536
25
+
26
+ /* Per-function profiling data */
27
+ typedef struct {
28
+ const char* name;
29
+ uint64_t call_count;
30
+ uint64_t total_time_ns;
31
+ } FunctionProfile;
32
+
33
+ /* Stack sample for flame graph */
34
+ typedef struct {
35
+ int func_ids[MAX_CALL_DEPTH];
36
+ int depth;
37
+ uint64_t time_ns; /* Time spent at this exact stack */
38
+ } StackSample;
39
+
40
+ /* Thread-local call stack for timing */
41
+ typedef struct {
42
+ int func_id;
43
+ uint64_t entry_time;
44
+ } CallStackEntry;
45
+
46
+ /* Global profiling state */
47
+ static FunctionProfile g_profiles[MAX_FUNCTIONS];
48
+ static int g_num_functions = 0;
49
+ static char g_output_path[1024] = "konpeito_profile.json";
50
+ static int g_initialized = 0;
51
+
52
+ /* Flame graph stack samples - stores aggregated time per unique call stack */
53
+ static StackSample g_stack_samples[MAX_STACK_SAMPLES];
54
+ static int g_num_stack_samples = 0;
55
+
56
+ /* Thread-local storage for call stack */
57
+ static __thread CallStackEntry tls_call_stack[MAX_CALL_DEPTH];
58
+ static __thread int tls_stack_depth = 0;
59
+ static __thread int tls_current_stack[MAX_CALL_DEPTH]; /* Current stack for flame graph */
60
+
61
+ #ifdef __APPLE__
62
+ static mach_timebase_info_data_t g_timebase_info;
63
+ #endif
64
+
65
+ /* Get current time in nanoseconds */
66
+ static inline uint64_t get_time_ns(void) {
67
+ #ifdef __APPLE__
68
+ uint64_t mach_time = mach_absolute_time();
69
+ return mach_time * g_timebase_info.numer / g_timebase_info.denom;
70
+ #else
71
+ struct timespec ts;
72
+ clock_gettime(CLOCK_MONOTONIC, &ts);
73
+ return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
74
+ #endif
75
+ }
76
+
77
+ /* Forward declaration */
78
+ void konpeito_profile_finalize(void);
79
+
80
+ /* Find or create a stack sample for the current call stack */
81
+ static int find_or_create_stack_sample(int* stack, int depth) {
82
+ /* Search for existing sample with same stack */
83
+ for (int i = 0; i < g_num_stack_samples; i++) {
84
+ if (g_stack_samples[i].depth == depth) {
85
+ int match = 1;
86
+ for (int j = 0; j < depth; j++) {
87
+ if (g_stack_samples[i].func_ids[j] != stack[j]) {
88
+ match = 0;
89
+ break;
90
+ }
91
+ }
92
+ if (match) return i;
93
+ }
94
+ }
95
+
96
+ /* Create new sample if space available */
97
+ if (g_num_stack_samples >= MAX_STACK_SAMPLES) return -1;
98
+
99
+ int idx = g_num_stack_samples++;
100
+ g_stack_samples[idx].depth = depth;
101
+ for (int j = 0; j < depth; j++) {
102
+ g_stack_samples[idx].func_ids[j] = stack[j];
103
+ }
104
+ g_stack_samples[idx].time_ns = 0;
105
+ return idx;
106
+ }
107
+
108
+ /* Record time for current stack */
109
+ static void record_stack_time(uint64_t time_ns) {
110
+ if (tls_stack_depth <= 0) return;
111
+
112
+ int idx = find_or_create_stack_sample(tls_current_stack, tls_stack_depth);
113
+ if (idx >= 0) {
114
+ g_stack_samples[idx].time_ns += time_ns;
115
+ }
116
+ }
117
+
118
+ /* Initialize profiling system */
119
+ void konpeito_profile_init(int num_functions, const char* output_path) {
120
+ if (g_initialized) return;
121
+
122
+ g_num_functions = num_functions < MAX_FUNCTIONS ? num_functions : MAX_FUNCTIONS;
123
+
124
+ if (output_path && strlen(output_path) < sizeof(g_output_path)) {
125
+ strncpy(g_output_path, output_path, sizeof(g_output_path) - 1);
126
+ g_output_path[sizeof(g_output_path) - 1] = '\0';
127
+ }
128
+
129
+ for (int i = 0; i < MAX_FUNCTIONS; i++) {
130
+ g_profiles[i].name = NULL;
131
+ g_profiles[i].call_count = 0;
132
+ g_profiles[i].total_time_ns = 0;
133
+ }
134
+
135
+ #ifdef __APPLE__
136
+ mach_timebase_info(&g_timebase_info);
137
+ #endif
138
+
139
+ g_initialized = 1;
140
+
141
+ /* Register atexit handler */
142
+ atexit(konpeito_profile_finalize);
143
+ }
144
+
145
+ /* Called at function entry */
146
+ void konpeito_profile_enter(int func_id, const char* func_name) {
147
+ if (!g_initialized) return;
148
+ if (func_id < 0 || func_id >= MAX_FUNCTIONS) return;
149
+ if (tls_stack_depth >= MAX_CALL_DEPTH) return;
150
+
151
+ /* Register function name (only first call matters) */
152
+ if (g_profiles[func_id].name == NULL) {
153
+ g_profiles[func_id].name = func_name;
154
+ }
155
+
156
+ /* Increment call count */
157
+ g_profiles[func_id].call_count++;
158
+
159
+ /* Push entry onto call stack with timestamp */
160
+ tls_call_stack[tls_stack_depth].func_id = func_id;
161
+ tls_call_stack[tls_stack_depth].entry_time = get_time_ns();
162
+
163
+ /* Track current stack for flame graph */
164
+ tls_current_stack[tls_stack_depth] = func_id;
165
+
166
+ tls_stack_depth++;
167
+ }
168
+
169
+ /* Called at function exit */
170
+ void konpeito_profile_exit(int func_id) {
171
+ if (!g_initialized) return;
172
+ if (func_id < 0 || func_id >= MAX_FUNCTIONS) return;
173
+ if (tls_stack_depth <= 0) return;
174
+
175
+ /* Calculate elapsed time */
176
+ uint64_t exit_time = get_time_ns();
177
+
178
+ /* Verify we're exiting the right function */
179
+ if (tls_call_stack[tls_stack_depth - 1].func_id == func_id) {
180
+ uint64_t elapsed = exit_time - tls_call_stack[tls_stack_depth - 1].entry_time;
181
+ g_profiles[func_id].total_time_ns += elapsed;
182
+
183
+ /* Record time for flame graph at current stack depth */
184
+ record_stack_time(elapsed);
185
+ }
186
+
187
+ /* Pop from call stack */
188
+ tls_stack_depth--;
189
+ }
190
+
191
+ /* Escape string for JSON output */
192
+ static void write_json_string(FILE* fp, const char* str) {
193
+ fputc('"', fp);
194
+ while (*str) {
195
+ switch (*str) {
196
+ case '"': fputs("\\\"", fp); break;
197
+ case '\\': fputs("\\\\", fp); break;
198
+ case '\n': fputs("\\n", fp); break;
199
+ case '\r': fputs("\\r", fp); break;
200
+ case '\t': fputs("\\t", fp); break;
201
+ default: fputc(*str, fp); break;
202
+ }
203
+ str++;
204
+ }
205
+ fputc('"', fp);
206
+ }
207
+
208
+ /* Write flame graph folded format */
209
+ static void write_flame_graph_folded(void) {
210
+ /* Generate folded file path */
211
+ char folded_path[1024];
212
+ strncpy(folded_path, g_output_path, sizeof(folded_path) - 1);
213
+ folded_path[sizeof(folded_path) - 1] = '\0';
214
+
215
+ /* Replace .json with .folded */
216
+ char* ext = strstr(folded_path, ".json");
217
+ if (ext) {
218
+ strcpy(ext, ".folded");
219
+ } else {
220
+ strncat(folded_path, ".folded", sizeof(folded_path) - strlen(folded_path) - 1);
221
+ }
222
+
223
+ FILE* fp = fopen(folded_path, "w");
224
+ if (!fp) {
225
+ fprintf(stderr, "Warning: Could not write flame graph to %s\n", folded_path);
226
+ return;
227
+ }
228
+
229
+ /* Write folded format: func1;func2;func3 samples
230
+ * Use microseconds as sample count for better granularity */
231
+ for (int i = 0; i < g_num_stack_samples; i++) {
232
+ if (g_stack_samples[i].time_ns == 0) continue;
233
+
234
+ /* Write stack (semicolon-separated function names) */
235
+ for (int j = 0; j < g_stack_samples[i].depth; j++) {
236
+ int func_id = g_stack_samples[i].func_ids[j];
237
+ const char* name = g_profiles[func_id].name;
238
+ if (name) {
239
+ if (j > 0) fputc(';', fp);
240
+ fputs(name, fp);
241
+ }
242
+ }
243
+
244
+ /* Write sample count (microseconds) */
245
+ uint64_t samples = g_stack_samples[i].time_ns / 1000; /* ns to us */
246
+ if (samples == 0) samples = 1; /* At least 1 sample */
247
+ fprintf(fp, " %llu\n", (unsigned long long)samples);
248
+ }
249
+
250
+ fclose(fp);
251
+ fprintf(stderr, "Flame graph data written to: %s\n", folded_path);
252
+ fprintf(stderr, " Generate SVG with: flamegraph.pl %s > profile.svg\n", folded_path);
253
+ }
254
+
255
+ /* Finalize and write profile data */
256
+ void konpeito_profile_finalize(void) {
257
+ if (!g_initialized) return;
258
+ g_initialized = 0; /* Prevent double finalization */
259
+
260
+ /* Write flame graph folded format */
261
+ write_flame_graph_folded();
262
+
263
+ FILE* fp = fopen(g_output_path, "w");
264
+ if (!fp) {
265
+ fprintf(stderr, "Warning: Could not write profile to %s\n", g_output_path);
266
+ return;
267
+ }
268
+
269
+ /* Calculate total time */
270
+ uint64_t total_time = 0;
271
+ for (int i = 0; i < g_num_functions; i++) {
272
+ if (g_profiles[i].name) {
273
+ total_time += g_profiles[i].total_time_ns;
274
+ }
275
+ }
276
+
277
+ /* Write JSON output */
278
+ fprintf(fp, "{\n \"functions\": [\n");
279
+
280
+ int first = 1;
281
+ for (int i = 0; i < g_num_functions; i++) {
282
+ if (g_profiles[i].name == NULL) continue;
283
+
284
+ uint64_t calls = g_profiles[i].call_count;
285
+ uint64_t time_ns = g_profiles[i].total_time_ns;
286
+ double time_ms = time_ns / 1000000.0;
287
+ double percent = total_time > 0 ? (time_ns * 100.0 / total_time) : 0.0;
288
+
289
+ if (!first) fprintf(fp, ",\n");
290
+ first = 0;
291
+
292
+ fprintf(fp, " {\n");
293
+ fprintf(fp, " \"name\": ");
294
+ write_json_string(fp, g_profiles[i].name);
295
+ fprintf(fp, ",\n");
296
+ fprintf(fp, " \"calls\": %llu,\n", (unsigned long long)calls);
297
+ fprintf(fp, " \"time_ms\": %.3f,\n", time_ms);
298
+ fprintf(fp, " \"percent\": %.2f\n", percent);
299
+ fprintf(fp, " }");
300
+ }
301
+
302
+ fprintf(fp, "\n ],\n");
303
+ fprintf(fp, " \"total_time_ms\": %.3f\n", total_time / 1000000.0);
304
+ fprintf(fp, "}\n");
305
+
306
+ fclose(fp);
307
+
308
+ /* Print summary to stderr */
309
+ fprintf(stderr, "\n=== Konpeito Profile Summary ===\n");
310
+ fprintf(stderr, "%-40s %12s %12s %8s\n", "Function", "Calls", "Time (ms)", "%");
311
+ fprintf(stderr, "%-40s %12s %12s %8s\n",
312
+ "----------------------------------------",
313
+ "------------", "------------", "--------");
314
+
315
+ for (int i = 0; i < g_num_functions; i++) {
316
+ if (g_profiles[i].name == NULL) continue;
317
+
318
+ uint64_t calls = g_profiles[i].call_count;
319
+ uint64_t time_ns = g_profiles[i].total_time_ns;
320
+ double time_ms = time_ns / 1000000.0;
321
+ double percent = total_time > 0 ? (time_ns * 100.0 / total_time) : 0.0;
322
+
323
+ /* Truncate function name if too long */
324
+ char truncated_name[41];
325
+ if (strlen(g_profiles[i].name) > 40) {
326
+ strncpy(truncated_name, g_profiles[i].name, 37);
327
+ truncated_name[37] = '.';
328
+ truncated_name[38] = '.';
329
+ truncated_name[39] = '.';
330
+ truncated_name[40] = '\0';
331
+ } else {
332
+ strncpy(truncated_name, g_profiles[i].name, 40);
333
+ truncated_name[40] = '\0';
334
+ }
335
+
336
+ fprintf(stderr, "%-40s %12llu %12.3f %7.2f%%\n",
337
+ truncated_name, (unsigned long long)calls, time_ms, percent);
338
+ }
339
+
340
+ fprintf(stderr, "\nProfile data written to: %s\n", g_output_path);
341
+ }