konpeito 0.2.4 → 0.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +645 -0
- data/CHANGELOG.md +37 -0
- data/Justfile +107 -0
- data/README.md +143 -43
- data/konpeito.gemspec +3 -2
- data/lib/konpeito/cli/build_command.rb +21 -3
- data/lib/konpeito/cli/completion_command.rb +298 -0
- data/lib/konpeito/cli/deps_command.rb +129 -21
- data/lib/konpeito/cli/fmt_command.rb +24 -132
- data/lib/konpeito/cli/run_command.rb +29 -3
- data/lib/konpeito/cli.rb +45 -14
- data/lib/konpeito/codegen/builtin_methods.rb +16 -0
- data/lib/konpeito/codegen/cruby_backend.rb +76 -6
- data/lib/konpeito/codegen/jvm_generator.rb +100 -9
- data/lib/konpeito/codegen/llvm_generator.rb +907 -195
- data/lib/konpeito/dependency_resolver.rb +32 -9
- data/lib/konpeito/hir/builder.rb +369 -57
- data/lib/konpeito/hir/nodes.rb +25 -5
- data/lib/konpeito/type_checker/rbs_loader.rb +3 -2
- data/lib/konpeito/ui/app.rb +1 -1
- data/lib/konpeito/version.rb +1 -1
- data/lib/konpeito.rb +0 -7
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +32 -0
- metadata +6 -23
- data/lib/konpeito/cli/lsp_command.rb +0 -40
- data/lib/konpeito/formatter/formatter.rb +0 -1214
- data/lib/konpeito/lsp/document_manager.rb +0 -820
- data/lib/konpeito/lsp/server.rb +0 -183
- data/lib/konpeito/lsp/transport.rb +0 -38
- data/test_native_array.rb +0 -172
- data/test_native_array_class.rb +0 -197
- data/test_native_class.rb +0 -151
|
@@ -7,6 +7,9 @@ module Konpeito
|
|
|
7
7
|
module Codegen
|
|
8
8
|
# Generates LLVM IR from HIR
|
|
9
9
|
class LLVMGenerator
|
|
10
|
+
# Special capture name used to pass outer `self` into block callbacks.
|
|
11
|
+
# Allows @ivar access inside blocks to use the enclosing method's self.
|
|
12
|
+
BLOCK_SELF_CAPTURE = "__blk_self__"
|
|
10
13
|
attr_reader :mod, :builder, :hir_program
|
|
11
14
|
|
|
12
15
|
def initialize(module_name: "konpeito", monomorphizer: nil, rbs_loader: nil, debug: false, profile: false, source_file: nil)
|
|
@@ -30,6 +33,9 @@ module Konpeito
|
|
|
30
33
|
@variables = {} # For SSA values (temps, etc.)
|
|
31
34
|
@variable_allocas = {} # For local variables (alloca-based for loop safety)
|
|
32
35
|
@variable_types = {} # Track unboxed types: :i64, :double, or :value
|
|
36
|
+
@block_exit_overrides = {} # When safe navigation expands a HIR block into multiple LLVM blocks,
|
|
37
|
+
# maps HIR block label → actual LLVM exit block (for phi predecessors)
|
|
38
|
+
@generating_block_label = nil # Label of the HIR block currently being generated
|
|
33
39
|
@hir_program = nil
|
|
34
40
|
@monomorphizer = monomorphizer
|
|
35
41
|
@rbs_loader = rbs_loader
|
|
@@ -42,6 +48,8 @@ module Konpeito
|
|
|
42
48
|
@variadic_functions = {} # Track functions with **kwargs or *args
|
|
43
49
|
@keyword_param_functions = {} # Track functions with keyword params (for direct call kwargs passing)
|
|
44
50
|
@comparison_result_vars = Set.new # Track variables holding comparison results (0/1 boolean)
|
|
51
|
+
@polymorphic_methods = Set.new # Method names defined in multiple classes (must not use direct call)
|
|
52
|
+
@hir_functions = {} # name -> HIR::Function; used to detect &blk params (proc-storing methods)
|
|
45
53
|
|
|
46
54
|
# Register all NativeClass types from RBS upfront
|
|
47
55
|
register_native_classes_from_rbs
|
|
@@ -126,7 +134,11 @@ module Konpeito
|
|
|
126
134
|
@alias_renamed_methods = {} # "ClassName#old_name" -> renamed_func_name
|
|
127
135
|
|
|
128
136
|
functions.each_with_index do |func, i|
|
|
129
|
-
|
|
137
|
+
# Use the mangled LLVM name as the dedup key.
|
|
138
|
+
# Two HIR functions with the same owner_class but different owner_module
|
|
139
|
+
# still produce the same LLVM symbol (e.g. rn_Style_align), so they must
|
|
140
|
+
# be deduplicated even if their [owner_class, owner_module, name] triples differ.
|
|
141
|
+
key = mangle_name(func)
|
|
130
142
|
if seen.key?(key) && alias_targets["#{func.owner_class}##{func.name}"]
|
|
131
143
|
# This method was aliased and is now being redefined.
|
|
132
144
|
# Rename the earlier definition so the alias can point to it.
|
|
@@ -139,12 +151,20 @@ module Konpeito
|
|
|
139
151
|
end
|
|
140
152
|
seen[key] = i
|
|
141
153
|
end
|
|
142
|
-
# Keep
|
|
143
|
-
|
|
154
|
+
# Keep only the last occurrence of each function key.
|
|
155
|
+
# Also keep alias-renamed originals (needed for alias resolution).
|
|
156
|
+
keep_indices = seen.values.to_set
|
|
157
|
+
functions.each_with_index do |func, i|
|
|
158
|
+
keep_indices << i if func.name.to_s.end_with?("$alias_orig")
|
|
159
|
+
end
|
|
160
|
+
functions.select.with_index { |_, i| keep_indices.include?(i) }
|
|
144
161
|
end
|
|
145
162
|
|
|
146
163
|
# Declare a function (create LLVM function without body)
|
|
147
164
|
def declare_function(hir_func)
|
|
165
|
+
# Record HIR function for later inspection (e.g. detecting &blk params)
|
|
166
|
+
@hir_functions[hir_func.name.to_s] = hir_func
|
|
167
|
+
|
|
148
168
|
native_class_type = detect_native_class_for_function(hir_func)
|
|
149
169
|
|
|
150
170
|
if native_class_type
|
|
@@ -159,8 +179,8 @@ module Konpeito
|
|
|
159
179
|
# e.g. for monomorphized functions like dot_Vec2 whose name has no matching method entry).
|
|
160
180
|
def native_param_type_sym_from_hir(param)
|
|
161
181
|
t = param.type
|
|
162
|
-
return :Float64 if t == TypeChecker::Types::FLOAT || t
|
|
163
|
-
return :Int64 if t == TypeChecker::Types::INTEGER || t
|
|
182
|
+
return :Float64 if t == TypeChecker::Types::FLOAT || (t.is_a?(TypeChecker::Types::ClassInstance) && t.name == :Float)
|
|
183
|
+
return :Int64 if t == TypeChecker::Types::INTEGER || (t.is_a?(TypeChecker::Types::ClassInstance) && t.name == :Integer)
|
|
164
184
|
# Everything else (NativeClass, unknown) → use pointer (else branch in caller)
|
|
165
185
|
:OtherNativeClass
|
|
166
186
|
end
|
|
@@ -202,6 +222,10 @@ module Konpeito
|
|
|
202
222
|
|
|
203
223
|
mangled = mangle_name(hir_func)
|
|
204
224
|
func = @mod.functions.add(mangled, param_types, return_type)
|
|
225
|
+
# Track methods that are defined in multiple classes — these require virtual dispatch
|
|
226
|
+
if (existing = @functions[hir_func.name]) && existing.name != mangled
|
|
227
|
+
@polymorphic_methods << hir_func.name
|
|
228
|
+
end
|
|
205
229
|
@functions[hir_func.name] = func
|
|
206
230
|
@functions[mangled] = func
|
|
207
231
|
@native_method_funcs ||= {}
|
|
@@ -257,6 +281,10 @@ module Konpeito
|
|
|
257
281
|
|
|
258
282
|
return_type = value_type
|
|
259
283
|
func = @mod.functions.add(mangled, param_types, return_type)
|
|
284
|
+
# Track methods that are defined in multiple classes — these require virtual dispatch
|
|
285
|
+
if (existing = @functions[hir_func.name]) && existing.name != mangled
|
|
286
|
+
@polymorphic_methods << hir_func.name
|
|
287
|
+
end
|
|
260
288
|
@functions[hir_func.name] = func
|
|
261
289
|
@functions[mangled] = func
|
|
262
290
|
end
|
|
@@ -504,6 +532,11 @@ module Konpeito
|
|
|
504
532
|
# We'll use rb_funcallv for variadic args: VALUE rb_funcallv(VALUE recv, ID mid, int argc, VALUE *argv)
|
|
505
533
|
@rb_funcallv = @mod.functions.add("rb_funcallv", [value_type, id_type, LLVM::Int32, LLVM::Pointer(value_type)], value_type)
|
|
506
534
|
|
|
535
|
+
# rb_funcallv_kw - call a method with keyword argument flag
|
|
536
|
+
# VALUE rb_funcallv_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat)
|
|
537
|
+
# kw_splat: RB_PASS_KEYWORDS (1) means last element of argv is a keyword Hash
|
|
538
|
+
@rb_funcallv_kw = @mod.functions.add("rb_funcallv_kw", [value_type, id_type, LLVM::Int32, LLVM::Pointer(value_type), LLVM::Int32], value_type)
|
|
539
|
+
|
|
507
540
|
# rb_int2inum - convert C int to Ruby Integer
|
|
508
541
|
@rb_int2inum = @mod.functions.add("rb_int2inum", [int_type], value_type) do |fn|
|
|
509
542
|
fn.linkage = :external
|
|
@@ -699,6 +732,14 @@ module Konpeito
|
|
|
699
732
|
LLVM::Pointer(block_callback_type), value_type],
|
|
700
733
|
value_type)
|
|
701
734
|
|
|
735
|
+
# rb_block_call_kw - call method with block callback and keyword argument flag
|
|
736
|
+
# VALUE rb_block_call_kw(VALUE obj, ID mid, int argc, const VALUE *argv,
|
|
737
|
+
# rb_block_call_func_t proc, VALUE data2, int kw_splat)
|
|
738
|
+
@rb_block_call_kw = @mod.functions.add("rb_block_call_kw",
|
|
739
|
+
[value_type, id_type, LLVM::Int32, LLVM::Pointer(value_type),
|
|
740
|
+
LLVM::Pointer(block_callback_type), value_type, LLVM::Int32],
|
|
741
|
+
value_type)
|
|
742
|
+
|
|
702
743
|
# Proc functions
|
|
703
744
|
# VALUE rb_proc_new(rb_block_call_func_t func, VALUE val)
|
|
704
745
|
# Creates a Proc object from a C callback function
|
|
@@ -1045,6 +1086,7 @@ module Konpeito
|
|
|
1045
1086
|
@variables = {}
|
|
1046
1087
|
@variable_allocas = {}
|
|
1047
1088
|
@variable_types = {}
|
|
1089
|
+
@block_exit_overrides = {}
|
|
1048
1090
|
@current_function = func
|
|
1049
1091
|
@current_hir_func = hir_func
|
|
1050
1092
|
@current_native_class = native_class_type # Track for self field access
|
|
@@ -1119,6 +1161,7 @@ module Konpeito
|
|
|
1119
1161
|
@variables = {}
|
|
1120
1162
|
@variable_allocas = {}
|
|
1121
1163
|
@variable_types = {}
|
|
1164
|
+
@block_exit_overrides = {}
|
|
1122
1165
|
@current_function = func
|
|
1123
1166
|
@current_hir_func = hir_func
|
|
1124
1167
|
|
|
@@ -1155,6 +1198,34 @@ module Konpeito
|
|
|
1155
1198
|
@variable_types[var_name] = type_tag
|
|
1156
1199
|
end
|
|
1157
1200
|
|
|
1201
|
+
# Pre-scan for thread-captured variables.
|
|
1202
|
+
# Replace their stack allocas with heap cells at function entry so that
|
|
1203
|
+
# all code (loop conditions, loop bodies, after-loop) uses the same
|
|
1204
|
+
# shared heap location consistently. This avoids the mid-loop alloca
|
|
1205
|
+
# replacement issue where loop conditions read from stale stack allocas.
|
|
1206
|
+
thread_captured_vars = collect_thread_captured_vars(hir_func)
|
|
1207
|
+
@thread_boxed_vars = {} # Reset per-function (each function has its own heap cells)
|
|
1208
|
+
unless thread_captured_vars.empty?
|
|
1209
|
+
declare_malloc
|
|
1210
|
+
thread_captured_vars.each do |cap_name|
|
|
1211
|
+
next if @thread_boxed_vars[cap_name]
|
|
1212
|
+
next unless @variable_allocas[cap_name]
|
|
1213
|
+
|
|
1214
|
+
# Allocate a heap cell (8 bytes = sizeof(VALUE)) and replace the alloca
|
|
1215
|
+
cell = @builder.call(@malloc, LLVM::Int64.from_i(8), "tbox_#{cap_name}")
|
|
1216
|
+
cell_int = @builder.ptr2int(cell, LLVM::Int64, "tbox_#{cap_name}_int")
|
|
1217
|
+
cell_typed = @builder.int2ptr(cell_int, LLVM::Pointer(value_type), "tbox_#{cap_name}_ptr")
|
|
1218
|
+
|
|
1219
|
+
# Initialize with Qnil (variables haven't been assigned yet at entry)
|
|
1220
|
+
@builder.store(@qnil, cell_typed)
|
|
1221
|
+
|
|
1222
|
+
# Replace stack alloca with heap cell — all subsequent reads/writes
|
|
1223
|
+
# in the function go through this shared heap location
|
|
1224
|
+
@variable_allocas[cap_name] = cell_typed
|
|
1225
|
+
@thread_boxed_vars[cap_name] = cell_typed
|
|
1226
|
+
end
|
|
1227
|
+
end
|
|
1228
|
+
|
|
1158
1229
|
# Separate regular, keyword, keyword_rest, and block parameters
|
|
1159
1230
|
regular_params = hir_func.params.reject { |p| p.keyword || p.keyword_rest || p.rest || p.block }
|
|
1160
1231
|
keyword_params = hir_func.params.select(&:keyword)
|
|
@@ -1507,67 +1578,93 @@ module Konpeito
|
|
|
1507
1578
|
# Topologically sort blocks based on phi dependencies
|
|
1508
1579
|
# Ensures blocks are generated after the blocks their phi nodes reference
|
|
1509
1580
|
def sort_blocks_by_phi_dependencies(blocks)
|
|
1510
|
-
|
|
1581
|
+
return blocks if blocks.size <= 1
|
|
1582
|
+
|
|
1511
1583
|
block_map = blocks.each_with_object({}) { |b, h| h[b.label] = b }
|
|
1512
1584
|
|
|
1513
|
-
# Build
|
|
1514
|
-
|
|
1585
|
+
# Build CFG successor edges from terminators.
|
|
1586
|
+
# A block's successors are the blocks it branches/jumps to.
|
|
1587
|
+
successors = {}
|
|
1588
|
+
blocks.each do |block|
|
|
1589
|
+
succs = []
|
|
1590
|
+
term = block.terminator
|
|
1591
|
+
case term
|
|
1592
|
+
when HIR::Branch
|
|
1593
|
+
succs << term.then_block if block_map[term.then_block]
|
|
1594
|
+
succs << term.else_block if block_map[term.else_block]
|
|
1595
|
+
when HIR::Jump
|
|
1596
|
+
succs << term.target if block_map[term.target]
|
|
1597
|
+
end
|
|
1598
|
+
successors[block.label] = succs
|
|
1599
|
+
end
|
|
1600
|
+
|
|
1601
|
+
# Also add phi-based dependencies: if a block has a phi that references a
|
|
1602
|
+
# result_var defined in another block, that other block should come first.
|
|
1603
|
+
phi_deps = Hash.new { |h, k| h[k] = Set.new }
|
|
1515
1604
|
blocks.each do |block|
|
|
1516
|
-
deps = Set.new
|
|
1517
1605
|
block.instructions.each do |inst|
|
|
1518
1606
|
next unless inst.is_a?(HIR::Phi)
|
|
1519
1607
|
|
|
1520
|
-
inst.incoming.each do |
|
|
1521
|
-
# If the incoming value is from a result_var in another block,
|
|
1522
|
-
# we depend on that block being generated first
|
|
1608
|
+
inst.incoming.each do |_label, value|
|
|
1523
1609
|
if value.respond_to?(:result_var) && value.result_var
|
|
1524
|
-
# Find which block defines this result_var
|
|
1525
1610
|
blocks.each do |other_block|
|
|
1526
1611
|
next if other_block.label == block.label
|
|
1527
1612
|
|
|
1528
1613
|
other_block.instructions.each do |other_inst|
|
|
1529
1614
|
if other_inst.respond_to?(:result_var) && other_inst.result_var == value.result_var
|
|
1530
|
-
|
|
1615
|
+
phi_deps[block.label] << other_block.label
|
|
1531
1616
|
end
|
|
1532
1617
|
end
|
|
1533
1618
|
end
|
|
1534
1619
|
end
|
|
1535
1620
|
end
|
|
1536
1621
|
end
|
|
1537
|
-
dependencies[block.label] = deps
|
|
1538
1622
|
end
|
|
1539
1623
|
|
|
1540
|
-
#
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1624
|
+
# Reverse Post-Order (RPO) traversal of the CFG.
|
|
1625
|
+
# This ensures a block is processed after all its predecessors (except back-edges).
|
|
1626
|
+
visited = Set.new
|
|
1627
|
+
post_order = []
|
|
1628
|
+
|
|
1629
|
+
dfs = lambda do |label|
|
|
1630
|
+
return if visited.include?(label)
|
|
1631
|
+
|
|
1632
|
+
visited << label
|
|
1633
|
+
successors[label]&.each do |succ|
|
|
1634
|
+
dfs.call(succ)
|
|
1545
1635
|
end
|
|
1636
|
+
post_order << label
|
|
1546
1637
|
end
|
|
1547
1638
|
|
|
1548
|
-
|
|
1549
|
-
queue = blocks.select { |b| in_degree[b.label] == 0 }
|
|
1550
|
-
sorted = []
|
|
1639
|
+
dfs.call(blocks.first.label)
|
|
1551
1640
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
sorted << current
|
|
1641
|
+
# Any unreachable blocks (not visited) are appended at the end
|
|
1642
|
+
rpo = post_order.reverse.filter_map { |label| block_map[label] }
|
|
1643
|
+
remaining = blocks.select { |b| !visited.include?(b.label) }
|
|
1556
1644
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1645
|
+
sorted = rpo + remaining
|
|
1646
|
+
|
|
1647
|
+
# Final pass: respect phi dependencies by moving blocks forward if needed.
|
|
1648
|
+
# If block B depends on block A (via phi), ensure A comes before B.
|
|
1649
|
+
changed = true
|
|
1650
|
+
while changed
|
|
1651
|
+
changed = false
|
|
1652
|
+
sorted.each_with_index do |block, idx|
|
|
1653
|
+
phi_deps[block.label].each do |dep_label|
|
|
1654
|
+
dep_idx = sorted.index { |b| b.label == dep_label }
|
|
1655
|
+
next unless dep_idx && dep_idx > idx
|
|
1656
|
+
|
|
1657
|
+
# Move dep_block before current block
|
|
1658
|
+
dep_block = sorted.delete_at(dep_idx)
|
|
1659
|
+
sorted.insert(idx, dep_block)
|
|
1660
|
+
changed = true
|
|
1661
|
+
break
|
|
1564
1662
|
end
|
|
1663
|
+
break if changed
|
|
1565
1664
|
end
|
|
1566
1665
|
end
|
|
1567
1666
|
|
|
1568
|
-
|
|
1569
|
-
remaining = blocks - sorted
|
|
1570
|
-
sorted + remaining
|
|
1667
|
+
sorted
|
|
1571
1668
|
end
|
|
1572
1669
|
|
|
1573
1670
|
def collect_local_variables_with_types(hir_func)
|
|
@@ -1627,6 +1724,22 @@ module Konpeito
|
|
|
1627
1724
|
vars
|
|
1628
1725
|
end
|
|
1629
1726
|
|
|
1727
|
+
# Collect all variable names that are captured by any Thread.new block
|
|
1728
|
+
# in the function. Used for pre-boxing at function entry.
|
|
1729
|
+
def collect_thread_captured_vars(hir_func)
|
|
1730
|
+
captured = Set.new
|
|
1731
|
+
hir_func.body.each do |block|
|
|
1732
|
+
block.instructions.each do |inst|
|
|
1733
|
+
if inst.is_a?(HIR::ThreadNew) && inst.block_def
|
|
1734
|
+
inst.block_def.captures.each do |cap|
|
|
1735
|
+
captured << cap.name
|
|
1736
|
+
end
|
|
1737
|
+
end
|
|
1738
|
+
end
|
|
1739
|
+
end
|
|
1740
|
+
captured
|
|
1741
|
+
end
|
|
1742
|
+
|
|
1630
1743
|
# Collect local variables used in a block definition
|
|
1631
1744
|
# Similar to collect_local_variables_with_types but for block bodies
|
|
1632
1745
|
def collect_local_variables_in_block(block_def)
|
|
@@ -1722,7 +1835,12 @@ module Konpeito
|
|
|
1722
1835
|
|
|
1723
1836
|
# Get the self VALUE for the current function.
|
|
1724
1837
|
# For variadic functions (arity -1), self is at params[2] instead of params[0].
|
|
1838
|
+
# Inside block/rescue callbacks, returns the captured self (set by callback generators).
|
|
1839
|
+
# Inside thread callbacks, params[0] is ptr (void*) not VALUE, so returns Qnil.
|
|
1725
1840
|
def get_self_value
|
|
1841
|
+
return @block_callback_self if @block_callback_self
|
|
1842
|
+
return @qnil if @in_thread_callback
|
|
1843
|
+
|
|
1726
1844
|
func = @builder.insert_block.parent
|
|
1727
1845
|
mangled = func.name
|
|
1728
1846
|
if @variadic_functions[mangled]
|
|
@@ -1736,10 +1854,15 @@ module Konpeito
|
|
|
1736
1854
|
llvm_block = @blocks[hir_block.label]
|
|
1737
1855
|
@builder.position_at_end(llvm_block)
|
|
1738
1856
|
|
|
1857
|
+
# Track the current HIR block label so safe navigation can record exit overrides
|
|
1858
|
+
saved_generating_block_label = @generating_block_label
|
|
1859
|
+
@generating_block_label = hir_block.label
|
|
1860
|
+
|
|
1739
1861
|
# Collect instructions that are owned by BeginRescue nodes
|
|
1740
1862
|
# These will be generated inside callbacks, not in the main function
|
|
1741
1863
|
rescue_owned = collect_rescue_owned_instructions(hir_block.instructions)
|
|
1742
1864
|
|
|
1865
|
+
|
|
1743
1866
|
# Generate instructions
|
|
1744
1867
|
hir_block.instructions.each do |inst|
|
|
1745
1868
|
next if rescue_owned.include?(inst.object_id)
|
|
@@ -1748,29 +1871,42 @@ module Konpeito
|
|
|
1748
1871
|
|
|
1749
1872
|
# Generate terminator
|
|
1750
1873
|
generate_terminator(hir_block.terminator) if hir_block.terminator
|
|
1874
|
+
|
|
1875
|
+
@generating_block_label = saved_generating_block_label
|
|
1751
1876
|
end
|
|
1752
1877
|
|
|
1753
|
-
# Collect all instructions that are owned by BeginRescue nodes
|
|
1754
|
-
# These instructions will be generated inside
|
|
1755
|
-
# not in the main function body
|
|
1878
|
+
# Collect all instructions that are owned by BeginRescue or CaseStatement nodes.
|
|
1879
|
+
# These instructions will be generated inside their proper blocks (callbacks or
|
|
1880
|
+
# when_body blocks), not in the main function body's linear pass.
|
|
1756
1881
|
def collect_rescue_owned_instructions(instructions)
|
|
1757
1882
|
owned = Set.new
|
|
1758
1883
|
instructions.each do |inst|
|
|
1759
|
-
|
|
1884
|
+
if inst.is_a?(HIR::BeginRescue)
|
|
1885
|
+
# All sub-expression instructions emitted during rescue/else/ensure visitation
|
|
1886
|
+
# are tracked in non_try_instruction_ids (same approach as JVM generator)
|
|
1887
|
+
owned.merge(inst.non_try_instruction_ids) if inst.non_try_instruction_ids
|
|
1888
|
+
|
|
1889
|
+
# Try block instructions (skip Phi nodes — they must stay at the top of their
|
|
1890
|
+
# natural HIR block; generate_phi will return a cached value when called inline)
|
|
1891
|
+
inst.try_blocks&.each { |i| owned << i.object_id unless i.is_a?(HIR::Phi) }
|
|
1892
|
+
|
|
1893
|
+
# Rescue clause body instructions
|
|
1894
|
+
inst.rescue_clauses&.each do |clause|
|
|
1895
|
+
clause.body_blocks&.each { |i| owned << i.object_id }
|
|
1896
|
+
end
|
|
1760
1897
|
|
|
1761
|
-
|
|
1762
|
-
|
|
1898
|
+
# Else block instructions
|
|
1899
|
+
inst.else_blocks&.each { |i| owned << i.object_id }
|
|
1763
1900
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1901
|
+
# Ensure block instructions
|
|
1902
|
+
inst.ensure_blocks&.each { |i| owned << i.object_id }
|
|
1903
|
+
elsif inst.is_a?(HIR::CaseStatement)
|
|
1904
|
+
# When/else body instructions (and their condition expressions) are emitted
|
|
1905
|
+
# into the current block by build_when_clause, but they must only be generated
|
|
1906
|
+
# inside their proper when_body/else LLVM blocks by generate_case_statement.
|
|
1907
|
+
# Skip them here so they are not executed unconditionally before the dispatch.
|
|
1908
|
+
owned.merge(inst.sub_instruction_ids) if inst.sub_instruction_ids
|
|
1767
1909
|
end
|
|
1768
|
-
|
|
1769
|
-
# Else block instructions
|
|
1770
|
-
inst.else_blocks&.each { |i| owned << i.object_id }
|
|
1771
|
-
|
|
1772
|
-
# Ensure block instructions
|
|
1773
|
-
inst.ensure_blocks&.each { |i| owned << i.object_id }
|
|
1774
1910
|
end
|
|
1775
1911
|
owned
|
|
1776
1912
|
end
|
|
@@ -1813,6 +1949,8 @@ module Konpeito
|
|
|
1813
1949
|
generate_store_constant(inst)
|
|
1814
1950
|
when HIR::Call
|
|
1815
1951
|
generate_call(inst)
|
|
1952
|
+
when HIR::CaseEqualityCheck
|
|
1953
|
+
generate_case_equality_check(inst)
|
|
1816
1954
|
when HIR::SelfRef
|
|
1817
1955
|
generate_self_ref(inst)
|
|
1818
1956
|
when HIR::Phi
|
|
@@ -2103,9 +2241,12 @@ module Konpeito
|
|
|
2103
2241
|
end
|
|
2104
2242
|
|
|
2105
2243
|
# Generate dynamic parts first (they may need to_s conversion)
|
|
2106
|
-
# and collect their lengths
|
|
2107
2244
|
dynamic_values = []
|
|
2108
|
-
|
|
2245
|
+
|
|
2246
|
+
# Declare rb_obj_as_string (returns String as-is, calls to_s only for non-String)
|
|
2247
|
+
rb_obj_as_string = @mod.functions["rb_obj_as_string"] || @mod.functions.add(
|
|
2248
|
+
"rb_obj_as_string", [value_type], value_type
|
|
2249
|
+
)
|
|
2109
2250
|
|
|
2110
2251
|
part_info.each do |info|
|
|
2111
2252
|
next if info[:type] == :static
|
|
@@ -2116,19 +2257,13 @@ module Konpeito
|
|
|
2116
2257
|
val = get_value_as_ruby(info[:hir])
|
|
2117
2258
|
|
|
2118
2259
|
# Convert to string if needed (for non-string interpolated values)
|
|
2260
|
+
# rb_obj_as_string returns String as-is, avoids rb_funcallv method lookup
|
|
2119
2261
|
val_type = info[:hir].respond_to?(:type) ? info[:hir].type : nil
|
|
2120
2262
|
unless val_type == TypeChecker::Types::STRING
|
|
2121
|
-
|
|
2122
|
-
to_s_id = @builder.call(@rb_intern, to_s_ptr)
|
|
2123
|
-
val = @builder.call(@rb_funcallv, val, to_s_id, LLVM::Int32.from_i(0), LLVM::Pointer(value_type).null)
|
|
2263
|
+
val = @builder.call(rb_obj_as_string, val)
|
|
2124
2264
|
end
|
|
2125
2265
|
|
|
2126
2266
|
dynamic_values << val
|
|
2127
|
-
# Get string length (returns Fixnum, need to unbox)
|
|
2128
|
-
len_val = @builder.call(@rb_str_length, val)
|
|
2129
|
-
# Unbox Fixnum: (value >> 1)
|
|
2130
|
-
len_i64 = @builder.ashr(len_val, LLVM::Int64.from_i(1))
|
|
2131
|
-
dynamic_lengths << len_i64
|
|
2132
2267
|
end
|
|
2133
2268
|
|
|
2134
2269
|
# If only one part, just return it
|
|
@@ -2143,14 +2278,9 @@ module Konpeito
|
|
|
2143
2278
|
return result
|
|
2144
2279
|
end
|
|
2145
2280
|
|
|
2146
|
-
#
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
total_length = @builder.add(total_length, len)
|
|
2150
|
-
end
|
|
2151
|
-
|
|
2152
|
-
# Pre-allocate buffer with total capacity
|
|
2153
|
-
result = @builder.call(@rb_str_buf_new, total_length)
|
|
2281
|
+
# Pre-allocate buffer: static length + padding for dynamic parts (avoid runtime length scan)
|
|
2282
|
+
total_capacity = LLVM::Int64.from_i(static_length + 128)
|
|
2283
|
+
result = @builder.call(@rb_str_buf_new, total_capacity)
|
|
2154
2284
|
|
|
2155
2285
|
# Append each part using efficient methods
|
|
2156
2286
|
dynamic_idx = 0
|
|
@@ -2571,6 +2701,15 @@ module Konpeito
|
|
|
2571
2701
|
@variables[var_name] = value_to_store
|
|
2572
2702
|
@variable_types[var_name] = target_type
|
|
2573
2703
|
|
|
2704
|
+
# Write-back to escape array when inside a rescue/try callback.
|
|
2705
|
+
# This ensures variable mutations inside callbacks are visible to the
|
|
2706
|
+
# outer function after rb_rescue2 returns.
|
|
2707
|
+
if @rescue_escape_array && @rescue_escape_indices&.key?(var_name)
|
|
2708
|
+
idx = @rescue_escape_indices[var_name]
|
|
2709
|
+
boxed = convert_value(value_to_store, target_type, :value)
|
|
2710
|
+
@builder.call(@rb_ary_store, @rescue_escape_array, LLVM::Int64.from_i(idx), boxed)
|
|
2711
|
+
end
|
|
2712
|
+
|
|
2574
2713
|
# Propagate comparison result flag through variable assignments
|
|
2575
2714
|
src_var = inst.value.respond_to?(:result_var) ? inst.value.result_var : nil
|
|
2576
2715
|
if src_var && @comparison_result_vars.include?(src_var)
|
|
@@ -2600,9 +2739,17 @@ module Konpeito
|
|
|
2600
2739
|
value = @variables[hir_value.result_var]
|
|
2601
2740
|
type_tag = @variable_types[hir_value.result_var] || :value
|
|
2602
2741
|
[value, type_tag]
|
|
2603
|
-
|
|
2742
|
+
elsif @variables.key?(hir_value.var.name)
|
|
2604
2743
|
# Fall back to var.name (for pattern-bound variables or inliner-created refs)
|
|
2605
2744
|
var_name = hir_value.var.name
|
|
2745
|
+
value = @variables[var_name]
|
|
2746
|
+
type_tag = @variable_types[var_name] || :value
|
|
2747
|
+
[value, type_tag]
|
|
2748
|
+
else
|
|
2749
|
+
# Variable not yet loaded (e.g. sub-expression in rescue handler body).
|
|
2750
|
+
# Generate the LoadLocal instruction to load from @variable_allocas.
|
|
2751
|
+
generate_instruction(hir_value)
|
|
2752
|
+
var_name = hir_value.result_var || hir_value.var.name
|
|
2606
2753
|
value = @variables[var_name] || @qnil
|
|
2607
2754
|
type_tag = @variable_types[var_name] || :value
|
|
2608
2755
|
[value, type_tag]
|
|
@@ -3008,6 +3155,11 @@ module Konpeito
|
|
|
3008
3155
|
return generate_unboxed_unary(inst)
|
|
3009
3156
|
end
|
|
3010
3157
|
|
|
3158
|
+
# Try inline String methods (empty?)
|
|
3159
|
+
if can_inline_string_method?(inst)
|
|
3160
|
+
return generate_inline_string_method(inst)
|
|
3161
|
+
end
|
|
3162
|
+
|
|
3011
3163
|
# Try unboxed arithmetic optimization
|
|
3012
3164
|
if can_use_unboxed_arithmetic?(inst)
|
|
3013
3165
|
# generate_unboxed_arithmetic sets @variables and @variable_types internally
|
|
@@ -3036,9 +3188,11 @@ module Konpeito
|
|
|
3036
3188
|
return result
|
|
3037
3189
|
end
|
|
3038
3190
|
|
|
3039
|
-
# Check if we can call a local function directly
|
|
3191
|
+
# Check if we can call a local function directly.
|
|
3192
|
+
# Skip if the method is defined in multiple classes (polymorphic) — such
|
|
3193
|
+
# calls must use rb_funcallv so the correct subclass override is invoked.
|
|
3040
3194
|
local_func = @functions[inst.method_name]
|
|
3041
|
-
if local_func && self_receiver?(inst.receiver)
|
|
3195
|
+
if local_func && self_receiver?(inst.receiver) && !@polymorphic_methods.include?(inst.method_name)
|
|
3042
3196
|
result = generate_direct_call(inst, inst.method_name)
|
|
3043
3197
|
@variables[inst.result_var] = result if inst.result_var
|
|
3044
3198
|
return result
|
|
@@ -3091,6 +3245,12 @@ module Konpeito
|
|
|
3091
3245
|
return result
|
|
3092
3246
|
end
|
|
3093
3247
|
|
|
3248
|
+
# Try optimized String method calls before rb_funcallv fallback
|
|
3249
|
+
if (result = try_optimized_string_call(inst, receiver_type))
|
|
3250
|
+
@variables[inst.result_var] = result if inst.result_var
|
|
3251
|
+
return result
|
|
3252
|
+
end
|
|
3253
|
+
|
|
3094
3254
|
# Get receiver as Ruby VALUE (box if needed)
|
|
3095
3255
|
receiver = get_value_as_ruby(inst.receiver)
|
|
3096
3256
|
|
|
@@ -3101,7 +3261,7 @@ module Konpeito
|
|
|
3101
3261
|
# Build keyword arguments hash if present
|
|
3102
3262
|
kwargs_hash = nil
|
|
3103
3263
|
if inst.has_keyword_args?
|
|
3104
|
-
kwargs_hash = build_keyword_args_hash(inst.keyword_args)
|
|
3264
|
+
kwargs_hash = build_keyword_args_hash(inst.keyword_args, keyword_splat: inst.keyword_splat)
|
|
3105
3265
|
end
|
|
3106
3266
|
|
|
3107
3267
|
# Check if any argument is a splat
|
|
@@ -3136,7 +3296,14 @@ module Konpeito
|
|
|
3136
3296
|
argv = @builder.bit_cast(argv, LLVM::Pointer(value_type))
|
|
3137
3297
|
end
|
|
3138
3298
|
|
|
3139
|
-
|
|
3299
|
+
# Use rb_funcallv_kw when keyword arguments are present so Ruby
|
|
3300
|
+
# correctly separates them from positional arguments (Ruby 3.0+)
|
|
3301
|
+
if kwargs_hash
|
|
3302
|
+
rb_pass_keywords = LLVM::Int32.from_i(1) # RB_PASS_KEYWORDS
|
|
3303
|
+
result = @builder.call(@rb_funcallv_kw, receiver, method_id, argc, argv, rb_pass_keywords)
|
|
3304
|
+
else
|
|
3305
|
+
result = @builder.call(@rb_funcallv, receiver, method_id, argc, argv)
|
|
3306
|
+
end
|
|
3140
3307
|
end
|
|
3141
3308
|
|
|
3142
3309
|
@variables[inst.result_var] = result if inst.result_var
|
|
@@ -3229,6 +3396,14 @@ module Konpeito
|
|
|
3229
3396
|
end
|
|
3230
3397
|
end
|
|
3231
3398
|
end
|
|
3399
|
+
|
|
3400
|
+
# Record that the enclosing HIR block now exits through safe_merge_bb.
|
|
3401
|
+
# Any HIR::Phi that references the enclosing HIR block as a predecessor
|
|
3402
|
+
# must use safe_merge_bb instead of the original LLVM block.
|
|
3403
|
+
if @generating_block_label
|
|
3404
|
+
@block_exit_overrides[@generating_block_label] = safe_merge_bb
|
|
3405
|
+
end
|
|
3406
|
+
|
|
3232
3407
|
phi
|
|
3233
3408
|
end
|
|
3234
3409
|
|
|
@@ -3439,13 +3614,30 @@ module Konpeito
|
|
|
3439
3614
|
end
|
|
3440
3615
|
end
|
|
3441
3616
|
|
|
3442
|
-
# Build a Ruby Hash from keyword arguments
|
|
3617
|
+
# Build a Ruby Hash from keyword arguments (and optional **hash splat)
|
|
3443
3618
|
# Returns an LLVM VALUE representing the Hash
|
|
3444
|
-
def build_keyword_args_hash(keyword_args)
|
|
3619
|
+
def build_keyword_args_hash(keyword_args, keyword_splat: nil)
|
|
3620
|
+
# If only a keyword splat with no explicit keyword args, use the hash directly
|
|
3621
|
+
if keyword_splat && keyword_args.empty?
|
|
3622
|
+
return get_value_as_ruby(keyword_splat)
|
|
3623
|
+
end
|
|
3624
|
+
|
|
3445
3625
|
# Create new hash
|
|
3446
3626
|
hash = @builder.call(@rb_hash_new)
|
|
3447
3627
|
|
|
3448
|
-
#
|
|
3628
|
+
# Merge splatted hash if present (mixed case: **hash + explicit key: val)
|
|
3629
|
+
if keyword_splat
|
|
3630
|
+
splat_value = get_value_as_ruby(keyword_splat)
|
|
3631
|
+
update_id_ptr = @builder.global_string_pointer("update")
|
|
3632
|
+
update_id = @builder.call(@rb_intern, update_id_ptr)
|
|
3633
|
+
argv = @builder.alloca(LLVM::Array(value_type, 1))
|
|
3634
|
+
ptr = @builder.gep(argv, [LLVM::Int32.from_i(0), LLVM::Int32.from_i(0)])
|
|
3635
|
+
@builder.store(splat_value, ptr)
|
|
3636
|
+
argv_ptr = @builder.bit_cast(argv, LLVM::Pointer(value_type))
|
|
3637
|
+
@builder.call(@rb_funcallv, hash, update_id, LLVM::Int32.from_i(1), argv_ptr)
|
|
3638
|
+
end
|
|
3639
|
+
|
|
3640
|
+
# Add each explicit keyword argument
|
|
3449
3641
|
keyword_args.each do |key_name, value_inst|
|
|
3450
3642
|
# Convert key name to Ruby Symbol
|
|
3451
3643
|
key_str_ptr = @builder.global_string_pointer(key_name.to_s)
|
|
@@ -4309,37 +4501,108 @@ module Konpeito
|
|
|
4309
4501
|
method_ptr = @builder.global_string_pointer(inst.method_name)
|
|
4310
4502
|
method_id = @builder.call(@rb_intern, method_ptr)
|
|
4311
4503
|
|
|
4312
|
-
#
|
|
4313
|
-
|
|
4504
|
+
# Build keyword arguments hash if present
|
|
4505
|
+
kwargs_hash = nil
|
|
4506
|
+
if inst.has_keyword_args?
|
|
4507
|
+
kwargs_hash = build_keyword_args_hash(inst.keyword_args, keyword_splat: inst.keyword_splat)
|
|
4508
|
+
end
|
|
4314
4509
|
|
|
4315
|
-
|
|
4510
|
+
# Prepare arguments array (regular args + optional kwargs hash)
|
|
4511
|
+
total_args = inst.args.dup
|
|
4512
|
+
total_args << kwargs_hash if kwargs_hash
|
|
4513
|
+
|
|
4514
|
+
argc = LLVM::Int32.from_i(total_args.size)
|
|
4515
|
+
|
|
4516
|
+
if total_args.empty?
|
|
4316
4517
|
argv = LLVM::Pointer(value_type).null
|
|
4317
4518
|
else
|
|
4318
|
-
argv = @builder.alloca(LLVM::Array(value_type,
|
|
4319
|
-
|
|
4320
|
-
arg_value = get_value_as_ruby(arg)
|
|
4519
|
+
argv = @builder.alloca(LLVM::Array(value_type, total_args.size))
|
|
4520
|
+
total_args.each_with_index do |arg, i|
|
|
4521
|
+
arg_value = arg.is_a?(LLVM::Value) ? arg : get_value_as_ruby(arg)
|
|
4321
4522
|
ptr = @builder.gep(argv, [LLVM::Int32.from_i(0), LLVM::Int32.from_i(i)])
|
|
4322
4523
|
@builder.store(arg_value, ptr)
|
|
4323
4524
|
end
|
|
4324
4525
|
argv = @builder.bit_cast(argv, LLVM::Pointer(value_type))
|
|
4325
4526
|
end
|
|
4326
4527
|
|
|
4327
|
-
# Create capture struct for closure variables
|
|
4528
|
+
# Create capture struct for closure variables.
|
|
4529
|
+
# Also capture outer self so @ivar access inside blocks works correctly.
|
|
4328
4530
|
captures = inst.block.captures.select { |c| @variable_allocas[c.name] }
|
|
4329
|
-
|
|
4531
|
+
|
|
4532
|
+
outer_self_val = get_self_value
|
|
4533
|
+
self_slot = @builder.alloca(value_type, "blk_self_slot")
|
|
4534
|
+
@builder.store(outer_self_val, self_slot)
|
|
4535
|
+
@variable_allocas[BLOCK_SELF_CAPTURE] = self_slot
|
|
4536
|
+
self_capture = HIR::Capture.new(name: BLOCK_SELF_CAPTURE, type: nil)
|
|
4537
|
+
all_captures = [self_capture] + captures
|
|
4330
4538
|
|
|
4331
4539
|
# Collect type info for captured variables
|
|
4332
|
-
capture_types = {}
|
|
4540
|
+
capture_types = { BLOCK_SELF_CAPTURE => :value }
|
|
4333
4541
|
captures.each do |capture|
|
|
4334
4542
|
capture_types[capture.name] = @variable_types[capture.name] || :value
|
|
4335
4543
|
end
|
|
4336
4544
|
|
|
4337
|
-
#
|
|
4338
|
-
|
|
4545
|
+
# Determine capture strategy.
|
|
4546
|
+
#
|
|
4547
|
+
# - Builtin block iterators (each, times, map, etc.) call the block inline
|
|
4548
|
+
# before returning, so the stack remains alive and pointer-to-alloca is safe.
|
|
4549
|
+
# These arrive via generate_block_iterator_call with a non-nil `builtin`.
|
|
4550
|
+
#
|
|
4551
|
+
# - Compiled user functions that use `yield` call blocks inline → pointer-to-alloca safe,
|
|
4552
|
+
# and write-back through captured variable allocas works correctly.
|
|
4553
|
+
#
|
|
4554
|
+
# - Compiled user functions with a `&blk` parameter (block: true in HIR::Param) STORE the
|
|
4555
|
+
# block as a Proc (e.g. Button#on_click stores the block in @click_handler). The stack
|
|
4556
|
+
# frame of the caller will be gone when the proc is eventually called. These MUST use
|
|
4557
|
+
# escape-cells to avoid dangling-pointer segfaults.
|
|
4558
|
+
#
|
|
4559
|
+
# - External / unknown methods (not in the builtin table and not compiled by Konpeito)
|
|
4560
|
+
# may also store the block. Use escape-cells for safety.
|
|
4561
|
+
#
|
|
4562
|
+
# Additionally, when we are already inside a block callback (@in_block_callback),
|
|
4563
|
+
# the outer callback's stack will also be freed before any nested proc runs, so
|
|
4564
|
+
# escape-cells is required there too.
|
|
4565
|
+
hir_target = @hir_functions[inst.method_name.to_s]
|
|
4566
|
+
target_stores_block = hir_target&.params&.any? { |p| p.block }
|
|
4567
|
+
is_external_method = builtin.nil? && @functions[inst.method_name].nil?
|
|
4568
|
+
use_escape_cells = target_stores_block || is_external_method || @in_block_callback
|
|
4569
|
+
|
|
4570
|
+
if use_escape_cells
|
|
4571
|
+
esc_ary = @builder.call(@rb_ary_new_capa, LLVM::Int64.from_i(all_captures.size), "blk_esc_ary")
|
|
4572
|
+
all_captures.each do |cap|
|
|
4573
|
+
val = if @variable_allocas[cap.name]
|
|
4574
|
+
@builder.load2(value_type, @variable_allocas[cap.name], "blk_esc_#{cap.name}")
|
|
4575
|
+
else
|
|
4576
|
+
@qnil
|
|
4577
|
+
end
|
|
4578
|
+
@builder.call(@rb_ary_push, esc_ary, val)
|
|
4579
|
+
end
|
|
4339
4580
|
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4581
|
+
block_func = generate_block_callback(inst.block, inst.method_name, all_captures, capture_types,
|
|
4582
|
+
escape_cells_mode: true)
|
|
4583
|
+
if kwargs_hash
|
|
4584
|
+
rb_pass_keywords = LLVM::Int32.from_i(1)
|
|
4585
|
+
result = @builder.call(@rb_block_call_kw,
|
|
4586
|
+
receiver, method_id, argc, argv, block_func, esc_ary, rb_pass_keywords)
|
|
4587
|
+
else
|
|
4588
|
+
result = @builder.call(@rb_block_call,
|
|
4589
|
+
receiver, method_id, argc, argv, block_func, esc_ary)
|
|
4590
|
+
end
|
|
4591
|
+
else
|
|
4592
|
+
# Pointer-to-alloca mode: captures are stored as VALUE* pointers in a struct.
|
|
4593
|
+
# The callback reads/writes through those pointers, so mutations propagate back.
|
|
4594
|
+
capture_data = create_capture_struct(all_captures)
|
|
4595
|
+
block_func = generate_block_callback(inst.block, inst.method_name, all_captures, capture_types,
|
|
4596
|
+
escape_cells_mode: false)
|
|
4597
|
+
if kwargs_hash
|
|
4598
|
+
rb_pass_keywords = LLVM::Int32.from_i(1)
|
|
4599
|
+
result = @builder.call(@rb_block_call_kw,
|
|
4600
|
+
receiver, method_id, argc, argv, block_func, capture_data, rb_pass_keywords)
|
|
4601
|
+
else
|
|
4602
|
+
result = @builder.call(@rb_block_call,
|
|
4603
|
+
receiver, method_id, argc, argv, block_func, capture_data)
|
|
4604
|
+
end
|
|
4605
|
+
end
|
|
4343
4606
|
|
|
4344
4607
|
result
|
|
4345
4608
|
end
|
|
@@ -4395,6 +4658,7 @@ module Konpeito
|
|
|
4395
4658
|
saved_types = @variable_types.dup
|
|
4396
4659
|
saved_allocas = @variable_allocas.dup
|
|
4397
4660
|
saved_in_block_callback = @in_block_callback
|
|
4661
|
+
saved_block_callback_self = @block_callback_self
|
|
4398
4662
|
|
|
4399
4663
|
# Create entry block for callback
|
|
4400
4664
|
entry = callback_func.basic_blocks.append("entry")
|
|
@@ -4450,6 +4714,14 @@ module Konpeito
|
|
|
4450
4714
|
end
|
|
4451
4715
|
end
|
|
4452
4716
|
|
|
4717
|
+
# Extract the captured outer self for @ivar access inside blocks.
|
|
4718
|
+
# BLOCK_SELF_CAPTURE is always included by generate_rb_block_call.
|
|
4719
|
+
@block_callback_self = nil
|
|
4720
|
+
if @variable_allocas[BLOCK_SELF_CAPTURE]
|
|
4721
|
+
@block_callback_self = @builder.load2(value_type,
|
|
4722
|
+
@variable_allocas[BLOCK_SELF_CAPTURE], "blk_self")
|
|
4723
|
+
end
|
|
4724
|
+
|
|
4453
4725
|
# Bind block parameters to yielded values
|
|
4454
4726
|
# For single-parameter blocks, use yielded_arg directly
|
|
4455
4727
|
# For multi-parameter blocks, check argc at runtime:
|
|
@@ -4551,6 +4823,12 @@ module Konpeito
|
|
|
4551
4823
|
val = @builder.load2(value_type, param_allocas[i], param.name)
|
|
4552
4824
|
@variables[param.name] = val
|
|
4553
4825
|
@variable_types[param.name] = :value
|
|
4826
|
+
# Register the alloca so that generate_store_local uses the correct
|
|
4827
|
+
# pointer when the block body assigns to a parameter variable
|
|
4828
|
+
# (e.g. `completely = true` inside an |painter, completely| block).
|
|
4829
|
+
# Without this, generate_store_local creates a new alloca with the
|
|
4830
|
+
# same name as the already-loaded i64 value, causing an LLVM type error.
|
|
4831
|
+
@variable_allocas[param.name] = param_allocas[i]
|
|
4554
4832
|
end
|
|
4555
4833
|
end
|
|
4556
4834
|
|
|
@@ -4597,8 +4875,10 @@ module Konpeito
|
|
|
4597
4875
|
# Jump from entry to first HIR block
|
|
4598
4876
|
@builder.br(@blocks[block_def.body.first.label])
|
|
4599
4877
|
|
|
4600
|
-
# Generate each block (
|
|
4601
|
-
|
|
4878
|
+
# Generate each block in phi-dependency order (same as setup_function)
|
|
4879
|
+
# Without sorting, nested if/else phi nodes may be placed in the wrong block
|
|
4880
|
+
sorted_callback_blocks = sort_blocks_by_phi_dependencies(block_def.body)
|
|
4881
|
+
sorted_callback_blocks.each do |hir_block|
|
|
4602
4882
|
generate_block(callback_func, hir_block)
|
|
4603
4883
|
end
|
|
4604
4884
|
|
|
@@ -4635,7 +4915,9 @@ module Konpeito
|
|
|
4635
4915
|
result_type = :value
|
|
4636
4916
|
last_inst = nil
|
|
4637
4917
|
block_def.body.each do |basic_block|
|
|
4918
|
+
rescue_owned = collect_rescue_owned_instructions(basic_block.instructions)
|
|
4638
4919
|
basic_block.instructions.each do |hir_inst|
|
|
4920
|
+
next if rescue_owned.include?(hir_inst.object_id)
|
|
4639
4921
|
result = generate_instruction(hir_inst)
|
|
4640
4922
|
last_inst = hir_inst
|
|
4641
4923
|
end
|
|
@@ -4665,6 +4947,7 @@ module Konpeito
|
|
|
4665
4947
|
@variable_types = saved_types
|
|
4666
4948
|
@variable_allocas = saved_allocas
|
|
4667
4949
|
@in_block_callback = saved_in_block_callback
|
|
4950
|
+
@block_callback_self = saved_block_callback_self
|
|
4668
4951
|
|
|
4669
4952
|
callback_func
|
|
4670
4953
|
end
|
|
@@ -5025,33 +5308,58 @@ module Konpeito
|
|
|
5025
5308
|
capture_types[cap.name] = @variable_types[cap.name]
|
|
5026
5309
|
end
|
|
5027
5310
|
|
|
5311
|
+
# Determine if all captures have been pre-boxed to heap cells.
|
|
5312
|
+
# When ThreadNew is at function top-level, collect_thread_captured_vars finds it
|
|
5313
|
+
# and pre-boxes the captures. When ThreadNew is inside a rescue callback or
|
|
5314
|
+
# other nested scope, pre-boxing doesn't happen — we must use the safe value-copy
|
|
5315
|
+
# protocol instead (snapshot values, no bidirectional mutation).
|
|
5316
|
+
all_preboxed = !captures.empty? && captures.all? { |c| @thread_boxed_vars[c.name] }
|
|
5317
|
+
|
|
5028
5318
|
# Create thread-specific callback (different signature from fiber/proc)
|
|
5029
|
-
callback_func = generate_thread_callback(block_def, captures, capture_types)
|
|
5319
|
+
callback_func = generate_thread_callback(block_def, captures, capture_types, cell_protocol: all_preboxed)
|
|
5030
5320
|
|
|
5031
|
-
# Setup captures data (pass as intptr_t, cast back in callback)
|
|
5032
5321
|
if captures.empty?
|
|
5033
|
-
|
|
5322
|
+
captures_ptr = LLVM::Pointer(LLVM::Int8).null
|
|
5034
5323
|
else
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
captures_array = @builder.alloca(array_type, "thread_captures")
|
|
5324
|
+
declare_malloc
|
|
5325
|
+
n = captures.size
|
|
5038
5326
|
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5327
|
+
if all_preboxed
|
|
5328
|
+
# Cell-pointer protocol: pass pointers to shared heap cells.
|
|
5329
|
+
# Enables bidirectional mutation visibility between threads.
|
|
5330
|
+
buf = @builder.call(@malloc, LLVM::Int64.from_i(n * 8), "thread_ptrbuf")
|
|
5331
|
+
buf_int = @builder.ptr2int(buf, LLVM::Int64, "thread_ptrbuf_int")
|
|
5332
|
+
buf_typed = @builder.int2ptr(buf_int, LLVM::Pointer(LLVM::Pointer(value_type)), "thread_ptrbuf_typed")
|
|
5333
|
+
|
|
5334
|
+
captures.each_with_index do |capture, i|
|
|
5335
|
+
cell_ptr = @thread_boxed_vars[capture.name]
|
|
5336
|
+
elem_ptr = @builder.gep2(LLVM::Pointer(value_type), buf_typed, [LLVM::Int64.from_i(i)], "tpb_elem_#{i}")
|
|
5337
|
+
@builder.store(cell_ptr, elem_ptr)
|
|
5047
5338
|
end
|
|
5048
|
-
end
|
|
5049
5339
|
|
|
5050
|
-
|
|
5051
|
-
|
|
5340
|
+
captures_ptr = buf
|
|
5341
|
+
else
|
|
5342
|
+
# Value-copy protocol: snapshot current VALUES into the buffer.
|
|
5343
|
+
# Safe for async threads (no pointer dependency on caller's stack).
|
|
5344
|
+
# No bidirectional mutation — thread gets independent copies.
|
|
5345
|
+
buf_size = LLVM::Int64.from_i(n * 8)
|
|
5346
|
+
raw_ptr = @builder.call(@malloc, buf_size, "thread_cap_buf")
|
|
5347
|
+
raw_int = @builder.ptr2int(raw_ptr, LLVM::Int64, "thread_cap_int")
|
|
5348
|
+
val_arr_ptr = @builder.int2ptr(raw_int, LLVM::Pointer(value_type), "thread_cap_vals")
|
|
5349
|
+
|
|
5350
|
+
captures.each_with_index do |capture, i|
|
|
5351
|
+
val = if @variable_allocas[capture.name]
|
|
5352
|
+
@builder.load2(value_type, @variable_allocas[capture.name], "tc_#{capture.name}")
|
|
5353
|
+
else
|
|
5354
|
+
@qnil
|
|
5355
|
+
end
|
|
5356
|
+
elem_ptr = @builder.gep2(value_type, val_arr_ptr, [LLVM::Int64.from_i(i)], "tc_elem_#{i}")
|
|
5357
|
+
@builder.store(val, elem_ptr)
|
|
5358
|
+
end
|
|
5052
5359
|
|
|
5053
|
-
|
|
5054
|
-
|
|
5360
|
+
captures_ptr = raw_ptr
|
|
5361
|
+
end
|
|
5362
|
+
end
|
|
5055
5363
|
result = @builder.call(@rb_thread_create, callback_func, captures_ptr)
|
|
5056
5364
|
|
|
5057
5365
|
if inst.result_var
|
|
@@ -5063,7 +5371,9 @@ module Konpeito
|
|
|
5063
5371
|
end
|
|
5064
5372
|
|
|
5065
5373
|
# Generate thread callback function with correct signature: VALUE func(void*)
|
|
5066
|
-
|
|
5374
|
+
# cell_protocol: true = buffer contains cell POINTERS (shared heap cells, bidirectional)
|
|
5375
|
+
# false = buffer contains VALUE copies (snapshot, safe for async)
|
|
5376
|
+
def generate_thread_callback(block_def, captures = [], capture_types = {}, cell_protocol: false)
|
|
5067
5377
|
return nil unless block_def
|
|
5068
5378
|
|
|
5069
5379
|
# Create a unique name for the callback
|
|
@@ -5082,38 +5392,63 @@ module Konpeito
|
|
|
5082
5392
|
saved_types = @variable_types.dup
|
|
5083
5393
|
saved_allocas = @variable_allocas.dup
|
|
5084
5394
|
saved_current_function = @current_function
|
|
5395
|
+
saved_in_block_callback = @in_block_callback
|
|
5396
|
+
saved_block_callback_self = @block_callback_self
|
|
5397
|
+
saved_in_thread_callback = @in_thread_callback
|
|
5398
|
+
saved_thread_boxed_vars = @thread_boxed_vars
|
|
5085
5399
|
|
|
5086
5400
|
# Create entry block for callback
|
|
5087
5401
|
entry = callback_func.basic_blocks.append("entry")
|
|
5088
5402
|
@builder.position_at_end(entry)
|
|
5089
5403
|
@current_function = callback_func
|
|
5090
5404
|
|
|
5091
|
-
# Reset variable tracking for callback scope
|
|
5405
|
+
# Reset variable tracking for callback scope.
|
|
5406
|
+
# Thread callbacks are independent execution contexts — reset block callback state.
|
|
5092
5407
|
@variables = {}
|
|
5093
5408
|
@variable_types = {}
|
|
5094
5409
|
@variable_allocas = {}
|
|
5410
|
+
@in_block_callback = false
|
|
5411
|
+
@block_callback_self = nil
|
|
5412
|
+
@in_thread_callback = true
|
|
5095
5413
|
|
|
5096
|
-
# Setup captured variable access through void* arg
|
|
5097
5414
|
unless captures.empty?
|
|
5098
|
-
|
|
5099
|
-
ptr_type = LLVM::Pointer(value_type)
|
|
5100
|
-
array_type = LLVM::Array(ptr_type, captures.size)
|
|
5415
|
+
declare_free
|
|
5101
5416
|
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5417
|
+
if cell_protocol
|
|
5418
|
+
# Cell-pointer protocol: buffer contains pointers to shared heap cells.
|
|
5419
|
+
# Reads/writes through cell pointers are visible to both sides.
|
|
5420
|
+
arg_int = @builder.ptr2int(callback_func.params[0], LLVM::Int64, "tc_arg_int")
|
|
5421
|
+
buf_typed = @builder.int2ptr(arg_int, LLVM::Pointer(LLVM::Pointer(value_type)), "tc_buf_typed")
|
|
5105
5422
|
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
[
|
|
5110
|
-
|
|
5111
|
-
|
|
5423
|
+
captures.each_with_index do |capture, i|
|
|
5424
|
+
elem_ptr = @builder.gep2(LLVM::Pointer(value_type), buf_typed, [LLVM::Int64.from_i(i)], "tc_bp_#{i}")
|
|
5425
|
+
cell_ptr = @builder.load2(LLVM::Pointer(value_type), elem_ptr, "tc_cell_#{capture.name}")
|
|
5426
|
+
@variable_allocas[capture.name] = cell_ptr
|
|
5427
|
+
@variable_types[capture.name] = capture_types[capture.name] || :value
|
|
5428
|
+
val = @builder.load2(value_type, cell_ptr, "tc_#{capture.name}_val")
|
|
5429
|
+
@variables[capture.name] = val
|
|
5430
|
+
end
|
|
5112
5431
|
|
|
5113
|
-
#
|
|
5114
|
-
@
|
|
5115
|
-
|
|
5116
|
-
|
|
5432
|
+
# Free the pointer buffer (NOT the cells — they're shared)
|
|
5433
|
+
@builder.call(@free, callback_func.params[0])
|
|
5434
|
+
else
|
|
5435
|
+
# Value-copy protocol: buffer contains snapshot VALUE copies.
|
|
5436
|
+
# Load each VALUE into a local alloca, then free the buffer.
|
|
5437
|
+
arg_int = @builder.ptr2int(callback_func.params[0], LLVM::Int64, "tc_arg_int")
|
|
5438
|
+
val_arr_ptr = @builder.int2ptr(arg_int, LLVM::Pointer(value_type), "tc_vals")
|
|
5439
|
+
|
|
5440
|
+
captures.each_with_index do |capture, i|
|
|
5441
|
+
elem_ptr = @builder.gep2(value_type, val_arr_ptr, [LLVM::Int64.from_i(i)], "tc_elem_#{i}")
|
|
5442
|
+
val = @builder.load2(value_type, elem_ptr, "tc_#{capture.name}_val")
|
|
5443
|
+
alloca = @builder.alloca(value_type, "tc_#{capture.name}")
|
|
5444
|
+
@builder.store(val, alloca)
|
|
5445
|
+
@variable_allocas[capture.name] = alloca
|
|
5446
|
+
@variable_types[capture.name] = capture_types[capture.name] || :value
|
|
5447
|
+
@variables[capture.name] = val
|
|
5448
|
+
end
|
|
5449
|
+
|
|
5450
|
+
# Free the malloc'd buffer
|
|
5451
|
+
@builder.call(@free, callback_func.params[0])
|
|
5117
5452
|
end
|
|
5118
5453
|
end
|
|
5119
5454
|
|
|
@@ -5151,8 +5486,9 @@ module Konpeito
|
|
|
5151
5486
|
# Jump from entry to first HIR block
|
|
5152
5487
|
@builder.br(@blocks[block_def.body.first.label])
|
|
5153
5488
|
|
|
5154
|
-
# Generate each block (
|
|
5155
|
-
block_def.body
|
|
5489
|
+
# Generate each block in phi-dependency order (same as setup_function)
|
|
5490
|
+
sorted_callback_blocks = sort_blocks_by_phi_dependencies(block_def.body)
|
|
5491
|
+
sorted_callback_blocks.each do |hir_block|
|
|
5156
5492
|
generate_block(callback_func, hir_block)
|
|
5157
5493
|
end
|
|
5158
5494
|
|
|
@@ -5177,7 +5513,9 @@ module Konpeito
|
|
|
5177
5513
|
result_type = :value
|
|
5178
5514
|
last_inst = nil
|
|
5179
5515
|
block_def.body.each do |basic_block|
|
|
5516
|
+
rescue_owned = collect_rescue_owned_instructions(basic_block.instructions)
|
|
5180
5517
|
basic_block.instructions.each do |hir_inst|
|
|
5518
|
+
next if rescue_owned.include?(hir_inst.object_id)
|
|
5181
5519
|
result = generate_instruction(hir_inst)
|
|
5182
5520
|
last_inst = hir_inst
|
|
5183
5521
|
end
|
|
@@ -5203,6 +5541,10 @@ module Konpeito
|
|
|
5203
5541
|
@variable_types = saved_types
|
|
5204
5542
|
@variable_allocas = saved_allocas
|
|
5205
5543
|
@current_function = saved_current_function
|
|
5544
|
+
@in_block_callback = saved_in_block_callback
|
|
5545
|
+
@block_callback_self = saved_block_callback_self
|
|
5546
|
+
@in_thread_callback = saved_in_thread_callback
|
|
5547
|
+
@thread_boxed_vars = saved_thread_boxed_vars
|
|
5206
5548
|
|
|
5207
5549
|
callback_func
|
|
5208
5550
|
end
|
|
@@ -5428,7 +5770,9 @@ module Konpeito
|
|
|
5428
5770
|
last_inst = nil
|
|
5429
5771
|
|
|
5430
5772
|
block_def.body.each do |basic_block|
|
|
5773
|
+
rescue_owned = collect_rescue_owned_instructions(basic_block.instructions)
|
|
5431
5774
|
basic_block.instructions.each do |hir_inst|
|
|
5775
|
+
next if rescue_owned.include?(hir_inst.object_id)
|
|
5432
5776
|
result = generate_instruction(hir_inst) || result
|
|
5433
5777
|
last_inst = hir_inst
|
|
5434
5778
|
end
|
|
@@ -6043,6 +6387,8 @@ module Konpeito
|
|
|
6043
6387
|
@builder.fmul(left, right)
|
|
6044
6388
|
when "/"
|
|
6045
6389
|
@builder.fdiv(left, right)
|
|
6390
|
+
when "%"
|
|
6391
|
+
@builder.frem(left, right)
|
|
6046
6392
|
else
|
|
6047
6393
|
raise "Unknown float operation: #{inst.method_name}"
|
|
6048
6394
|
end
|
|
@@ -6212,6 +6558,88 @@ module Konpeito
|
|
|
6212
6558
|
end
|
|
6213
6559
|
end
|
|
6214
6560
|
|
|
6561
|
+
# Try optimized String method calls (String#[] 2-arg, String#split with literal)
|
|
6562
|
+
def try_optimized_string_call(inst, receiver_type)
|
|
6563
|
+
resolved_type = receiver_type.is_a?(TypeChecker::TypeVar) ? resolve_type_var(receiver_type) : receiver_type
|
|
6564
|
+
is_string = resolved_type == TypeChecker::Types::STRING ||
|
|
6565
|
+
(resolved_type.is_a?(TypeChecker::Types::ClassInstance) && resolved_type.name == :String)
|
|
6566
|
+
return nil unless is_string
|
|
6567
|
+
|
|
6568
|
+
case inst.method_name
|
|
6569
|
+
when "[]"
|
|
6570
|
+
# String#[start, length] → rb_str_substr(str, start, length)
|
|
6571
|
+
return nil unless inst.args.size == 2
|
|
6572
|
+
|
|
6573
|
+
receiver = get_value_as_ruby(inst.receiver)
|
|
6574
|
+
start_val = get_value_as_ruby(inst.args[0])
|
|
6575
|
+
start_long = @builder.call(@rb_num2long, start_val)
|
|
6576
|
+
len_val = get_value_as_ruby(inst.args[1])
|
|
6577
|
+
len_long = @builder.call(@rb_num2long, len_val)
|
|
6578
|
+
|
|
6579
|
+
rb_str_substr = @mod.functions["rb_str_substr"] || @mod.functions.add(
|
|
6580
|
+
"rb_str_substr", [value_type, LLVM::Int64, LLVM::Int64], value_type
|
|
6581
|
+
)
|
|
6582
|
+
@builder.call(rb_str_substr, receiver, start_long, len_long)
|
|
6583
|
+
when "split"
|
|
6584
|
+
# String#split(separator) → rb_str_split(str, separator_cstr)
|
|
6585
|
+
return nil unless inst.args.size == 1
|
|
6586
|
+
|
|
6587
|
+
receiver = get_value_as_ruby(inst.receiver)
|
|
6588
|
+
|
|
6589
|
+
# rb_str_split takes (VALUE str, const char *sep)
|
|
6590
|
+
rb_str_split = @mod.functions["rb_str_split"] || @mod.functions.add(
|
|
6591
|
+
"rb_str_split", [value_type, LLVM::Pointer(LLVM::Int8)], value_type
|
|
6592
|
+
)
|
|
6593
|
+
|
|
6594
|
+
if inst.args[0].is_a?(HIR::StringLit)
|
|
6595
|
+
# Literal separator: use compile-time C string directly
|
|
6596
|
+
sep_ptr = @builder.global_string_pointer(inst.args[0].value)
|
|
6597
|
+
else
|
|
6598
|
+
# Dynamic separator: convert to C string
|
|
6599
|
+
sep_value = get_value_as_ruby(inst.args[0])
|
|
6600
|
+
sep_value_ptr = @builder.alloca(LLVM::Int64, "split_sep_ptr")
|
|
6601
|
+
@builder.store(sep_value, sep_value_ptr)
|
|
6602
|
+
sep_ptr = @builder.call(@rb_string_value_cstr, sep_value_ptr, "split_sep_cstr")
|
|
6603
|
+
end
|
|
6604
|
+
@builder.call(rb_str_split, receiver, sep_ptr)
|
|
6605
|
+
end
|
|
6606
|
+
end
|
|
6607
|
+
|
|
6608
|
+
# Inline String method constants
|
|
6609
|
+
INLINE_STRING_METHODS = %w[empty?].freeze
|
|
6610
|
+
|
|
6611
|
+
# Check if a method call can use inline String optimization
|
|
6612
|
+
def can_inline_string_method?(inst)
|
|
6613
|
+
return false unless inst.args.empty?
|
|
6614
|
+
return false unless INLINE_STRING_METHODS.include?(inst.method_name)
|
|
6615
|
+
|
|
6616
|
+
receiver_type = get_type(inst.receiver)
|
|
6617
|
+
receiver_type = resolve_type_var(receiver_type) if receiver_type.is_a?(TypeChecker::TypeVar)
|
|
6618
|
+
receiver_type == TypeChecker::Types::STRING ||
|
|
6619
|
+
(receiver_type.is_a?(TypeChecker::Types::ClassInstance) && receiver_type.name == :String)
|
|
6620
|
+
end
|
|
6621
|
+
|
|
6622
|
+
# Generate inline String method
|
|
6623
|
+
def generate_inline_string_method(inst)
|
|
6624
|
+
receiver = get_value_as_ruby(inst.receiver)
|
|
6625
|
+
|
|
6626
|
+
case inst.method_name
|
|
6627
|
+
when "empty?"
|
|
6628
|
+
# rb_str_strlen returns long (byte count), avoids rb_funcallv method lookup
|
|
6629
|
+
rb_str_strlen = @mod.functions["rb_str_strlen"] || @mod.functions.add(
|
|
6630
|
+
"rb_str_strlen", [value_type], LLVM::Int64
|
|
6631
|
+
)
|
|
6632
|
+
str_len = @builder.call(rb_str_strlen, receiver, "str_len")
|
|
6633
|
+
is_empty = @builder.icmp(:eq, str_len, LLVM::Int64.from_i(0), "is_empty")
|
|
6634
|
+
result = @builder.select(is_empty, @qtrue, @qfalse)
|
|
6635
|
+
if inst.result_var
|
|
6636
|
+
@variables[inst.result_var] = result
|
|
6637
|
+
@variable_types[inst.result_var] = :value
|
|
6638
|
+
end
|
|
6639
|
+
result
|
|
6640
|
+
end
|
|
6641
|
+
end
|
|
6642
|
+
|
|
6215
6643
|
# Generate Array#[] using rb_ary_entry
|
|
6216
6644
|
def generate_array_index_get(inst)
|
|
6217
6645
|
receiver = get_value_as_ruby(inst.receiver)
|
|
@@ -6743,7 +7171,7 @@ module Konpeito
|
|
|
6743
7171
|
kw_info = @keyword_param_functions[func_name.to_s] || @keyword_param_functions[func_name.to_sym]
|
|
6744
7172
|
if kw_info
|
|
6745
7173
|
if inst.has_keyword_args?
|
|
6746
|
-
kwargs_hash = build_keyword_args_hash(inst.keyword_args)
|
|
7174
|
+
kwargs_hash = build_keyword_args_hash(inst.keyword_args, keyword_splat: inst.keyword_splat)
|
|
6747
7175
|
call_args << kwargs_hash
|
|
6748
7176
|
else
|
|
6749
7177
|
# No keyword args provided - pass an empty hash
|
|
@@ -6783,7 +7211,7 @@ module Konpeito
|
|
|
6783
7211
|
# Collect all arguments (regular + keyword hash if present)
|
|
6784
7212
|
all_args = inst.args.map { |arg| get_value_as_ruby(arg) }
|
|
6785
7213
|
if inst.has_keyword_args?
|
|
6786
|
-
kwargs_hash = build_keyword_args_hash(inst.keyword_args)
|
|
7214
|
+
kwargs_hash = build_keyword_args_hash(inst.keyword_args, keyword_splat: inst.keyword_splat)
|
|
6787
7215
|
all_args << kwargs_hash
|
|
6788
7216
|
end
|
|
6789
7217
|
|
|
@@ -6941,23 +7369,60 @@ module Konpeito
|
|
|
6941
7369
|
end
|
|
6942
7370
|
result = scope
|
|
6943
7371
|
else
|
|
6944
|
-
# Unqualified constant
|
|
7372
|
+
# Unqualified constant — follow Ruby's nesting lookup:
|
|
7373
|
+
# 1. owner_class (e.g. RanmaFrame) — check with rb_const_defined_at
|
|
7374
|
+
# 2. owner_module (e.g. Kumiki) if constant not directly in class
|
|
7375
|
+
# 3. rb_cObject as final fallback
|
|
7376
|
+
#
|
|
7377
|
+
# Uses `select` to pick the lookup scope without creating new basic blocks
|
|
7378
|
+
# (creating blocks mid-HIR-block corrupts phi node predecessor relationships).
|
|
6945
7379
|
const_name = parts.first
|
|
6946
|
-
|
|
7380
|
+
const_name_ptr = @builder.global_string_pointer(const_name)
|
|
7381
|
+
const_id = @builder.call(@rb_intern, const_name_ptr)
|
|
7382
|
+
rb_cobject_val = @builder.load2(value_type, @rb_cObject, "rb_cObject")
|
|
7383
|
+
|
|
7384
|
+
owner_class = @current_hir_func&.owner_class
|
|
7385
|
+
owner_module = @current_hir_func&.owner_module
|
|
7386
|
+
|
|
6947
7387
|
if owner_class
|
|
6948
|
-
#
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
7388
|
+
# Resolve the owner class value (qualified by module if present)
|
|
7389
|
+
if owner_module
|
|
7390
|
+
owner_mod_ptr = @builder.global_string_pointer(owner_module)
|
|
7391
|
+
owner_mod_id = @builder.call(@rb_intern, owner_mod_ptr)
|
|
7392
|
+
owner_mod_val = @builder.call(@rb_const_get, rb_cobject_val, owner_mod_id)
|
|
7393
|
+
owner_cls_ptr = @builder.global_string_pointer(owner_class)
|
|
7394
|
+
owner_cls_id = @builder.call(@rb_intern, owner_cls_ptr)
|
|
7395
|
+
owner_cls_val = @builder.call(@rb_const_get, owner_mod_val, owner_cls_id)
|
|
7396
|
+
else
|
|
7397
|
+
owner_cls_ptr = @builder.global_string_pointer(owner_class)
|
|
7398
|
+
owner_cls_id = @builder.call(@rb_intern, owner_cls_ptr)
|
|
7399
|
+
owner_cls_val = @builder.call(@rb_const_get, rb_cobject_val, owner_cls_id)
|
|
7400
|
+
owner_mod_val = rb_cobject_val
|
|
7401
|
+
end
|
|
7402
|
+
|
|
7403
|
+
if owner_module
|
|
7404
|
+
# Use rb_const_defined_at to select the scope without branching:
|
|
7405
|
+
# if constant is defined directly in owner_class → look in owner_class
|
|
7406
|
+
# otherwise → look in owner_module
|
|
7407
|
+
@rb_const_defined_at ||= @mod.functions.add("rb_const_defined_at",
|
|
7408
|
+
[value_type, id_type], LLVM::Int32)
|
|
7409
|
+
defined_in_cls = @builder.call(@rb_const_defined_at, owner_cls_val, const_id, "def_in_cls")
|
|
7410
|
+
is_in_cls = @builder.icmp(:ne, defined_in_cls, LLVM::Int32.from_i(0))
|
|
7411
|
+
# Select lookup scope: class if defined there, module otherwise
|
|
7412
|
+
lookup_scope = @builder.select(is_in_cls, owner_cls_val, owner_mod_val, "const_scope")
|
|
7413
|
+
result = @builder.call(@rb_const_get, lookup_scope, const_id, "const_val")
|
|
7414
|
+
else
|
|
7415
|
+
result = @builder.call(@rb_const_get, owner_cls_val, const_id)
|
|
7416
|
+
end
|
|
7417
|
+
|
|
7418
|
+
elsif owner_module
|
|
7419
|
+
# Only a module context (no class), look in module
|
|
7420
|
+
owner_mod_ptr = @builder.global_string_pointer(owner_module)
|
|
7421
|
+
owner_mod_id = @builder.call(@rb_intern, owner_mod_ptr)
|
|
7422
|
+
owner_mod_val = @builder.call(@rb_const_get, rb_cobject_val, owner_mod_id)
|
|
7423
|
+
result = @builder.call(@rb_const_get, owner_mod_val, const_id)
|
|
6956
7424
|
else
|
|
6957
7425
|
# Top-level constant
|
|
6958
|
-
const_name_ptr = @builder.global_string_pointer(const_name)
|
|
6959
|
-
const_id = @builder.call(@rb_intern, const_name_ptr)
|
|
6960
|
-
rb_cobject_val = @builder.load2(value_type, @rb_cObject, "rb_cObject")
|
|
6961
7426
|
result = @builder.call(@rb_const_get, rb_cobject_val, const_id)
|
|
6962
7427
|
end
|
|
6963
7428
|
end
|
|
@@ -7285,7 +7750,57 @@ module Konpeito
|
|
|
7285
7750
|
# Generate try callback function
|
|
7286
7751
|
@rescue_counter ||= 0
|
|
7287
7752
|
@rescue_counter += 1
|
|
7288
|
-
|
|
7753
|
+
|
|
7754
|
+
# Rescue callbacks run as separate C functions that only receive a single VALUE
|
|
7755
|
+
# as data. Any local variables (method params, locals) in @variable_allocas are
|
|
7756
|
+
# NOT accessible inside those callbacks unless we pack them into an escape array.
|
|
7757
|
+
# This applies to ALL contexts — not just thread/block callbacks — because the
|
|
7758
|
+
# rescue callback is always a separate LLVM function.
|
|
7759
|
+
#
|
|
7760
|
+
# Native pointer types (NativeArray, NativeClass, ByteBuffer, etc.) cannot be
|
|
7761
|
+
# stored in a Ruby Array, so we skip them. Unboxed types (:i64, :double) are
|
|
7762
|
+
# boxed before pushing and unboxed after unpacking.
|
|
7763
|
+
native_pointer_types = %i[native_array native_class byte_buffer byte_slice
|
|
7764
|
+
native_string slice_int64 slice_float64
|
|
7765
|
+
static_array native_hash]
|
|
7766
|
+
escapable_vars = @variable_allocas.keys.select do |vname|
|
|
7767
|
+
!native_pointer_types.include?(@variable_types[vname])
|
|
7768
|
+
end
|
|
7769
|
+
|
|
7770
|
+
needs_escape = !escapable_vars.empty?
|
|
7771
|
+
escape_var_names = nil
|
|
7772
|
+
rescue_data = nil
|
|
7773
|
+
|
|
7774
|
+
if needs_escape
|
|
7775
|
+
escape_var_names = escapable_vars
|
|
7776
|
+
n = 1 + escape_var_names.size # index-0 = self
|
|
7777
|
+
rescue_esc_ary = @builder.call(@rb_ary_new_capa, LLVM::Int64.from_i(n), "rescue_esc_ary")
|
|
7778
|
+
# Push self (index 0)
|
|
7779
|
+
actual_self = @block_callback_self || get_self_value
|
|
7780
|
+
@builder.call(@rb_ary_push, rescue_esc_ary, actual_self)
|
|
7781
|
+
# Push captured variables (index 1..n), boxing unboxed types
|
|
7782
|
+
escape_var_names.each do |vname|
|
|
7783
|
+
var_type = @variable_types[vname] || :value
|
|
7784
|
+
case var_type
|
|
7785
|
+
when :double
|
|
7786
|
+
raw = @builder.load2(LLVM::Double, @variable_allocas[vname], "esc_load_#{vname}")
|
|
7787
|
+
val = @builder.call(@rb_float_new, raw, "esc_box_#{vname}")
|
|
7788
|
+
when :i64, :i8
|
|
7789
|
+
raw = @builder.load2(LLVM::Int64, @variable_allocas[vname], "esc_load_#{vname}")
|
|
7790
|
+
val = @builder.call(@rb_int2inum, raw, "esc_box_#{vname}")
|
|
7791
|
+
else
|
|
7792
|
+
val = @builder.load2(value_type, @variable_allocas[vname], "esc_load_#{vname}")
|
|
7793
|
+
end
|
|
7794
|
+
@builder.call(@rb_ary_push, rescue_esc_ary, val)
|
|
7795
|
+
end
|
|
7796
|
+
rescue_data = rescue_esc_ary
|
|
7797
|
+
else
|
|
7798
|
+
rescue_data = get_self_value
|
|
7799
|
+
end
|
|
7800
|
+
|
|
7801
|
+
try_func = generate_rescue_try_callback(inst.try_blocks, @rescue_counter,
|
|
7802
|
+
try_hir_blocks: inst.try_hir_blocks,
|
|
7803
|
+
escape_var_names: escape_var_names)
|
|
7289
7804
|
|
|
7290
7805
|
# Collect exception classes
|
|
7291
7806
|
exception_classes = []
|
|
@@ -7299,18 +7814,18 @@ module Konpeito
|
|
|
7299
7814
|
|
|
7300
7815
|
has_else = inst.else_blocks && !inst.else_blocks.empty?
|
|
7301
7816
|
|
|
7302
|
-
# If else blocks present, use a global flag to detect if rescue ran
|
|
7303
|
-
# A module-level global i32 is set to 1 by the rescue callback
|
|
7304
7817
|
if has_else
|
|
7305
7818
|
flag_global = @mod.globals.add(LLVM::Int32, "rescue_else_flag_#{@rescue_counter}")
|
|
7306
7819
|
flag_global.initializer = LLVM::Int32.from_i(0)
|
|
7307
7820
|
@builder.store(LLVM::Int32.from_i(0), flag_global)
|
|
7308
7821
|
|
|
7309
|
-
rescue_func = generate_rescue_handler_with_global_flag_callback(inst.rescue_clauses, @rescue_counter, flag_global
|
|
7310
|
-
|
|
7822
|
+
rescue_func = generate_rescue_handler_with_global_flag_callback(inst.rescue_clauses, @rescue_counter, flag_global,
|
|
7823
|
+
escape_var_names: escape_var_names)
|
|
7824
|
+
args = [try_func, rescue_data, rescue_func, rescue_data]
|
|
7311
7825
|
else
|
|
7312
|
-
rescue_func = generate_rescue_handler_callback(inst.rescue_clauses, @rescue_counter
|
|
7313
|
-
|
|
7826
|
+
rescue_func = generate_rescue_handler_callback(inst.rescue_clauses, @rescue_counter,
|
|
7827
|
+
escape_var_names: escape_var_names)
|
|
7828
|
+
args = [try_func, rescue_data, rescue_func, rescue_data]
|
|
7314
7829
|
end
|
|
7315
7830
|
|
|
7316
7831
|
args.concat(exception_classes)
|
|
@@ -7320,6 +7835,19 @@ module Konpeito
|
|
|
7320
7835
|
rescue2_func = declare_rb_rescue2_variadic(exception_classes.size)
|
|
7321
7836
|
result = @builder.call(rescue2_func, *args)
|
|
7322
7837
|
|
|
7838
|
+
# Write-back: after rb_rescue2 returns, the escape array contains updated values
|
|
7839
|
+
# from try/rescue callbacks (written via rb_ary_store in generate_store_local).
|
|
7840
|
+
# Copy them back to the outer function's allocas.
|
|
7841
|
+
if needs_escape && escape_var_names
|
|
7842
|
+
escape_var_names.each_with_index do |vname, idx|
|
|
7843
|
+
next unless @variable_allocas[vname]
|
|
7844
|
+
val = @builder.call(@rb_ary_entry, rescue_data, LLVM::Int64.from_i(idx + 1), "wb_#{vname}")
|
|
7845
|
+
@builder.store(val, @variable_allocas[vname])
|
|
7846
|
+
@variables[vname] = val
|
|
7847
|
+
@variable_types[vname] = :value
|
|
7848
|
+
end
|
|
7849
|
+
end
|
|
7850
|
+
|
|
7323
7851
|
# Execute else blocks if no exception was raised
|
|
7324
7852
|
if has_else
|
|
7325
7853
|
flag_val = @builder.load2(LLVM::Int32, flag_global, "flag_val")
|
|
@@ -7396,53 +7924,127 @@ module Konpeito
|
|
|
7396
7924
|
end
|
|
7397
7925
|
|
|
7398
7926
|
# Generate try callback function: VALUE func(VALUE data)
|
|
7399
|
-
|
|
7927
|
+
# When escape_var_names is given, params[0] is a Ruby Array [self, var0, var1, ...].
|
|
7928
|
+
def generate_rescue_try_callback(try_instructions, counter, try_hir_blocks: nil, escape_var_names: nil)
|
|
7400
7929
|
callback_name = "rescue_try_#{counter}"
|
|
7401
7930
|
|
|
7402
|
-
# VALUE func(VALUE data)
|
|
7931
|
+
# VALUE func(VALUE data) — data (params[0]) = self or escape array
|
|
7403
7932
|
callback_func = @mod.functions.add(callback_name, [value_type], value_type)
|
|
7404
7933
|
|
|
7405
7934
|
# Save current builder state
|
|
7406
|
-
saved_block
|
|
7407
|
-
saved_vars
|
|
7408
|
-
saved_types
|
|
7409
|
-
saved_allocas
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
@
|
|
7935
|
+
saved_block = @builder.insert_block
|
|
7936
|
+
saved_vars = @variables.dup
|
|
7937
|
+
saved_types = @variable_types.dup
|
|
7938
|
+
saved_allocas = @variable_allocas.dup
|
|
7939
|
+
saved_cur_func = @current_function
|
|
7940
|
+
saved_return_blocks = @return_blocks
|
|
7941
|
+
saved_block_callback_self = @block_callback_self
|
|
7942
|
+
saved_rescue_escape_array = @rescue_escape_array
|
|
7943
|
+
saved_rescue_escape_indices = @rescue_escape_indices
|
|
7414
7944
|
|
|
7415
7945
|
# Reset variable tracking for callback scope
|
|
7416
7946
|
@variables = {}
|
|
7417
7947
|
@variable_types = {}
|
|
7418
7948
|
@variable_allocas = {}
|
|
7419
7949
|
|
|
7420
|
-
|
|
7950
|
+
if try_hir_blocks && !try_hir_blocks.empty?
|
|
7951
|
+
# Structured try body: process HIR BasicBlock list (handles control flow inside try).
|
|
7952
|
+
if escape_var_names
|
|
7953
|
+
# In escape mode, create a dedicated entry block FIRST (LLVM entry = first BB).
|
|
7954
|
+
# It unpacks self + local vars from the escape array, then branches to the first HIR block.
|
|
7955
|
+
escape_entry_bb = callback_func.basic_blocks.append("rescue_escape_entry")
|
|
7956
|
+
else
|
|
7957
|
+
# Normal mode: params[0] is self.
|
|
7958
|
+
@block_callback_self = callback_func.params[0]
|
|
7959
|
+
end
|
|
7421
7960
|
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7961
|
+
# Pre-create all LLVM basic blocks so forward references resolve.
|
|
7962
|
+
try_hir_blocks.each do |hb|
|
|
7963
|
+
llvm_bb = callback_func.basic_blocks.append(hb.label)
|
|
7964
|
+
@blocks[hb.label] = llvm_bb
|
|
7965
|
+
end
|
|
7966
|
+
# Set @current_function so generate_store_local creates allocas in the callback.
|
|
7967
|
+
@current_function = callback_func
|
|
7968
|
+
# Populate @return_blocks so generate_phi skips Return-predecessor entries.
|
|
7969
|
+
@return_blocks = Set.new
|
|
7970
|
+
try_hir_blocks.each do |hb|
|
|
7971
|
+
@return_blocks << hb.label if hb.terminator.is_a?(HIR::Return)
|
|
7972
|
+
end
|
|
7973
|
+
|
|
7974
|
+
if escape_var_names
|
|
7975
|
+
# Fill the escape-entry block: unpack array → allocas, then branch to first HIR block.
|
|
7976
|
+
@builder.position_at_end(escape_entry_bb)
|
|
7977
|
+
escape_ary = callback_func.params[0]
|
|
7978
|
+
self_val = @builder.call(@rb_ary_entry, escape_ary, LLVM::Int64.from_i(0), "rescue_esc_self")
|
|
7979
|
+
@block_callback_self = self_val
|
|
7980
|
+
escape_var_names.each_with_index do |vname, i|
|
|
7981
|
+
val = @builder.call(@rb_ary_entry, escape_ary, LLVM::Int64.from_i(i + 1), "rescue_esc_#{vname}")
|
|
7982
|
+
alloca = @builder.alloca(value_type, "rescue_esc_alloca_#{vname}")
|
|
7983
|
+
@builder.store(val, alloca)
|
|
7984
|
+
@variable_allocas[vname] = alloca
|
|
7985
|
+
@variable_types[vname] = :value
|
|
7986
|
+
end
|
|
7987
|
+
# Set escape write-back info so StoreLocal writes to escape array
|
|
7988
|
+
@rescue_escape_array = escape_ary
|
|
7989
|
+
@rescue_escape_indices = {}
|
|
7990
|
+
escape_var_names.each_with_index { |vname, i| @rescue_escape_indices[vname] = i + 1 }
|
|
7991
|
+
@builder.br(@blocks[try_hir_blocks.first.label])
|
|
7992
|
+
end
|
|
7993
|
+
|
|
7994
|
+
sorted = sort_blocks_by_phi_dependencies(try_hir_blocks)
|
|
7995
|
+
sorted.each { |hb| generate_block(callback_func, hb) }
|
|
7996
|
+
else
|
|
7997
|
+
# Flat try body: original behaviour (no control flow in try body).
|
|
7998
|
+
entry = callback_func.basic_blocks.append("entry")
|
|
7999
|
+
@builder.position_at_end(entry)
|
|
8000
|
+
|
|
8001
|
+
if escape_var_names
|
|
8002
|
+
escape_ary = callback_func.params[0]
|
|
8003
|
+
self_val = @builder.call(@rb_ary_entry, escape_ary, LLVM::Int64.from_i(0), "rescue_esc_self")
|
|
8004
|
+
@block_callback_self = self_val
|
|
8005
|
+
escape_var_names.each_with_index do |vname, i|
|
|
8006
|
+
val = @builder.call(@rb_ary_entry, escape_ary, LLVM::Int64.from_i(i + 1), "rescue_esc_#{vname}")
|
|
8007
|
+
alloca = @builder.alloca(value_type, "rescue_esc_alloca_#{vname}")
|
|
8008
|
+
@builder.store(val, alloca)
|
|
8009
|
+
@variable_allocas[vname] = alloca
|
|
8010
|
+
@variable_types[vname] = :value
|
|
8011
|
+
end
|
|
8012
|
+
# Set escape write-back info so StoreLocal writes to escape array
|
|
8013
|
+
@rescue_escape_array = escape_ary
|
|
8014
|
+
@rescue_escape_indices = {}
|
|
8015
|
+
escape_var_names.each_with_index { |vname, i| @rescue_escape_indices[vname] = i + 1 }
|
|
8016
|
+
else
|
|
8017
|
+
@block_callback_self = callback_func.params[0]
|
|
8018
|
+
end
|
|
8019
|
+
|
|
8020
|
+
result = @qnil
|
|
8021
|
+
if try_instructions && !try_instructions.empty?
|
|
8022
|
+
try_instructions.each { |inst| result = generate_instruction(inst) }
|
|
7426
8023
|
end
|
|
7427
|
-
end
|
|
7428
8024
|
|
|
7429
|
-
|
|
7430
|
-
|
|
8025
|
+
@builder.ret(result || @qnil)
|
|
8026
|
+
end
|
|
7431
8027
|
|
|
7432
8028
|
# Restore builder state
|
|
7433
8029
|
@builder.position_at_end(saved_block) if saved_block
|
|
7434
|
-
@variables
|
|
7435
|
-
@variable_types
|
|
8030
|
+
@variables = saved_vars
|
|
8031
|
+
@variable_types = saved_types
|
|
7436
8032
|
@variable_allocas = saved_allocas
|
|
8033
|
+
@current_function = saved_cur_func
|
|
8034
|
+
@return_blocks = saved_return_blocks
|
|
8035
|
+
@block_callback_self = saved_block_callback_self
|
|
8036
|
+
@rescue_escape_array = saved_rescue_escape_array
|
|
8037
|
+
@rescue_escape_indices = saved_rescue_escape_indices
|
|
7437
8038
|
|
|
7438
8039
|
callback_func
|
|
7439
8040
|
end
|
|
7440
8041
|
|
|
7441
|
-
# Generate rescue handler callback: VALUE func(VALUE
|
|
7442
|
-
|
|
8042
|
+
# Generate rescue handler callback: VALUE func(VALUE data2, VALUE exception)
|
|
8043
|
+
# data2 (params[0]) = self or escape array (when escape_var_names is given)
|
|
8044
|
+
def generate_rescue_handler_callback(rescue_clauses, counter, escape_var_names: nil)
|
|
7443
8045
|
callback_name = "rescue_handler_#{counter}"
|
|
7444
8046
|
|
|
7445
|
-
# VALUE func(VALUE
|
|
8047
|
+
# VALUE func(VALUE data2, VALUE exception)
|
|
7446
8048
|
callback_func = @mod.functions.add(callback_name, [value_type, value_type], value_type)
|
|
7447
8049
|
|
|
7448
8050
|
# Save current builder state
|
|
@@ -7450,6 +8052,12 @@ module Konpeito
|
|
|
7450
8052
|
saved_vars = @variables.dup
|
|
7451
8053
|
saved_types = @variable_types.dup
|
|
7452
8054
|
saved_allocas = @variable_allocas.dup
|
|
8055
|
+
saved_block_callback_self = @block_callback_self
|
|
8056
|
+
saved_rescue_escape_array = @rescue_escape_array
|
|
8057
|
+
saved_rescue_escape_indices = @rescue_escape_indices
|
|
8058
|
+
|
|
8059
|
+
# In normal mode: params[0] = self. In escape mode, set @block_callback_self after unpacking.
|
|
8060
|
+
@block_callback_self = callback_func.params[0] unless escape_var_names
|
|
7453
8061
|
|
|
7454
8062
|
# Create entry block for callback
|
|
7455
8063
|
entry = callback_func.basic_blocks.append("entry")
|
|
@@ -7460,10 +8068,30 @@ module Konpeito
|
|
|
7460
8068
|
@variable_types = {}
|
|
7461
8069
|
@variable_allocas = {}
|
|
7462
8070
|
|
|
7463
|
-
# Get exception parameter
|
|
7464
8071
|
# CRuby rescue callback: (VALUE data2, VALUE exception)
|
|
7465
8072
|
exception_val = callback_func.params[1]
|
|
7466
8073
|
|
|
8074
|
+
# In escape mode: unpack self and local variables from the escape array (params[0]).
|
|
8075
|
+
# escape_allocas_map holds the allocas so we can restore them after each body-block reset.
|
|
8076
|
+
escape_allocas_map = {}
|
|
8077
|
+
if escape_var_names
|
|
8078
|
+
escape_ary = callback_func.params[0]
|
|
8079
|
+
self_val = @builder.call(@rb_ary_entry, escape_ary, LLVM::Int64.from_i(0), "rescue_esc_self")
|
|
8080
|
+
@block_callback_self = self_val
|
|
8081
|
+
escape_var_names.each_with_index do |vname, i|
|
|
8082
|
+
val = @builder.call(@rb_ary_entry, escape_ary, LLVM::Int64.from_i(i + 1), "rescue_esc_#{vname}")
|
|
8083
|
+
alloca = @builder.alloca(value_type, "rescue_esc_alloca_#{vname}")
|
|
8084
|
+
@builder.store(val, alloca)
|
|
8085
|
+
@variable_allocas[vname] = alloca
|
|
8086
|
+
@variable_types[vname] = :value
|
|
8087
|
+
escape_allocas_map[vname] = alloca
|
|
8088
|
+
end
|
|
8089
|
+
# Set escape write-back info so StoreLocal writes to escape array
|
|
8090
|
+
@rescue_escape_array = escape_ary
|
|
8091
|
+
@rescue_escape_indices = {}
|
|
8092
|
+
escape_var_names.each_with_index { |vname, i| @rescue_escape_indices[vname] = i + 1 }
|
|
8093
|
+
end
|
|
8094
|
+
|
|
7467
8095
|
# Match exception class to find correct handler
|
|
7468
8096
|
if rescue_clauses && !rescue_clauses.empty?
|
|
7469
8097
|
# Create blocks for each rescue clause and a fallback
|
|
@@ -7523,6 +8151,12 @@ module Konpeito
|
|
|
7523
8151
|
@variable_types = {}
|
|
7524
8152
|
@variable_allocas = {}
|
|
7525
8153
|
|
|
8154
|
+
# In escape mode, restore captured variable allocas (created in entry block).
|
|
8155
|
+
escape_allocas_map.each do |vname, alloca|
|
|
8156
|
+
@variable_allocas[vname] = alloca
|
|
8157
|
+
@variable_types[vname] = :value
|
|
8158
|
+
end
|
|
8159
|
+
|
|
7526
8160
|
# Bind exception variable if specified
|
|
7527
8161
|
if clause.exception_var
|
|
7528
8162
|
@variables[clause.exception_var] = exception_val
|
|
@@ -7559,20 +8193,30 @@ module Konpeito
|
|
|
7559
8193
|
@variables = saved_vars
|
|
7560
8194
|
@variable_types = saved_types
|
|
7561
8195
|
@variable_allocas = saved_allocas
|
|
8196
|
+
@block_callback_self = saved_block_callback_self
|
|
8197
|
+
@rescue_escape_array = saved_rescue_escape_array
|
|
8198
|
+
@rescue_escape_indices = saved_rescue_escape_indices
|
|
7562
8199
|
|
|
7563
8200
|
callback_func
|
|
7564
8201
|
end
|
|
7565
8202
|
|
|
7566
8203
|
# Generate rescue handler callback that sets a global flag when called
|
|
7567
8204
|
# The flag_global is an LLVM global variable that is set to 1 when rescue is invoked
|
|
7568
|
-
def generate_rescue_handler_with_global_flag_callback(rescue_clauses, counter, flag_global)
|
|
8205
|
+
def generate_rescue_handler_with_global_flag_callback(rescue_clauses, counter, flag_global, escape_var_names: nil)
|
|
7569
8206
|
callback_name = "rescue_handler_gflag_#{counter}"
|
|
8207
|
+
# VALUE func(VALUE data2, VALUE exception) — data2 (params[0]) = self or escape array
|
|
7570
8208
|
callback_func = @mod.functions.add(callback_name, [value_type, value_type], value_type)
|
|
7571
8209
|
|
|
7572
8210
|
saved_block = @builder.insert_block
|
|
7573
8211
|
saved_vars = @variables.dup
|
|
7574
8212
|
saved_types = @variable_types.dup
|
|
7575
8213
|
saved_allocas = @variable_allocas.dup
|
|
8214
|
+
saved_block_callback_self = @block_callback_self
|
|
8215
|
+
saved_rescue_escape_array = @rescue_escape_array
|
|
8216
|
+
saved_rescue_escape_indices = @rescue_escape_indices
|
|
8217
|
+
|
|
8218
|
+
# In normal mode: params[0] = self. In escape mode, set @block_callback_self after unpacking.
|
|
8219
|
+
@block_callback_self = callback_func.params[0] unless escape_var_names
|
|
7576
8220
|
|
|
7577
8221
|
entry = callback_func.basic_blocks.append("entry")
|
|
7578
8222
|
@builder.position_at_end(entry)
|
|
@@ -7587,6 +8231,26 @@ module Konpeito
|
|
|
7587
8231
|
# Set the global flag to 1 (rescue was invoked)
|
|
7588
8232
|
@builder.store(LLVM::Int32.from_i(1), flag_global)
|
|
7589
8233
|
|
|
8234
|
+
# In escape mode: unpack self and local variables from the escape array.
|
|
8235
|
+
escape_allocas_map = {}
|
|
8236
|
+
if escape_var_names
|
|
8237
|
+
escape_ary = callback_func.params[0]
|
|
8238
|
+
self_val = @builder.call(@rb_ary_entry, escape_ary, LLVM::Int64.from_i(0), "rescue_esc_self")
|
|
8239
|
+
@block_callback_self = self_val
|
|
8240
|
+
escape_var_names.each_with_index do |vname, i|
|
|
8241
|
+
val = @builder.call(@rb_ary_entry, escape_ary, LLVM::Int64.from_i(i + 1), "rescue_esc_#{vname}")
|
|
8242
|
+
alloca = @builder.alloca(value_type, "rescue_esc_alloca_#{vname}")
|
|
8243
|
+
@builder.store(val, alloca)
|
|
8244
|
+
@variable_allocas[vname] = alloca
|
|
8245
|
+
@variable_types[vname] = :value
|
|
8246
|
+
escape_allocas_map[vname] = alloca
|
|
8247
|
+
end
|
|
8248
|
+
# Set escape write-back info so StoreLocal writes to escape array
|
|
8249
|
+
@rescue_escape_array = escape_ary
|
|
8250
|
+
@rescue_escape_indices = {}
|
|
8251
|
+
escape_var_names.each_with_index { |vname, i| @rescue_escape_indices[vname] = i + 1 }
|
|
8252
|
+
end
|
|
8253
|
+
|
|
7590
8254
|
# Same rescue matching logic as generate_rescue_handler_callback
|
|
7591
8255
|
if rescue_clauses && !rescue_clauses.empty?
|
|
7592
8256
|
clause_blocks = rescue_clauses.map.with_index do |_, i|
|
|
@@ -7632,6 +8296,12 @@ module Konpeito
|
|
|
7632
8296
|
@variable_types = {}
|
|
7633
8297
|
@variable_allocas = {}
|
|
7634
8298
|
|
|
8299
|
+
# In escape mode, restore captured variable allocas after the reset.
|
|
8300
|
+
escape_allocas_map.each do |vname, alloca|
|
|
8301
|
+
@variable_allocas[vname] = alloca
|
|
8302
|
+
@variable_types[vname] = :value
|
|
8303
|
+
end
|
|
8304
|
+
|
|
7635
8305
|
if clause.exception_var
|
|
7636
8306
|
@variables[clause.exception_var] = exception_val
|
|
7637
8307
|
@variable_types[clause.exception_var] = :value
|
|
@@ -7661,6 +8331,9 @@ module Konpeito
|
|
|
7661
8331
|
@variables = saved_vars
|
|
7662
8332
|
@variable_types = saved_types
|
|
7663
8333
|
@variable_allocas = saved_allocas
|
|
8334
|
+
@block_callback_self = saved_block_callback_self
|
|
8335
|
+
@rescue_escape_array = saved_rescue_escape_array
|
|
8336
|
+
@rescue_escape_indices = saved_rescue_escape_indices
|
|
7664
8337
|
|
|
7665
8338
|
callback_func
|
|
7666
8339
|
end
|
|
@@ -7892,6 +8565,27 @@ module Konpeito
|
|
|
7892
8565
|
result
|
|
7893
8566
|
end
|
|
7894
8567
|
|
|
8568
|
+
# Generate a CaseEqualityCheck: condition === predicate (returns VALUE Qtrue/Qfalse)
|
|
8569
|
+
# Used by the new HIR-block-based case/when dispatch
|
|
8570
|
+
def generate_case_equality_check(inst)
|
|
8571
|
+
cond_val = get_value_as_ruby(inst.condition)
|
|
8572
|
+
|
|
8573
|
+
result = if inst.predicate.nil?
|
|
8574
|
+
# No predicate: treat condition itself as Ruby truthy value
|
|
8575
|
+
cond_val
|
|
8576
|
+
else
|
|
8577
|
+
pred_val = get_value_as_ruby(inst.predicate)
|
|
8578
|
+
# call_case_equality returns VALUE (Qtrue or Qfalse)
|
|
8579
|
+
call_case_equality(cond_val, pred_val, receiver_hir: inst.condition)
|
|
8580
|
+
end
|
|
8581
|
+
|
|
8582
|
+
if inst.result_var
|
|
8583
|
+
@variables[inst.result_var] = result
|
|
8584
|
+
@variable_types[inst.result_var] = :value
|
|
8585
|
+
end
|
|
8586
|
+
result
|
|
8587
|
+
end
|
|
8588
|
+
|
|
7895
8589
|
# Call === (case equality) on receiver with argument
|
|
7896
8590
|
# Call case equality with optimized inline comparison for basic types
|
|
7897
8591
|
def call_case_equality(receiver, arg, receiver_hir: nil)
|
|
@@ -7950,8 +8644,8 @@ module Konpeito
|
|
|
7950
8644
|
def inline_class_case_eq(klass, arg)
|
|
7951
8645
|
# Use rb_obj_is_kind_of(arg, klass) which returns int (0 or non-zero)
|
|
7952
8646
|
result_int = @builder.call(@rb_obj_is_kind_of, arg, klass)
|
|
7953
|
-
#
|
|
7954
|
-
is_true = @builder.icmp(:ne, result_int, LLVM::
|
|
8647
|
+
# rb_obj_is_kind_of returns VALUE (i64); compare with i64 0 (Qfalse)
|
|
8648
|
+
is_true = @builder.icmp(:ne, result_int, LLVM::Int64.from_i(0))
|
|
7955
8649
|
@builder.select(is_true, @qtrue, @qfalse)
|
|
7956
8650
|
end
|
|
7957
8651
|
|
|
@@ -8638,11 +9332,19 @@ module Konpeito
|
|
|
8638
9332
|
end
|
|
8639
9333
|
|
|
8640
9334
|
def generate_phi(inst)
|
|
9335
|
+
# If this phi was already generated (e.g., it lives in an earlier HIR block but
|
|
9336
|
+
# also appears in a when_body or inline-rescue try_blocks), return the cached value.
|
|
9337
|
+
if inst.result_var && @variables.key?(inst.result_var)
|
|
9338
|
+
return @variables[inst.result_var]
|
|
9339
|
+
end
|
|
9340
|
+
|
|
8641
9341
|
# Collect incoming values with their type tags for optimization
|
|
8642
9342
|
incoming_data = []
|
|
8643
9343
|
|
|
8644
9344
|
inst.incoming.each do |label, hir_value|
|
|
8645
|
-
|
|
9345
|
+
# If safe navigation inside this HIR block created additional LLVM blocks,
|
|
9346
|
+
# use the actual exit block instead of the original HIR block's LLVM block.
|
|
9347
|
+
llvm_block = @block_exit_overrides[label] || @blocks[label]
|
|
8646
9348
|
next unless llvm_block
|
|
8647
9349
|
# Skip blocks with Return terminators — they don't branch to the
|
|
8648
9350
|
# merge block, so they cannot be predecessors in the phi node.
|
|
@@ -10118,6 +10820,14 @@ module Konpeito
|
|
|
10118
10820
|
end
|
|
10119
10821
|
|
|
10120
10822
|
# Declare memchr if not already declared
|
|
10823
|
+
def declare_strlen
|
|
10824
|
+
@strlen ||= @mod.functions["strlen"] || @mod.functions.add(
|
|
10825
|
+
"strlen",
|
|
10826
|
+
[LLVM::Pointer(LLVM::Int8)],
|
|
10827
|
+
LLVM::Int64
|
|
10828
|
+
)
|
|
10829
|
+
end
|
|
10830
|
+
|
|
10121
10831
|
def declare_memchr
|
|
10122
10832
|
@memchr ||= @mod.functions["memchr"] || @mod.functions.add(
|
|
10123
10833
|
"memchr",
|
|
@@ -11131,22 +11841,24 @@ module Konpeito
|
|
|
11131
11841
|
# Get C string pointer
|
|
11132
11842
|
data_ptr = @builder.call(@rb_string_value_cstr, value_ptr, "data_ptr")
|
|
11133
11843
|
|
|
11134
|
-
# Get byte length using
|
|
11135
|
-
|
|
11136
|
-
|
|
11844
|
+
# Get byte length using strlen on the C string
|
|
11845
|
+
# (rb_str_length returns character count, not byte count)
|
|
11846
|
+
declare_strlen
|
|
11847
|
+
byte_len = @builder.call(@strlen, data_ptr, "byte_len")
|
|
11137
11848
|
|
|
11138
|
-
# Check ASCII-only using
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11849
|
+
# Check ASCII-only using rb_enc_str_asciionly_p (avoids rb_funcallv method lookup)
|
|
11850
|
+
rb_enc_str_asciionly_p = @mod.functions["rb_enc_str_asciionly_p"] || @mod.functions.add(
|
|
11851
|
+
"rb_enc_str_asciionly_p", [value_type], LLVM::Int32
|
|
11852
|
+
)
|
|
11853
|
+
ascii_int = @builder.call(rb_enc_str_asciionly_p, string_value, "ascii_int")
|
|
11854
|
+
# rb_enc_str_asciionly_p returns non-zero for ASCII-only
|
|
11855
|
+
ascii_result = @builder.icmp(:ne, ascii_int, LLVM::Int32.from_i(0), "is_ascii_raw")
|
|
11142
11856
|
|
|
11143
|
-
# Convert Ruby boolean to flags (Qtrue = 20, Qfalse = 0)
|
|
11144
11857
|
# flags bit 0 = ASCII_ONLY
|
|
11145
|
-
|
|
11146
|
-
flags_val = @builder.select(is_ascii_true, LLVM::Int64.from_i(1), LLVM::Int64.from_i(0), "flags_val")
|
|
11858
|
+
flags_val = @builder.select(ascii_result, LLVM::Int64.from_i(1), LLVM::Int64.from_i(0), "flags_val")
|
|
11147
11859
|
|
|
11148
11860
|
# For ASCII strings, char_len = byte_len; otherwise -1 (not computed)
|
|
11149
|
-
char_len = @builder.select(
|
|
11861
|
+
char_len = @builder.select(ascii_result, byte_len, LLVM::Int64.from_i(-1), "char_len")
|
|
11150
11862
|
|
|
11151
11863
|
# Allocate struct on stack
|
|
11152
11864
|
ns_ptr = @builder.alloca(struct_type, "native_string")
|