konpeito 0.2.2 → 0.2.3

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/lib/konpeito/codegen/builtin_methods.rb +4 -2
  4. data/lib/konpeito/codegen/cruby_backend.rb +122 -10
  5. data/lib/konpeito/codegen/inliner.rb +9 -3
  6. data/lib/konpeito/codegen/jvm_generator.rb +3986 -919
  7. data/lib/konpeito/codegen/llvm_generator.rb +334 -45
  8. data/lib/konpeito/codegen/monomorphizer.rb +14 -2
  9. data/lib/konpeito/hir/builder.rb +150 -20
  10. data/lib/konpeito/hir/nodes.rb +16 -0
  11. data/lib/konpeito/type_checker/hm_inferrer.rb +100 -41
  12. data/lib/konpeito/type_checker/types.rb +6 -6
  13. data/lib/konpeito/type_checker/unification.rb +8 -1
  14. data/lib/konpeito/version.rb +1 -1
  15. data/tools/konpeito-asm/build.sh +1 -0
  16. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  17. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  18. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFiber.class +0 -0
  19. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  20. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.class +0 -0
  21. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRubyException.class +0 -0
  22. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  23. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  24. data/tools/konpeito-asm/src/KonpeitoAssembler.java +17 -1
  25. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +97 -4
  26. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +20 -25
  27. data/tools/konpeito-asm/src/konpeito/runtime/KFiber.java +112 -0
  28. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +67 -0
  29. data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +55 -0
  30. data/tools/konpeito-asm/src/konpeito/runtime/KRubyException.java +79 -0
  31. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +5 -0
  32. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +285 -19
  33. metadata +7 -1
@@ -56,7 +56,7 @@ module Konpeito
56
56
  end
57
57
  end
58
58
 
59
- attr_reader :profiler, :variadic_functions
59
+ attr_reader :profiler, :variadic_functions, :alias_renamed_methods
60
60
 
61
61
  def generate(hir_program)
62
62
  @hir_program = hir_program
@@ -83,16 +83,23 @@ module Konpeito
83
83
  # Scan HIR to register NativeClassTypes before code generation
84
84
  scan_for_native_class_types(hir_program)
85
85
 
86
+ # Pre-scan: detect parameters that can receive nil at call sites
87
+ @nil_possible_params = scan_nil_possible_params(hir_program)
88
+
86
89
  # Generate vtables for classes that use them
87
90
  generate_vtables
88
91
 
92
+ # Deduplicate functions: when a method is redefined (def speak ... def speak),
93
+ # keep only the last definition to avoid LLVM name collisions (.1 suffix)
94
+ functions = deduplicate_functions(hir_program.functions)
95
+
89
96
  # First pass: declare all functions (needed for forward references / monomorphization)
90
- hir_program.functions.each do |func|
97
+ functions.each do |func|
91
98
  declare_function(func)
92
99
  end
93
100
 
94
101
  # Second pass: generate code for each function
95
- hir_program.functions.each do |func|
102
+ functions.each do |func|
96
103
  generate_function_body(func)
97
104
  end
98
105
 
@@ -104,6 +111,38 @@ module Konpeito
104
111
  @mod
105
112
  end
106
113
 
114
+ # Remove earlier definitions when a method is redefined in the same class.
115
+ # Keeps the last definition (which is the override).
116
+ def deduplicate_functions(functions)
117
+ # Build alias map: method_name -> [alias_names] per class
118
+ alias_targets = {} # "ClassName#method_name" -> true (method is aliased)
119
+ @hir_program.classes.each do |cd|
120
+ (cd.aliases || []).each do |new_name, old_name|
121
+ alias_targets["#{cd.name}##{old_name}"] = true
122
+ end
123
+ end
124
+
125
+ seen = {}
126
+ @alias_renamed_methods = {} # "ClassName#old_name" -> renamed_func_name
127
+
128
+ functions.each_with_index do |func, i|
129
+ key = [func.owner_class, func.owner_module, func.name.to_s, func.class_method?]
130
+ if seen.key?(key) && alias_targets["#{func.owner_class}##{func.name}"]
131
+ # This method was aliased and is now being redefined.
132
+ # Rename the earlier definition so the alias can point to it.
133
+ old_idx = seen[key]
134
+ old_func = functions[old_idx]
135
+ renamed = "#{old_func.name}$alias_orig"
136
+ @alias_renamed_methods["#{func.owner_class}##{func.name}"] = renamed
137
+ # Create a copy of the old function with the renamed name
138
+ old_func.instance_variable_set(:@name, renamed.to_sym)
139
+ end
140
+ seen[key] = i
141
+ end
142
+ # Keep all functions (including renamed originals)
143
+ functions
144
+ end
145
+
107
146
  # Declare a function (create LLVM function without body)
108
147
  def declare_function(hir_func)
109
148
  native_class_type = detect_native_class_for_function(hir_func)
@@ -158,8 +197,8 @@ module Konpeito
158
197
 
159
198
  # Declare a standard Ruby function signature
160
199
  def declare_ruby_function(hir_func)
161
- # Count regular parameters (non-keyword, non-keyword_rest, non-rest)
162
- regular_params = hir_func.params.reject { |p| p.keyword || p.keyword_rest || p.rest }
200
+ # Count regular parameters (non-keyword, non-keyword_rest, non-rest, non-block)
201
+ regular_params = hir_func.params.reject { |p| p.keyword || p.keyword_rest || p.rest || p.block }
163
202
  keyword_params = hir_func.params.select(&:keyword)
164
203
  keyword_rest_param = hir_func.params.find(&:keyword_rest)
165
204
  rest_param = hir_func.params.find(&:rest)
@@ -234,6 +273,53 @@ module Konpeito
234
273
  end
235
274
  end
236
275
 
276
+ # Pre-scan all HIR functions to detect parameters that can receive nil at call sites.
277
+ # Returns a Hash: { "ClassName#method_name" => Set[param_index], ... }
278
+ # so codegen can keep those parameters as VALUE type (not unboxed).
279
+ def scan_nil_possible_params(hir_program)
280
+ # Build function index: function_name → HIR::Function (for param count)
281
+ func_index = {}
282
+ hir_program.functions.each do |func|
283
+ key = func.owner_class ? "#{func.owner_class}##{func.name}" : func.name
284
+ func_index[key] = func
285
+ end
286
+
287
+ nil_params = {} # "ClassName#method" => Set[param_index]
288
+
289
+ hir_program.functions.each do |func|
290
+ func.body.each do |block|
291
+ block.instructions.each do |inst|
292
+ next unless inst.is_a?(HIR::Call)
293
+
294
+ # Detect ClassName.new(nil, ...) → maps to ClassName#initialize
295
+ target_key = nil
296
+ if inst.method_name == "new" && inst.receiver.is_a?(HIR::ConstantLookup)
297
+ class_name = inst.receiver.name.to_s
298
+ target_key = "#{class_name}#initialize"
299
+ elsif inst.receiver.is_a?(HIR::SelfRef) || inst.receiver.nil?
300
+ # Self call or bare function call
301
+ if func.owner_class
302
+ target_key = "#{func.owner_class}##{inst.method_name}"
303
+ else
304
+ target_key = inst.method_name
305
+ end
306
+ end
307
+
308
+ next unless target_key
309
+
310
+ inst.args.each_with_index do |arg, i|
311
+ if arg.is_a?(HIR::NilLit)
312
+ nil_params[target_key] ||= Set.new
313
+ nil_params[target_key] << i
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end
319
+
320
+ nil_params
321
+ end
322
+
237
323
  # Auto-generate NativeClassType from HM-inferred ivar types (RBS-free classes)
238
324
  def auto_generate_native_class_types(hir_program)
239
325
  hir_program.classes.each do |class_def|
@@ -567,8 +653,8 @@ module Konpeito
567
653
  # void rb_cvar_set(VALUE klass, ID id, VALUE val)
568
654
  @rb_cvar_set = @mod.functions.add("rb_cvar_set", [value_type, id_type, value_type], LLVM.Void)
569
655
 
570
- # VALUE rb_class_of(VALUE obj) - get class of object
571
- @rb_class_of = @mod.functions.add("rb_class_of", [value_type], value_type)
656
+ # VALUE rb_obj_class(VALUE obj) - get class of object (exported C API)
657
+ @rb_obj_class = @mod.functions.add("rb_obj_class", [value_type], value_type)
572
658
 
573
659
  # Block/Yield functions
574
660
  # VALUE rb_yield(VALUE val) - yield with single value
@@ -581,6 +667,9 @@ module Konpeito
581
667
  # int rb_block_given_p(void) - check if block is given
582
668
  @rb_block_given_p = @mod.functions.add("rb_block_given_p", [], LLVM::Int32)
583
669
 
670
+ # VALUE rb_block_proc(void) - capture the current block as a Proc object
671
+ @rb_block_proc = @mod.functions.add("rb_block_proc", [], value_type)
672
+
584
673
  # rb_block_call - call method with block callback
585
674
  # VALUE rb_block_call(VALUE obj, ID mid, int argc, const VALUE *argv,
586
675
  # rb_block_call_func_t proc, VALUE data2)
@@ -1048,10 +1137,11 @@ module Konpeito
1048
1137
  @variable_types[var_name] = type_tag
1049
1138
  end
1050
1139
 
1051
- # Separate regular, keyword, and keyword_rest parameters
1052
- regular_params = hir_func.params.reject { |p| p.keyword || p.keyword_rest || p.rest }
1140
+ # Separate regular, keyword, keyword_rest, and block parameters
1141
+ regular_params = hir_func.params.reject { |p| p.keyword || p.keyword_rest || p.rest || p.block }
1053
1142
  keyword_params = hir_func.params.select(&:keyword)
1054
1143
  keyword_rest_param = hir_func.params.find(&:keyword_rest)
1144
+ block_param = hir_func.params.find(&:block)
1055
1145
 
1056
1146
  # Check if this is a variadic function
1057
1147
  variadic_info = @variadic_functions[mangled]
@@ -1341,6 +1431,36 @@ module Konpeito
1341
1431
  end
1342
1432
  end # End of variadic_info else block
1343
1433
 
1434
+ # Handle &blk block parameter: capture the current block as a Proc
1435
+ if block_param
1436
+ alloca = @variable_allocas[block_param.name]
1437
+ if alloca
1438
+ # Use rb_block_given_p to check, then rb_block_proc to capture
1439
+ current_func = @builder.insert_block.parent
1440
+ has_block_bb = current_func.basic_blocks.append("has_block_param")
1441
+ no_block_bb = current_func.basic_blocks.append("no_block_param")
1442
+ block_param_continue = current_func.basic_blocks.append("block_param_continue")
1443
+
1444
+ given = @builder.call(@rb_block_given_p)
1445
+ is_given = @builder.icmp(:ne, given, LLVM::Int32.from_i(0))
1446
+ @builder.cond(is_given, has_block_bb, no_block_bb)
1447
+
1448
+ @builder.position_at_end(has_block_bb)
1449
+ proc_val = @builder.call(@rb_block_proc)
1450
+ @builder.br(block_param_continue)
1451
+
1452
+ @builder.position_at_end(no_block_bb)
1453
+ @builder.br(block_param_continue)
1454
+
1455
+ @builder.position_at_end(block_param_continue)
1456
+ block_value = @builder.phi(value_type, { has_block_bb => proc_val, no_block_bb => @qnil })
1457
+ @builder.store(block_value, alloca)
1458
+
1459
+ # Remap entry block since we created new basic blocks
1460
+ @blocks[hir_func.body.first.label] = block_param_continue
1461
+ end
1462
+ end
1463
+
1344
1464
  # Insert profiling entry probe after parameter setup
1345
1465
  insert_profile_entry_probe(hir_func)
1346
1466
 
@@ -1435,9 +1555,18 @@ module Konpeito
1435
1555
  def collect_local_variables_with_types(hir_func)
1436
1556
  vars = {}
1437
1557
 
1558
+ # Build function key for nil-possible param lookup
1559
+ func_key = hir_func.owner_class ? "#{hir_func.owner_class}##{hir_func.name}" : hir_func.name
1560
+ nil_param_indices = @nil_possible_params&.[](func_key)
1561
+
1438
1562
  # Add parameters with their types
1439
- hir_func.params.each do |p|
1440
- vars[p.name] = p.type
1563
+ hir_func.params.each_with_index do |p, i|
1564
+ if nil_param_indices&.include?(i)
1565
+ # Parameter can receive nil at some call site — force VALUE type
1566
+ vars[p.name] = TypeChecker::Types::UNTYPED
1567
+ else
1568
+ vars[p.name] = p.type
1569
+ end
1441
1570
  end
1442
1571
 
1443
1572
  # First pass: collect temp vars that come from safe navigation calls
@@ -1897,6 +2026,8 @@ module Konpeito
1897
2026
  generate_super_call(inst)
1898
2027
  when HIR::MultiWriteExtract
1899
2028
  generate_multi_write_extract(inst)
2029
+ when HIR::MultiWriteSplat
2030
+ generate_multi_write_splat(inst)
1900
2031
  else
1901
2032
  # Unknown instruction, store nil
1902
2033
  if inst.result_var
@@ -2215,7 +2346,7 @@ module Konpeito
2215
2346
  when :i8 then LLVM::Int8
2216
2347
  else value_type
2217
2348
  end
2218
- @builder.load2(llvm_type, alloca, var_name)
2349
+ @builder.load2(llvm_type, alloca, "#{var_name}_val")
2219
2350
  else
2220
2351
  @variables[var_name] || @qnil
2221
2352
  end
@@ -2457,6 +2588,13 @@ module Konpeito
2457
2588
  type_tag = @variable_types[var_name] || :value
2458
2589
  [value, type_tag]
2459
2590
  end
2591
+ when HIR::SelfRef
2592
+ # SelfRef may not have a result_var (e.g. implicit self in method calls)
2593
+ if hir_value.result_var && @variables.key?(hir_value.result_var)
2594
+ [@variables[hir_value.result_var], @variable_types[hir_value.result_var] || :value]
2595
+ else
2596
+ [get_self_value, :value]
2597
+ end
2460
2598
  when HIR::Instruction
2461
2599
  if hir_value.result_var
2462
2600
  # Check if instruction was already generated
@@ -2717,7 +2855,7 @@ module Konpeito
2717
2855
  elsif @current_hir_func&.owner_class
2718
2856
  # For instance methods, get the class from self using rb_class_of
2719
2857
  self_value = get_self_value
2720
- @builder.call(@rb_class_of, self_value)
2858
+ @builder.call(@rb_obj_class, self_value)
2721
2859
  else
2722
2860
  # Top-level: use rb_cObject (need to load the global)
2723
2861
  @builder.load2(value_type, @rb_cObject, "rb_cObject")
@@ -2995,7 +3133,6 @@ module Konpeito
2995
3133
  if arg.is_a?(HIR::SplatArg)
2996
3134
  # Concat the splat array into the args array
2997
3135
  splat_value = get_value_as_ruby(arg.expression)
2998
- # Convert to array if not already (rb_ary_to_ary)
2999
3136
  @builder.call(@rb_ary_concat, args_array, splat_value)
3000
3137
  else
3001
3138
  # Push regular arg
@@ -3234,9 +3371,14 @@ module Konpeito
3234
3371
  # Bool can be either TrueClass or FalseClass; return TrueClass as representative
3235
3372
  @builder.load2(value_type, @rb_cTrueClass, "rb_cTrueClass")
3236
3373
  else
3237
- # For other types, use rb_path2class
3238
- type_ptr = @builder.global_string_pointer(type_str)
3239
- @builder.call(@rb_path2class, type_ptr)
3374
+ # Skip unresolved RBS type parameters (Elem, K, V, etc.) — return rb_cObject as fallback
3375
+ if type_str.match?(/\A[A-Z][a-z]*\z/) && %w[Elem K V U T S R E A B C D N M].include?(type_str)
3376
+ @builder.load2(value_type, @rb_cObject, "rb_cObject")
3377
+ else
3378
+ # For other types, use rb_path2class
3379
+ type_ptr = @builder.global_string_pointer(type_str)
3380
+ @builder.call(@rb_path2class, type_ptr)
3381
+ end
3240
3382
  end
3241
3383
  end
3242
3384
 
@@ -3668,9 +3810,21 @@ module Konpeito
3668
3810
 
3669
3811
  # Generate block body instructions
3670
3812
  body_result = nil
3813
+ body_result_type = :value
3814
+ last_body_inst = nil
3671
3815
  block.body.each do |basic_block|
3672
3816
  basic_block.instructions.each do |body_inst|
3673
3817
  body_result = generate_instruction(body_inst)
3818
+ last_body_inst = body_inst
3819
+ end
3820
+ end
3821
+
3822
+ # Determine the type tag of the block body result
3823
+ if last_body_inst
3824
+ if last_body_inst.respond_to?(:result_var) && last_body_inst.result_var
3825
+ body_result_type = @variable_types[last_body_inst.result_var] || :value
3826
+ elsif last_body_inst.is_a?(HIR::StoreLocal)
3827
+ body_result_type = @variable_types[last_body_inst.var.name] || :value
3674
3828
  end
3675
3829
  end
3676
3830
 
@@ -3682,16 +3836,13 @@ module Konpeito
3682
3836
  # Handle result based on method type
3683
3837
  case method_sym
3684
3838
  when :map, :collect
3685
- # Push block result to result array (box if unboxed)
3839
+ # Push block result to result array (box if needed)
3840
+ # Only re-box if the result is actually unboxed (not a VALUE from a method call)
3686
3841
  mapped_val = if body_result
3687
- if use_unboxed && body_result.is_a?(LLVM::Value)
3688
- if body_result.type == LLVM::Int64
3689
- @builder.call(@rb_int2inum, body_result)
3690
- elsif body_result.type == LLVM::Double
3691
- @builder.call(@rb_float_new, body_result)
3692
- else
3693
- ensure_ruby_value(body_result)
3694
- end
3842
+ if body_result_type == :i64 && body_result.is_a?(LLVM::Value)
3843
+ @builder.call(@rb_int2inum, body_result)
3844
+ elsif body_result_type == :double && body_result.is_a?(LLVM::Value)
3845
+ @builder.call(@rb_float_new, body_result)
3695
3846
  else
3696
3847
  ensure_ruby_value(body_result)
3697
3848
  end
@@ -4261,6 +4412,10 @@ module Konpeito
4261
4412
  # - If argc < params.size: destructure yielded_arg with rb_ary_entry (e.g., Hash#each yields [k,v] pair)
4262
4413
  if block_def.params.size == 1
4263
4414
  param_name = block_def.params.first.name
4415
+ # Create alloca for single parameter (needed for method calls like it.to_s)
4416
+ param_alloca = @builder.alloca(value_type, "#{param_name}_alloca")
4417
+ @builder.store(callback_func.params[0], param_alloca)
4418
+ @variable_allocas[param_name] = param_alloca
4264
4419
  @variables[param_name] = callback_func.params[0] # yielded_arg
4265
4420
  @variable_types[param_name] = :value
4266
4421
  elsif block_def.params.size > 1
@@ -4807,10 +4962,12 @@ module Konpeito
4807
4962
  saved_vars = @variables.dup
4808
4963
  saved_types = @variable_types.dup
4809
4964
  saved_allocas = @variable_allocas.dup
4965
+ saved_current_function = @current_function
4810
4966
 
4811
4967
  # Create entry block for callback
4812
4968
  entry = callback_func.basic_blocks.append("entry")
4813
4969
  @builder.position_at_end(entry)
4970
+ @current_function = callback_func
4814
4971
 
4815
4972
  # Reset variable tracking for callback scope
4816
4973
  @variables = {}
@@ -4872,6 +5029,7 @@ module Konpeito
4872
5029
  @variables = saved_vars
4873
5030
  @variable_types = saved_types
4874
5031
  @variable_allocas = saved_allocas
5032
+ @current_function = saved_current_function
4875
5033
 
4876
5034
  callback_func
4877
5035
  end
@@ -6593,16 +6751,39 @@ module Konpeito
6593
6751
  return result
6594
6752
  end
6595
6753
 
6596
- # Look up a constant (typically a class name)
6597
- # Use rb_const_get(rb_cObject, rb_intern(name))
6598
- const_name_ptr = @builder.global_string_pointer(inst.name)
6599
- const_id = @builder.call(@rb_intern, const_name_ptr)
6600
-
6601
- # Load rb_cObject (for top-level constants)
6602
- rb_cobject_val = @builder.load2(value_type, @rb_cObject, "rb_cObject")
6603
-
6604
- # Look up the constant
6605
- result = @builder.call(@rb_const_get, rb_cobject_val, const_id)
6754
+ parts = inst.name.to_s.split("::")
6755
+
6756
+ if parts.size > 1
6757
+ # Qualified constant path (e.g., CoMath::PI, Foo::Bar::BAZ)
6758
+ # Chain rb_const_get calls: rb_const_get(rb_const_get(rb_cObject, "CoMath"), "PI")
6759
+ scope = @builder.load2(value_type, @rb_cObject, "rb_cObject")
6760
+ parts.each do |part|
6761
+ part_ptr = @builder.global_string_pointer(part)
6762
+ part_id = @builder.call(@rb_intern, part_ptr)
6763
+ scope = @builder.call(@rb_const_get, scope, part_id)
6764
+ end
6765
+ result = scope
6766
+ else
6767
+ # Unqualified constant - search current class/module first, then Object
6768
+ const_name = parts.first
6769
+ owner_class = @current_hir_func&.owner_class || @current_hir_func&.owner_module
6770
+ if owner_class
6771
+ # Look up constant in the owning class/module
6772
+ owner_ptr = @builder.global_string_pointer(owner_class)
6773
+ owner_id = @builder.call(@rb_intern, owner_ptr)
6774
+ rb_cobject_val = @builder.load2(value_type, @rb_cObject, "rb_cObject")
6775
+ owner_value = @builder.call(@rb_const_get, rb_cobject_val, owner_id)
6776
+ const_name_ptr = @builder.global_string_pointer(const_name)
6777
+ const_id = @builder.call(@rb_intern, const_name_ptr)
6778
+ result = @builder.call(@rb_const_get, owner_value, const_id)
6779
+ else
6780
+ # Top-level constant
6781
+ const_name_ptr = @builder.global_string_pointer(const_name)
6782
+ const_id = @builder.call(@rb_intern, const_name_ptr)
6783
+ rb_cobject_val = @builder.load2(value_type, @rb_cObject, "rb_cObject")
6784
+ result = @builder.call(@rb_const_get, rb_cobject_val, const_id)
6785
+ end
6786
+ end
6606
6787
 
6607
6788
  if inst.result_var
6608
6789
  @variables[inst.result_var] = result
@@ -6659,6 +6840,65 @@ module Konpeito
6659
6840
  result
6660
6841
  end
6661
6842
 
6843
+ # Multi-write splat extraction: *rest = arr[start..-(end_offset+1)]
6844
+ # Collects a sub-array from the source array
6845
+ def generate_multi_write_splat(inst)
6846
+ array_val = get_value_as_ruby(inst.array)
6847
+
6848
+ # Get array length via rb_funcallv("length")
6849
+ len_id = @builder.call(@rb_intern, @builder.global_string_pointer("length"))
6850
+ arr_len_val = @builder.call(@rb_funcallv, array_val, len_id, LLVM::Int.from_i(0), LLVM::Pointer(LLVM::Int64).null_pointer)
6851
+ arr_len = @builder.call(@rb_num2long, arr_len_val)
6852
+
6853
+ start_idx = LLVM::Int64.from_i(inst.start_index)
6854
+ end_off = LLVM::Int64.from_i(inst.end_offset)
6855
+
6856
+ # splat_len = max(0, arr_len - start_index - end_offset)
6857
+ splat_len = @builder.sub(arr_len, start_idx)
6858
+ splat_len = @builder.sub(splat_len, end_off)
6859
+ zero = LLVM::Int64.from_i(0)
6860
+ is_negative = @builder.icmp(:slt, splat_len, zero)
6861
+ splat_len = @builder.select(is_negative, zero, splat_len)
6862
+
6863
+ # Create new array: rb_ary_new_capa(splat_len)
6864
+ result_arr = @builder.call(@rb_ary_new_capa, splat_len)
6865
+
6866
+ # Loop: for i = 0; i < splat_len; i++ { rb_ary_push(result, rb_ary_entry(arr, start + i)) }
6867
+ func = @builder.insert_block.parent
6868
+ loop_bb = func.basic_blocks.append("splat_loop")
6869
+ body_bb = func.basic_blocks.append("splat_body")
6870
+ done_bb = func.basic_blocks.append("splat_done")
6871
+
6872
+ # alloca for loop counter
6873
+ counter_alloca = @builder.alloca(LLVM::Int64, "splat_i")
6874
+ @builder.store(zero, counter_alloca)
6875
+ @builder.br(loop_bb)
6876
+
6877
+ # Loop condition
6878
+ @builder.position_at_end(loop_bb)
6879
+ counter = @builder.load2(LLVM::Int64, counter_alloca, "splat_counter")
6880
+ cond = @builder.icmp(:slt, counter, splat_len)
6881
+ @builder.cond(cond, body_bb, done_bb)
6882
+
6883
+ # Loop body
6884
+ @builder.position_at_end(body_bb)
6885
+ idx = @builder.add(start_idx, counter)
6886
+ elem = @builder.call(@rb_ary_entry, array_val, idx)
6887
+ @builder.call(@rb_ary_push, result_arr, elem)
6888
+ next_counter = @builder.add(counter, LLVM::Int64.from_i(1))
6889
+ @builder.store(next_counter, counter_alloca)
6890
+ @builder.br(loop_bb)
6891
+
6892
+ # Done
6893
+ @builder.position_at_end(done_bb)
6894
+
6895
+ if inst.result_var
6896
+ @variables[inst.result_var] = result_arr
6897
+ @variable_types[inst.result_var] = :value
6898
+ end
6899
+ result_arr
6900
+ end
6901
+
6662
6902
  # Generate defined? check
6663
6903
  def generate_defined_check(inst)
6664
6904
  func = @builder.insert_block.parent
@@ -6670,16 +6910,56 @@ module Konpeito
6670
6910
  result = @builder.call(@rb_str_new_cstr, str_ptr)
6671
6911
  when :constant
6672
6912
  # Use rb_const_defined to check at runtime
6913
+ # Handle path-style names like "CoMath::PI" by resolving each component
6673
6914
  rb_const_defined = @mod.functions["rb_const_defined"] || @mod.functions.add(
6674
6915
  "rb_const_defined",
6675
6916
  [value_type, value_type],
6676
6917
  LLVM::Int32
6677
6918
  )
6678
- rb_cobject_val = @builder.load2(value_type, @rb_cObject, "rb_cObject")
6679
- name_ptr = @builder.global_string_pointer(inst.name)
6680
- name_id = @builder.call(@rb_intern, name_ptr)
6681
- is_defined = @builder.call(rb_const_defined, rb_cobject_val, name_id)
6682
- is_true = @builder.icmp(:ne, is_defined, LLVM::Int32.from_i(0))
6919
+
6920
+ parts = inst.name.split("::")
6921
+ current_mod = @builder.load2(value_type, @rb_cObject, "rb_cObject")
6922
+
6923
+ if parts.size == 1
6924
+ # Simple constant: defined?(FOO)
6925
+ name_ptr = @builder.global_string_pointer(parts[0])
6926
+ name_id = @builder.call(@rb_intern, name_ptr)
6927
+ is_defined = @builder.call(rb_const_defined, current_mod, name_id)
6928
+ is_true = @builder.icmp(:ne, is_defined, LLVM::Int32.from_i(0))
6929
+ else
6930
+ # Path constant: defined?(Foo::Bar::BAZ)
6931
+ # Check each component exists, then resolve all but last, check last
6932
+ # For simplicity, check all components exist then return "constant"
6933
+ all_ok = nil
6934
+ parts.each_with_index do |part, i|
6935
+ name_ptr = @builder.global_string_pointer(part)
6936
+ name_id = @builder.call(@rb_intern, name_ptr)
6937
+ check = @builder.call(rb_const_defined, current_mod, name_id)
6938
+ check_bool = @builder.icmp(:ne, check, LLVM::Int32.from_i(0))
6939
+
6940
+ if i < parts.size - 1
6941
+ # Resolve intermediate module (only if defined)
6942
+ # Use rb_const_get to get the module for the next lookup
6943
+ resolve_bb = func.basic_blocks.append("resolve_#{part}")
6944
+ fail_bb = func.basic_blocks.append("fail_#{part}")
6945
+ @builder.cond(check_bool, resolve_bb, fail_bb)
6946
+
6947
+ @builder.position_at_end(resolve_bb)
6948
+ current_mod = @builder.call(@rb_const_get, current_mod, name_id)
6949
+
6950
+ # We'll merge fail paths at the end
6951
+ if all_ok.nil?
6952
+ all_ok = { fail_bbs: [fail_bb] }
6953
+ else
6954
+ all_ok[:fail_bbs] << fail_bb
6955
+ end
6956
+ else
6957
+ # Last component: just check if defined
6958
+ all_ok ||= { fail_bbs: [] }
6959
+ is_true = check_bool
6960
+ end
6961
+ end
6962
+ end
6683
6963
 
6684
6964
  defined_bb = func.basic_blocks.append("defined_yes")
6685
6965
  undefined_bb = func.basic_blocks.append("defined_no")
@@ -6692,11 +6972,22 @@ module Konpeito
6692
6972
  defined_val = @builder.call(@rb_str_new_cstr, str_ptr)
6693
6973
  @builder.br(merge_bb)
6694
6974
 
6975
+ # All fail paths (intermediate components not found) go to undefined
6695
6976
  @builder.position_at_end(undefined_bb)
6696
6977
  @builder.br(merge_bb)
6978
+ if all_ok && all_ok[:fail_bbs]
6979
+ all_ok[:fail_bbs].each do |fail_bb|
6980
+ @builder.position_at_end(fail_bb)
6981
+ @builder.br(merge_bb)
6982
+ end
6983
+ end
6697
6984
 
6698
6985
  @builder.position_at_end(merge_bb)
6699
- result = @builder.phi(value_type, { defined_bb => defined_val, undefined_bb => @qnil })
6986
+ phi_incoming = { defined_bb => defined_val, undefined_bb => @qnil }
6987
+ if all_ok && all_ok[:fail_bbs]
6988
+ all_ok[:fail_bbs].each { |fb| phi_incoming[fb] = @qnil }
6989
+ end
6990
+ result = @builder.phi(value_type, phi_incoming)
6700
6991
  when :method
6701
6992
  # Use rb_respond_to to check if method exists
6702
6993
  rb_respond_to = @mod.functions["rb_respond_to"] || @mod.functions.add(
@@ -6705,9 +6996,7 @@ module Konpeito
6705
6996
  LLVM::Int32
6706
6997
  )
6707
6998
  # Check on main object (self)
6708
- self_val = @variables["self"] || @builder.call(@rb_funcallv, @rb_cObject,
6709
- @builder.call(@rb_intern, @builder.global_string_pointer("new")),
6710
- LLVM::Int32.from_i(0), LLVM::Pointer(value_type).null)
6999
+ self_val = @variables["self"] || @builder.load2(value_type, @rb_cObject, "rb_cObject_self")
6711
7000
  name_ptr = @builder.global_string_pointer(inst.name)
6712
7001
  name_id = @builder.call(@rb_intern, name_ptr)
6713
7002
  is_defined = @builder.call(rb_respond_to, self_val, name_id)
@@ -194,8 +194,10 @@ module Konpeito
194
194
  skip_functions.merge(detect_nil_compared_functions)
195
195
 
196
196
  grouped.each do |(func_name, types), sites|
197
- next if types.all? { |t| t == TypeChecker::Types::UNTYPED }
197
+ next if types.any? { |t| t == TypeChecker::Types::UNTYPED || t.is_a?(TypeChecker::Types::Untyped) }
198
198
  next if skip_functions.include?(func_name.to_s)
199
+ # Skip if any type is an unresolved RBS type parameter (Elem, K, V, etc.)
200
+ next if types.any? { |t| t.is_a?(TypeChecker::Types::ClassInstance) && RBS_TYPE_PARAMS.include?(t.name) }
199
201
 
200
202
  type_suffix = types.map { |t| type_to_suffix(t) }.join("_")
201
203
  specialized = "#{func_name}_#{type_suffix}"
@@ -326,10 +328,20 @@ module Konpeito
326
328
  end
327
329
  end
328
330
 
331
+ # RBS type parameter names that should not be used as monomorphized suffixes
332
+ # These are unresolved generic type variables, not concrete Ruby classes
333
+ RBS_TYPE_PARAMS = Set.new(%w[Elem K V U T S R E A B C D N M].map(&:to_sym)).freeze
334
+
329
335
  def type_to_suffix(type)
330
336
  case type
331
337
  when TypeChecker::Types::ClassInstance
332
- type.name.to_s
338
+ # If the type name is an unresolved RBS type parameter (Elem, K, V, etc.),
339
+ # treat it as untyped to avoid generating rb_const_get("Elem") at runtime
340
+ if RBS_TYPE_PARAMS.include?(type.name)
341
+ "Any"
342
+ else
343
+ type.name.to_s
344
+ end
333
345
  when TypeChecker::Types::NilType
334
346
  "Nil"
335
347
  when TypeChecker::Types::BoolType