konpeito 0.4.2 → 0.6.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: 18af8ec6ef271ba48955db2f05fc19b70529a318494bced5fe4e2a83febc3544
4
- data.tar.gz: 076eb7d785e5bff312c1ec5b8c093c6fc1db034eabe397d5cc1d32d57438e332
3
+ metadata.gz: c6e49cc678968c9afbf41e3483ce19ac9d0f506bf18b31def5de3468fdef18b3
4
+ data.tar.gz: f6f85c8b6d646c56ace6b35338ea66adeabc53eec3a21b50b0953346145b0d59
5
5
  SHA512:
6
- metadata.gz: 30fe2f9fc64bd7c4d4cff705e8a3e46bef719e947efb6d81418ba51e54ccaad89f5cc6e145fb28a5d84960c864abcc4a284cf2868e81da2b161102619cbaae08
7
- data.tar.gz: 7653068f209f65ae14e1258b0cf17c5b9b3f956a2af006d3af87375eeebc7dcaf694f8384184c8b84e98fedb3485b32ce9374d6cd148ced09bda7e6dbcc228d8
6
+ metadata.gz: cc766128892195b93c5acf173b7a84c3ee86415932b9d477a952a824cb9167b15c7e63b74ab894dcd7e8213e731b5c6b2dfd1f2ef829c2e380583d7e85f1536b
7
+ data.tar.gz: 692f32eee6feb583b24b7f468fbd659c784f01c41ecde853272966c13bdda0032c47e8d55d0d0f0d084dd9493f0df50d9f0f483245f7d9f1a8ef98f43bbb6c70
data/CHANGELOG.md CHANGED
@@ -5,6 +5,45 @@ 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.6.0] - 2026-03-11
11
+
12
+ ### Added
13
+ - **Raylib stdlib expansion**: 119 new cfunc bindings (87 → 206 total) for 2D RPG/SLG game development
14
+ - Texture/Sprite management (10): load, unload, draw, draw_rec, draw_pro, draw_scaled, dimensions, validity check
15
+ - Audio — Sound (10): load, unload, play, stop, pause, resume, playing?, volume, pitch
16
+ - Audio — Music (13): load, unload, play, stop, pause, resume, update, playing?, volume, pitch, time_length, time_played, seek
17
+ - Audio — Device (4): init, close, ready?, master volume
18
+ - Camera2D (4): begin/end mode, world-to-screen coordinate conversion (x/y)
19
+ - File I/O (4): save/load text files, file/directory existence checks
20
+ - Font management (6): load, load_ex, unload, draw_text_ex, measure_text_ex (x/y)
21
+ - Gamepad input (7 + 21 constants): button pressed/down/released/up, axis movement/count, D-pad/face/trigger/middle button and axis constants
22
+ - Extended shapes (5): draw_rectangle_pro, draw_rectangle_rounded, gradient v/h, circle sector
23
+ - Collision detection (5): recs, circles, circle-rec, point-rec, point-circle
24
+ - ID table pattern for resource management: textures (256), sounds (128), music (32), fonts (32)
25
+ - **RPG tilemap demo**: `examples/mruby_rpg_demo/rpg_demo.rb` — 40×40 tilemap, smooth camera scrolling, 4-direction player movement with animation, sign interaction, HUD with step counter
26
+ - macOS audio framework linker flags (CoreAudio, AudioToolbox, CoreFoundation)
27
+
28
+ ### Fixed
29
+ - **Inliner CFuncCall bug**: `clone_and_rename()` now handles `CFuncCall`, `ExternConstructorCall`, and `ExternMethodCall` — parameters are correctly remapped when user functions calling `@cfunc` are inlined (previously fell through to default case, returning instructions with unmapped parameters)
30
+ - **CI lint**: use anonymous block forwarding (`&`) in `rbs_loader.rb`
31
+ - **CI test isolation**: use unique symbol names in `module_native_array_test` to avoid ELF symbol interposition on Linux
32
+
33
+ ## [0.5.0] - 2026-03-10
34
+
35
+ ### Added
36
+ - **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/`.
37
+ - **Clay UI demo**: `examples/mruby_clay_ui/clay_demo.rb` — sidebar + main content layout with TTF fonts
38
+ - **Memory Match game**: `examples/mruby_clay_ui/memory_game.rb` — card matching game using Clay layout system with module NativeArray game state
39
+ - **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.
40
+ - **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.
41
+ - **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
42
+ - **Third-party license file**: `THIRD_PARTY_LICENSES.md` summarizing vendored library licenses (yyjson, Clay)
43
+
44
+ ### Fixed
45
+ - **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.
46
+
8
47
  ## [0.4.2] - 2026-03-10
9
48
 
10
49
  ### Fixed
@@ -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.
@@ -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,50 @@ 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
+
545
+ when HIR::CFuncCall
546
+ new_result = inst.result_var ? prefix + inst.result_var : nil
547
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
548
+ HIR::CFuncCall.new(
549
+ c_func_name: inst.c_func_name,
550
+ args: new_args,
551
+ cfunc_type: inst.cfunc_type,
552
+ result_var: new_result
553
+ )
554
+
555
+ when HIR::ExternConstructorCall
556
+ new_result = inst.result_var ? prefix + inst.result_var : nil
557
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
558
+ HIR::ExternConstructorCall.new(
559
+ extern_type: inst.extern_type,
560
+ c_func_name: inst.c_func_name,
561
+ args: new_args,
562
+ method_sig: inst.method_sig,
563
+ result_var: new_result
564
+ )
565
+
566
+ when HIR::ExternMethodCall
567
+ new_result = inst.result_var ? prefix + inst.result_var : nil
568
+ new_receiver = transform_value(inst.receiver, prefix, param_map)
569
+ new_args = inst.args.map { |a| transform_value(a, prefix, param_map) }
570
+ HIR::ExternMethodCall.new(
571
+ receiver: new_receiver,
572
+ c_func_name: inst.c_func_name,
573
+ args: new_args,
574
+ extern_type: inst.extern_type,
575
+ method_sig: inst.method_sig,
576
+ result_var: new_result
577
+ )
578
+
535
579
  else
536
580
  # For other instructions, just return as-is with renamed result
537
581
  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
@@ -9841,8 +9846,8 @@ module Konpeito
9841
9846
  # Normalize negative indices
9842
9847
  index_i64 = normalize_native_index(index_i64, inst.receiver)
9843
9848
 
9844
- # GEP to get element pointer
9845
- 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")
9846
9851
 
9847
9852
  # Load element
9848
9853
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
@@ -9872,8 +9877,9 @@ module Konpeito
9872
9877
  target_type = element_type == :Int64 ? :i64 : :double
9873
9878
  converted_value = convert_value(store_value, value_type, target_type)
9874
9879
 
9875
- # GEP to get element pointer
9876
- 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")
9877
9883
 
9878
9884
  # Store element
9879
9885
  @builder.store(converted_value, elem_ptr)
@@ -9893,46 +9899,19 @@ module Konpeito
9893
9899
  len_value = @variables["#{receiver_var}_len"]
9894
9900
  return index_i64 unless len_value
9895
9901
 
9896
- 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.
9897
9904
  is_negative = @builder.icmp(:slt, index_i64, LLVM::Int64.from_i(0))
9898
-
9899
- neg_bb = func.basic_blocks.append("idx_neg")
9900
- pos_bb = func.basic_blocks.append("idx_pos")
9901
- merge_bb = func.basic_blocks.append("idx_merge")
9902
-
9903
- @builder.cond(is_negative, neg_bb, pos_bb)
9904
-
9905
- @builder.position_at_end(neg_bb)
9906
9905
  normalized = @builder.add(len_value, index_i64, "neg_idx")
9907
- @builder.br(merge_bb)
9908
-
9909
- @builder.position_at_end(pos_bb)
9910
- @builder.br(merge_bb)
9911
-
9912
- @builder.position_at_end(merge_bb)
9913
- @builder.phi(LLVM::Int64, { neg_bb => normalized, pos_bb => index_i64 })
9906
+ @builder.select(is_negative, normalized, index_i64, "norm_idx")
9914
9907
  end
9915
9908
 
9916
9909
  # Normalize negative index for StaticArray (compile-time known size)
9917
9910
  def normalize_static_index(index_i64, size)
9918
- func = @builder.insert_block.parent
9911
+ # Use branchless select to avoid creating new basic blocks
9919
9912
  is_negative = @builder.icmp(:slt, index_i64, LLVM::Int64.from_i(0))
9920
-
9921
- neg_bb = func.basic_blocks.append("sidx_neg")
9922
- pos_bb = func.basic_blocks.append("sidx_pos")
9923
- merge_bb = func.basic_blocks.append("sidx_merge")
9924
-
9925
- @builder.cond(is_negative, neg_bb, pos_bb)
9926
-
9927
- @builder.position_at_end(neg_bb)
9928
9913
  normalized = @builder.add(LLVM::Int64.from_i(size), index_i64, "neg_sidx")
9929
- @builder.br(merge_bb)
9930
-
9931
- @builder.position_at_end(pos_bb)
9932
- @builder.br(merge_bb)
9933
-
9934
- @builder.position_at_end(merge_bb)
9935
- @builder.phi(LLVM::Int64, { neg_bb => normalized, pos_bb => index_i64 })
9914
+ @builder.select(is_negative, normalized, index_i64, "norm_sidx")
9936
9915
  end
9937
9916
 
9938
9917
  # Generate NativeArray length access: arr.length
@@ -9990,7 +9969,7 @@ module Konpeito
9990
9969
  @builder.position_at_end(loop_body)
9991
9970
 
9992
9971
  # Get element via GEP + load (no rb_ary_entry!)
9993
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
9972
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
9994
9973
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
9995
9974
 
9996
9975
  # Set up block parameter (unboxed!)
@@ -10040,7 +10019,7 @@ module Konpeito
10040
10019
  convert_value(val, val_type, type_tag)
10041
10020
  else
10042
10021
  # Use first element as initial, start from index 1
10043
- 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")
10044
10023
  @builder.load2(llvm_elem_type, elem_ptr, "first_elem")
10045
10024
  end
10046
10025
 
@@ -10072,7 +10051,7 @@ module Konpeito
10072
10051
 
10073
10052
  # Load accumulator and element (both unboxed)
10074
10053
  acc_value = @builder.load2(llvm_elem_type, acc_alloca, "acc")
10075
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10054
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10076
10055
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10077
10056
 
10078
10057
  # Set up block parameters
@@ -10172,7 +10151,7 @@ module Konpeito
10172
10151
  @builder.position_at_end(loop_body)
10173
10152
 
10174
10153
  # Load element (unboxed)
10175
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10154
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10176
10155
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10177
10156
 
10178
10157
  # Set up block parameter
@@ -10255,7 +10234,7 @@ module Konpeito
10255
10234
  @builder.position_at_end(loop_body)
10256
10235
 
10257
10236
  # Load element (unboxed)
10258
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10237
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10259
10238
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10260
10239
 
10261
10240
  # Set up block parameter
@@ -10353,7 +10332,7 @@ module Konpeito
10353
10332
  @builder.position_at_end(loop_body)
10354
10333
 
10355
10334
  # Load element and store to alloca for later use
10356
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10335
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10357
10336
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10358
10337
  @builder.store(elem_value, elem_alloca)
10359
10338
 
@@ -10445,7 +10424,7 @@ module Konpeito
10445
10424
  @builder.position_at_end(loop_body)
10446
10425
 
10447
10426
  # Load element
10448
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10427
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10449
10428
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10450
10429
 
10451
10430
  # Set up block parameter
@@ -10553,7 +10532,7 @@ module Konpeito
10553
10532
 
10554
10533
  # Load accumulator and element
10555
10534
  acc_value = @builder.load2(llvm_elem_type, acc_alloca, "acc")
10556
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10535
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10557
10536
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10558
10537
 
10559
10538
  # Add (unboxed)
@@ -10614,7 +10593,7 @@ module Konpeito
10614
10593
  # Non-empty: initialize with first element
10615
10594
  @builder.position_at_end(non_empty_block)
10616
10595
  result_alloca = @builder.alloca(llvm_elem_type, "na_minmax_result")
10617
- 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")
10618
10597
  first_elem = @builder.load2(llvm_elem_type, first_ptr, "first")
10619
10598
  @builder.store(first_elem, result_alloca)
10620
10599
 
@@ -10634,7 +10613,7 @@ module Konpeito
10634
10613
 
10635
10614
  # Load current result and element
10636
10615
  current_result = @builder.load2(llvm_elem_type, result_alloca, "current")
10637
- elem_ptr = @builder.gep(array_ptr, [current_idx], "elem_ptr")
10616
+ elem_ptr = @builder.gep2(llvm_elem_type, array_ptr, [current_idx], "elem_ptr")
10638
10617
  elem_value = @builder.load2(llvm_elem_type, elem_ptr, "elem")
10639
10618
 
10640
10619
  # Compare and select
@@ -10704,11 +10683,16 @@ module Konpeito
10704
10683
  # Get the array pointer from a receiver HIR value
10705
10684
  def get_native_array_ptr(receiver)
10706
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")
10707
10691
  when HIR::Instruction
10708
10692
  if receiver.result_var
10709
10693
  @variables[receiver.result_var]
10710
10694
  else
10711
- raise "NativeArray receiver has no result_var"
10695
+ raise "NativeArray receiver has no result_var: #{receiver.class}"
10712
10696
  end
10713
10697
  when String
10714
10698
  @variables[receiver]
@@ -10720,6 +10704,8 @@ module Konpeito
10720
10704
  # Get the variable name from a receiver for length lookup
10721
10705
  def get_receiver_var_name(receiver)
10722
10706
  case receiver
10707
+ when HIR::LoadLocal
10708
+ receiver.var.name
10723
10709
  when HIR::Instruction
10724
10710
  receiver.result_var
10725
10711
  when String
@@ -10729,6 +10715,62 @@ module Konpeito
10729
10715
  end
10730
10716
  end
10731
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
+
10732
10774
  # Allocate a NativeArray on the stack
10733
10775
  # Returns a pointer to contiguous memory + stores length
10734
10776
  def generate_native_array_alloc(inst)
@@ -10778,8 +10820,8 @@ module Konpeito
10778
10820
  index_value, index_type = get_value_with_type(inst.index)
10779
10821
  index_i64 = index_type == :i64 ? index_value : @builder.call(@rb_num2long, index_value)
10780
10822
 
10781
- # GEP to get element pointer
10782
- 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")
10783
10825
 
10784
10826
  if native_array_has_class_element?(element_type)
10785
10827
  # For NativeClass elements, return pointer to struct (for field access)
@@ -10820,8 +10862,9 @@ module Konpeito
10820
10862
  target_type = element_type == :Int64 ? :i64 : :double
10821
10863
  converted_value = convert_value(store_value, value_type, target_type)
10822
10864
 
10823
- # GEP to get element pointer
10824
- 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")
10825
10868
 
10826
10869
  # Store element
10827
10870
  @builder.store(converted_value, elem_ptr)
@@ -12919,8 +12962,8 @@ module Konpeito
12919
12962
  # Get JSON element at index
12920
12963
  elem_val = @builder.call(@yyjson_arr_get, root, idx, "json_elem")
12921
12964
 
12922
- # Get pointer to NativeClass struct in the array
12923
- 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")
12924
12967
 
12925
12968
  # Parse fields from JSON object into struct
12926
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") }
@@ -1139,7 +1171,9 @@ module Konpeito
1139
1171
  # Known framework dependencies for popular libraries
1140
1172
  case lib_name
1141
1173
  when "raylib"
1142
- ["-framework", "IOKit", "-framework", "Cocoa", "-framework", "OpenGL"]
1174
+ ["-framework", "IOKit", "-framework", "Cocoa", "-framework", "OpenGL",
1175
+ "-framework", "CoreAudio", "-framework", "AudioToolbox",
1176
+ "-framework", "CoreFoundation"]
1143
1177
  when "SDL2", "sdl2"
1144
1178
  ["-framework", "IOKit", "-framework", "Cocoa", "-framework", "Carbon",
1145
1179
  "-framework", "CoreAudio", "-framework", "AudioToolbox",
@@ -1160,8 +1194,41 @@ module Konpeito
1160
1194
  end
1161
1195
  end
1162
1196
 
1197
+ # Compile vendored clay.h implementation if Clay stdlib is used
1198
+ # Returns array of object file paths
1199
+ def ensure_clay_compiled
1200
+ clay_used = @extra_c_files.any? { |f| File.basename(f).include?("clay") }
1201
+ return [] unless clay_used
1202
+
1203
+ clay_dir = File.expand_path("../../../vendor/clay", __dir__)
1204
+ clay_impl_c = File.join(clay_dir, "clay_impl.c")
1205
+ clay_impl_obj = File.join(clay_dir, "clay_impl.o")
1206
+
1207
+ return [] unless File.exist?(clay_impl_c)
1208
+
1209
+ cc, cc_flags = cross_cc_with_flags
1210
+ cflags = cross_compiling? ? Platform.cross_mruby_cflags(@cross_mruby_dir) : (Platform.mruby_cflags rescue "-O2")
1211
+
1212
+ # Compile clay_impl.c (only if stale)
1213
+ unless File.exist?(clay_impl_obj) && File.mtime(clay_impl_obj) > File.mtime(clay_impl_c)
1214
+ cmd = [*cc, "-c", "-O2"]
1215
+ cmd.concat(cc_flags)
1216
+ cmd += ["-o", clay_impl_obj, clay_impl_c]
1217
+ system(*cmd) or return []
1218
+ end
1219
+
1220
+ [clay_impl_obj]
1221
+ end
1222
+
1163
1223
  def ffi_include_flags
1164
1224
  flags = []
1225
+
1226
+ # Always add vendored Clay include path if Clay stdlib is used
1227
+ clay_dir = File.expand_path("../../../vendor/clay", __dir__)
1228
+ if @extra_c_files.any? { |f| File.basename(f).include?("clay") } && Dir.exist?(clay_dir)
1229
+ flags << "-I#{clay_dir}"
1230
+ end
1231
+
1165
1232
  if cross_compiling?
1166
1233
  # When cross-compiling, use cross library include paths
1167
1234
  flags << "-I#{@cross_libs_dir}/../include" if @cross_libs_dir && Dir.exist?("#{@cross_libs_dir}/../include")
@@ -652,7 +652,8 @@ module Konpeito
652
652
 
653
653
  # Map of stdlib modules: module name pattern => stdlib directory name
654
654
  STDLIB_MODULE_MAP = {
655
- "Raylib" => "raylib"
655
+ "Raylib" => "raylib",
656
+ "Clay" => "clay"
656
657
  }.freeze
657
658
 
658
659
  # Scan AST for known stdlib module references and auto-add their RBS paths
@@ -694,6 +695,7 @@ module Konpeito
694
695
  end
695
696
 
696
697
  # Auto-include stdlib C files based on FFI libraries detected by RBS loader
698
+ # and vendored stdlibs detected by module reference
697
699
  def inject_stdlib_c_files(extra_c_files)
698
700
  return extra_c_files unless @rbs_loader
699
701
 
@@ -716,6 +718,21 @@ module Konpeito
716
718
  end
717
719
  end
718
720
 
721
+ # Also include vendored stdlib C files detected by module reference
722
+ # (for stdlibs without %a{ffi} that use vendored C libraries)
723
+ @rbs_paths.each do |rbs_path|
724
+ STDLIB_MODULE_MAP.each_value do |stdlib_name|
725
+ stdlib_rbs = File.expand_path("stdlib/#{stdlib_name}/#{stdlib_name}.rbs", __dir__)
726
+ next unless rbs_path == stdlib_rbs
727
+
728
+ stdlib_c = File.expand_path("stdlib/#{stdlib_name}/#{stdlib_name}_native.c", __dir__)
729
+ if File.exist?(stdlib_c) && !extra_c_files.include?(stdlib_c)
730
+ extra_c_files << stdlib_c
731
+ log "Auto-including stdlib C wrapper: #{stdlib_name}" if verbose
732
+ end
733
+ end
734
+ end
735
+
719
736
  extra_c_files
720
737
  end
721
738
  end