konpeito 0.4.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e006122255b4a6a62dcd3c3ec066b97b9be8e3b3142e4d7c5fc0dd2ccd6b010
4
- data.tar.gz: afb890b5639796f4085af9191493241c9dff65ef9abb67c204884998743bde06
3
+ metadata.gz: 453b0bb32e4ef66b8b6020d262200e070caf01fb483cff4a0ebd6ceba46495b3
4
+ data.tar.gz: 7b38fb2807e1649150a0febae5defcb8f964967a715770db1de857d6409f88b3
5
5
  SHA512:
6
- metadata.gz: e6c22d15f78ad5963d979d7bd528e39e202032e8bf9296c5ee4a2dd2b4627b117b70bafb766b118ea7d2c22e315da7a1db3837b102e2992c81be1fde10114ff6
7
- data.tar.gz: db28cfe4761900dd2acadd218dbef736ec4e1b7f8c0debe02827059f3540f4e654438176f3fd9b9dc3d372a3e0e46188f7d5890d2d37a14856a6d3c106eecc68
6
+ metadata.gz: afa5fd10db773c8e1bb3c399a39cf140b1c7577271420750368551ee80cab864683d9455860ddba185b0d53cc221df42b5beb3a7334fbc67b56747e4a7bd6e0f
7
+ data.tar.gz: 7fbe956b09d489f9af313c0fa6600f81960cb02cb72d95f912a4d50d9f658b4b7bff35528d43b73c223d011d0aa5c35f6c63bf09ce5f1873c14605cd7274395e
data/CHANGELOG.md CHANGED
@@ -5,6 +5,38 @@ All notable changes to Konpeito will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.5.0] - 2026-03-10
11
+
12
+ ### Added
13
+ - **Clay UI layout stdlib**: Flexbox-style UI layout library for the mruby backend. `module Clay` auto-detected like raylib — 40+ `%a{cfunc}` bindings covering lifecycle, element construction, text, borders, scrolling, floating elements, and bulk rendering via the official raylib renderer. Vendored Clay v0.14 under `vendor/clay/`.
14
+ - **Clay UI demo**: `examples/mruby_clay_ui/clay_demo.rb` — sidebar + main content layout with TTF fonts
15
+ - **Memory Match game**: `examples/mruby_clay_ui/memory_game.rb` — card matching game using Clay layout system with module NativeArray game state
16
+ - **Module NativeArray**: fixed-size global arrays shared across functions via RBS module instance variables (`@field: NativeArray[T, N]`). Compiles to LLVM global arrays — no C wrapper file needed. Available on LLVM (CRuby) and mruby backends.
17
+ - **Inline RBS module blocks**: `# @rbs module Foo ... # @rbs end` comment blocks are now extracted by the preprocessor and emitted as raw RBS, enabling module NativeArray declarations without separate `.rbs` files or empty `module Foo; end` stubs.
18
+ - **Space Invaders example rewrite**: `examples/mruby_space_invaders/` rewritten to use module NativeArray with inline RBS — single `.rb` file, no C wrapper or separate `.rbs` needed
19
+ - **Third-party license file**: `THIRD_PARTY_LICENSES.md` summarizing vendored library licenses (yyjson, Clay)
20
+
21
+ ### Fixed
22
+ - **NativeArray GEP stride**: fix `gep` → `gep2` for all NativeArray element access (14 call sites). With LLVM opaque pointers, `gep` used array type as stride instead of element type, causing memory corruption and SIGSEGV crashes.
23
+
24
+ ## [0.4.2] - 2026-03-10
25
+
26
+ ### Fixed
27
+ - **Linux symbol collision**: use `internal` linkage for LLVM callback functions to prevent flat namespace collisions on Linux
28
+ - **NativeClass ptr→VALUE**: add missing `ptr2int` conversion for NativeClass objects passed to CRuby APIs
29
+ - **JSON codegen tests**: skip when vendored yyjson source is unavailable (CI environments)
30
+ - **CI stabilization**: run codegen tests per-file in separate processes to prevent `.so` accumulation crashes
31
+
32
+ ### Added
33
+ - **macOS ARM CI job**: unit tests and codegen tests on `macos-latest` (ARM)
34
+ - **mruby CI job**: build and run verification with `konpeito run --target mruby`
35
+ - **Japanese tutorial**: add mruby backend section (5.5) matching English tutorial
36
+
37
+ ### Changed
38
+ - Update `actions/checkout` v4 → v6, `actions/setup-java` v4 → v5
39
+
8
40
  ## [0.4.1] - 2026-03-09
9
41
 
10
42
  ### Added
data/Rakefile CHANGED
@@ -5,7 +5,34 @@ require "rake/testtask"
5
5
  Rake::TestTask.new(:test) do |t|
6
6
  t.libs << "test"
7
7
  t.libs << "lib"
8
- t.test_files = FileList["test/**/*_test.rb"]
8
+ t.test_files = FileList["test/**/*_test.rb"].exclude("test/codegen/**/*_test.rb")
9
+ end
10
+
11
+ desc "Run codegen tests (each file in a separate process to avoid .so accumulation crashes)"
12
+ task "test:codegen" do
13
+ test_files = FileList["test/codegen/**/*_test.rb"].sort
14
+ failed = []
15
+ test_files.each do |f|
16
+ print "#{File.basename(f, '.rb')} "
17
+ unless system("bundle", "exec", "ruby", "-Ilib:test", f)
18
+ failed << f
19
+ end
20
+ end
21
+ puts
22
+ unless failed.empty?
23
+ abort "#{failed.size}/#{test_files.size} codegen test files failed:\n #{failed.join("\n ")}"
24
+ end
25
+ puts "All #{test_files.size} codegen test files passed."
26
+ end
27
+
28
+ desc "Run all tests (non-codegen + codegen in separate processes)"
29
+ task "test:all" => [:test] do
30
+ # Run codegen tests in a separate process so a crash doesn't kill non-codegen results
31
+ sh "bundle exec rake test:codegen" do |ok, _status|
32
+ unless ok
33
+ warn "Codegen tests failed (possibly due to native extension crash on ruby-head)"
34
+ end
35
+ end
9
36
  end
10
37
 
11
38
  desc "Run conformance tests against Ruby/Native/JVM backends"
@@ -23,4 +50,9 @@ task "conformance:jvm" do
23
50
  ruby "spec/conformance/runner.rb", "--jvm-only"
24
51
  end
25
52
 
53
+ desc "Run CI compilation diagnostics"
54
+ task "test:diagnose" do
55
+ ruby "test/ci_diagnostic.rb"
56
+ end
57
+
26
58
  task default: :test
@@ -0,0 +1,26 @@
1
+ # Third-Party Licenses
2
+
3
+ Konpeito vendors the following third-party libraries. Each library's license
4
+ file is kept alongside its source under `vendor/`.
5
+
6
+ ---
7
+
8
+ ## yyjson
9
+
10
+ - **Location:** `vendor/yyjson/`
11
+ - **License:** MIT
12
+ - **Copyright:** (c) 2020 YaoYuan
13
+ - **URL:** https://github.com/ibireme/yyjson
14
+
15
+ See `vendor/yyjson/LICENSE` for the full license text.
16
+
17
+ ---
18
+
19
+ ## Clay
20
+
21
+ - **Location:** `vendor/clay/`
22
+ - **License:** zlib/libpng
23
+ - **Copyright:** (c) 2024 Nic Barker
24
+ - **URL:** https://github.com/nicbarker/clay
25
+
26
+ See `vendor/clay/LICENSE` for the full license text.
@@ -277,7 +277,7 @@ module Konpeito
277
277
 
278
278
  # Include extra C files in cache key (they affect the binary)
279
279
  source_dir = File.dirname(File.expand_path(source_file))
280
- extra_c_files = Dir.glob(File.join(source_dir, "*.c")).sort
280
+ extra_c_files = Dir.glob(File.join(source_dir, "*.c"))
281
281
 
282
282
  options_hash = {
283
283
  "inline_rbs" => options[:inline_rbs].to_s,
@@ -996,6 +996,13 @@ module Konpeito
996
996
  flags.concat(yyjson_objs)
997
997
  end
998
998
 
999
+ # Add clay object files if Clay stdlib is used
1000
+ clay_used = @rbs_loader&.cfunc_methods&.any? { |k, _| k.start_with?("Clay.") }
1001
+ if clay_used
1002
+ clay_objs = ensure_clay_compiled
1003
+ flags.concat(clay_objs)
1004
+ end
1005
+
999
1006
  if @rbs_loader
1000
1007
  stdlib_ui_dir = File.expand_path("../stdlib/ui", __dir__)
1001
1008
 
@@ -1051,6 +1058,25 @@ module Konpeito
1051
1058
  [yyjson_obj, wrapper_obj]
1052
1059
  end
1053
1060
 
1061
+ # Compile vendored clay.h implementation if Clay stdlib is used
1062
+ # Returns array of object file paths
1063
+ def ensure_clay_compiled
1064
+ clay_dir = File.expand_path("../../../vendor/clay", __dir__)
1065
+ clay_impl_c = File.join(clay_dir, "clay_impl.c")
1066
+ clay_impl_obj = File.join(clay_dir, "clay_impl.o")
1067
+
1068
+ return [] unless File.exist?(clay_impl_c)
1069
+
1070
+ cc = find_llvm_tool("clang") || "cc"
1071
+
1072
+ unless File.exist?(clay_impl_obj) && File.mtime(clay_impl_obj) > File.mtime(clay_impl_c)
1073
+ cmd = [cc, "-c", "-O2", "-fPIC", "-o", clay_impl_obj, clay_impl_c]
1074
+ system(*cmd) or return []
1075
+ end
1076
+
1077
+ [clay_impl_obj]
1078
+ end
1079
+
1054
1080
  def ruby_link_flags
1055
1081
  # Get Ruby's linker flags for extensions
1056
1082
  [
@@ -532,6 +532,16 @@ module Konpeito
532
532
  new_array = transform_value(inst.array, prefix, param_map)
533
533
  HIR::MultiWriteExtract.new(array: new_array, index: inst.index, type: inst.type, result_var: new_result)
534
534
 
535
+ when HIR::ModuleNativeArrayRef
536
+ new_result = inst.result_var ? prefix + inst.result_var : nil
537
+ HIR::ModuleNativeArrayRef.new(
538
+ module_name: inst.module_name,
539
+ field_name: inst.field_name,
540
+ element_type: inst.element_type,
541
+ size: inst.size,
542
+ result_var: new_result
543
+ )
544
+
535
545
  else
536
546
  # For other instructions, just return as-is with renamed result
537
547
  inst
@@ -100,6 +100,9 @@ module Konpeito
100
100
  # Scan HIR to register NativeClassTypes before code generation
101
101
  scan_for_native_class_types(hir_program)
102
102
 
103
+ # Declare LLVM globals for module-level NativeArray fields
104
+ declare_module_native_arrays(hir_program)
105
+
103
106
  # Pre-scan: detect parameters that can receive nil at call sites
104
107
  @nil_possible_params = scan_nil_possible_params(hir_program)
105
108
 
@@ -2182,6 +2185,8 @@ module Konpeito
2182
2185
  generate_sized_queue_push(inst)
2183
2186
  when HIR::SizedQueuePop
2184
2187
  generate_sized_queue_pop(inst)
2188
+ when HIR::ModuleNativeArrayRef
2189
+ generate_module_native_array_ref(inst)
2185
2190
  when HIR::NativeArrayAlloc
2186
2191
  generate_native_array_alloc(inst)
2187
2192
  when HIR::NativeArrayGet
@@ -2985,6 +2990,9 @@ module Konpeito
2985
2990
  # Convert bool to Ruby true/false
2986
2991
  is_true = @builder.icmp(:ne, value, LLVM::Int8.from_i(0))
2987
2992
  @builder.select(is_true, qtrue, qfalse)
2993
+ when [:native_class, :value]
2994
+ # NativeClass struct pointer to VALUE (i64)
2995
+ @builder.ptr2int(value, LLVM::Int64)
2988
2996
  when [:i64, :double]
2989
2997
  @builder.si2fp(value, LLVM::Double)
2990
2998
  when [:double, :i64]
@@ -4826,6 +4834,7 @@ module Konpeito
4826
4834
  callback_func = @mod.functions.add(callback_name,
4827
4835
  [value_type, value_type, LLVM::Int32, LLVM::Pointer(value_type), value_type],
4828
4836
  value_type)
4837
+ callback_func.linkage = :internal
4829
4838
 
4830
4839
  # Save current builder state
4831
4840
  saved_block = @builder.insert_block
@@ -5221,6 +5230,7 @@ module Konpeito
5221
5230
  # --- Shared lambda_true helper (created once per module) ---
5222
5231
  @lambda_true_func ||= begin
5223
5232
  f = @mod.functions.add("__konpeito_lambda_true", [value_type], value_type)
5233
+ f.linkage = :internal
5224
5234
  bb = f.basic_blocks.append("entry")
5225
5235
  @builder.position_at_end(bb)
5226
5236
  @builder.ret(qtrue)
@@ -5231,6 +5241,7 @@ module Konpeito
5231
5241
  arity_func_name = "__konpeito_proc_arity_#{param_count}"
5232
5242
  arity_func = @mod.functions[arity_func_name] || begin
5233
5243
  f = @mod.functions.add(arity_func_name, [value_type], value_type)
5244
+ f.linkage = :internal
5234
5245
  bb = f.basic_blocks.append("entry")
5235
5246
  @builder.position_at_end(bb)
5236
5247
  arity_ruby = @builder.call(@rb_int2inum, LLVM::Int64.from_i(param_count), "arity_val")
@@ -5560,6 +5571,7 @@ module Konpeito
5560
5571
  callback_func = @mod.functions.add(callback_name,
5561
5572
  [LLVM::Pointer(LLVM::Int8)],
5562
5573
  value_type)
5574
+ callback_func.linkage = :internal
5563
5575
 
5564
5576
  # Save current builder state
5565
5577
  saved_block = @builder.insert_block
@@ -5893,6 +5905,7 @@ module Konpeito
5893
5905
  # VALUE callback(VALUE data) - data is pointer to captures array
5894
5906
  callback_type = LLVM::Type.function([value_type], value_type)
5895
5907
  callback_func = @mod.functions.add(callback_name, [value_type], value_type)
5908
+ callback_func.linkage = :internal
5896
5909
 
5897
5910
  # Save current builder state
5898
5911
  saved_block = @builder.insert_block
@@ -5988,6 +6001,7 @@ module Konpeito
5988
6001
 
5989
6002
  # VALUE callback(VALUE mutex) - mutex is passed as data2
5990
6003
  callback_func = @mod.functions.add(callback_name, [value_type], value_type)
6004
+ callback_func.linkage = :internal
5991
6005
 
5992
6006
  # Save current builder state
5993
6007
  saved_block = @builder.insert_block
@@ -8108,6 +8122,7 @@ module Konpeito
8108
8122
 
8109
8123
  # VALUE func(VALUE data) — data (params[0]) = self or escape array
8110
8124
  callback_func = @mod.functions.add(callback_name, [value_type], value_type)
8125
+ callback_func.linkage = :internal
8111
8126
 
8112
8127
  # Save current builder state
8113
8128
  saved_block = @builder.insert_block
@@ -8227,6 +8242,7 @@ module Konpeito
8227
8242
 
8228
8243
  # VALUE func(VALUE data2, VALUE exception)
8229
8244
  callback_func = @mod.functions.add(callback_name, [value_type, value_type], value_type)
8245
+ callback_func.linkage = :internal
8230
8246
 
8231
8247
  # Save current builder state
8232
8248
  saved_block = @builder.insert_block
@@ -8387,6 +8403,7 @@ module Konpeito
8387
8403
  callback_name = "rescue_handler_gflag_#{counter}"
8388
8404
  # VALUE func(VALUE data2, VALUE exception) — data2 (params[0]) = self or escape array
8389
8405
  callback_func = @mod.functions.add(callback_name, [value_type, value_type], value_type)
8406
+ callback_func.linkage = :internal
8390
8407
 
8391
8408
  saved_block = @builder.insert_block
8392
8409
  saved_vars = @variables.dup
@@ -8524,6 +8541,7 @@ module Konpeito
8524
8541
  def generate_rescue_handler_with_flag_callback(rescue_clauses, counter)
8525
8542
  callback_name = "rescue_handler_flag_#{counter}"
8526
8543
  callback_func = @mod.functions.add(callback_name, [value_type, value_type], value_type)
8544
+ callback_func.linkage = :internal
8527
8545
 
8528
8546
  saved_block = @builder.insert_block
8529
8547
  saved_vars = @variables.dup
@@ -9828,8 +9846,8 @@ module Konpeito
9828
9846
  # Normalize negative indices
9829
9847
  index_i64 = normalize_native_index(index_i64, inst.receiver)
9830
9848
 
9831
- # GEP to get element pointer
9832
- elem_ptr = @builder.gep(array_ptr, [index_i64], "elem_ptr")
9849
+ # GEP to get element pointer (use gep2 with element type for correct stride)
9850
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [index_i64], "elem_ptr")
9833
9851
 
9834
9852
  # Load element
9835
9853
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
@@ -9859,8 +9877,9 @@ module Konpeito
9859
9877
  target_type = element_type == :Int64 ? :i64 : :double
9860
9878
  converted_value = convert_value(store_value, value_type, target_type)
9861
9879
 
9862
- # GEP to get element pointer
9863
- elem_ptr = @builder.gep(array_ptr, [index_i64], "elem_ptr")
9880
+ # GEP to get element pointer (use gep2 with element type for correct stride)
9881
+ llvm_elem_type = native_array_element_llvm_type(element_type)
9882
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [index_i64], "elem_ptr")
9864
9883
 
9865
9884
  # Store element
9866
9885
  @builder.store(converted_value, elem_ptr)
@@ -9880,46 +9899,19 @@ module Konpeito
9880
9899
  len_value = @variables["#{receiver_var}_len"]
9881
9900
  return index_i64 unless len_value
9882
9901
 
9883
- func = @builder.insert_block.parent
9902
+ # Use branchless select instead of conditional branches to avoid
9903
+ # creating new basic blocks that break surrounding PHI node predecessors.
9884
9904
  is_negative = @builder.icmp(:slt, index_i64, LLVM::Int64.from_i(0))
9885
-
9886
- neg_bb = func.basic_blocks.append("idx_neg")
9887
- pos_bb = func.basic_blocks.append("idx_pos")
9888
- merge_bb = func.basic_blocks.append("idx_merge")
9889
-
9890
- @builder.cond(is_negative, neg_bb, pos_bb)
9891
-
9892
- @builder.position_at_end(neg_bb)
9893
9905
  normalized = @builder.add(len_value, index_i64, "neg_idx")
9894
- @builder.br(merge_bb)
9895
-
9896
- @builder.position_at_end(pos_bb)
9897
- @builder.br(merge_bb)
9898
-
9899
- @builder.position_at_end(merge_bb)
9900
- @builder.phi(LLVM::Int64, { neg_bb => normalized, pos_bb => index_i64 })
9906
+ @builder.select(is_negative, normalized, index_i64, "norm_idx")
9901
9907
  end
9902
9908
 
9903
9909
  # Normalize negative index for StaticArray (compile-time known size)
9904
9910
  def normalize_static_index(index_i64, size)
9905
- func = @builder.insert_block.parent
9911
+ # Use branchless select to avoid creating new basic blocks
9906
9912
  is_negative = @builder.icmp(:slt, index_i64, LLVM::Int64.from_i(0))
9907
-
9908
- neg_bb = func.basic_blocks.append("sidx_neg")
9909
- pos_bb = func.basic_blocks.append("sidx_pos")
9910
- merge_bb = func.basic_blocks.append("sidx_merge")
9911
-
9912
- @builder.cond(is_negative, neg_bb, pos_bb)
9913
-
9914
- @builder.position_at_end(neg_bb)
9915
9913
  normalized = @builder.add(LLVM::Int64.from_i(size), index_i64, "neg_sidx")
9916
- @builder.br(merge_bb)
9917
-
9918
- @builder.position_at_end(pos_bb)
9919
- @builder.br(merge_bb)
9920
-
9921
- @builder.position_at_end(merge_bb)
9922
- @builder.phi(LLVM::Int64, { neg_bb => normalized, pos_bb => index_i64 })
9914
+ @builder.select(is_negative, normalized, index_i64, "norm_sidx")
9923
9915
  end
9924
9916
 
9925
9917
  # Generate NativeArray length access: arr.length
@@ -9977,7 +9969,7 @@ module Konpeito
9977
9969
  @builder.position_at_end(loop_body)
9978
9970
 
9979
9971
  # Get element via GEP + load (no rb_ary_entry!)
9980
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
9972
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
9981
9973
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
9982
9974
 
9983
9975
  # Set up block parameter (unboxed!)
@@ -10027,7 +10019,7 @@ module Konpeito
10027
10019
  convert_value(val, val_type, type_tag)
10028
10020
  else
10029
10021
  # Use first element as initial, start from index 1
10030
- elem_ptr = @builder.gep(array_ptr, [LLVM::Int64.from_i(0)], "first_ptr")
10022
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [LLVM::Int64.from_i(0)], "first_ptr")
10031
10023
  @builder.load2(llvm_elem_type, elem_ptr, "first_elem")
10032
10024
  end
10033
10025
 
@@ -10059,7 +10051,7 @@ module Konpeito
10059
10051
 
10060
10052
  # Load accumulator and element (both unboxed)
10061
10053
  acc_value = @builder.load2(llvm_elem_type, acc_alloca, "acc")
10062
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10054
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10063
10055
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10064
10056
 
10065
10057
  # Set up block parameters
@@ -10159,7 +10151,7 @@ module Konpeito
10159
10151
  @builder.position_at_end(loop_body)
10160
10152
 
10161
10153
  # Load element (unboxed)
10162
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10154
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10163
10155
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10164
10156
 
10165
10157
  # Set up block parameter
@@ -10242,7 +10234,7 @@ module Konpeito
10242
10234
  @builder.position_at_end(loop_body)
10243
10235
 
10244
10236
  # Load element (unboxed)
10245
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10237
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10246
10238
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10247
10239
 
10248
10240
  # Set up block parameter
@@ -10340,7 +10332,7 @@ module Konpeito
10340
10332
  @builder.position_at_end(loop_body)
10341
10333
 
10342
10334
  # Load element and store to alloca for later use
10343
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10335
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10344
10336
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10345
10337
  @builder.store(elem_value, elem_alloca)
10346
10338
 
@@ -10432,7 +10424,7 @@ module Konpeito
10432
10424
  @builder.position_at_end(loop_body)
10433
10425
 
10434
10426
  # Load element
10435
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10427
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10436
10428
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10437
10429
 
10438
10430
  # Set up block parameter
@@ -10540,7 +10532,7 @@ module Konpeito
10540
10532
 
10541
10533
  # Load accumulator and element
10542
10534
  acc_value = @builder.load2(llvm_elem_type, acc_alloca, "acc")
10543
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10535
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10544
10536
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10545
10537
 
10546
10538
  # Add (unboxed)
@@ -10601,7 +10593,7 @@ module Konpeito
10601
10593
  # Non-empty: initialize with first element
10602
10594
  @builder.position_at_end(non_empty_block)
10603
10595
  result_alloca = @builder.alloca(llvm_elem_type, "na_minmax_result")
10604
- first_ptr = @builder.gep(array_ptr, [LLVM::Int64.from_i(0)], "first_ptr")
10596
+ first_ptr = @builder.gep2(llvm_elem_type, array_ptr, [LLVM::Int64.from_i(0)], "first_ptr")
10605
10597
  first_elem = @builder.load2(llvm_elem_type, first_ptr, "first")
10606
10598
  @builder.store(first_elem, result_alloca)
10607
10599
 
@@ -10621,7 +10613,7 @@ module Konpeito
10621
10613
 
10622
10614
  # Load current result and element
10623
10615
  current_result = @builder.load2(llvm_elem_type, result_alloca, "current")
10624
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10616
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10625
10617
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10626
10618
 
10627
10619
  # Compare and select
@@ -10691,11 +10683,16 @@ module Konpeito
10691
10683
  # Get the array pointer from a receiver HIR value
10692
10684
  def get_native_array_ptr(receiver)
10693
10685
  case receiver
10686
+ when HIR::LoadLocal
10687
+ # LoadLocal may have result_var=nil (e.g., from inliner transform_value).
10688
+ # Use the variable name to look up the array pointer.
10689
+ @variables[receiver.var.name] || (receiver.result_var && @variables[receiver.result_var]) ||
10690
+ raise("NativeArray LoadLocal var '#{receiver.var.name}' not in @variables")
10694
10691
  when HIR::Instruction
10695
10692
  if receiver.result_var
10696
10693
  @variables[receiver.result_var]
10697
10694
  else
10698
- raise "NativeArray receiver has no result_var"
10695
+ raise "NativeArray receiver has no result_var: #{receiver.class}"
10699
10696
  end
10700
10697
  when String
10701
10698
  @variables[receiver]
@@ -10707,6 +10704,8 @@ module Konpeito
10707
10704
  # Get the variable name from a receiver for length lookup
10708
10705
  def get_receiver_var_name(receiver)
10709
10706
  case receiver
10707
+ when HIR::LoadLocal
10708
+ receiver.var.name
10710
10709
  when HIR::Instruction
10711
10710
  receiver.result_var
10712
10711
  when String
@@ -10716,6 +10715,62 @@ module Konpeito
10716
10715
  end
10717
10716
  end
10718
10717
 
10718
+ # ========================================
10719
+ # Module-level NativeArray (LLVM global variables)
10720
+ # ========================================
10721
+
10722
+ # Declare LLVM global arrays for all modules with NativeArray fields
10723
+ def declare_module_native_arrays(hir)
10724
+ @module_native_arrays ||= {}
10725
+
10726
+ hir.modules.each do |module_def|
10727
+ next unless module_def.native_array_fields&.any?
10728
+
10729
+ module_def.native_array_fields.each do |field_name, info|
10730
+ elem_type = info[:element_type]
10731
+ size = info[:size]
10732
+ llvm_elem_type = elem_type == :Int64 ? LLVM::Int64 : LLVM::Double
10733
+
10734
+ global_name = "konpeito_#{module_def.name}_#{field_name}"
10735
+ array_type = LLVM::Type.array(llvm_elem_type, size)
10736
+
10737
+ global = @mod.globals.add(array_type, global_name)
10738
+ global.initializer = LLVM::Constant.null(array_type)
10739
+ global.linkage = :internal
10740
+
10741
+ @module_native_arrays["#{module_def.name}_#{field_name}"] = {
10742
+ global: global,
10743
+ element_type: elem_type,
10744
+ size: size,
10745
+ llvm_elem_type: llvm_elem_type,
10746
+ array_type: array_type
10747
+ }
10748
+ end
10749
+ end
10750
+ end
10751
+
10752
+ # Generate a reference to a module-level NativeArray global
10753
+ def generate_module_native_array_ref(inst)
10754
+ key = "#{inst.module_name}_#{inst.field_name}"
10755
+ info = @module_native_arrays[key]
10756
+ raise "Unknown module NativeArray: #{key}" unless info
10757
+
10758
+ # GEP to get pointer to first element of global array
10759
+ array_ptr = @builder.gep2(
10760
+ info[:array_type], info[:global],
10761
+ [LLVM::Int64.from_i(0), LLVM::Int64.from_i(0)],
10762
+ "#{inst.field_name}_ptr"
10763
+ )
10764
+
10765
+ if inst.result_var
10766
+ @variables[inst.result_var] = array_ptr
10767
+ @variable_types[inst.result_var] = :native_array
10768
+ @variables["#{inst.result_var}_len"] = LLVM::Int64.from_i(inst.size)
10769
+ end
10770
+
10771
+ array_ptr
10772
+ end
10773
+
10719
10774
  # Allocate a NativeArray on the stack
10720
10775
  # Returns a pointer to contiguous memory + stores length
10721
10776
  def generate_native_array_alloc(inst)
@@ -10765,8 +10820,8 @@ module Konpeito
10765
10820
  index_value, index_type = get_value_with_type(inst.index)
10766
10821
  index_i64 = index_type == :i64 ? index_value : @builder.call(@rb_num2long, index_value)
10767
10822
 
10768
- # GEP to get element pointer
10769
- elem_ptr = @builder.gep(array_ptr, [index_i64], "elem_ptr")
10823
+ # GEP to get element pointer (use gep2 with element type for correct stride)
10824
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [index_i64], "elem_ptr")
10770
10825
 
10771
10826
  if native_array_has_class_element?(element_type)
10772
10827
  # For NativeClass elements, return pointer to struct (for field access)
@@ -10807,8 +10862,9 @@ module Konpeito
10807
10862
  target_type = element_type == :Int64 ? :i64 : :double
10808
10863
  converted_value = convert_value(store_value, value_type, target_type)
10809
10864
 
10810
- # GEP to get element pointer
10811
- elem_ptr = @builder.gep(array_ptr, [index_i64], "elem_ptr")
10865
+ # GEP to get element pointer (use gep2 with element type for correct stride)
10866
+ llvm_elem_type = native_array_element_llvm_type(element_type)
10867
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [index_i64], "elem_ptr")
10812
10868
 
10813
10869
  # Store element
10814
10870
  @builder.store(converted_value, elem_ptr)
@@ -12906,8 +12962,8 @@ module Konpeito
12906
12962
  # Get JSON element at index
12907
12963
  elem_val = @builder.call(@yyjson_arr_get, root, idx, "json_elem")
12908
12964
 
12909
- # Get pointer to NativeClass struct in the array
12910
- struct_ptr = @builder.gep(array_ptr, [idx], "elem_struct_ptr")
12965
+ # Get pointer to NativeClass struct in the array (use gep2 for correct stride)
12966
+ struct_ptr = @builder.gep2(llvm_elem_type, array_ptr, [idx], "elem_struct_ptr")
12911
12967
 
12912
12968
  # Parse fields from JSON object into struct
12913
12969
  fields = element_class.fields
@@ -58,6 +58,10 @@ module Konpeito
58
58
  extra_obj_files << extra_obj
59
59
  end
60
60
 
61
+ # Compile vendored Clay library if used
62
+ clay_objs = ensure_clay_compiled
63
+ extra_obj_files.concat(clay_objs)
64
+
61
65
  obj_files = [obj_file, init_obj_file, helpers_obj_file] + extra_obj_files
62
66
 
63
67
  # Link into standalone executable
@@ -901,6 +905,34 @@ module Konpeito
901
905
  )
902
906
  end
903
907
 
908
+ # Include Clay if used
909
+ clay_used = @extra_c_files.any? { |f| File.basename(f).include?("clay") }
910
+ if clay_used
911
+ sections << license_section(
912
+ "Clay",
913
+ "zlib/libpng",
914
+ "Copyright (c) 2024 Nic Barker",
915
+ "https://github.com/nicbarker/clay",
916
+ <<~ZLIB
917
+ This software is provided 'as-is', without any express or implied warranty.
918
+ In no event will the authors be held liable for any damages arising from the
919
+ use of this software.
920
+
921
+ Permission is granted to anyone to use this software for any purpose,
922
+ including commercial applications, and to alter it and redistribute it freely,
923
+ subject to the following restrictions:
924
+
925
+ 1. The origin of this software must not be misrepresented; you must not claim
926
+ that you wrote the original software. If you use this software in a product,
927
+ an acknowledgment in the product documentation would be appreciated but is
928
+ not required.
929
+ 2. Altered source versions must be plainly marked as such, and must not be
930
+ misrepresented as being the original software.
931
+ 3. This notice may not be removed or altered from any source distribution.
932
+ ZLIB
933
+ )
934
+ end
935
+
904
936
  # Include raylib if linked
905
937
  ffi_libs = @rbs_loader&.all_ffi_libraries || []
906
938
  if ffi_libs.any? { |lib| lib.to_s.include?("raylib") }
@@ -1160,8 +1192,41 @@ module Konpeito
1160
1192
  end
1161
1193
  end
1162
1194
 
1195
+ # Compile vendored clay.h implementation if Clay stdlib is used
1196
+ # Returns array of object file paths
1197
+ def ensure_clay_compiled
1198
+ clay_used = @extra_c_files.any? { |f| File.basename(f).include?("clay") }
1199
+ return [] unless clay_used
1200
+
1201
+ clay_dir = File.expand_path("../../../vendor/clay", __dir__)
1202
+ clay_impl_c = File.join(clay_dir, "clay_impl.c")
1203
+ clay_impl_obj = File.join(clay_dir, "clay_impl.o")
1204
+
1205
+ return [] unless File.exist?(clay_impl_c)
1206
+
1207
+ cc, cc_flags = cross_cc_with_flags
1208
+ cflags = cross_compiling? ? Platform.cross_mruby_cflags(@cross_mruby_dir) : (Platform.mruby_cflags rescue "-O2")
1209
+
1210
+ # Compile clay_impl.c (only if stale)
1211
+ unless File.exist?(clay_impl_obj) && File.mtime(clay_impl_obj) > File.mtime(clay_impl_c)
1212
+ cmd = [*cc, "-c", "-O2"]
1213
+ cmd.concat(cc_flags)
1214
+ cmd += ["-o", clay_impl_obj, clay_impl_c]
1215
+ system(*cmd) or return []
1216
+ end
1217
+
1218
+ [clay_impl_obj]
1219
+ end
1220
+
1163
1221
  def ffi_include_flags
1164
1222
  flags = []
1223
+
1224
+ # Always add vendored Clay include path if Clay stdlib is used
1225
+ clay_dir = File.expand_path("../../../vendor/clay", __dir__)
1226
+ if @extra_c_files.any? { |f| File.basename(f).include?("clay") } && Dir.exist?(clay_dir)
1227
+ flags << "-I#{clay_dir}"
1228
+ end
1229
+
1165
1230
  if cross_compiling?
1166
1231
  # When cross-compiling, use cross library include paths
1167
1232
  flags << "-I#{@cross_libs_dir}/../include" if @cross_libs_dir && Dir.exist?("#{@cross_libs_dir}/../include")