konpeito 0.2.3 → 0.2.4
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 +16 -0
- data/lib/konpeito/codegen/cruby_backend.rb +7 -0
- data/lib/konpeito/codegen/jvm_generator.rb +160 -40
- data/lib/konpeito/codegen/llvm_generator.rb +236 -58
- data/lib/konpeito/hir/builder.rb +61 -27
- data/lib/konpeito/hir/nodes.rb +2 -0
- data/lib/konpeito/version.rb +1 -1
- data/tools/konpeito-asm/build.sh +1 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +12 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMatchData.java +50 -1
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +721 -8
- metadata +1 -20
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFiber.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMatchData.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.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/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
|
@@ -155,6 +155,16 @@ module Konpeito
|
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
# Declare a native function signature
|
|
158
|
+
# Infer native param type symbol from HIR param type (used when no RBS method sig is available,
|
|
159
|
+
# e.g. for monomorphized functions like dot_Vec2 whose name has no matching method entry).
|
|
160
|
+
def native_param_type_sym_from_hir(param)
|
|
161
|
+
t = param.type
|
|
162
|
+
return :Float64 if t == TypeChecker::Types::FLOAT || t&.name == :Float
|
|
163
|
+
return :Int64 if t == TypeChecker::Types::INTEGER || t&.name == :Integer
|
|
164
|
+
# Everything else (NativeClass, unknown) → use pointer (else branch in caller)
|
|
165
|
+
:OtherNativeClass
|
|
166
|
+
end
|
|
167
|
+
|
|
158
168
|
def declare_native_function(hir_func, native_class_type)
|
|
159
169
|
struct_type = get_or_create_native_class_struct(native_class_type)
|
|
160
170
|
method_sig = native_class_type.methods[hir_func.name.to_sym]
|
|
@@ -163,11 +173,14 @@ module Konpeito
|
|
|
163
173
|
param_types = [LLVM::Pointer(struct_type)]
|
|
164
174
|
|
|
165
175
|
hir_func.params.each_with_index do |param, i|
|
|
166
|
-
|
|
176
|
+
# Prefer RBS-derived type; fall back to HIR param.type for monomorphized functions
|
|
177
|
+
# (monomorphized funcs like dot_Vec2 have no matching method_sig entry)
|
|
178
|
+
param_type_sym = method_sig&.param_types&.[](i) ||
|
|
179
|
+
native_param_type_sym_from_hir(param)
|
|
167
180
|
llvm_type = case param_type_sym
|
|
168
181
|
when :Int64 then LLVM::Int64
|
|
169
182
|
when :Float64 then LLVM::Double
|
|
170
|
-
else LLVM::Pointer(LLVM::Int8) # Other NativeClass
|
|
183
|
+
else LLVM::Pointer(LLVM::Int8) # Other NativeClass or value type
|
|
171
184
|
end
|
|
172
185
|
param_types << llvm_type
|
|
173
186
|
end
|
|
@@ -639,6 +652,10 @@ module Konpeito
|
|
|
639
652
|
# void rb_define_method(VALUE klass, const char *name, VALUE (*func)(...), int argc)
|
|
640
653
|
@rb_define_method = @mod.functions.add("rb_define_method", [value_type, ptr_type, ptr_type, LLVM::Int32], LLVM.Void)
|
|
641
654
|
|
|
655
|
+
# void rb_define_singleton_method(VALUE obj, const char *name, VALUE (*func)(...), int argc)
|
|
656
|
+
@rb_define_singleton_method = @mod.functions.add("rb_define_singleton_method",
|
|
657
|
+
[value_type, ptr_type, ptr_type, LLVM::Int32], LLVM.Void)
|
|
658
|
+
|
|
642
659
|
# Instance variable functions
|
|
643
660
|
# VALUE rb_ivar_get(VALUE obj, ID id)
|
|
644
661
|
@rb_ivar_get = @mod.functions.add("rb_ivar_get", [value_type, id_type], value_type)
|
|
@@ -1050,7 +1067,8 @@ module Konpeito
|
|
|
1050
1067
|
# Store parameters (already unboxed)
|
|
1051
1068
|
hir_func.params.each_with_index do |param, i|
|
|
1052
1069
|
param_value = func.params[i + 1] # +1 to skip self
|
|
1053
|
-
param_type_sym = method_sig&.param_types&.[](i) ||
|
|
1070
|
+
param_type_sym = method_sig&.param_types&.[](i) ||
|
|
1071
|
+
native_param_type_sym_from_hir(param)
|
|
1054
1072
|
|
|
1055
1073
|
# Determine type tag based on parameter type
|
|
1056
1074
|
type_tag = case param_type_sym
|
|
@@ -2057,9 +2075,10 @@ module Konpeito
|
|
|
2057
2075
|
end
|
|
2058
2076
|
|
|
2059
2077
|
def generate_string_lit(inst)
|
|
2060
|
-
# Create global string constant
|
|
2078
|
+
# Create global string constant with UTF-8 encoding (Ruby default for string literals)
|
|
2061
2079
|
str_ptr = @builder.global_string_pointer(inst.value)
|
|
2062
|
-
|
|
2080
|
+
len = LLVM::Int64.from_i(inst.value.bytesize)
|
|
2081
|
+
ruby_str = @builder.call(@rb_utf8_str_new, str_ptr, len)
|
|
2063
2082
|
@variables[inst.result_var] = ruby_str if inst.result_var
|
|
2064
2083
|
ruby_str
|
|
2065
2084
|
end
|
|
@@ -4354,7 +4373,9 @@ module Konpeito
|
|
|
4354
4373
|
|
|
4355
4374
|
# Generate a callback function for rb_block_call
|
|
4356
4375
|
# Signature: VALUE func(VALUE yielded_arg, VALUE data2, int argc, VALUE *argv, VALUE blockarg)
|
|
4357
|
-
|
|
4376
|
+
# escape_cells_mode: when true, data2 is a Ruby Array VALUE (GC-safe heap)
|
|
4377
|
+
# used for procs created inside block callbacks (nested lambda capture safety)
|
|
4378
|
+
def generate_block_callback(block_def, method_name, captures = [], capture_types = {}, escape_cells_mode: false)
|
|
4358
4379
|
return nil unless block_def
|
|
4359
4380
|
|
|
4360
4381
|
# Create a unique name for the callback
|
|
@@ -4373,35 +4394,59 @@ module Konpeito
|
|
|
4373
4394
|
saved_vars = @variables.dup
|
|
4374
4395
|
saved_types = @variable_types.dup
|
|
4375
4396
|
saved_allocas = @variable_allocas.dup
|
|
4397
|
+
saved_in_block_callback = @in_block_callback
|
|
4376
4398
|
|
|
4377
4399
|
# Create entry block for callback
|
|
4378
4400
|
entry = callback_func.basic_blocks.append("entry")
|
|
4379
4401
|
@builder.position_at_end(entry)
|
|
4380
4402
|
|
|
4381
|
-
# Reset variable tracking for callback scope
|
|
4403
|
+
# Reset variable tracking for callback scope.
|
|
4404
|
+
# Set @in_block_callback so nested proc creation uses GC-safe escape-cells mode.
|
|
4405
|
+
@in_block_callback = true
|
|
4382
4406
|
@variables = {}
|
|
4383
4407
|
@variable_types = {}
|
|
4384
4408
|
@variable_allocas = {}
|
|
4385
4409
|
|
|
4386
4410
|
# Setup captured variable access through data2 pointer
|
|
4387
|
-
# data2 is a pointer to array of VALUE* (pointers to captured variables)
|
|
4388
4411
|
unless captures.empty?
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4412
|
+
if escape_cells_mode
|
|
4413
|
+
# data2 is a Ruby Array VALUE (GC-safe heap snapshot).
|
|
4414
|
+
# Read each capture via rb_ary_entry(data2, i) and store in a local alloca.
|
|
4415
|
+
# This mode is used for procs created inside block callbacks to avoid
|
|
4416
|
+
# dangling stack pointers when the outer callback returns.
|
|
4417
|
+
ary_val = callback_func.params[1] # already VALUE (Ruby Array)
|
|
4418
|
+
captures.each_with_index do |capture, i|
|
|
4419
|
+
val = @builder.call(@rb_ary_entry, ary_val,
|
|
4420
|
+
LLVM::Int64.from_i(i), "esc_#{capture.name}")
|
|
4421
|
+
# Store in an alloca so LoadLocal/StoreLocal can use it normally.
|
|
4422
|
+
# Writes to this alloca stay local (no back-write to the GC array),
|
|
4423
|
+
# which is correct for snapshots from a returned outer scope.
|
|
4424
|
+
cap_alloca = @builder.alloca(value_type, "esc_alloca_#{capture.name}")
|
|
4425
|
+
@builder.store(val, cap_alloca)
|
|
4426
|
+
@variable_allocas[capture.name] = cap_alloca
|
|
4427
|
+
@variable_types[capture.name] = capture_types[capture.name] || :value
|
|
4428
|
+
end
|
|
4429
|
+
else
|
|
4430
|
+
# data2 is a pointer to array of VALUE* (pointers to captured variables).
|
|
4431
|
+
# Used for procs created in method scope — the outer stack stays alive
|
|
4432
|
+
# for the proc's lifetime, so dangling is not a concern.
|
|
4433
|
+
ptr_type = LLVM::Pointer(value_type)
|
|
4434
|
+
array_type = LLVM::Array(ptr_type, captures.size)
|
|
4435
|
+
captures_ptr = @builder.int2ptr(callback_func.params[1],
|
|
4436
|
+
LLVM::Pointer(array_type), "captures_ptr")
|
|
4437
|
+
|
|
4438
|
+
captures.each_with_index do |capture, i|
|
|
4439
|
+
# Get pointer to the pointer (VALUE**)
|
|
4440
|
+
elem_ptr_ptr = @builder.gep2(array_type, captures_ptr,
|
|
4441
|
+
[LLVM::Int32.from_i(0), LLVM::Int32.from_i(i)], "cap_#{capture.name}_ptr_ptr")
|
|
4442
|
+
# Load the pointer to the variable (VALUE*)
|
|
4443
|
+
elem_ptr = @builder.load2(ptr_type, elem_ptr_ptr, "cap_#{capture.name}_ptr")
|
|
4444
|
+
|
|
4445
|
+
# Store in variable_allocas so LoadLocal/StoreLocal can use it
|
|
4446
|
+
@variable_allocas[capture.name] = elem_ptr
|
|
4447
|
+
# Preserve the original type from outer scope
|
|
4448
|
+
@variable_types[capture.name] = capture_types[capture.name] || :value
|
|
4449
|
+
end
|
|
4405
4450
|
end
|
|
4406
4451
|
end
|
|
4407
4452
|
|
|
@@ -4619,6 +4664,7 @@ module Konpeito
|
|
|
4619
4664
|
@variables = saved_vars
|
|
4620
4665
|
@variable_types = saved_types
|
|
4621
4666
|
@variable_allocas = saved_allocas
|
|
4667
|
+
@in_block_callback = saved_in_block_callback
|
|
4622
4668
|
|
|
4623
4669
|
callback_func
|
|
4624
4670
|
end
|
|
@@ -4635,27 +4681,57 @@ module Konpeito
|
|
|
4635
4681
|
capture_types[cap.name] = @variable_types[cap.name]
|
|
4636
4682
|
end
|
|
4637
4683
|
|
|
4638
|
-
#
|
|
4639
|
-
|
|
4684
|
+
# Choose capture strategy based on whether we are inside a block callback.
|
|
4685
|
+
#
|
|
4686
|
+
# When @in_block_callback is true (i.e. this proc is being created inside
|
|
4687
|
+
# another lambda/block callback), the outer callback's stack will be freed
|
|
4688
|
+
# before this inner proc is ever called. Using pointer-to-stack-alloca would
|
|
4689
|
+
# leave dangling pointers. Instead we snapshot the current VALUES into a
|
|
4690
|
+
# GC-tracked Ruby Array so they survive after the outer callback returns.
|
|
4691
|
+
#
|
|
4692
|
+
# When @in_block_callback is false (method scope), the outer stack stays
|
|
4693
|
+
# alive for the proc's lifetime, so we use the pointer-to-alloca approach
|
|
4694
|
+
# which also supports cross-scope mutation (e.g. `x = 0; inc = -> { x += 1 }`).
|
|
4695
|
+
use_escape_cells = @in_block_callback && !captures.empty?
|
|
4640
4696
|
|
|
4641
|
-
#
|
|
4697
|
+
# Create the callback function
|
|
4698
|
+
callback_func = generate_block_callback(block_def, "proc", captures, capture_types,
|
|
4699
|
+
escape_cells_mode: use_escape_cells)
|
|
4700
|
+
|
|
4701
|
+
# Setup captures data
|
|
4642
4702
|
if captures.empty?
|
|
4643
4703
|
captures_data = LLVM::Int64.from_i(0) # NULL for no captures
|
|
4704
|
+
elsif use_escape_cells
|
|
4705
|
+
# Build a Ruby Array holding a snapshot of each captured VALUE.
|
|
4706
|
+
# data2 in the callback receives this Array VALUE directly — the GC
|
|
4707
|
+
# keeps it (and the stored VALUEs) alive regardless of stack lifetime.
|
|
4708
|
+
esc_ary = @builder.call(@rb_ary_new_capa, LLVM::Int64.from_i(captures.size),
|
|
4709
|
+
"esc_ary")
|
|
4710
|
+
captures.each do |capture|
|
|
4711
|
+
# Load current VALUE from the alloca (or use 0/Qnil if missing)
|
|
4712
|
+
val = if @variable_allocas[capture.name]
|
|
4713
|
+
@builder.load2(value_type, @variable_allocas[capture.name],
|
|
4714
|
+
"esc_load_#{capture.name}")
|
|
4715
|
+
else
|
|
4716
|
+
@qnil
|
|
4717
|
+
end
|
|
4718
|
+
@builder.call(@rb_ary_push, esc_ary, val)
|
|
4719
|
+
end
|
|
4720
|
+
# Pass the Ruby Array VALUE directly as data2 (cast to i64 / VALUE)
|
|
4721
|
+
captures_data = esc_ary
|
|
4644
4722
|
else
|
|
4645
|
-
# Allocate array of pointers to captured variables
|
|
4723
|
+
# Allocate array of pointers to captured variables (on stack — safe for
|
|
4724
|
+
# method-scope procs because the method outlives the proc calls).
|
|
4646
4725
|
ptr_type = LLVM::Pointer(value_type)
|
|
4647
4726
|
array_type = LLVM::Array(ptr_type, captures.size)
|
|
4648
4727
|
captures_array = @builder.alloca(array_type, "proc_captures")
|
|
4649
4728
|
|
|
4650
4729
|
captures.each_with_index do |capture, i|
|
|
4651
|
-
# Get the alloca for this captured variable
|
|
4652
4730
|
alloca = @variable_allocas[capture.name]
|
|
4731
|
+
ptr = @builder.gep(captures_array, [LLVM::Int32.from_i(0), LLVM::Int32.from_i(i)])
|
|
4653
4732
|
if alloca
|
|
4654
|
-
ptr = @builder.gep(captures_array, [LLVM::Int32.from_i(0), LLVM::Int32.from_i(i)])
|
|
4655
4733
|
@builder.store(alloca, ptr)
|
|
4656
4734
|
else
|
|
4657
|
-
# Variable not found, store null
|
|
4658
|
-
ptr = @builder.gep(captures_array, [LLVM::Int32.from_i(0), LLVM::Int32.from_i(i)])
|
|
4659
4735
|
@builder.store(LLVM::Pointer(value_type).null, ptr)
|
|
4660
4736
|
end
|
|
4661
4737
|
end
|
|
@@ -4666,6 +4742,11 @@ module Konpeito
|
|
|
4666
4742
|
# Call rb_proc_new to create the Proc object
|
|
4667
4743
|
result = @builder.call(@rb_proc_new, callback_func, captures_data)
|
|
4668
4744
|
|
|
4745
|
+
# Override lambda? and arity on the Proc singleton to match lambda semantics.
|
|
4746
|
+
# rb_proc_new always creates a regular Proc (lambda?=false, arity=-1).
|
|
4747
|
+
# We define singleton methods so the proc behaves like a lambda.
|
|
4748
|
+
attach_lambda_singleton_methods(result, block_def.params.size)
|
|
4749
|
+
|
|
4669
4750
|
if inst.result_var
|
|
4670
4751
|
@variables[inst.result_var] = result
|
|
4671
4752
|
@variable_types[inst.result_var] = :value
|
|
@@ -4674,6 +4755,44 @@ module Konpeito
|
|
|
4674
4755
|
result
|
|
4675
4756
|
end
|
|
4676
4757
|
|
|
4758
|
+
# Attach lambda? and arity singleton methods to a Proc created via rb_proc_new.
|
|
4759
|
+
# This makes the proc behave like a true Ruby lambda for introspection purposes.
|
|
4760
|
+
def attach_lambda_singleton_methods(proc_val, param_count)
|
|
4761
|
+
saved_block = @builder.insert_block
|
|
4762
|
+
|
|
4763
|
+
# --- Shared lambda_true helper (created once per module) ---
|
|
4764
|
+
@lambda_true_func ||= begin
|
|
4765
|
+
f = @mod.functions.add("__konpeito_lambda_true", [value_type], value_type)
|
|
4766
|
+
bb = f.basic_blocks.append("entry")
|
|
4767
|
+
@builder.position_at_end(bb)
|
|
4768
|
+
@builder.ret(@qtrue)
|
|
4769
|
+
f
|
|
4770
|
+
end
|
|
4771
|
+
|
|
4772
|
+
# --- Per-arity helper (arity_N) ---
|
|
4773
|
+
arity_func_name = "__konpeito_proc_arity_#{param_count}"
|
|
4774
|
+
arity_func = @mod.functions[arity_func_name] || begin
|
|
4775
|
+
f = @mod.functions.add(arity_func_name, [value_type], value_type)
|
|
4776
|
+
bb = f.basic_blocks.append("entry")
|
|
4777
|
+
@builder.position_at_end(bb)
|
|
4778
|
+
arity_ruby = @builder.call(@rb_int2inum, LLVM::Int64.from_i(param_count), "arity_val")
|
|
4779
|
+
@builder.ret(arity_ruby)
|
|
4780
|
+
f
|
|
4781
|
+
end
|
|
4782
|
+
|
|
4783
|
+
@builder.position_at_end(saved_block)
|
|
4784
|
+
|
|
4785
|
+
# rb_define_singleton_method(obj, name, func, argc)
|
|
4786
|
+
# argc=0 means the Ruby method takes no arguments (VALUE self only in C)
|
|
4787
|
+
name_lambda_q = @builder.global_string_pointer("lambda?", "str_lambda_q")
|
|
4788
|
+
name_arity = @builder.global_string_pointer("arity", "str_arity")
|
|
4789
|
+
|
|
4790
|
+
@builder.call(@rb_define_singleton_method, proc_val, name_lambda_q,
|
|
4791
|
+
@lambda_true_func, LLVM::Int32.from_i(0))
|
|
4792
|
+
@builder.call(@rb_define_singleton_method, proc_val, name_arity,
|
|
4793
|
+
arity_func, LLVM::Int32.from_i(0))
|
|
4794
|
+
end
|
|
4795
|
+
|
|
4677
4796
|
# Generate a call to a Proc object (from ProcCall HIR instruction)
|
|
4678
4797
|
def generate_proc_call(inst)
|
|
4679
4798
|
# Get the Proc value
|
|
@@ -4998,31 +5117,85 @@ module Konpeito
|
|
|
4998
5117
|
end
|
|
4999
5118
|
end
|
|
5000
5119
|
|
|
5120
|
+
# Pre-allocate allocas for block-local variables
|
|
5121
|
+
block_local_vars = collect_local_variables_in_block(block_def)
|
|
5122
|
+
capture_names = captures.map(&:name)
|
|
5123
|
+
block_local_vars.each do |var_name, var_type|
|
|
5124
|
+
next if capture_names.include?(var_name)
|
|
5125
|
+
next if @variable_allocas[var_name]
|
|
5126
|
+
llvm_type, type_tag = llvm_type_for_ruby_type(var_type)
|
|
5127
|
+
alloca = @builder.alloca(llvm_type, "blk_#{var_name}")
|
|
5128
|
+
@variable_allocas[var_name] = alloca
|
|
5129
|
+
@variable_types[var_name] = type_tag
|
|
5130
|
+
end
|
|
5131
|
+
|
|
5001
5132
|
# Compile block body
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5133
|
+
if block_def.body.size > 1
|
|
5134
|
+
# Multi-block body (contains while/if control flow)
|
|
5135
|
+
saved_blocks = @blocks.dup
|
|
5136
|
+
saved_return_blocks = @return_blocks
|
|
5137
|
+
saved_loop_stack = @loop_stack
|
|
5138
|
+
|
|
5139
|
+
@return_blocks = Set.new
|
|
5140
|
+
@loop_stack = [] if @loop_stack.nil?
|
|
5141
|
+
|
|
5142
|
+
block_def.body.each do |hir_block|
|
|
5143
|
+
llvm_block = callback_func.basic_blocks.append(hir_block.label)
|
|
5144
|
+
@blocks[hir_block.label] = llvm_block
|
|
5009
5145
|
end
|
|
5010
|
-
end
|
|
5011
5146
|
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
result_type = @variable_types[var_name] || :value
|
|
5016
|
-
elsif last_inst && last_inst.respond_to?(:result_var) && last_inst.result_var
|
|
5017
|
-
result_type = @variable_types[last_inst.result_var] || :value
|
|
5018
|
-
end
|
|
5147
|
+
block_def.body.each do |hir_block|
|
|
5148
|
+
@return_blocks << hir_block.label if hir_block.terminator.is_a?(HIR::Return)
|
|
5149
|
+
end
|
|
5019
5150
|
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5151
|
+
# Jump from entry to first HIR block
|
|
5152
|
+
@builder.br(@blocks[block_def.body.first.label])
|
|
5153
|
+
|
|
5154
|
+
# Generate each block (instructions + terminators)
|
|
5155
|
+
block_def.body.each do |hir_block|
|
|
5156
|
+
generate_block(callback_func, hir_block)
|
|
5157
|
+
end
|
|
5158
|
+
|
|
5159
|
+
# Ensure all blocks have terminators
|
|
5160
|
+
callback_func.basic_blocks.each do |llvm_block|
|
|
5161
|
+
next if llvm_block.name == "entry"
|
|
5162
|
+
last_instr = llvm_block.instructions.last
|
|
5163
|
+
has_terminator = last_instr && (last_instr.opcode == :ret || last_instr.opcode == :br ||
|
|
5164
|
+
last_instr.opcode == :cond_br || last_instr.opcode == :switch ||
|
|
5165
|
+
last_instr.opcode == :unreachable)
|
|
5166
|
+
unless has_terminator
|
|
5167
|
+
@builder.position_at_end(llvm_block)
|
|
5168
|
+
@builder.ret(last_instr || @qnil)
|
|
5169
|
+
end
|
|
5170
|
+
end
|
|
5171
|
+
|
|
5172
|
+
@blocks = saved_blocks
|
|
5173
|
+
@return_blocks = saved_return_blocks
|
|
5174
|
+
@loop_stack = saved_loop_stack
|
|
5175
|
+
else
|
|
5176
|
+
result = @qnil
|
|
5177
|
+
result_type = :value
|
|
5178
|
+
last_inst = nil
|
|
5179
|
+
block_def.body.each do |basic_block|
|
|
5180
|
+
basic_block.instructions.each do |hir_inst|
|
|
5181
|
+
result = generate_instruction(hir_inst)
|
|
5182
|
+
last_inst = hir_inst
|
|
5183
|
+
end
|
|
5184
|
+
end
|
|
5185
|
+
|
|
5186
|
+
if last_inst.is_a?(HIR::StoreLocal)
|
|
5187
|
+
var_name = last_inst.var.name
|
|
5188
|
+
result_type = @variable_types[var_name] || :value
|
|
5189
|
+
elsif last_inst && last_inst.respond_to?(:result_var) && last_inst.result_var
|
|
5190
|
+
result_type = @variable_types[last_inst.result_var] || :value
|
|
5191
|
+
end
|
|
5192
|
+
|
|
5193
|
+
result = @qnil if result.nil?
|
|
5194
|
+
if result.is_a?(LLVM::Value) && result_type != :value
|
|
5195
|
+
result = convert_value(result, result_type, :value)
|
|
5196
|
+
end
|
|
5197
|
+
@builder.ret(result.is_a?(LLVM::Value) ? result : @qnil)
|
|
5024
5198
|
end
|
|
5025
|
-
@builder.ret(result.is_a?(LLVM::Value) ? result : @qnil)
|
|
5026
5199
|
|
|
5027
5200
|
# Restore builder state
|
|
5028
5201
|
@builder.position_at_end(saved_block) if saved_block
|
|
@@ -6584,7 +6757,11 @@ module Konpeito
|
|
|
6584
6757
|
# Set type tag if this is a native method call
|
|
6585
6758
|
# Check if we're calling a method on self that's a NativeClass method
|
|
6586
6759
|
if @current_native_class && inst.result_var
|
|
6587
|
-
|
|
6760
|
+
# Try both the (possibly monomorphized) func_name and the original method name.
|
|
6761
|
+
# Monomorphized names like "rn_Vec2_dot_Vec2" won't match the methods hash key
|
|
6762
|
+
# ":dot", so fall back to inst.method_name to find the right method signature.
|
|
6763
|
+
method_sig = @current_native_class.methods[func_name.to_sym] ||
|
|
6764
|
+
@current_native_class.methods[inst.method_name.to_sym]
|
|
6588
6765
|
if method_sig
|
|
6589
6766
|
result_type_tag = case method_sig.return_type
|
|
6590
6767
|
when :Int64 then :i64
|
|
@@ -6989,17 +7166,18 @@ module Konpeito
|
|
|
6989
7166
|
end
|
|
6990
7167
|
result = @builder.phi(value_type, phi_incoming)
|
|
6991
7168
|
when :method
|
|
6992
|
-
# Use
|
|
6993
|
-
rb_respond_to
|
|
6994
|
-
|
|
6995
|
-
|
|
7169
|
+
# Use rb_obj_respond_to with include_private=1 to check if method exists
|
|
7170
|
+
# (rb_respond_to only checks public methods, but defined? should find private methods too)
|
|
7171
|
+
rb_obj_respond_to = @mod.functions["rb_obj_respond_to"] || @mod.functions.add(
|
|
7172
|
+
"rb_obj_respond_to",
|
|
7173
|
+
[value_type, value_type, LLVM::Int32],
|
|
6996
7174
|
LLVM::Int32
|
|
6997
7175
|
)
|
|
6998
7176
|
# Check on main object (self)
|
|
6999
7177
|
self_val = @variables["self"] || @builder.load2(value_type, @rb_cObject, "rb_cObject_self")
|
|
7000
7178
|
name_ptr = @builder.global_string_pointer(inst.name)
|
|
7001
7179
|
name_id = @builder.call(@rb_intern, name_ptr)
|
|
7002
|
-
is_defined = @builder.call(
|
|
7180
|
+
is_defined = @builder.call(rb_obj_respond_to, self_val, name_id, LLVM::Int32.from_i(1))
|
|
7003
7181
|
is_true = @builder.icmp(:ne, is_defined, LLVM::Int32.from_i(0))
|
|
7004
7182
|
|
|
7005
7183
|
defined_bb = func.basic_blocks.append("defined_method_yes")
|
data/lib/konpeito/hir/builder.rb
CHANGED
|
@@ -1704,6 +1704,13 @@ module Konpeito
|
|
|
1704
1704
|
# Determine scope (module/class context)
|
|
1705
1705
|
scope = @current_module || @current_class
|
|
1706
1706
|
|
|
1707
|
+
# Track top-level constants (scope == nil) for static Init registration in native backend.
|
|
1708
|
+
# __main__ is never called from Init, so top-level constants must be set explicitly.
|
|
1709
|
+
if scope.nil?
|
|
1710
|
+
literal_node = visit_literal_value(typed_node.children.first)
|
|
1711
|
+
@program.toplevel_constants << [name, literal_node]
|
|
1712
|
+
end
|
|
1713
|
+
|
|
1707
1714
|
inst = StoreConstant.new(name: name, value: value, scope: scope, type: typed_node.type)
|
|
1708
1715
|
emit(inst)
|
|
1709
1716
|
value
|
|
@@ -2133,38 +2140,65 @@ module Konpeito
|
|
|
2133
2140
|
block = visit_block_def(block_child)
|
|
2134
2141
|
end
|
|
2135
2142
|
|
|
2136
|
-
# Handle &blk block argument reference (e.g., arr.map(&blk))
|
|
2143
|
+
# Handle &blk block argument reference (e.g., arr.map(&blk)) or
|
|
2144
|
+
# &:symbol Symbol#to_proc (e.g., arr.map(&:upcase))
|
|
2137
2145
|
unless block
|
|
2138
2146
|
block_arg_child = typed_node.children.find { |c| c.node_type == :block_argument }
|
|
2139
2147
|
if block_arg_child
|
|
2140
2148
|
blk_node = block_arg_child.node
|
|
2141
2149
|
if blk_node.respond_to?(:expression) && blk_node.expression
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2150
|
+
expr = blk_node.expression
|
|
2151
|
+
|
|
2152
|
+
if expr.is_a?(Prism::SymbolNode)
|
|
2153
|
+
# &:method_name — Symbol#to_proc: create { |x| x.method_name }
|
|
2154
|
+
sym_method = expr.value.to_s
|
|
2155
|
+
param_name = "__sym_proc_param"
|
|
2156
|
+
param = Param.new(name: param_name, type: TypeChecker::Types::UNTYPED)
|
|
2157
|
+
param_var = LocalVar.new(name: param_name, type: TypeChecker::Types::UNTYPED)
|
|
2158
|
+
param_load = LoadLocal.new(var: param_var, type: TypeChecker::Types::UNTYPED, result_var: new_temp_var)
|
|
2159
|
+
call_inst = Call.new(
|
|
2160
|
+
receiver: param_load,
|
|
2161
|
+
method_name: sym_method,
|
|
2162
|
+
args: [],
|
|
2163
|
+
type: TypeChecker::Types::UNTYPED,
|
|
2164
|
+
result_var: new_temp_var
|
|
2165
|
+
)
|
|
2166
|
+
bb = BasicBlock.new(label: "sym_proc_body")
|
|
2167
|
+
bb.add_instruction(param_load)
|
|
2168
|
+
bb.add_instruction(call_inst)
|
|
2169
|
+
block = BlockDef.new(
|
|
2170
|
+
params: [param],
|
|
2171
|
+
body: [bb],
|
|
2172
|
+
captures: []
|
|
2173
|
+
)
|
|
2174
|
+
else
|
|
2175
|
+
blk_name = expr.name.to_s
|
|
2176
|
+
# Create a wrapper BlockDef: { |__block_arg_param| blk.call(__block_arg_param) }
|
|
2177
|
+
# Load blk from captures inside the block body (not from outer scope)
|
|
2178
|
+
param_name = "__block_arg_param"
|
|
2179
|
+
param = Param.new(name: param_name, type: TypeChecker::Types::UNTYPED)
|
|
2180
|
+
param_var = LocalVar.new(name: param_name, type: TypeChecker::Types::UNTYPED)
|
|
2181
|
+
param_load = LoadLocal.new(var: param_var, type: TypeChecker::Types::UNTYPED, result_var: new_temp_var)
|
|
2182
|
+
blk_local_var = LocalVar.new(name: blk_name, type: TypeChecker::Types::UNTYPED)
|
|
2183
|
+
blk_load = LoadLocal.new(var: blk_local_var, type: TypeChecker::Types::UNTYPED, result_var: new_temp_var)
|
|
2184
|
+
call_inst = Call.new(
|
|
2185
|
+
receiver: blk_load,
|
|
2186
|
+
method_name: "call",
|
|
2187
|
+
args: [param_load],
|
|
2188
|
+
type: TypeChecker::Types::UNTYPED,
|
|
2189
|
+
result_var: new_temp_var
|
|
2190
|
+
)
|
|
2191
|
+
# Wrap in a BasicBlock (BlockDef.body expects Array[BasicBlock])
|
|
2192
|
+
bb = BasicBlock.new(label: "block_arg_body")
|
|
2193
|
+
bb.add_instruction(blk_load)
|
|
2194
|
+
bb.add_instruction(param_load)
|
|
2195
|
+
bb.add_instruction(call_inst)
|
|
2196
|
+
block = BlockDef.new(
|
|
2197
|
+
params: [param],
|
|
2198
|
+
body: [bb],
|
|
2199
|
+
captures: [Capture.new(name: blk_name, type: TypeChecker::Types::UNTYPED)]
|
|
2200
|
+
)
|
|
2201
|
+
end
|
|
2168
2202
|
end
|
|
2169
2203
|
end
|
|
2170
2204
|
end
|
data/lib/konpeito/hir/nodes.rb
CHANGED
|
@@ -37,12 +37,14 @@ module Konpeito
|
|
|
37
37
|
# Program is the top-level container
|
|
38
38
|
class Program < Node
|
|
39
39
|
attr_reader :functions, :classes, :modules
|
|
40
|
+
attr_accessor :toplevel_constants # Array of [name, literal_node] for top-level constants
|
|
40
41
|
|
|
41
42
|
def initialize(functions: [], classes: [], modules: [])
|
|
42
43
|
super(type: TypeChecker::Types::NIL)
|
|
43
44
|
@functions = functions
|
|
44
45
|
@classes = classes
|
|
45
46
|
@modules = modules
|
|
47
|
+
@toplevel_constants = []
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
data/lib/konpeito/version.rb
CHANGED
data/tools/konpeito-asm/build.sh
CHANGED
|
@@ -45,6 +45,7 @@ mkdir -p "$BUILD_DIR/konpeito/runtime"
|
|
|
45
45
|
"$SCRIPT_DIR/src/konpeito/runtime/KRactorPort.java" \
|
|
46
46
|
"$SCRIPT_DIR/src/konpeito/runtime/KMatchData.java" \
|
|
47
47
|
"$SCRIPT_DIR/src/konpeito/runtime/KFiber.java" \
|
|
48
|
+
"$SCRIPT_DIR/src/konpeito/runtime/KRubyException.java" \
|
|
48
49
|
"$SCRIPT_DIR/src/konpeito/runtime/RubyDispatch.java"
|
|
49
50
|
|
|
50
51
|
# Copy runtime classes to a separate directory for JAR bundling
|
|
@@ -14,6 +14,7 @@ import java.util.*;
|
|
|
14
14
|
*/
|
|
15
15
|
public class KHash<K, V> implements Map<K, V> {
|
|
16
16
|
private final LinkedHashMap<K, V> data;
|
|
17
|
+
private boolean frozen = false;
|
|
17
18
|
|
|
18
19
|
// ========================================================================
|
|
19
20
|
// Constructors
|
|
@@ -31,6 +32,17 @@ public class KHash<K, V> implements Map<K, V> {
|
|
|
31
32
|
// Ruby-specific methods
|
|
32
33
|
// ========================================================================
|
|
33
34
|
|
|
35
|
+
/** Ruby: hash.freeze — makes this hash immutable */
|
|
36
|
+
public KHash<K, V> freeze() {
|
|
37
|
+
this.frozen = true;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Ruby: hash.frozen? */
|
|
42
|
+
public boolean isFrozen() {
|
|
43
|
+
return frozen;
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
/** Ruby: hash.deconstruct_keys(keys) — returns self (for pattern matching) */
|
|
35
47
|
@SuppressWarnings("unchecked")
|
|
36
48
|
public KHash<K, V> deconstruct_keys(Object keys) {
|