carray 3.0.0 → 3.0.1

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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +325 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +32 -32
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_group.c +55 -19
  8. data/ext/ca_binop_dispatch.c +5 -6
  9. data/ext/ca_binop_dispatch.h +0 -7
  10. data/ext/ca_categorical_iterator.c +13 -13
  11. data/ext/ca_for_each_element.h +6 -8
  12. data/ext/ca_group_iter.c +1 -1
  13. data/ext/ca_kernel_iterator.c +69 -23
  14. data/ext/ca_kernel_iterator.h +3 -10
  15. data/ext/ca_obj_bincmp.c +57 -27
  16. data/ext/ca_obj_binop.c +86 -30
  17. data/ext/ca_obj_const_string.c +3 -1
  18. data/ext/ca_obj_face.c +80 -0
  19. data/ext/ca_obj_face.h +33 -1
  20. data/ext/ca_obj_fake.c +11 -0
  21. data/ext/ca_obj_fixlen_string.c +3 -1
  22. data/ext/ca_obj_grid.c +11 -3
  23. data/ext/ca_obj_meld.c +13 -8
  24. data/ext/ca_obj_moncmp.c +33 -14
  25. data/ext/ca_obj_monop.c +19 -7
  26. data/ext/ca_obj_object.c +99 -4
  27. data/ext/ca_obj_record.c +3 -1
  28. data/ext/ca_obj_refer.c +30 -6
  29. data/ext/ca_obj_roll.c +13 -5
  30. data/ext/ca_obj_select_axis.c +16 -6
  31. data/ext/ca_obj_shift.c +3 -3
  32. data/ext/ca_obj_stride.c +106 -23
  33. data/ext/ca_obj_string.c +3 -1
  34. data/ext/ca_obj_tile.c +13 -5
  35. data/ext/ca_obj_time.c +3 -1
  36. data/ext/ca_obj_timedelta.c +3 -1
  37. data/ext/ca_obj_transpose.c +1 -1
  38. data/ext/ca_obj_triop.c +62 -23
  39. data/ext/ca_obj_window.c +86 -17
  40. data/ext/ca_op_cmplx64.h +123 -0
  41. data/ext/ca_op_ipower.c +0 -3
  42. data/ext/ca_sort_kernels.h +5 -5
  43. data/ext/ca_sweep_engine.c +78 -35
  44. data/ext/ca_sweep_engine.h +13 -4
  45. data/ext/ca_transform_common.c +7 -0
  46. data/ext/carray.h +127 -130
  47. data/ext/carray_access.c +56 -34
  48. data/ext/carray_bincount.c +8 -8
  49. data/ext/carray_broadcast.c +99 -6
  50. data/ext/carray_build_flags.h +3 -0
  51. data/ext/carray_call_cfunc.c +1491 -0
  52. data/ext/carray_call_cfunc.h +153 -0
  53. data/ext/carray_cast.c +69 -31
  54. data/ext/carray_conversion.c +22 -22
  55. data/ext/carray_copy.c +12 -2
  56. data/ext/carray_core.c +57 -10
  57. data/ext/carray_count.c +1 -1
  58. data/ext/carray_element.c +7 -7
  59. data/ext/carray_factorize.c +28 -28
  60. data/ext/carray_hold.c +1 -1
  61. data/ext/carray_index_classifier.c +7 -21
  62. data/ext/carray_internal.h +19 -3
  63. data/ext/carray_kernels_bincmp.c +1 -0
  64. data/ext/carray_kernels_binop.c +320 -298
  65. data/ext/carray_kernels_init.c +1095 -0
  66. data/ext/carray_kernels_map.c +1 -0
  67. data/ext/carray_kernels_moncmp.c +1 -0
  68. data/ext/carray_kernels_monop.c +412 -411
  69. data/ext/carray_kernels_reduce_aggregate.c +88 -87
  70. data/ext/carray_kernels_reduce_boolean.c +1 -0
  71. data/ext/carray_kernels_reduce_cumulative.c +54 -53
  72. data/ext/carray_kernels_reduce_extreme.c +51 -50
  73. data/ext/carray_kernels_reduce_variance.c +1 -0
  74. data/ext/carray_kernels_scan.c +1 -0
  75. data/ext/carray_kernels_search.c +93 -92
  76. data/ext/carray_kernels_sort.c +1 -0
  77. data/ext/carray_kernels_triop.c +1 -0
  78. data/ext/carray_lazy.c +175 -5
  79. data/ext/carray_mask.c +6 -5
  80. data/ext/carray_median_percentile.c +1 -1
  81. data/ext/carray_memory_view.c +46 -21
  82. data/ext/carray_operator.c +46 -49
  83. data/ext/carray_partition.c +4 -4
  84. data/ext/carray_random.c +7 -7
  85. data/ext/carray_scatter.c +1 -1
  86. data/ext/carray_sort.c +3 -3
  87. data/ext/carray_sort_kernel.c +10 -10
  88. data/ext/carray_test.c +1 -1
  89. data/ext/extconf.rb +18 -0
  90. data/ext/mk_call_cfunc.rb +243 -52
  91. data/ext/mkkernel.rb +354 -100
  92. data/ext/ruby_carray.c +18 -16
  93. data/ext/version.h +4 -4
  94. data/lib/carray/autoload_carray.rb +19 -0
  95. data/lib/carray/autoload_method_extension.rb +2 -1
  96. data/lib/carray/axis_group.rb +7 -7
  97. data/lib/carray/basics.rb +59 -59
  98. data/lib/carray/bincount_nd.rb +12 -12
  99. data/lib/carray/block_iterator.rb +24 -14
  100. data/lib/carray/categorical.rb +4 -4
  101. data/lib/carray/categorical_iterator.rb +98 -76
  102. data/lib/carray/conditional.rb +14 -14
  103. data/lib/carray/construct.rb +21 -0
  104. data/lib/carray/core_extensions.rb +62 -5
  105. data/lib/carray/data_type_extension.rb +18 -1
  106. data/lib/carray/frame/concat.rb +3 -3
  107. data/lib/carray/frame/convert.rb +1 -1
  108. data/lib/carray/frame/frame.rb +4 -11
  109. data/lib/carray/frame/io.rb +159 -2
  110. data/lib/carray/frame/sort.rb +1 -1
  111. data/lib/carray/frame/verbs.rb +18 -1
  112. data/lib/carray/fuse_source.rb +123 -0
  113. data/lib/carray/fusion.rb +218 -0
  114. data/lib/carray/histogram.rb +16 -16
  115. data/lib/carray/inspect.rb +1 -7
  116. data/lib/carray/iterator.rb +4 -3
  117. data/lib/carray/lazy.rb +125 -73
  118. data/lib/carray/meld_reduce.rb +2 -2
  119. data/lib/carray/methods/align_addr.rb +1 -1
  120. data/lib/carray/methods/composition.rb +1 -1
  121. data/lib/carray/methods/is_in.rb +12 -12
  122. data/lib/carray/methods/locate_addr.rb +6 -1
  123. data/lib/carray/methods/mask_duplicates.rb +1 -1
  124. data/lib/carray/methods/meshgrid.rb +4 -5
  125. data/lib/carray/methods/mode.rb +2 -2
  126. data/lib/carray/methods/nunique.rb +1 -1
  127. data/lib/carray/methods/snap.rb +7 -2
  128. data/lib/carray/methods/unique.rb +3 -3
  129. data/lib/carray/methods/value_counts.rb +2 -2
  130. data/lib/carray/runtime.rb +0 -19
  131. data/lib/carray/slab_iterator.rb +20 -7
  132. data/lib/carray/string_operation_extension.rb +5 -5
  133. data/lib/carray/time.rb +890 -491
  134. data/lib/carray/window_iterator.rb +285 -13
  135. data/lib/carray.rb +5 -5
  136. data/yard-stubs/ca_obj_array.rb +385 -0
  137. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  138. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  139. data/yard-stubs/ca_obj_block.rb +73 -0
  140. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  141. data/yard-stubs/ca_obj_fake.rb +31 -0
  142. data/yard-stubs/ca_obj_farray.rb +32 -0
  143. data/yard-stubs/ca_obj_field.rb +45 -0
  144. data/yard-stubs/ca_obj_grid.rb +35 -0
  145. data/yard-stubs/ca_obj_refer.rb +72 -0
  146. data/yard-stubs/ca_obj_roll.rb +45 -0
  147. data/yard-stubs/ca_obj_shift.rb +43 -0
  148. data/yard-stubs/ca_obj_stride.rb +181 -0
  149. data/yard-stubs/ca_obj_tile.rb +29 -0
  150. data/yard-stubs/ca_obj_transpose.rb +40 -0
  151. data/yard-stubs/ca_obj_window.rb +49 -0
  152. data/yard-stubs/carray_access.rb +131 -0
  153. data/yard-stubs/carray_attribute.rb +246 -0
  154. data/yard-stubs/carray_broadcast.rb +37 -0
  155. data/yard-stubs/carray_cast.rb +489 -0
  156. data/yard-stubs/carray_class.rb +65 -0
  157. data/yard-stubs/carray_conversion.rb +76 -0
  158. data/yard-stubs/carray_copy.rb +79 -0
  159. data/yard-stubs/carray_core.rb +114 -0
  160. data/yard-stubs/carray_count.rb +79 -0
  161. data/yard-stubs/carray_element.rb +108 -0
  162. data/yard-stubs/carray_generate.rb +66 -0
  163. data/yard-stubs/carray_lazy.rb +23 -0
  164. data/yard-stubs/carray_loop.rb +140 -0
  165. data/yard-stubs/carray_mask.rb +259 -0
  166. data/yard-stubs/carray_math.rb +132 -0
  167. data/yard-stubs/carray_mathfunc.rb +45 -0
  168. data/yard-stubs/carray_median_percentile.rb +89 -0
  169. data/yard-stubs/carray_memory_view.rb +163 -0
  170. data/yard-stubs/carray_order.rb +312 -0
  171. data/yard-stubs/carray_random.rb +89 -0
  172. data/yard-stubs/carray_scatter.rb +106 -0
  173. data/yard-stubs/carray_slab.rb +57 -0
  174. data/yard-stubs/carray_sort.rb +163 -0
  175. data/yard-stubs/carray_test.rb +85 -0
  176. data/yard-stubs/carray_undef.rb +64 -0
  177. data/yard-stubs/carray_utils.rb +97 -0
  178. data/yard-stubs/ruby_carray.rb +193 -0
  179. metadata +59 -10
  180. data/ext/ca_obj_unbound_repeat.c +0 -496
data/ext/mk_call_cfunc.rb CHANGED
@@ -32,6 +32,7 @@
32
32
  # --- spec -------------------------------------------------------------------
33
33
 
34
34
  RAW_ARITIES = (1..7).to_a # ca_call_cfunc_1 .. _7
35
+ SLAB_ARITIES = (1..7).to_a # ca_call_cslab_1 .. _7
35
36
  TYPED_PAIRS = [ # ca_call_cfunc_<m>_<n> (m outputs, n inputs)
36
37
  [1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6],
37
38
  [2, 1], [2, 2], [2, 3], [2, 4],
@@ -97,6 +98,28 @@ def sig_typed_r(m, n)
97
98
  "VALUE\nca_call_cfunc_#{m}_#{n}_r (#{typed_params(m, n, r: true)})"
98
99
  end
99
100
 
101
+ # The typed slab family declares its callback as ca_cslab_t rather than an
102
+ # arity-shaped function pointer: a slab callback takes its operands through
103
+ # `base`, so its signature does not grow with M + N.
104
+ def typed_slab_params(m, n, r: false)
105
+ dty = ->(k) { m == 1 ? "dty" : "dty#{k + 1}" }
106
+ out_data_type_params = (0...m).map { |k| "int8_t #{dty.(k)}" }
107
+ in_data_type_params = (0...n).map { |k| "int8_t dtx#{k + 1}" }
108
+ in_value_params = (0...n).map { |k| "volatile VALUE rx#{k + 1}" }
109
+ func_sig = r ? "ca_cslab_r_t slabfunc" : "ca_cslab_t slabfunc"
110
+ params = out_data_type_params + in_data_type_params + [func_sig] + in_value_params
111
+ params << "void *userdata" if r
112
+ params.join(", ")
113
+ end
114
+
115
+ def sig_typed_slab(m, n)
116
+ "VALUE\nca_call_cslab_#{m}_#{n} (#{typed_slab_params(m, n)})"
117
+ end
118
+
119
+ def sig_typed_slab_r(m, n)
120
+ "VALUE\nca_call_cslab_#{m}_#{n}_r (#{typed_slab_params(m, n, r: true)})"
121
+ end
122
+
100
123
  # Re-indent each non-blank line of `s` with `prefix`. `<<~` strips the
101
124
  # common leading whitespace from a heredoc, which would leave our body
102
125
  # at column 0; we want a 2-space body indent inside the function brace.
@@ -242,21 +265,22 @@ def emit_raw_r(n)
242
265
  END_C
243
266
  end
244
267
 
245
- # --- typed ca_call_cfunc_M_N ------------------------------------------------
246
-
247
- def emit_typed(m, n)
248
- total = m + n
249
- # Naming convention (matches legacy ext/carray_call_cfunc.c byte-for-byte):
250
- # M==1 : output data_type = `dty`, output var = `ry`
251
- # M >1 : output data_types = `dty1..dtyM`, output vars = `ry1..ryM`
268
+ # --- typed dispatchers: M outputs, N inputs ---------------------------------
269
+ #
270
+ # The four typed emitters (cfunc / cfunc_r / cslab / cslab_r) differ in one
271
+ # line -- which lower-level family they delegate to -- and are identical in
272
+ # the wrapping, template allocation and return handling around it. That
273
+ # shared part lives in emit_typed_body so a change to the coercion rules is
274
+ # made once rather than four times, which is the same reason this file is a
275
+ # generator at all.
276
+ #
277
+ # Naming convention (matches legacy ext/carray_call_cfunc.c byte-for-byte):
278
+ # M==1 : output data_type = `dty`, output var = `ry`
279
+ # M >1 : output data_types = `dty1..dtyM`, output vars = `ry1..ryM`
280
+ def emit_typed_body(m, n)
252
281
  dty = ->(k) { m == 1 ? "dty" : "dty#{k + 1}" }
253
282
  ry = ->(k) { m == 1 ? "ry" : "ry#{k + 1}" }
254
283
 
255
- fsync_str = "1" * m + "0" * n
256
-
257
- $src.puts sig_typed(m, n)
258
- $src.puts "{"
259
-
260
284
  # output VALUE declarations
261
285
  out_decl = (0...m).map { |k| "#{ry.(k)} = Qnil" }.join(", ")
262
286
  $src.puts " volatile VALUE #{out_decl};"
@@ -284,9 +308,9 @@ def emit_typed(m, n)
284
308
  end
285
309
  $src.puts ""
286
310
 
287
- # delegate to lower-level ca_call_cfunc_(m+n)
311
+ # delegate to the lower-level family
288
312
  args = (0...m).map { |k| ry.(k) } + (0...n).map { |k| "rx#{k + 1}" }
289
- $src.puts " ca_call_cfunc_#{total}(mathfunc, \"#{fsync_str}\", #{args.join(", ")});"
313
+ yield args.join(", ")
290
314
  $src.puts ""
291
315
 
292
316
  # return: scalar-fetch each output if rank-0, then assemble
@@ -305,62 +329,162 @@ def emit_typed(m, n)
305
329
  $src.puts ""
306
330
  end
307
331
 
332
+ def fsync_literal(m, n)
333
+ "1" * m + "0" * n
334
+ end
335
+
336
+ def emit_typed(m, n)
337
+ $src.puts sig_typed(m, n)
338
+ $src.puts "{"
339
+ emit_typed_body(m, n) do |args|
340
+ $src.puts " ca_call_cfunc_#{m + n}(mathfunc, \"#{fsync_literal(m, n)}\", #{args});"
341
+ end
342
+ end
343
+
308
344
  # --- typed ca_call_cfunc_M_N_r (reentrant variant) -------------------------
309
345
  #
310
346
  # Same as emit_typed but the callback signature and outer function gain a
311
347
  # trailing `void *userdata`; the inner delegate calls ca_call_cfunc_(M+N)_r
312
348
  # and forwards `userdata` through.
313
349
  def emit_typed_r(m, n)
314
- total = m + n
315
- dty = ->(k) { m == 1 ? "dty" : "dty#{k + 1}" }
316
- ry = ->(k) { m == 1 ? "ry" : "ry#{k + 1}" }
350
+ $src.puts sig_typed_r(m, n)
351
+ $src.puts "{"
352
+ emit_typed_body(m, n) do |args|
353
+ $src.puts " ca_call_cfunc_#{m + n}_r(mathfunc, \"#{fsync_literal(m, n)}\", #{args}, userdata);"
354
+ end
355
+ end
317
356
 
318
- fsync_str = "1" * m + "0" * n
357
+ # --- typed ca_call_cslab_M_N (chunked variant) -----------------------------
358
+ #
359
+ # The same convenience over the slab family: the caller declares the data
360
+ # types its callback works in and gets back a freshly allocated output,
361
+ # rather than allocating one and matching dtypes by hand.
362
+ #
363
+ # This is where chunking pays most, and not by coincidence. Coercion is
364
+ # what this layer is for, and rb_ca_wrap_readonly implements it as a lazy
365
+ # readonly cast view -- which is never attach-alias, so it is exactly the
366
+ # operand kind the whole-buffer path materialises with
367
+ # xmalloc(elements * bytes). Declaring CA_DOUBLE over an int32 array
368
+ # therefore costs a full converted copy through ca_call_cfunc_M_N and one
369
+ # chunk of arena scratch through ca_call_cslab_M_N. The typical use of the
370
+ # typed layer is the case chunking was built for.
371
+ def emit_typed_slab(m, n)
372
+ $src.puts sig_typed_slab(m, n)
373
+ $src.puts "{"
374
+ emit_typed_body(m, n) do |args|
375
+ $src.puts " ca_call_cslab_#{m + n}(slabfunc, \"#{fsync_literal(m, n)}\", #{args});"
376
+ end
377
+ end
319
378
 
320
- $src.puts sig_typed_r(m, n)
379
+ def emit_typed_slab_r(m, n)
380
+ $src.puts sig_typed_slab_r(m, n)
321
381
  $src.puts "{"
382
+ emit_typed_body(m, n) do |args|
383
+ $src.puts " ca_call_cslab_#{m + n}_r(slabfunc, \"#{fsync_literal(m, n)}\", #{args}, userdata);"
384
+ end
385
+ end
322
386
 
323
- out_decl = (0...m).map { |k| "#{ry.(k)} = Qnil" }.join(", ")
324
- $src.puts " volatile VALUE #{out_decl};"
325
- $src.puts ""
387
+ # --- slab ca_call_cslab_N ---------------------------------------------------
388
+ #
389
+ # The chunked counterpart of ca_call_cfunc_N. Two things differ, and they
390
+ # are the same thing seen from two sides:
391
+ #
392
+ # memory -- ca_sweep_acquire_chunked holds one arena scratch of ~32KB per
393
+ # non-alias INPUT for the whole walk and re-gathers it per chunk, where
394
+ # ca_sweep_acquire materialises each such operand whole. Peak input
395
+ # memory stops scaling with the operand.
396
+ #
397
+ # speed -- the callback is handed a whole chunk rather than one cell, so
398
+ # the indirect call is paid once per few thousand cells instead of once
399
+ # per cell. Measured on `y = a + b*2.0` over 10M doubles: 1.48 ns per
400
+ # element through a per-cell callback against 0.34 ns for the same
401
+ # arithmetic in a loop the compiler can see. A slab callback keeps the
402
+ # loop, so it keeps the 0.34, and it is the shape a vectoriser can work
403
+ # with -- a per-cell callback defeats one by construction.
404
+ #
405
+ # The callback signature does not depend on the arity, because the operands
406
+ # reach it through `base` / `stride` rather than as separate parameters.
407
+ # One typedef therefore serves every ca_call_cslab_N.
408
+ #
409
+ # Masked cells are NOT skipped for the author, as the per-cell form skips
410
+ # them: a slab has no way to leave a hole. The chunk's slice of the mask
411
+ # arrives as `m0` instead -- one byte per cell, indexed 0..n-1 alongside the
412
+ # data, or NULL when no INPUT carried a mask.
413
+ def sig_slab(n)
414
+ "VALUE\nca_call_cslab_#{n} (ca_cslab_t func, const char *fsync,\n #{value_param_list(n)})"
415
+ end
416
+
417
+ def sig_slab_r(n)
418
+ "VALUE\nca_call_cslab_#{n}_r (ca_cslab_r_t func, const char *fsync,\n #{value_param_list(n)},\n void *userdata)"
419
+ end
420
+
421
+ def emit_slab_body(n, name, call)
422
+ $src.puts indent(<<~END_C)
423
+ CArray *cx[#{n}];
424
+ char *base[#{n}];
425
+ char *base_orig[#{n}];
426
+ ca_size_t stride[#{n}];
427
+ char *owned_buf[#{n}];
428
+ int attached[#{n}];
429
+ ca_sweep_state_t state;
326
430
 
431
+ END_C
327
432
  (0...n).each do |k|
328
- $src.puts " rx#{k + 1} = rb_ca_wrap_readonly(rx#{k + 1}, INT2NUM(dtx#{k + 1}));"
433
+ $src.puts " TypedData_Get_Struct(rcx#{k}, CArray, &carray_data_type, cx[#{k}]);"
329
434
  end
330
435
  $src.puts ""
436
+ $src.puts <<~END_C
437
+ /* sweep engine, chunked path: per-operand acquire (attach for OUTPUT
438
+ and alias INPUT, arena chunk scratch for non-alias INPUT), broadcast
439
+ shape check, mask OR across INPUTs, mask propagate to OUTPUTs at
440
+ release. Lifecycle template lives in ext/ca_sweep_engine.{c,h}.
441
+ base_orig is the field the whole-buffer path leaves zero; the
442
+ chunked path walks it, so it has to be given here. */
443
+ state.n_ops = #{n};
444
+ state.fsync = fsync;
445
+ state.cx = cx;
446
+ state.base = base;
447
+ state.base_orig = base_orig;
448
+ state.stride = stride;
449
+ state.owned_buf = owned_buf;
450
+ state.attached = attached;
451
+ state.no_mask = 0;
452
+ state.src_label = "#{name}";
331
453
 
332
- (0...m).each do |out_k|
333
- cur_dty = dty.(out_k)
334
- conds = (0...n).map { |in_k| "#{cur_dty} != dtx#{in_k + 1}" }
335
- wrapped = (0...n).map { |in_k|
336
- "rb_ca_wrap_readonly(rx#{in_k + 1}, INT2NUM(#{cur_dty}))"
454
+ ca_sweep_acquire_chunked(&state);
455
+
456
+ /* outer loop: hand the author one chunk at a time. base[] is rewritten
457
+ per chunk by ca_sweep_next_chunk -- for a non-alias INPUT it points
458
+ at the arena scratch the chunk was just gathered into, which is
459
+ packed, so stride[] is the element size and the author's inner loop
460
+ sees contiguous data. */
461
+ while ( ca_sweep_next_chunk(&state) ) {
462
+ #{call};
463
+ }
464
+
465
+ ca_sweep_release_chunked(&state);
466
+
467
+ return rcx0;
337
468
  }
338
- plain = (0...n).map { |in_k| "rx#{in_k + 1}" }
339
- $src.puts " if ( #{conds.join(" || ")} ) {"
340
- $src.puts " #{ry.(out_k)} = rb_ca_template_n(#{n}, #{wrapped.join(", ")});"
341
- $src.puts " } else {"
342
- $src.puts " #{ry.(out_k)} = rb_ca_template_n(#{n}, #{plain.join(", ")});"
343
- $src.puts " }"
344
- end
345
- $src.puts ""
346
469
 
347
- args = (0...m).map { |k| ry.(k) } + (0...n).map { |k| "rx#{k + 1}" }
348
- $src.puts " ca_call_cfunc_#{total}_r(mathfunc, \"#{fsync_str}\", #{args.join(", ")}, userdata);"
349
- $src.puts ""
470
+ END_C
471
+ end
350
472
 
351
- (0...m).each do |k|
352
- $src.puts " if ( rb_ca_is_scalar(#{ry.(k)}) ) {"
353
- $src.puts " #{ry.(k)} = rb_ca_fetch_addr(#{ry.(k)}, 0);"
354
- $src.puts " }"
355
- end
356
- if m == 1
357
- $src.puts " return #{ry.(0)};"
358
- else
359
- list = (0...m).map { |k| ry.(k) }.join(", ")
360
- $src.puts " return rb_ary_new3(#{m}, #{list});"
361
- end
362
- $src.puts "}"
363
- $src.puts ""
473
+ def emit_slab(n)
474
+ $src.puts sig_slab(n)
475
+ $src.puts "{"
476
+ emit_slab_body(n, "ca_call_cslab_#{n}",
477
+ "func(base, stride, state.chunk_n, ca_sweep_chunk_mask(&state))")
478
+ end
479
+
480
+ # Variant of emit_slab with a trailing `void *userdata` parameter passed
481
+ # through to every chunk invocation as its last argument, following the same
482
+ # POSIX `_r` convention as ca_call_cfunc_N_r.
483
+ def emit_slab_r(n)
484
+ $src.puts sig_slab_r(n)
485
+ $src.puts "{"
486
+ emit_slab_body(n, "ca_call_cslab_#{n}_r",
487
+ "func(base, stride, state.chunk_n, ca_sweep_chunk_mask(&state), userdata)")
364
488
  end
365
489
 
366
490
  # --- header declaration emitters --------------------------------------------
@@ -369,6 +493,10 @@ def decl_raw(n) ; $hdr.puts sig_raw(n) + ";"; $hdr.puts ""; end
369
493
  def decl_raw_r(n) ; $hdr.puts sig_raw_r(n) + ";"; $hdr.puts ""; end
370
494
  def decl_typed(m, n) ; $hdr.puts sig_typed(m, n) + ";"; $hdr.puts ""; end
371
495
  def decl_typed_r(m, n) ; $hdr.puts sig_typed_r(m, n) + ";"; $hdr.puts ""; end
496
+ def decl_typed_slab(m, n) ; $hdr.puts sig_typed_slab(m, n) + ";"; $hdr.puts ""; end
497
+ def decl_typed_slab_r(m, n) ; $hdr.puts sig_typed_slab_r(m, n) + ";"; $hdr.puts ""; end
498
+ def decl_slab(n) ; $hdr.puts sig_slab(n) + ";"; $hdr.puts ""; end
499
+ def decl_slab_r(n) ; $hdr.puts sig_slab_r(n) + ";"; $hdr.puts ""; end
372
500
 
373
501
  # --- main -------------------------------------------------------------------
374
502
 
@@ -406,6 +534,18 @@ $src.puts <<~END_C
406
534
  * operand invariant established in PROPOSAL_EAGER_ELEMENTWISE_NO_ATTACH
407
535
  * and extended in PROPOSAL_EAGER_SLOWPATH_CHUNKING_ARENA).
408
536
  *
537
+ * Slab variants `ca_call_cslab_N` / `ca_call_cslab_N_r` take the chunked
538
+ * path instead (ca_sweep_acquire_chunked / ca_sweep_next_chunk /
539
+ * ca_sweep_release_chunked) and hand the callback a whole chunk -- base /
540
+ * stride per operand, a cell count, and the chunk's slice of the mask --
541
+ * rather than one cell. A non-alias INPUT is then re-gathered into a
542
+ * ~32KB arena scratch per chunk rather than materialised whole, so input
543
+ * memory peak stops scaling with the operand; and the indirect call is
544
+ * paid once per chunk, so the author's inner loop is one the compiler can
545
+ * vectorise. Use cfunc when the per-cell body is what you have (a math
546
+ * function to wrap); use cslab when the operand is large, virtual, or the
547
+ * body is worth vectorising.
548
+ *
409
549
  * L0.1 (PROPOSAL_L0_AUTHOR_SURFACE, 2026-06-11): the per-operand acquire
410
550
  * + broadcast check + mask OR + release lifecycle is now factored out
411
551
  * into ext/ca_sweep_engine.{c,h} (ca_sweep_acquire / ca_sweep_release). This
@@ -417,6 +557,16 @@ $src.puts <<~END_C
417
557
  #include "ca_sweep_engine.h"
418
558
  #include <string.h>
419
559
 
560
+ /* The chunk's iteration mask, or NULL when no INPUT operand carried one.
561
+ m0 is chunk-sized and re-gathered per chunk by ca_sweep_next_chunk, so
562
+ it is already the slice -- one byte per cell, indexed 0..chunk_n-1
563
+ alongside base[] and stride[]. */
564
+ static const boolean8_t *
565
+ ca_sweep_chunk_mask (ca_sweep_state_t *st)
566
+ {
567
+ return st->m0;
568
+ }
569
+
420
570
  END_C
421
571
 
422
572
  $hdr.puts <<~END_H
@@ -438,6 +588,18 @@ $hdr.puts <<~END_H
438
588
  #ifndef CARRAY_CALL_CFUNC_H
439
589
  #define CARRAY_CALL_CFUNC_H
440
590
 
591
+ /* Chunk callback for the ca_call_cslab_N family. `base` and `stride` are
592
+ one entry per operand, in the order the operands were passed; `n` is the
593
+ number of cells in this chunk; `m0` is the chunk's slice of the mask,
594
+ one byte per cell, or NULL when no INPUT carried a mask. The arity does
595
+ not appear in the signature -- the operands arrive through `base` -- so
596
+ one typedef serves every arity. */
597
+ typedef void (*ca_cslab_t) (char **base, ca_size_t *stride, ca_size_t n,
598
+ const boolean8_t *m0);
599
+
600
+ typedef void (*ca_cslab_r_t) (char **base, ca_size_t *stride, ca_size_t n,
601
+ const boolean8_t *m0, void *userdata);
602
+
441
603
  END_H
442
604
 
443
605
  RAW_ARITIES.each do |n|
@@ -474,6 +636,35 @@ TYPED_PAIRS.each do |m, n|
474
636
  decl_typed_r(m, n)
475
637
  end
476
638
 
639
+ $src.puts "/* -------------------------------------------------------------------- */"
640
+ $src.puts "/* Slab variants: the chunked counterpart of ca_call_cfunc_N. The */"
641
+ $src.puts "/* callback is handed a whole chunk (base / stride / count / mask */"
642
+ $src.puts "/* slice) rather than one cell, and non-alias INPUTs are gathered */"
643
+ $src.puts "/* into a ~32KB arena scratch per chunk instead of materialised */"
644
+ $src.puts "/* whole, so input memory peak stops scaling with the operand. */"
645
+ $src.puts "/* -------------------------------------------------------------------- */"
646
+ $src.puts ""
647
+
648
+ SLAB_ARITIES.each do |n|
649
+ emit_slab(n)
650
+ decl_slab(n)
651
+ end
652
+
653
+ SLAB_ARITIES.each do |n|
654
+ emit_slab_r(n)
655
+ decl_slab_r(n)
656
+ end
657
+
658
+ TYPED_PAIRS.each do |m, n|
659
+ emit_typed_slab(m, n)
660
+ decl_typed_slab(m, n)
661
+ end
662
+
663
+ TYPED_PAIRS.each do |m, n|
664
+ emit_typed_slab_r(m, n)
665
+ decl_typed_slab_r(m, n)
666
+ end
667
+
477
668
  $hdr.puts "#endif /* CARRAY_CALL_CFUNC_H */"
478
669
 
479
670
  $src.close