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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/lib/konpeito/codegen/builtin_methods.rb +4 -2
- data/lib/konpeito/codegen/cruby_backend.rb +122 -10
- data/lib/konpeito/codegen/inliner.rb +9 -3
- data/lib/konpeito/codegen/jvm_generator.rb +3986 -919
- data/lib/konpeito/codegen/llvm_generator.rb +334 -45
- data/lib/konpeito/codegen/monomorphizer.rb +14 -2
- data/lib/konpeito/hir/builder.rb +150 -20
- data/lib/konpeito/hir/nodes.rb +16 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +100 -41
- data/lib/konpeito/type_checker/types.rb +6 -6
- data/lib/konpeito/type_checker/unification.rb +8 -1
- data/lib/konpeito/version.rb +1 -1
- data/tools/konpeito-asm/build.sh +1 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFiber.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRubyException.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +17 -1
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +97 -4
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +20 -25
- data/tools/konpeito-asm/src/konpeito/runtime/KFiber.java +112 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +67 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +55 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRubyException.java +79 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +5 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +285 -19
- 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
|
-
|
|
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
|
-
|
|
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
|
|
571
|
-
@
|
|
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
|
|
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.
|
|
1440
|
-
|
|
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(@
|
|
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
|
-
#
|
|
3238
|
-
|
|
3239
|
-
|
|
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
|
|
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
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
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
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
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
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|