carray 2.0.0 → 3.0.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/.yardopts +5 -25
- data/CHANGELOG.md +16 -0
- data/LICENSE +1 -1
- data/NEWS.md +3 -0
- data/README.md +128 -44
- data/carray.gemspec +22 -24
- data/ext/ca_array_pool.c +91 -0
- data/ext/ca_axis_descriptor.h +186 -0
- data/ext/ca_axis_dispatch.c +924 -0
- data/ext/ca_axis_group.c +1208 -0
- data/ext/ca_bincmp_dispatch.c +76 -0
- data/ext/ca_bincmp_dispatch.h +85 -0
- data/ext/ca_binop_dispatch.c +125 -0
- data/ext/ca_binop_dispatch.h +159 -0
- data/ext/ca_categorical_iterator.c +1375 -0
- data/ext/ca_compare.c +94 -0
- data/ext/ca_compare.h +26 -0
- data/ext/ca_composite_dispatch.c +414 -0
- data/ext/ca_composite_dispatch.h +116 -0
- data/ext/ca_for_buffer.h +96 -0
- data/ext/ca_for_each_element.h +241 -0
- data/ext/ca_group_iter.c +304 -0
- data/ext/ca_iter_substrate.h +325 -0
- data/ext/ca_kernel_iterator.c +4321 -0
- data/ext/ca_kernel_iterator.h +2603 -0
- data/ext/ca_moncmp_dispatch.c +37 -0
- data/ext/ca_moncmp_dispatch.h +62 -0
- data/ext/ca_monop_dispatch.c +200 -0
- data/ext/ca_monop_dispatch.h +235 -0
- data/ext/ca_obj_array.c +355 -359
- data/ext/ca_obj_bincmp.c +809 -0
- data/ext/ca_obj_binop.c +892 -0
- data/ext/ca_obj_bitarray.c +369 -164
- data/ext/ca_obj_bitfield.c +294 -234
- data/ext/ca_obj_block.c +189 -711
- data/ext/ca_obj_byte_swap.c +766 -0
- data/ext/ca_obj_const_string.c +965 -0
- data/ext/ca_obj_face.c +670 -0
- data/ext/ca_obj_face.h +247 -0
- data/ext/ca_obj_fake.c +228 -100
- data/ext/ca_obj_farray.c +54 -441
- data/ext/ca_obj_field.c +82 -529
- data/ext/ca_obj_fixlen_string.c +306 -0
- data/ext/ca_obj_grid.c +858 -440
- data/ext/ca_obj_meld.c +1034 -0
- data/ext/ca_obj_moncmp.c +569 -0
- data/ext/ca_obj_monop.c +1111 -0
- data/ext/ca_obj_object.c +774 -298
- data/ext/ca_obj_record.c +468 -0
- data/ext/ca_obj_reduce.c +97 -82
- data/ext/ca_obj_refer.c +569 -459
- data/ext/ca_obj_remap.c +475 -0
- data/ext/ca_obj_repeat.c +92 -477
- data/ext/ca_obj_roll.c +616 -0
- data/ext/ca_obj_select.c +344 -296
- data/ext/ca_obj_select_axis.c +1296 -0
- data/ext/ca_obj_shift.c +230 -792
- data/ext/ca_obj_source.c +78 -0
- data/ext/ca_obj_stack.c +1173 -0
- data/ext/ca_obj_stride.c +2501 -0
- data/ext/ca_obj_string.c +268 -0
- data/ext/ca_obj_tile.c +614 -0
- data/ext/ca_obj_time.c +546 -0
- data/ext/ca_obj_timedelta.c +435 -0
- data/ext/ca_obj_transpose.c +62 -516
- data/ext/ca_obj_triop.c +746 -0
- data/ext/ca_obj_unbound_repeat.c +208 -241
- data/ext/ca_obj_window.c +1131 -563
- data/ext/ca_op_byte_swap.c +175 -0
- data/ext/ca_op_ipower.c +319 -0
- data/ext/ca_op_powi.h +88 -0
- data/ext/ca_sort_kernels.h +132 -0
- data/ext/ca_sweep_engine.c +430 -0
- data/ext/ca_sweep_engine.h +157 -0
- data/ext/ca_transform_common.c +228 -0
- data/ext/ca_triop_dispatch.c +55 -0
- data/ext/ca_triop_dispatch.h +62 -0
- data/ext/carray.h +795 -402
- data/ext/carray_access.c +831 -711
- data/ext/carray_attribute.c +98 -330
- data/ext/carray_bincount.c +255 -0
- data/ext/carray_broadcast.c +283 -0
- data/ext/carray_call_cfunc.c +1360 -828
- data/ext/carray_call_cfunc.h +160 -0
- data/ext/carray_cast.c +1212 -301
- data/ext/carray_cast_func.rb +81 -40
- data/ext/carray_class.c +53 -63
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +350 -346
- data/ext/carray_copy.c +156 -268
- data/ext/carray_core.c +1342 -199
- data/ext/carray_count.c +312 -0
- data/ext/carray_data_type.c +43 -19
- data/ext/carray_element.c +585 -213
- data/ext/carray_factorize.c +2542 -0
- data/ext/carray_generate.c +230 -559
- data/ext/carray_histogram.c +490 -0
- data/ext/carray_hold.c +228 -0
- data/ext/carray_index_classifier.c +1035 -0
- data/ext/carray_index_classifier.h +27 -0
- data/ext/carray_internal.h +120 -0
- data/ext/carray_kernels_bincmp.c +4445 -0
- data/ext/carray_kernels_binop.c +10979 -0
- data/ext/carray_kernels_init.c +36 -0
- data/ext/carray_kernels_map.c +3466 -0
- data/ext/carray_kernels_moncmp.c +2096 -0
- data/ext/carray_kernels_monop.c +18312 -0
- data/ext/carray_kernels_reduce_aggregate.c +25836 -0
- data/ext/carray_kernels_reduce_boolean.c +329 -0
- data/ext/carray_kernels_reduce_cumulative.c +14592 -0
- data/ext/carray_kernels_reduce_extreme.c +16947 -0
- data/ext/carray_kernels_reduce_variance.c +3909 -0
- data/ext/carray_kernels_scan.c +3692 -0
- data/ext/carray_kernels_search.c +32137 -0
- data/ext/carray_kernels_sort.c +10625 -0
- data/ext/carray_kernels_triop.c +1391 -0
- data/ext/carray_lazy.c +567 -0
- data/ext/carray_loop.c +88 -200
- data/ext/carray_mask.c +848 -154
- data/ext/carray_math_kernel.h +120 -0
- data/ext/carray_mathfunc.c +10 -241
- data/ext/carray_median_percentile.c +1257 -0
- data/ext/carray_memory_view.c +1625 -0
- data/ext/carray_operator.c +1526 -318
- data/ext/carray_order.c +664 -1394
- data/ext/carray_partition.c +416 -0
- data/ext/carray_random.c +518 -0
- data/ext/carray_scatter.c +357 -0
- data/ext/carray_slab.c +1219 -0
- data/ext/carray_slab.h +84 -0
- data/ext/carray_sort.c +829 -0
- data/ext/carray_sort_kernel.c +620 -0
- data/ext/carray_struct.c +695 -0
- data/ext/carray_test.c +343 -229
- data/ext/carray_undef.c +34 -17
- data/ext/carray_utils.c +175 -74
- data/ext/extconf.rb +216 -55
- data/ext/mk_call_cfunc.rb +480 -0
- data/ext/mkkernel.rb +8842 -0
- data/ext/ruby_carray.c +202 -101
- data/ext/version.h +4 -14
- data/ext/version.rb +5 -13
- data/lib/carray/arrow_tensor.rb +401 -0
- data/lib/carray/attribute.rb +166 -0
- data/lib/carray/autoload_carray.rb +220 -0
- data/lib/carray/autoload_method_extension.rb +44 -0
- data/lib/carray/axis_group.rb +711 -0
- data/lib/carray/basics.rb +481 -0
- data/lib/carray/bincount_nd.rb +358 -0
- data/lib/carray/block_iterator.rb +604 -0
- data/lib/carray/boolean_reduce.rb +109 -0
- data/lib/carray/categorical.rb +561 -0
- data/lib/carray/categorical_iterator.rb +1062 -0
- data/lib/carray/complex.rb +150 -0
- data/lib/carray/conditional.rb +216 -0
- data/lib/carray/const_string.rb +228 -0
- data/lib/carray/construct.rb +139 -328
- data/lib/carray/core_extensions.rb +240 -0
- data/lib/carray/data_type_extension.rb +233 -0
- data/lib/carray/fixlen_string.rb +95 -0
- data/lib/carray/frame/concat.rb +132 -0
- data/lib/carray/frame/convert.rb +95 -0
- data/lib/carray/frame/csv_parser.rb +211 -0
- data/lib/carray/frame/frame.rb +649 -0
- data/lib/carray/frame/group.rb +186 -0
- data/lib/carray/frame/io.rb +164 -0
- data/lib/carray/frame/join.rb +248 -0
- data/lib/carray/frame/records.rb +99 -0
- data/lib/carray/frame/sort.rb +113 -0
- data/lib/carray/frame/verbs.rb +299 -0
- data/lib/carray/frame.rb +16 -0
- data/lib/carray/histogram.rb +512 -0
- data/lib/carray/inspect.rb +37 -20
- data/lib/carray/iterator.rb +57 -349
- data/lib/carray/lazy.rb +889 -0
- data/lib/carray/mask_gap_fill.rb +200 -0
- data/lib/carray/math.rb +78 -342
- data/lib/carray/meld_reduce.rb +289 -0
- data/lib/carray/methods/align_addr.rb +116 -0
- data/lib/carray/methods/bin.rb +128 -0
- data/lib/carray/methods/bincount.rb +87 -0
- data/lib/carray/methods/bit_string.rb +92 -0
- data/lib/carray/methods/broadcast.rb +63 -0
- data/lib/carray/methods/choose.rb +39 -0
- data/lib/carray/methods/composition.rb +280 -0
- data/lib/carray/methods/gather_nd.rb +206 -0
- data/lib/carray/methods/index.rb +39 -0
- data/lib/carray/methods/insert_block.rb +99 -0
- data/lib/carray/methods/is_in.rb +141 -0
- data/lib/carray/methods/join.rb +90 -0
- data/lib/carray/methods/locate_addr.rb +47 -0
- data/lib/carray/methods/mask_duplicates.rb +41 -0
- data/lib/carray/methods/meshgrid.rb +91 -0
- data/lib/carray/methods/mode.rb +126 -0
- data/lib/carray/methods/nunique.rb +46 -0
- data/lib/carray/methods/resize.rb +56 -0
- data/lib/carray/methods/snap.rb +156 -0
- data/lib/carray/methods/string_format.rb +57 -0
- data/lib/carray/methods/unique.rb +47 -0
- data/lib/carray/methods/value_counts.rb +71 -0
- data/lib/carray/mkmf.rb +124 -101
- data/lib/carray/runtime.rb +108 -0
- data/lib/carray/serialize.rb +478 -167
- data/lib/carray/slab_iterator.rb +292 -0
- data/lib/carray/stack.rb +291 -0
- data/lib/carray/string.rb +56 -180
- data/lib/carray/string_operation_extension.rb +289 -0
- data/lib/carray/struct.rb +335 -323
- data/lib/carray/struct_builder.rb +697 -0
- data/lib/carray/table.rb +41 -2
- data/lib/carray/time.rb +2255 -38
- data/lib/carray/window_iterator.rb +655 -0
- data/lib/carray.rb +55 -57
- metadata +163 -130
- data/Rakefile +0 -51
- data/TODO.md +0 -18
- data/ext/ca_iter_block.c +0 -257
- data/ext/ca_iter_dimension.c +0 -299
- data/ext/ca_iter_window.c +0 -214
- data/ext/ca_obj_mapping.c +0 -644
- data/ext/carray_iterator.c +0 -641
- data/ext/carray_math.rb +0 -850
- data/ext/carray_numeric.c +0 -259
- data/ext/carray_sort_addr.c +0 -254
- data/ext/carray_stat.c +0 -2100
- data/ext/carray_stat_proc.rb +0 -1999
- data/ext/mkmath.rb +0 -741
- data/ext/ruby_ccomplex.c +0 -509
- data/ext/ruby_float_func.c +0 -86
- data/lib/carray/array.rb +0 -8
- data/lib/carray/autoload/autoload_base.rb +0 -19
- data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
- data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
- data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
- data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
- data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
- data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
- data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
- data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
- data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
- data/lib/carray/autoload/autoload_gem_random.rb +0 -8
- data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
- data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
- data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
- data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
- data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
- data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
- data/lib/carray/autoload/autoload_object_link.rb +0 -1
- data/lib/carray/autoload/autoload_object_pack.rb +0 -2
- data/lib/carray/autoload.rb +0 -141
- data/lib/carray/basic.rb +0 -191
- data/lib/carray/broadcast.rb +0 -101
- data/lib/carray/compose.rb +0 -315
- data/lib/carray/convert.rb +0 -115
- data/lib/carray/info.rb +0 -110
- data/lib/carray/io/imagemagick.rb +0 -235
- data/lib/carray/mask.rb +0 -102
- data/lib/carray/math/histogram.rb +0 -177
- data/lib/carray/math/recurrence.rb +0 -93
- data/lib/carray/object/ca_obj_iterator.rb +0 -50
- data/lib/carray/object/ca_obj_link.rb +0 -50
- data/lib/carray/object/ca_obj_pack.rb +0 -99
- data/lib/carray/obsolete.rb +0 -256
- data/lib/carray/ordering.rb +0 -181
- data/lib/carray/testing.rb +0 -51
- data/lib/carray/transform.rb +0 -109
- data/misc/Methods.ja.md +0 -182
- data/misc/NOTE +0 -51
- data/spec/Classes/CABitfield_spec.rb +0 -58
- data/spec/Classes/CABlockIterator_spec.rb +0 -114
- data/spec/Classes/CABlock_spec.rb +0 -205
- data/spec/Classes/CAField_spec.rb +0 -39
- data/spec/Classes/CAGrid_spec.rb +0 -75
- data/spec/Classes/CAMap_spec.rb +0 -0
- data/spec/Classes/CAMapping_spec.rb +0 -105
- data/spec/Classes/CAObject_attribute_spec.rb +0 -33
- data/spec/Classes/CAObject_spec.rb +0 -33
- data/spec/Classes/CARefer_spec.rb +0 -93
- data/spec/Classes/CARepeat_spec.rb +0 -65
- data/spec/Classes/CASelect_spec.rb +0 -22
- data/spec/Classes/CAShift_spec.rb +0 -16
- data/spec/Classes/CAStruct_spec.rb +0 -71
- data/spec/Classes/CATranspose_spec.rb +0 -60
- data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
- data/spec/Classes/CAWindow_spec.rb +0 -54
- data/spec/Classes/CAWrap_spec.rb +0 -8
- data/spec/Classes/CArray_spec.rb +0 -184
- data/spec/Classes/CScalar_spec.rb +0 -55
- data/spec/Classes/ex1.rb +0 -46
- data/spec/Features/feature_130_spec.rb +0 -19
- data/spec/Features/feature_attributes_spec.rb +0 -280
- data/spec/Features/feature_boolean_spec.rb +0 -98
- data/spec/Features/feature_broadcast.rb +0 -116
- data/spec/Features/feature_cast_function.rb +0 -19
- data/spec/Features/feature_cast_spec.rb +0 -33
- data/spec/Features/feature_class_spec.rb +0 -84
- data/spec/Features/feature_complex_spec.rb +0 -42
- data/spec/Features/feature_composite_spec.rb +0 -124
- data/spec/Features/feature_convert_spec.rb +0 -46
- data/spec/Features/feature_copy_spec.rb +0 -123
- data/spec/Features/feature_creation_spec.rb +0 -84
- data/spec/Features/feature_element_spec.rb +0 -144
- data/spec/Features/feature_extream_spec.rb +0 -54
- data/spec/Features/feature_generate_spec.rb +0 -74
- data/spec/Features/feature_index_spec.rb +0 -69
- data/spec/Features/feature_mask_spec.rb +0 -580
- data/spec/Features/feature_math_spec.rb +0 -97
- data/spec/Features/feature_order_spec.rb +0 -146
- data/spec/Features/feature_ref_store_spec.rb +0 -209
- data/spec/Features/feature_serialization_spec.rb +0 -125
- data/spec/Features/feature_stat_spec.rb +0 -397
- data/spec/Features/feature_virtual_spec.rb +0 -48
- data/spec/Features/method_eq_spec.rb +0 -81
- data/spec/Features/method_is_nan_spec.rb +0 -12
- data/spec/Features/method_map_spec.rb +0 -54
- data/spec/Features/method_max_with.rb +0 -20
- data/spec/Features/method_min_with.rb +0 -19
- data/spec/Features/method_ne_spec.rb +0 -18
- data/spec/Features/method_project_spec.rb +0 -188
- data/spec/Features/method_ref_spec.rb +0 -27
- data/spec/Features/method_round_spec.rb +0 -11
- data/spec/Features/method_s_linspace_spec.rb +0 -48
- data/spec/Features/method_s_span_spec.rb +0 -14
- data/spec/Features/method_seq_spec.rb +0 -47
- data/spec/Features/method_sort_with.rb +0 -43
- data/spec/Features/method_sorted_with.rb +0 -29
- data/spec/Features/method_span_spec.rb +0 -42
- data/spec/Features/method_wrap_readonly_spec.rb +0 -43
- data/spec/UnitTest/test_CAVirtual.rb +0 -214
- data/spec/spec_all.rb +0 -10
- data/utils/ca_ase.rb +0 -21
- data/utils/ca_methods.rb +0 -15
- data/utils/cast_checker.rb +0 -30
- data/utils/convert_test.rb +0 -73
- data/utils/extract_yard.rb +0 -22
- data/utils/guess_shape.rb +0 -76
- data/utils/monkey_patch_methods.rb +0 -62
- data/utils/remove_resource_fork.sh +0 -5
|
@@ -0,0 +1,1625 @@
|
|
|
1
|
+
/* ---------------------------------------------------------------------------
|
|
2
|
+
|
|
3
|
+
Ruby MemoryView protocol adapter for CArray.
|
|
4
|
+
|
|
5
|
+
Exporter (zero-copy, alias-only producer):
|
|
6
|
+
- entity arrays (CArray, CScalar, CAWrap): direct, contiguous
|
|
7
|
+
- CARefer / CABlock / CAFarray / CATranspose / CAField / plain
|
|
8
|
+
CAStride: zero-copy strided (composed to the root entity)
|
|
9
|
+
- CARepeat: zero-copy via stride=0 on repeated axes
|
|
10
|
+
- non-alias views (CASelect, CAGrid, CAShift, CAWindow, CAFake,
|
|
11
|
+
CAReduce, CABitarray, CABitfield, CAObject): rejected by the
|
|
12
|
+
producer; use CArray.from_memory_view for a snapshot copy
|
|
13
|
+
|
|
14
|
+
Importer:
|
|
15
|
+
- CArray.from_memory_view (copy; accepts strided)
|
|
16
|
+
- CArray.wrap_memory_view (zero-copy; contiguous only)
|
|
17
|
+
|
|
18
|
+
Format-string contract:
|
|
19
|
+
- Producer emits PEP 3118 strict; consumer is Postel-permissive
|
|
20
|
+
(accepts synonyms other producers emit).
|
|
21
|
+
- See docs/interop/MemoryViewFormat.md for the format-string contract.
|
|
22
|
+
- See devel/DESIGN_MemoryView.md for the design rationale.
|
|
23
|
+
- See docs/interop/MemoryView.md for the user-facing MV API.
|
|
24
|
+
|
|
25
|
+
---------------------------------------------------------------------------- */
|
|
26
|
+
|
|
27
|
+
#include "carray.h"
|
|
28
|
+
#include "ca_obj_face.h" /* ca_strip_face for Face producers */
|
|
29
|
+
#include "ruby/memory_view.h"
|
|
30
|
+
#include <string.h>
|
|
31
|
+
|
|
32
|
+
/* private_data is not used: ca_mv_get is alias-only and ca_mv_release
|
|
33
|
+
needs no per-view state. Kept as a NULL field for API compatibility. */
|
|
34
|
+
|
|
35
|
+
/* ---------------- forward declarations for non-public structs ----------------
|
|
36
|
+
CABlock, CARefer, CARepeat are declared in carray.h. CAFarray
|
|
37
|
+
and CATranspose are CAStride subclasses -- this file reads
|
|
38
|
+
CAStride's strides[]/base_offset directly for them. */
|
|
39
|
+
|
|
40
|
+
/* ---------------- runtime-assigned obj_type ids ---------------- */
|
|
41
|
+
|
|
42
|
+
#define CA_MV_NUM_RUNTIME_OBJ_TYPES 11
|
|
43
|
+
|
|
44
|
+
typedef struct {
|
|
45
|
+
const char *class_name;
|
|
46
|
+
const char *const_name;
|
|
47
|
+
int id; /* -1 until resolved at Init */
|
|
48
|
+
VALUE klass; /* Qnil until resolved at Init */
|
|
49
|
+
int strategy; /* one of ca_mv_strategy_t (filled in lookup table) */
|
|
50
|
+
} ca_mv_runtime_type_t;
|
|
51
|
+
|
|
52
|
+
/* Set strategy via numeric constants below; ca_mv_strategy_t enum is
|
|
53
|
+
forward-declared. We can't put a forward-declared enum into a static
|
|
54
|
+
initialiser, so use a parallel integer table. */
|
|
55
|
+
|
|
56
|
+
/* ---------------- obj_type strategy ---------------- */
|
|
57
|
+
|
|
58
|
+
/* Strategy = label only; ca_mv_get is alias-only and all CAStride-family
|
|
59
|
+
strategies share the same code path (compose-to-root + entity->ptr +
|
|
60
|
+
composed_strides). The labels are kept for ca_mv_runtime_types[]
|
|
61
|
+
readability and reject diagnostics. CA_MV_ATTACH stays defined but is
|
|
62
|
+
structurally unreachable -- ca_mv_check_alias_chain rejects every
|
|
63
|
+
obj_type that maps to it. */
|
|
64
|
+
typedef enum {
|
|
65
|
+
CA_MV_REJECT,
|
|
66
|
+
CA_MV_DIRECT, /* entity / wrap / scalar; ca->ptr valid as-is */
|
|
67
|
+
CA_MV_REFER, /* CARefer; alias via compose-to-root */
|
|
68
|
+
CA_MV_BLOCK, /* CABlock; alias with strides via compose-to-root */
|
|
69
|
+
CA_MV_FARRAY, /* CAFarray; alias with column-major strides */
|
|
70
|
+
CA_MV_TRANS, /* CATranspose; alias with permuted strides */
|
|
71
|
+
CA_MV_REPEAT, /* CARepeat; alias with stride=0 on repeated dims */
|
|
72
|
+
CA_MV_ATTACH, /* unreachable: rejected by alias-chain check */
|
|
73
|
+
} ca_mv_strategy_t;
|
|
74
|
+
|
|
75
|
+
/* Resolved at Init_carray_memory_view. The order is the same as
|
|
76
|
+
ca_mv_runtime_types[] below. Runtime obj_type ids are stored here so
|
|
77
|
+
ca_mv_strategy_for can match them with a simple linear scan. */
|
|
78
|
+
static ca_mv_runtime_type_t ca_mv_runtime_types[CA_MV_NUM_RUNTIME_OBJ_TYPES] = {
|
|
79
|
+
{ "CAFarray", "CA_OBJ_FARRAY", -1, Qnil, /* CA_MV_FARRAY */ 4 },
|
|
80
|
+
{ "CATranspose", "CA_OBJ_TRANSPOSE", -1, Qnil, /* CA_MV_TRANS */ 5 },
|
|
81
|
+
/* CAMapping is retired; a[mapper] now builds a CAGrid/CAStride chain
|
|
82
|
+
whose layers are covered above. */
|
|
83
|
+
{ "CAGrid", "CA_OBJ_GRID", -1, Qnil, /* CA_MV_ATTACH */ 7 },
|
|
84
|
+
{ "CAShift", "CA_OBJ_SHIFT", -1, Qnil, /* CA_MV_ATTACH */ 7 },
|
|
85
|
+
{ "CAWindow", "CA_OBJ_WINDOW", -1, Qnil, /* CA_MV_ATTACH */ 7 },
|
|
86
|
+
{ "CAFake", "CA_OBJ_FAKE", -1, Qnil, /* CA_MV_ATTACH */ 7 },
|
|
87
|
+
/* CAField is a CAStride subclass: zero-copy strided export via
|
|
88
|
+
parent->ptr + base_offset, no materialise. */
|
|
89
|
+
{ "CAField", "CA_OBJ_FIELD", -1, Qnil, /* CA_MV_BLOCK */ 3 },
|
|
90
|
+
{ "CAReduce", "CA_OBJ_REDUCE", -1, Qnil, /* CA_MV_ATTACH */ 7 },
|
|
91
|
+
{ "CABitarray", "CA_OBJ_BITARRAY", -1, Qnil, /* CA_MV_REJECT */ 0 },
|
|
92
|
+
{ "CABitfield", "CA_OBJ_BITFIELD", -1, Qnil, /* CA_MV_REJECT */ 0 },
|
|
93
|
+
/* CARecord: composite Face wrapping a CA_FIXLEN entity 1:1
|
|
94
|
+
(ca->ptr aliases parent->ptr). ATTACH strategy
|
|
95
|
+
is the conservative choice — same as CAFake; storage layout is
|
|
96
|
+
identical to parent so direct export would also work in principle. */
|
|
97
|
+
{ "CARecord", "CA_OBJ_RECORD", -1, Qnil, /* CA_MV_ATTACH */ 7 },
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/* ---------------- format / data_type mapping ----------------
|
|
101
|
+
Bidirectional table. Outbound table (ca_mv_format_for) picks the
|
|
102
|
+
canonical PEP 3118 specifier for each CArray data_type. Inbound
|
|
103
|
+
table (ca_mv_data_type_from_format) is Postel: it accepts a wider
|
|
104
|
+
set of synonyms that other producers emit (they describe int32
|
|
105
|
+
differently). */
|
|
106
|
+
|
|
107
|
+
static const char *
|
|
108
|
+
ca_mv_format_for (int8_t data_type)
|
|
109
|
+
{
|
|
110
|
+
switch (data_type) {
|
|
111
|
+
case CA_BOOLEAN: return "?";
|
|
112
|
+
case CA_INT8: return "b";
|
|
113
|
+
case CA_UINT8: return "B";
|
|
114
|
+
case CA_INT16: return "h";
|
|
115
|
+
case CA_UINT16: return "H";
|
|
116
|
+
case CA_INT32: return "i";
|
|
117
|
+
case CA_UINT32: return "I";
|
|
118
|
+
case CA_INT64: return "q";
|
|
119
|
+
case CA_UINT64: return "Q";
|
|
120
|
+
case CA_FLOAT32: return "f";
|
|
121
|
+
case CA_FLOAT64: return "d";
|
|
122
|
+
case CA_CMPLX64: return "Zf";
|
|
123
|
+
case CA_CMPLX128: return "Zd";
|
|
124
|
+
default:
|
|
125
|
+
return NULL;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* PEP 3118 single-character specifier for a primitive data_type, for
|
|
130
|
+
use inside a `T{...}` struct format body. Identical to the top-level
|
|
131
|
+
producer specifier: since the 2026-06-29 PEP 3118 strict flip both the
|
|
132
|
+
top-level and struct-body contexts emit the same chars, so this
|
|
133
|
+
delegates to ca_mv_format_for. Kept as a distinct entry point so the
|
|
134
|
+
struct-body context can diverge again without touching call sites.
|
|
135
|
+
|
|
136
|
+
Returns NULL for non-primitive (CA_FIXLEN / CA_OBJECT) or for the
|
|
137
|
+
retired float128 / cmplx256 enum slots. */
|
|
138
|
+
static const char *
|
|
139
|
+
ca_mv_pep3118_char (int8_t data_type)
|
|
140
|
+
{
|
|
141
|
+
return ca_mv_format_for(data_type);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Build (or fetch from cache) the PEP 3118 struct format string for
|
|
145
|
+
a data_class. Walks MEMBER_TABLE in MEMBERS order and emits
|
|
146
|
+
"T{<fmt>:<name>:<fmt>:<name>:}". Returns Qnil if the class
|
|
147
|
+
contains a member type the producer cannot express (nested
|
|
148
|
+
sub-struct class, CArray template, :fixlen, :object).
|
|
149
|
+
|
|
150
|
+
The result is frozen and cached on the data_class via
|
|
151
|
+
@__mv_struct_format__ so successive calls are O(1) and the
|
|
152
|
+
underlying RSTRING_PTR is stable (the cached String is rooted by
|
|
153
|
+
the class, which outlives any MV produced from it). */
|
|
154
|
+
/* Helper: resolve a MEMBER_TABLE member-type slot to (fmt, bytes).
|
|
155
|
+
Returns 1 on success, 0 if the type is not expressible in PEP 3118
|
|
156
|
+
struct format (nested class, CArray template, :fixlen, :object,
|
|
157
|
+
:bitfield). Does NOT raise — :bitfield in particular is checked
|
|
158
|
+
before rb_ca_guess_type so the struct-format walk can give up
|
|
159
|
+
gracefully on bit-bearing structs. */
|
|
160
|
+
static int
|
|
161
|
+
ca_mv_struct_member_resolve (VALUE info, const char **fmt_out, ca_size_t *bytes_out)
|
|
162
|
+
{
|
|
163
|
+
VALUE type = rb_ary_entry(info, 1);
|
|
164
|
+
if (!SYMBOL_P(type)) return 0;
|
|
165
|
+
ID type_id = SYM2ID(type);
|
|
166
|
+
/* Reject explicitly unsupported symbol types up front (so
|
|
167
|
+
rb_ca_guess_type never sees them and never raises). */
|
|
168
|
+
if (type_id == rb_intern("bitfield") ||
|
|
169
|
+
type_id == rb_intern("fixlen") ||
|
|
170
|
+
type_id == rb_intern("object")) {
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
int8_t dt = rb_ca_guess_type(rb_sym2str(type));
|
|
174
|
+
const char *fmt = ca_mv_pep3118_char(dt);
|
|
175
|
+
if (!fmt) return 0;
|
|
176
|
+
*fmt_out = fmt;
|
|
177
|
+
*bytes_out = (ca_size_t) ca_sizeof[dt];
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static VALUE
|
|
182
|
+
ca_mv_struct_format_for_data_class (VALUE data_class)
|
|
183
|
+
{
|
|
184
|
+
ID iv = rb_intern("@__mv_struct_format__");
|
|
185
|
+
if (rb_ivar_defined(data_class, iv)) {
|
|
186
|
+
VALUE cached = rb_ivar_get(data_class, iv);
|
|
187
|
+
/* Qfalse is a sentinel meaning "tried, found unsupported". */
|
|
188
|
+
return (cached == Qfalse) ? Qnil : cached;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
VALUE members = rb_const_get(data_class, rb_intern("MEMBERS"));
|
|
192
|
+
VALUE table = rb_const_get(data_class, rb_intern("MEMBER_TABLE"));
|
|
193
|
+
long n = RARRAY_LEN(members);
|
|
194
|
+
long i;
|
|
195
|
+
VALUE buf = rb_str_buf_new(2 + 6 * n);
|
|
196
|
+
ca_size_t cursor = 0;
|
|
197
|
+
ca_size_t data_size = NUM2SIZE(rb_const_get(data_class, rb_intern("DATA_SIZE")));
|
|
198
|
+
|
|
199
|
+
rb_str_buf_cat_ascii(buf, "T{");
|
|
200
|
+
|
|
201
|
+
for (i = 0; i < n; i++) {
|
|
202
|
+
VALUE name = rb_ary_entry(members, i);
|
|
203
|
+
VALUE info = rb_hash_aref(table, name);
|
|
204
|
+
if (NIL_P(info)) goto unsupported;
|
|
205
|
+
ca_size_t offset = NUM2SIZE(rb_ary_entry(info, 0));
|
|
206
|
+
|
|
207
|
+
const char *fmt;
|
|
208
|
+
ca_size_t mb;
|
|
209
|
+
if (!ca_mv_struct_member_resolve(info, &fmt, &mb)) goto unsupported;
|
|
210
|
+
|
|
211
|
+
/* If alignment or explicit padding has left a gap before this
|
|
212
|
+
member, emit "<N>x:" to account for it. PEP 3118 uses 'x'
|
|
213
|
+
as the pad-byte specifier. The canonical form elides the name
|
|
214
|
+
slot after the pad spec (padding is anonymous by definition);
|
|
215
|
+
consumers MUST accept both elided and named (legacy "<N>x:_:")
|
|
216
|
+
forms. */
|
|
217
|
+
if (offset > cursor) {
|
|
218
|
+
char pad_decl[32];
|
|
219
|
+
snprintf(pad_decl, sizeof(pad_decl), "%lldx:",
|
|
220
|
+
(long long) (offset - cursor));
|
|
221
|
+
rb_str_buf_cat_ascii(buf, pad_decl);
|
|
222
|
+
cursor = offset;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
rb_str_buf_cat_ascii(buf, fmt);
|
|
226
|
+
rb_str_buf_cat_ascii(buf, ":");
|
|
227
|
+
/* MEMBERS entries are strings; emit verbatim. */
|
|
228
|
+
rb_str_buf_append(buf, rb_String(name));
|
|
229
|
+
rb_str_buf_cat_ascii(buf, ":");
|
|
230
|
+
cursor += mb;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* Trailing padding (DATA_SIZE > last member's end). Elided form
|
|
234
|
+
(see the alignment-gap branch above for rationale). */
|
|
235
|
+
if (cursor < data_size) {
|
|
236
|
+
char pad_decl[32];
|
|
237
|
+
snprintf(pad_decl, sizeof(pad_decl), "%lldx:",
|
|
238
|
+
(long long) (data_size - cursor));
|
|
239
|
+
rb_str_buf_cat_ascii(buf, pad_decl);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
rb_str_buf_cat_ascii(buf, "}");
|
|
243
|
+
rb_obj_freeze(buf);
|
|
244
|
+
rb_ivar_set(data_class, iv, buf);
|
|
245
|
+
return buf;
|
|
246
|
+
|
|
247
|
+
unsupported:
|
|
248
|
+
/* Cache Qfalse to remember "we tried and gave up" — avoids
|
|
249
|
+
re-walking MEMBER_TABLE on every wrap call. */
|
|
250
|
+
rb_ivar_set(data_class, iv, Qfalse);
|
|
251
|
+
return Qnil;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static int
|
|
255
|
+
ca_mv_data_type_exportable (int8_t data_type)
|
|
256
|
+
{
|
|
257
|
+
return ca_mv_format_for(data_type) != NULL;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* Same as ca_mv_data_type_exportable, but also accepts CA_FIXLEN:
|
|
261
|
+
- with a data_class -> emit PEP 3118 "T{...}" struct format
|
|
262
|
+
- without a data_class -> emit PEP 3118 "Ns" fixed-bytes format */
|
|
263
|
+
static int
|
|
264
|
+
ca_mv_ca_exportable (CArray *ca, VALUE obj)
|
|
265
|
+
{
|
|
266
|
+
if (ca_mv_data_type_exportable(ca->data_type)) return 1;
|
|
267
|
+
if (ca->data_type == CA_FIXLEN) {
|
|
268
|
+
if (RTEST(rb_ca_has_data_class(obj))) {
|
|
269
|
+
VALUE klass = rb_ca_data_class(obj);
|
|
270
|
+
if (NIL_P(klass)) return 0;
|
|
271
|
+
VALUE fmt = ca_mv_struct_format_for_data_class(klass);
|
|
272
|
+
return NIL_P(fmt) ? 0 : 1;
|
|
273
|
+
}
|
|
274
|
+
/* Plain CA_FIXLEN (bytes > 0) exports as "Ns". */
|
|
275
|
+
return (ca->bytes > 0) ? 1 : 0;
|
|
276
|
+
}
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Reverse mapping for the importer (Postel consumer; see
|
|
281
|
+
docs/interop/MemoryViewFormat.md for the contract):
|
|
282
|
+
- Strip a leading byte-order prefix ('<', '>', '=') if it matches host
|
|
283
|
+
endian; reject if it disagrees.
|
|
284
|
+
- Strip the alignment modifier '|'.
|
|
285
|
+
- Reject endian-bearing primary specifiers ('e','g','E','G','n','v',
|
|
286
|
+
'N','V'); cross-endian sources are not accepted.
|
|
287
|
+
- Dispatch on the (stripped_format, item_size) tuple.
|
|
288
|
+
- Canonical: '?' for bool, 'Zf' / 'Zd' for complex64/128 (PEP 3118).
|
|
289
|
+
- Synonyms accepted but not emitted: 'C'/1 maps to UINT8 (so other
|
|
290
|
+
producers' bool buffers are received as UINT8); 'ff' / 'dd' map to
|
|
291
|
+
complex64/128 (producers that split complex into two floats). */
|
|
292
|
+
static int8_t
|
|
293
|
+
ca_mv_data_type_from_format (const char *format, ssize_t item_size)
|
|
294
|
+
{
|
|
295
|
+
const char *fmt = format ? format : "";
|
|
296
|
+
/* Strip alignment and host-matching byte-order prefix. */
|
|
297
|
+
if (*fmt == '|') fmt++;
|
|
298
|
+
if (*fmt == '<' || *fmt == '>' || *fmt == '=') {
|
|
299
|
+
/* The carray runtime stores data in host-endian; cross-endian
|
|
300
|
+
sources are rejected. */
|
|
301
|
+
int host_is_le = (ca_endian == CA_LITTLE_ENDIAN);
|
|
302
|
+
int wants_le = (*fmt == '<') || (*fmt == '=' && host_is_le);
|
|
303
|
+
int wants_be = (*fmt == '>') || (*fmt == '=' && !host_is_le);
|
|
304
|
+
if ((wants_le && !host_is_le) || (wants_be && host_is_le)) return -1;
|
|
305
|
+
fmt++;
|
|
306
|
+
}
|
|
307
|
+
if (*fmt == '\0') return -1;
|
|
308
|
+
|
|
309
|
+
/* single-byte formats */
|
|
310
|
+
if (strcmp(fmt, "c") == 0 && item_size == 1) return CA_INT8;
|
|
311
|
+
if (strcmp(fmt, "C") == 0 && item_size == 1) return CA_UINT8;
|
|
312
|
+
if (strcmp(fmt, "b") == 0 && item_size == 1) return CA_INT8;
|
|
313
|
+
if (strcmp(fmt, "B") == 0 && item_size == 1) return CA_UINT8;
|
|
314
|
+
if (strcmp(fmt, "?") == 0 && item_size == 1) return CA_BOOLEAN;
|
|
315
|
+
|
|
316
|
+
/* 16-bit */
|
|
317
|
+
if ((strcmp(fmt, "s") == 0 || strcmp(fmt, "s!") == 0 ||
|
|
318
|
+
strcmp(fmt, "h") == 0) && item_size == 2) return CA_INT16;
|
|
319
|
+
if ((strcmp(fmt, "S") == 0 || strcmp(fmt, "S!") == 0 ||
|
|
320
|
+
strcmp(fmt, "H") == 0) && item_size == 2) return CA_UINT16;
|
|
321
|
+
|
|
322
|
+
/* 32-bit */
|
|
323
|
+
if ((strcmp(fmt, "l") == 0 || strcmp(fmt, "i") == 0 ||
|
|
324
|
+
strcmp(fmt, "i!") == 0 || strcmp(fmt, "l!") == 0) && item_size == 4) return CA_INT32;
|
|
325
|
+
if ((strcmp(fmt, "L") == 0 || strcmp(fmt, "I") == 0 ||
|
|
326
|
+
strcmp(fmt, "I!") == 0 || strcmp(fmt, "L!") == 0) && item_size == 4) return CA_UINT32;
|
|
327
|
+
|
|
328
|
+
/* 64-bit. `l`/`L` at item_size 8 accepted as LP64 native long
|
|
329
|
+
(numpy default for `np.array([1,2,3]).dtype == int64`). */
|
|
330
|
+
if ((strcmp(fmt, "q") == 0 || strcmp(fmt, "q!") == 0 ||
|
|
331
|
+
strcmp(fmt, "l!") == 0 || strcmp(fmt, "l") == 0) && item_size == 8) return CA_INT64;
|
|
332
|
+
if ((strcmp(fmt, "Q") == 0 || strcmp(fmt, "Q!") == 0 ||
|
|
333
|
+
strcmp(fmt, "L!") == 0 || strcmp(fmt, "L") == 0) && item_size == 8) return CA_UINT64;
|
|
334
|
+
|
|
335
|
+
/* floats: no endian-bearing variants accepted */
|
|
336
|
+
if (strcmp(fmt, "f") == 0 && item_size == 4) return CA_FLOAT32;
|
|
337
|
+
if (strcmp(fmt, "d") == 0 && item_size == 8) return CA_FLOAT64;
|
|
338
|
+
|
|
339
|
+
/* complex: PEP 3118 'Zf' / 'Zd' canonical. */
|
|
340
|
+
if (strcmp(fmt, "Zf") == 0 && item_size == 8) return CA_CMPLX64;
|
|
341
|
+
if (strcmp(fmt, "Zd") == 0 && item_size == 16) return CA_CMPLX128;
|
|
342
|
+
/* Consumer synonyms — accepted but not emitted. */
|
|
343
|
+
if (strcmp(fmt, "ff") == 0 && item_size == 8) return CA_CMPLX64;
|
|
344
|
+
if (strcmp(fmt, "dd") == 0 && item_size == 16) return CA_CMPLX128;
|
|
345
|
+
|
|
346
|
+
/* Fixed-length bytes: PEP 3118 "Ns" (e.g. "8s") -> CA_FIXLEN, bytes = N.
|
|
347
|
+
N must match item_size exactly; mismatch is rejected the same way the
|
|
348
|
+
numeric branches reject a width disagreement. N must be positive
|
|
349
|
+
(rejects "0s" — CA_FIXLEN's bytes > 0 invariant). */
|
|
350
|
+
{
|
|
351
|
+
size_t len = strlen(fmt);
|
|
352
|
+
if (len >= 2 && fmt[len - 1] == 's') {
|
|
353
|
+
char *endp = NULL;
|
|
354
|
+
long n = strtol(fmt, &endp, 10);
|
|
355
|
+
if (endp == fmt + len - 1 && n > 0 && n == (long) item_size) {
|
|
356
|
+
return CA_FIXLEN;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return -1;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Derive the data_type a MemoryView producer would import as, by parsing its
|
|
365
|
+
format — without importing the data. This is the canonical "what dtype is
|
|
366
|
+
this MV" question, decoupled from the import strategy (copy via
|
|
367
|
+
from_memory_view vs zero-copy via wrap_memory_view). Used by
|
|
368
|
+
ca_arg_to_data_type so CArray.result_type / promote_list can treat an MV
|
|
369
|
+
producer as an operand. Returns -1 (so the caller falls back to value
|
|
370
|
+
inference) when obj is not an MV producer, is typeless (format == NULL), or
|
|
371
|
+
carries a format carray cannot represent host-endian. */
|
|
372
|
+
int8_t
|
|
373
|
+
ca_mv_probe_data_type (VALUE obj)
|
|
374
|
+
{
|
|
375
|
+
rb_memory_view_t view;
|
|
376
|
+
int8_t data_type;
|
|
377
|
+
if ( ! rb_memory_view_available_p(obj) ) {
|
|
378
|
+
return -1;
|
|
379
|
+
}
|
|
380
|
+
/* Zero-init before get: release() unconditionally frees item_desc.components
|
|
381
|
+
even on producers that leave it uninitialized (see ca_mv_acquire_and_validate). */
|
|
382
|
+
memset(&view, 0, sizeof(view));
|
|
383
|
+
if ( ! rb_memory_view_get(obj, &view, RUBY_MEMORY_VIEW_STRIDES) ) {
|
|
384
|
+
if ( ! rb_memory_view_get(obj, &view, RUBY_MEMORY_VIEW_SIMPLE) ) {
|
|
385
|
+
return -1;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
data_type = ca_mv_data_type_from_format(view.format, view.item_size);
|
|
389
|
+
rb_memory_view_release(&view);
|
|
390
|
+
return data_type;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/* Per-element byte width for consumer-side wrap/from CArray allocation.
|
|
394
|
+
For numeric types it is `ca_sizeof[data_type]`; for CA_FIXLEN it is the
|
|
395
|
+
MemoryView item_size, since fixlen has no compile-time element width. */
|
|
396
|
+
static inline ca_size_t
|
|
397
|
+
ca_mv_carray_bytes_for (int8_t data_type, ssize_t item_size)
|
|
398
|
+
{
|
|
399
|
+
return (data_type == CA_FIXLEN) ? (ca_size_t) item_size
|
|
400
|
+
: (ca_size_t) ca_sizeof[data_type];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* ---------------- strategy lookup ---------------- */
|
|
404
|
+
|
|
405
|
+
static ca_mv_strategy_t
|
|
406
|
+
ca_mv_strategy_for (int16_t obj_type)
|
|
407
|
+
{
|
|
408
|
+
/* compile-time enum members */
|
|
409
|
+
if (obj_type == CA_OBJ_ARRAY ||
|
|
410
|
+
obj_type == CA_OBJ_ARRAY_WRAP ||
|
|
411
|
+
obj_type == CA_OBJ_SCALAR) return CA_MV_DIRECT;
|
|
412
|
+
if (obj_type == CA_OBJ_REFER) return CA_MV_REFER;
|
|
413
|
+
if (obj_type == CA_OBJ_BLOCK) return CA_MV_BLOCK;
|
|
414
|
+
if (obj_type == CA_OBJ_SELECT) return CA_MV_ATTACH;
|
|
415
|
+
if (obj_type == CA_OBJ_REPEAT) return CA_MV_REPEAT;
|
|
416
|
+
if (obj_type == CA_OBJ_OBJECT) return CA_MV_REJECT;
|
|
417
|
+
if (obj_type == CA_OBJ_UNBOUND_REPEAT) return CA_MV_REJECT;
|
|
418
|
+
/* Plain CAStride (created via #as_strided, or wrap_memory_view from
|
|
419
|
+
a strided producer). Reuse the CA_MV_TRANS handler since it
|
|
420
|
+
already reads strides[] / base_offset directly from the CAStride
|
|
421
|
+
layout -- no CATranspose-specific code in that path anymore. */
|
|
422
|
+
if (obj_type == CA_OBJ_STRIDE) return CA_MV_TRANS;
|
|
423
|
+
|
|
424
|
+
/* runtime-assigned obj_types */
|
|
425
|
+
for (int i = 0; i < CA_MV_NUM_RUNTIME_OBJ_TYPES; i++) {
|
|
426
|
+
if (ca_mv_runtime_types[i].id == obj_type) {
|
|
427
|
+
return (ca_mv_strategy_t) ca_mv_runtime_types[i].strategy;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return CA_MV_REJECT;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/* ---------------- helpers ---------------- */
|
|
434
|
+
|
|
435
|
+
static int
|
|
436
|
+
ca_mv_extract (VALUE obj, CArray **out)
|
|
437
|
+
{
|
|
438
|
+
if (! rb_typeddata_is_kind_of(obj, &carray_data_type)) {
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
*out = (CArray *) DATA_PTR(obj);
|
|
442
|
+
return 1;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
static ssize_t *
|
|
446
|
+
ca_mv_alloc_shape (CArray *ca)
|
|
447
|
+
{
|
|
448
|
+
ssize_t *shape;
|
|
449
|
+
int i;
|
|
450
|
+
if (ca->ndim <= 0) return NULL;
|
|
451
|
+
shape = (ssize_t *) xmalloc(sizeof(ssize_t) * ca->ndim);
|
|
452
|
+
for (i = 0; i < ca->ndim; i++) {
|
|
453
|
+
shape[i] = (ssize_t) ca->dim[i];
|
|
454
|
+
}
|
|
455
|
+
return shape;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
static ssize_t *
|
|
459
|
+
ca_mv_alloc_strides_contiguous (CArray *ca, ssize_t *shape, bool row_major)
|
|
460
|
+
{
|
|
461
|
+
ssize_t *strides;
|
|
462
|
+
if (ca->ndim <= 0) return NULL;
|
|
463
|
+
strides = (ssize_t *) xmalloc(sizeof(ssize_t) * ca->ndim);
|
|
464
|
+
rb_memory_view_fill_contiguous_strides(
|
|
465
|
+
(ssize_t) ca->ndim, (ssize_t) ca->bytes, shape, row_major, strides);
|
|
466
|
+
return strides;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* Byte strides for the CAStride family come from
|
|
470
|
+
ca_stride_compose_to_root, which produces them directly from the
|
|
471
|
+
CAStride prefix (composing through any CAStride parent chain to
|
|
472
|
+
entity); no per-view stride helpers live here. */
|
|
473
|
+
|
|
474
|
+
/* ---------------- alias-chain wrap predicate ----------------
|
|
475
|
+
wrap_memory_view exports zero-copy. A view is wrappable iff its
|
|
476
|
+
parent chain bottoms out at an entity through links that all
|
|
477
|
+
alias (contig CAStride family). Other ancestors (CASelect,
|
|
478
|
+
CAFake, non-contig CAStride, etc.) would require materialising a
|
|
479
|
+
snapshot, which has incorrect view semantics for MV consumers
|
|
480
|
+
(deferred sync on release, stale reads, no explicit-sync API).
|
|
481
|
+
Consumers that want a snapshot should use CArray.from_memory_view. */
|
|
482
|
+
|
|
483
|
+
typedef struct {
|
|
484
|
+
int depth; /* 0 = the view itself, 1 = parent, 2 = grand-, ... */
|
|
485
|
+
const char *class_name; /* class name of the offending link */
|
|
486
|
+
const char *reason; /* short explanation */
|
|
487
|
+
} ca_mv_reject_t;
|
|
488
|
+
|
|
489
|
+
static bool ca_mv_stride_is_contig (CAStride *ca); /* defined below */
|
|
490
|
+
|
|
491
|
+
/* True iff `ca`'s obj_type uses ca_stride_func's attach routine -- i.e.
|
|
492
|
+
it is CAStride or a subclass that inherits CAStride semantics. */
|
|
493
|
+
static inline bool
|
|
494
|
+
ca_mv_is_castride_family (CArray *ca)
|
|
495
|
+
{
|
|
496
|
+
extern ca_operation_function_t ca_stride_func;
|
|
497
|
+
return ca_func[ca->obj_type].attach == ca_stride_func.attach;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
static const char *
|
|
501
|
+
ca_mv_class_name_of (CArray *ca)
|
|
502
|
+
{
|
|
503
|
+
VALUE klass = ca_class[ca->obj_type];
|
|
504
|
+
return NIL_P(klass) ? "(unknown)" : rb_class2name(klass);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
static const char *
|
|
508
|
+
ca_mv_reject_reason_for (CArray *ca)
|
|
509
|
+
{
|
|
510
|
+
/* Compile-time obj_types we know about */
|
|
511
|
+
if (ca->obj_type == CA_OBJ_SELECT) return "boolean-mask selection (positions not expressible as strides)";
|
|
512
|
+
if (ca->obj_type == CA_OBJ_OBJECT) return "stores Ruby VALUEs, not raw bytes";
|
|
513
|
+
if (ca->obj_type == CA_OBJ_UNBOUND_REPEAT) return "shape is not bound";
|
|
514
|
+
/* Runtime-assigned obj_types: look up by name in the strategy table */
|
|
515
|
+
for (int i = 0; i < CA_MV_NUM_RUNTIME_OBJ_TYPES; i++) {
|
|
516
|
+
if (ca_mv_runtime_types[i].id == ca->obj_type) {
|
|
517
|
+
const char *n = ca_mv_runtime_types[i].class_name;
|
|
518
|
+
if (!strcmp(n, "CAGrid")) return "grid selection (positions not expressible as strides)";
|
|
519
|
+
if (!strcmp(n, "CAShift")) return "shifted view with bounds-fill (no in-place inverse)";
|
|
520
|
+
if (!strcmp(n, "CAWindow")) return "windowed view with bounds-fill (no in-place inverse)";
|
|
521
|
+
if (!strcmp(n, "CAFake")) return "lazy type conversion (values differ from underlying bytes)";
|
|
522
|
+
/* CAField exports as a zero-copy CAStride and never hits this
|
|
523
|
+
diagnostic. */
|
|
524
|
+
if (!strcmp(n, "CAReduce")) return "OR-reduce (no in-place inverse)";
|
|
525
|
+
if (!strcmp(n, "CABitarray")) return "sub-byte (bit) addressing";
|
|
526
|
+
if (!strcmp(n, "CABitfield")) return "sub-byte (bitfield) addressing";
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return "this kind of view is not alias-capable";
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* Resolve ca to an (entity, composed_strides, composed_base) triple
|
|
533
|
+
suitable for zero-copy strided export. Uses ca_stride_compose_to_root
|
|
534
|
+
to fold the CAStride parent chain (alias-eligible OR not, as long
|
|
535
|
+
as strides decompose cleanly into single parent-dim advances).
|
|
536
|
+
|
|
537
|
+
Succeeds when the resolved root is an entity (CArray/CAWrap/CScalar).
|
|
538
|
+
On failure (leaf is not CAStride family, or compose stops before
|
|
539
|
+
entity), fills *rej.
|
|
540
|
+
|
|
541
|
+
`out_strides` and `out_base` are filled only when ca is a CAStride
|
|
542
|
+
leaf (i.e. compose was performed). For entity-class ca, callers
|
|
543
|
+
compute contig row-major strides themselves. */
|
|
544
|
+
static bool
|
|
545
|
+
ca_mv_compose_to_entity (CArray *ca, ca_mv_reject_t *rej,
|
|
546
|
+
CArray **out_entity,
|
|
547
|
+
ca_size_t *out_strides,
|
|
548
|
+
ca_size_t *out_base)
|
|
549
|
+
{
|
|
550
|
+
/* Face (= CARecord etc.) is a 1:1 alias of parent->ptr, so
|
|
551
|
+
storage-layer-wise it is identical to the parent's entity / castride
|
|
552
|
+
layer. Strip Face, descend to parent, and feed the subsequent
|
|
553
|
+
compose. data_class / format come separately from the Face tail via
|
|
554
|
+
rb_ca_data_class universal dispatch. */
|
|
555
|
+
if (ca_is_face(ca)) {
|
|
556
|
+
ca = ca_strip_face(ca);
|
|
557
|
+
}
|
|
558
|
+
if (ca_is_entity(ca)) {
|
|
559
|
+
*out_entity = ca;
|
|
560
|
+
*out_base = 0;
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
if (!ca_mv_is_castride_family(ca)) {
|
|
564
|
+
rej->depth = 0;
|
|
565
|
+
rej->class_name = ca_mv_class_name_of(ca);
|
|
566
|
+
rej->reason = ca_mv_reject_reason_for(ca);
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
CArray *root;
|
|
570
|
+
ca_stride_compose_to_root((CAStride *) ca, &root, out_strides, out_base);
|
|
571
|
+
if (!ca_is_entity(root)) {
|
|
572
|
+
rej->depth = -1;
|
|
573
|
+
rej->class_name = ca_mv_class_name_of(root);
|
|
574
|
+
if (ca_mv_is_castride_family(root)) {
|
|
575
|
+
rej->reason = "CAStride chain stride pattern does not compose "
|
|
576
|
+
"linearly into entity (e.g. reshape across non-contig)";
|
|
577
|
+
} else {
|
|
578
|
+
rej->reason = ca_mv_reject_reason_for(root);
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
*out_entity = root;
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* Predicate wrapper for available_p / reject_reason: discards the
|
|
587
|
+
resolved (entity, strides, base). */
|
|
588
|
+
static bool
|
|
589
|
+
ca_mv_check_alias_chain (CArray *ca, ca_mv_reject_t *rej)
|
|
590
|
+
{
|
|
591
|
+
CArray *entity;
|
|
592
|
+
ca_size_t strides[CA_RANK_MAX];
|
|
593
|
+
ca_size_t base;
|
|
594
|
+
return ca_mv_compose_to_entity(ca, rej, &entity, strides, &base);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/* Local copy of ca_obj_stride.c's contiguity check (that one is
|
|
598
|
+
static). Returns true iff strides match a row-major contig
|
|
599
|
+
layout over the view's own dim. */
|
|
600
|
+
static bool
|
|
601
|
+
ca_mv_stride_is_contig (CAStride *ca)
|
|
602
|
+
{
|
|
603
|
+
ca_size_t expected = ca->bytes;
|
|
604
|
+
int8_t k;
|
|
605
|
+
for (k = ca->ndim - 1; k >= 0; k--) {
|
|
606
|
+
if (ca->dim[k] != 1 && ca->strides[k] != expected) return false;
|
|
607
|
+
expected *= ca->dim[k];
|
|
608
|
+
}
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/* ---------------- get / release / available_p ---------------- */
|
|
613
|
+
|
|
614
|
+
static bool
|
|
615
|
+
ca_mv_available_p (VALUE obj)
|
|
616
|
+
{
|
|
617
|
+
CArray *ca;
|
|
618
|
+
ca_mv_reject_t rej;
|
|
619
|
+
if (! ca_mv_extract(obj, &ca)) return false;
|
|
620
|
+
if (! ca_mv_ca_exportable(ca, obj)) return false;
|
|
621
|
+
if (ca_has_mask(ca)) return false;
|
|
622
|
+
/* An entity whose obj_type has no strategy of its own is one installed
|
|
623
|
+
from outside the core (ca_install_obj_type). It reaches this callback
|
|
624
|
+
only because it inherits CArray's MemoryView registration, and
|
|
625
|
+
ca_mv_get would then reject it — say so here rather than advertising
|
|
626
|
+
an export that cannot be produced. Built-in entities (CArray /
|
|
627
|
+
CAWrap / CScalar) map to DIRECT and are unaffected. */
|
|
628
|
+
if (ca_is_entity(ca) && ca_mv_strategy_for(ca->obj_type) == CA_MV_REJECT) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
/* Wrap-via-MV exports are zero-copy: require an alias chain to
|
|
632
|
+
entity. Non-alias views (CASelect, CAFake, etc.) and CAStride
|
|
633
|
+
chains broken by a non-contig link cannot be wrapped without a
|
|
634
|
+
snapshot, which has wrong semantics for MV consumers. */
|
|
635
|
+
return ca_mv_check_alias_chain(ca, &rej);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
static bool
|
|
639
|
+
ca_mv_get (VALUE obj, rb_memory_view_t *view, int flags)
|
|
640
|
+
{
|
|
641
|
+
CArray *ca;
|
|
642
|
+
ca_mv_strategy_t strategy;
|
|
643
|
+
ssize_t *shape = NULL;
|
|
644
|
+
ssize_t *strides = NULL;
|
|
645
|
+
void *data_ptr = NULL;
|
|
646
|
+
int writable_request;
|
|
647
|
+
|
|
648
|
+
if (! ca_mv_extract(obj, &ca)) return false;
|
|
649
|
+
if (! ca_mv_ca_exportable(ca, obj)) return false;
|
|
650
|
+
if (ca_has_mask(ca)) return false;
|
|
651
|
+
|
|
652
|
+
writable_request = (flags & RUBY_MEMORY_VIEW_WRITABLE) ? 1 : 0;
|
|
653
|
+
if (writable_request && ca_is_readonly(ca)) return false;
|
|
654
|
+
/* Defense in depth: reject WRITABLE requests on Ruby-level frozen objects
|
|
655
|
+
even if CA_FLAG_READ_ONLY wasn't set (e.g., freeze bypassed via
|
|
656
|
+
Object#freeze on a subclass). */
|
|
657
|
+
if (writable_request && OBJ_FROZEN(obj)) return false;
|
|
658
|
+
|
|
659
|
+
/* Resolve the chain to (entity, composed_strides, composed_base).
|
|
660
|
+
If ca isn't a CAStride family member, only entity-class is
|
|
661
|
+
accepted here. The CAStride-family case below uses composed_strides
|
|
662
|
+
/ composed_base directly; the DIRECT case ignores them (entity owns
|
|
663
|
+
ca->ptr already, no compose needed). */
|
|
664
|
+
CArray *resolved_entity = NULL;
|
|
665
|
+
ca_size_t composed_strides[CA_RANK_MAX];
|
|
666
|
+
ca_size_t composed_base = 0;
|
|
667
|
+
{
|
|
668
|
+
ca_mv_reject_t rej;
|
|
669
|
+
if (! ca_mv_compose_to_entity(ca, &rej, &resolved_entity,
|
|
670
|
+
composed_strides, &composed_base)) {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/* SIMPLE consumer on non-contig view would need a snapshot -- not
|
|
676
|
+
offered (consumers wanting a snapshot use from_memory_view). */
|
|
677
|
+
{
|
|
678
|
+
int wants_strides_early = (flags & RUBY_MEMORY_VIEW_STRIDES) ? 1 : 0;
|
|
679
|
+
if (!wants_strides_early &&
|
|
680
|
+
ca_mv_is_castride_family(ca) &&
|
|
681
|
+
!ca_mv_stride_is_contig((CAStride *) ca)) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
strategy = ca_mv_strategy_for(ca->obj_type);
|
|
687
|
+
|
|
688
|
+
switch (strategy) {
|
|
689
|
+
case CA_MV_DIRECT:
|
|
690
|
+
/* Entity / wrap / scalar: ca->ptr is the entity's own buffer, always
|
|
691
|
+
valid. No attach needed. */
|
|
692
|
+
data_ptr = ca->ptr;
|
|
693
|
+
if (ca->ndim >= 1) {
|
|
694
|
+
shape = ca_mv_alloc_shape(ca);
|
|
695
|
+
strides = ca_mv_alloc_strides_contiguous(ca, shape, true);
|
|
696
|
+
}
|
|
697
|
+
break;
|
|
698
|
+
|
|
699
|
+
/* All CAStride-family views (CARefer / CABlock / CAFarray / CATranspose
|
|
700
|
+
/ CARepeat / plain CAStride) reach this point only after
|
|
701
|
+
ca_mv_compose_to_entity succeeded -- the producer is alias-only by
|
|
702
|
+
ca_mv_available_p contract. Expose `entity->ptr + composed_base` as
|
|
703
|
+
data_ptr and composed_strides as strides, both in entity byte space.
|
|
704
|
+
The entity owns its bytes (always valid via view->obj's parent chain
|
|
705
|
+
while the consumer holds the view).
|
|
706
|
+
|
|
707
|
+
CAREFUL: do not ca_attach the view here (nor ca_sync / ca_detach at
|
|
708
|
+
release). Exposure is alias-only: the consumer writes directly into
|
|
709
|
+
root bytes, so a sync would scatter bytes back to root as a no-op
|
|
710
|
+
chain -- and that chain triggers ca_update_mask / ca_sync(mask)
|
|
711
|
+
recursion that can raise on an invariant violation. Raising from a
|
|
712
|
+
release callback is fatal during GC sweep (newobj_of during sweep ->
|
|
713
|
+
BUG). */
|
|
714
|
+
case CA_MV_REFER:
|
|
715
|
+
case CA_MV_BLOCK:
|
|
716
|
+
case CA_MV_FARRAY:
|
|
717
|
+
case CA_MV_TRANS:
|
|
718
|
+
case CA_MV_REPEAT: {
|
|
719
|
+
int8_t k;
|
|
720
|
+
shape = ca_mv_alloc_shape(ca);
|
|
721
|
+
strides = (ssize_t *) xmalloc(sizeof(ssize_t) * ca->ndim);
|
|
722
|
+
for (k = 0; k < ca->ndim; k++) strides[k] = (ssize_t) composed_strides[k];
|
|
723
|
+
data_ptr = resolved_entity->ptr + composed_base;
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
case CA_MV_ATTACH:
|
|
728
|
+
/* Should be unreachable: ca_mv_check_alias_chain rejects every
|
|
729
|
+
obj_type that maps to ATTACH (CASelect / CAMapping / CAGrid /
|
|
730
|
+
CAShift / CAWindow / CAFake / CAReduce). Materialise is no
|
|
731
|
+
longer offered via wrap_memory_view; from_memory_view gives a
|
|
732
|
+
snapshot copy instead. */
|
|
733
|
+
rb_raise(rb_eRuntimeError,
|
|
734
|
+
"[BUG] ATTACH strategy reached in ca_mv_get for %s; "
|
|
735
|
+
"alias-chain check should have rejected it",
|
|
736
|
+
ca_mv_class_name_of(ca));
|
|
737
|
+
|
|
738
|
+
case CA_MV_REJECT:
|
|
739
|
+
default:
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
view->obj = obj;
|
|
744
|
+
view->data = data_ptr;
|
|
745
|
+
/* PEP 3118 convention: byte_size is product(shape) * item_size for
|
|
746
|
+
every layout (contiguous or strided). NumPy, Numo, and Arrow all
|
|
747
|
+
follow this; consumers that need the addressable span for strided
|
|
748
|
+
views compute Σ (shape[k]-1)*|strides[k]| + item_size themselves. */
|
|
749
|
+
view->byte_size = (ssize_t) ca_length(ca);
|
|
750
|
+
view->readonly = ca_is_readonly(ca) ? true : false;
|
|
751
|
+
/* Format string: primitive types use the top-level PEP 3118 table;
|
|
752
|
+
CA_FIXLEN + data_class emits a PEP 3118 "T{...}" struct format
|
|
753
|
+
whose lifetime is bound to the class via @__mv_struct_format__,
|
|
754
|
+
so the view->format pointer is stable until the class is GC'd. */
|
|
755
|
+
if (ca->data_type == CA_FIXLEN && RTEST(rb_ca_has_data_class(obj))) {
|
|
756
|
+
VALUE klass = rb_ca_data_class(obj);
|
|
757
|
+
VALUE fmt = ca_mv_struct_format_for_data_class(klass);
|
|
758
|
+
view->format = RSTRING_PTR(fmt);
|
|
759
|
+
}
|
|
760
|
+
else if (ca->data_type == CA_FIXLEN) {
|
|
761
|
+
/* Plain CA_FIXLEN (no data_class): emit PEP 3118 "Ns" fixed-bytes.
|
|
762
|
+
Cache the format String on the source object so RSTRING_PTR stays
|
|
763
|
+
stable for the view's lifetime; the source object is anchored as
|
|
764
|
+
view->obj. */
|
|
765
|
+
VALUE fmt_str = rb_attr_get(obj, rb_intern("__mv_fixlen_format__"));
|
|
766
|
+
if (NIL_P(fmt_str)) {
|
|
767
|
+
char fmt_buf[32];
|
|
768
|
+
snprintf(fmt_buf, sizeof(fmt_buf), "%lds", (long) ca->bytes);
|
|
769
|
+
fmt_str = rb_str_new_cstr(fmt_buf);
|
|
770
|
+
rb_obj_freeze(fmt_str);
|
|
771
|
+
rb_ivar_set(obj, rb_intern("__mv_fixlen_format__"), fmt_str);
|
|
772
|
+
}
|
|
773
|
+
view->format = RSTRING_PTR(fmt_str);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
view->format = ca_mv_format_for(ca->data_type);
|
|
777
|
+
}
|
|
778
|
+
view->item_size = (ssize_t) ca->bytes;
|
|
779
|
+
view->item_desc.components = NULL;
|
|
780
|
+
view->item_desc.length = 0;
|
|
781
|
+
view->ndim = (ca->obj_type == CA_OBJ_SCALAR) ? 0 : (ssize_t) ca->ndim;
|
|
782
|
+
view->shape = (view->ndim == 0) ? NULL : shape;
|
|
783
|
+
view->strides = (view->ndim == 0) ? NULL : strides;
|
|
784
|
+
view->sub_offsets = NULL;
|
|
785
|
+
view->private_data = NULL;
|
|
786
|
+
|
|
787
|
+
/* Enforce caller's layout request. */
|
|
788
|
+
{
|
|
789
|
+
int wants_strides = (flags & RUBY_MEMORY_VIEW_STRIDES) ? 1 : 0;
|
|
790
|
+
int wants_row = (flags & RUBY_MEMORY_VIEW_ROW_MAJOR) == RUBY_MEMORY_VIEW_ROW_MAJOR;
|
|
791
|
+
int wants_col = (flags & RUBY_MEMORY_VIEW_COLUMN_MAJOR) == RUBY_MEMORY_VIEW_COLUMN_MAJOR;
|
|
792
|
+
bool row_ok = (view->ndim == 0) ? true : rb_memory_view_is_row_major_contiguous(view);
|
|
793
|
+
bool col_ok = (view->ndim == 0) ? true : rb_memory_view_is_column_major_contiguous(view);
|
|
794
|
+
if (wants_row && !row_ok) goto reject;
|
|
795
|
+
if (wants_col && !col_ok) goto reject;
|
|
796
|
+
if (!wants_strides && !row_ok) goto reject;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return true;
|
|
800
|
+
|
|
801
|
+
reject:
|
|
802
|
+
/* Layout-mismatch reject: caller wanted SIMPLE/contig but we built a
|
|
803
|
+
strided view, or vice versa. ca_mv_get is alias-only and does not
|
|
804
|
+
attach anything, so just free the helper shape/strides buffers and
|
|
805
|
+
return false; there is no attach state to unwind. */
|
|
806
|
+
if (shape) { xfree(shape); view->shape = NULL; }
|
|
807
|
+
if (strides) { xfree(strides); view->strides = NULL; }
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
static bool
|
|
812
|
+
ca_mv_release (VALUE obj, rb_memory_view_t *view)
|
|
813
|
+
{
|
|
814
|
+
(void) obj;
|
|
815
|
+
if (view->shape) {
|
|
816
|
+
xfree((void *) view->shape);
|
|
817
|
+
view->shape = NULL;
|
|
818
|
+
}
|
|
819
|
+
if (view->strides) {
|
|
820
|
+
xfree((void *) view->strides);
|
|
821
|
+
view->strides = NULL;
|
|
822
|
+
}
|
|
823
|
+
/* CAREFUL: no attach/sync/detach here. ca_mv_get is alias-only -- the
|
|
824
|
+
consumer wrote directly into root bytes, so a ca_sync would scatter
|
|
825
|
+
back as a no-op chain that can trigger ca_update_mask /
|
|
826
|
+
ca_sync(ca->mask) recursion and raise; raising from a release
|
|
827
|
+
callback is fatal during GC sweep. See ca_mv_get's CAStride-family
|
|
828
|
+
case comment for the full rationale. */
|
|
829
|
+
view->private_data = NULL;
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/* ---------------- importer ----------------
|
|
834
|
+
Two-tier API mirroring numo-narray-memoryview:
|
|
835
|
+
|
|
836
|
+
CArray.from_memory_view(obj) -> CArray (copy, independent buffer)
|
|
837
|
+
CArray.wrap_memory_view(obj) -> CAWrap (zero-copy, shared memory)
|
|
838
|
+
|
|
839
|
+
from_memory_view: borrow a SIMPLE view, copy bytes into a freshly
|
|
840
|
+
owned CArray, release the view immediately. The result is detached
|
|
841
|
+
from the source.
|
|
842
|
+
|
|
843
|
+
wrap_memory_view: borrow a SIMPLE view and keep it alive for the
|
|
844
|
+
lifetime of a CAWrap. The source object stays anchored via an ivar;
|
|
845
|
+
a TypedData holder (also stored as an ivar) releases the view in its
|
|
846
|
+
dfree callback when the CAWrap is collected. */
|
|
847
|
+
|
|
848
|
+
typedef struct {
|
|
849
|
+
rb_memory_view_t view;
|
|
850
|
+
bool valid;
|
|
851
|
+
} ca_mv_imported_holder_t;
|
|
852
|
+
|
|
853
|
+
static void
|
|
854
|
+
ca_mv_imported_holder_free (void *p)
|
|
855
|
+
{
|
|
856
|
+
ca_mv_imported_holder_t *h = (ca_mv_imported_holder_t *) p;
|
|
857
|
+
if (h->valid) {
|
|
858
|
+
rb_memory_view_release(&h->view);
|
|
859
|
+
h->valid = false;
|
|
860
|
+
}
|
|
861
|
+
xfree(h);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
static size_t
|
|
865
|
+
ca_mv_imported_holder_size (const void *p)
|
|
866
|
+
{
|
|
867
|
+
return sizeof(ca_mv_imported_holder_t);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
static const rb_data_type_t ca_mv_imported_holder_data_type = {
|
|
871
|
+
.wrap_struct_name = "CArrayImportedMemoryViewHolder",
|
|
872
|
+
.function = {
|
|
873
|
+
.dmark = NULL, /* source_obj is anchored via ivar on the wrapper */
|
|
874
|
+
.dfree = ca_mv_imported_holder_free,
|
|
875
|
+
.dsize = ca_mv_imported_holder_size,
|
|
876
|
+
.dcompact = NULL
|
|
877
|
+
},
|
|
878
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
/* Acquire a MemoryView from src and validate/derive data_type, ndim
|
|
882
|
+
and shape. Tries STRIDES first; falls back to SIMPLE so that
|
|
883
|
+
producers that advertise only contiguous still work. On any
|
|
884
|
+
failure, releases the view (if acquired) and raises ArgumentError;
|
|
885
|
+
on success, the view is left active and the caller owns the
|
|
886
|
+
responsibility to release it.
|
|
887
|
+
|
|
888
|
+
`explicit_data_type` is the caller-supplied target data_type (>= 0) or -1
|
|
889
|
+
if the caller has no preference. Behaviour:
|
|
890
|
+
- explicit_data_type < 0, format != NULL: derive data_type from format.
|
|
891
|
+
- explicit_data_type < 0, format == NULL: reject (typeless source
|
|
892
|
+
without consumer-supplied type cannot be interpreted).
|
|
893
|
+
- explicit_data_type >= 0, format != NULL: must match the format's
|
|
894
|
+
data_type; mismatch -> reject.
|
|
895
|
+
- explicit_data_type >= 0, format == NULL: use explicit_data_type; the
|
|
896
|
+
byte buffer is reinterpreted as that data_type. ndim is forced
|
|
897
|
+
to 1 (multi-dim from a typeless source is ambiguous; user
|
|
898
|
+
chains .reshape).
|
|
899
|
+
*/
|
|
900
|
+
/* Row-major contiguity test that tolerates a NULL shape array. ndim-0
|
|
901
|
+
scalars and SIMPLE-form producers (which omit the shape array for a
|
|
902
|
+
contiguous 1-D buffer, e.g. red-arrow's primitive arrays) are
|
|
903
|
+
contiguous by construction; rb_memory_view_is_row_major_contiguous
|
|
904
|
+
dereferences view->shape, so it must not be reached with shape NULL. */
|
|
905
|
+
static bool
|
|
906
|
+
ca_mv_view_is_contiguous (const rb_memory_view_t *view)
|
|
907
|
+
{
|
|
908
|
+
if (view->ndim == 0 || view->shape == NULL) {
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
return rb_memory_view_is_row_major_contiguous(view);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
static void
|
|
915
|
+
ca_mv_acquire_and_validate (VALUE src, rb_memory_view_t *view,
|
|
916
|
+
int8_t explicit_data_type,
|
|
917
|
+
int8_t *out_data_type, int8_t *out_ndim,
|
|
918
|
+
ca_size_t *out_dim)
|
|
919
|
+
{
|
|
920
|
+
int8_t data_type;
|
|
921
|
+
int i;
|
|
922
|
+
|
|
923
|
+
/* Defensive zero-init: rb_memory_view_release() unconditionally
|
|
924
|
+
xfree()s view->item_desc.components, but not every producer writes
|
|
925
|
+
that field. red-arrow 24.0.0 leaves it uninitialized, so a view
|
|
926
|
+
acquired from it frees stack garbage on release (abort). Zeroing
|
|
927
|
+
here makes the field NULL. Consumer side of apache/arrow#45187
|
|
928
|
+
(fixed in Arrow 25.0.0); harmless once producers initialize it. */
|
|
929
|
+
memset(view, 0, sizeof(*view));
|
|
930
|
+
|
|
931
|
+
if (! rb_memory_view_get(src, view, RUBY_MEMORY_VIEW_STRIDES)) {
|
|
932
|
+
if (! rb_memory_view_get(src, view, RUBY_MEMORY_VIEW_SIMPLE)) {
|
|
933
|
+
rb_raise(rb_eArgError,
|
|
934
|
+
"object does not support MemoryView");
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (view->format == NULL) {
|
|
939
|
+
/* Typeless producer (e.g. mmap-view, IO#read-style byte blob).
|
|
940
|
+
Consumer must supply the target data_type. */
|
|
941
|
+
if (explicit_data_type < 0) {
|
|
942
|
+
ssize_t got = view->byte_size;
|
|
943
|
+
rb_memory_view_release(view);
|
|
944
|
+
rb_raise(rb_eArgError,
|
|
945
|
+
"typeless MemoryView (format=NULL, %ld bytes); "
|
|
946
|
+
"specify the target data_type via data_type: kwarg or use a "
|
|
947
|
+
"concrete CArray::<Dtype> factory class",
|
|
948
|
+
(long) got);
|
|
949
|
+
}
|
|
950
|
+
if (ca_mv_format_for(explicit_data_type) == NULL) {
|
|
951
|
+
rb_memory_view_release(view);
|
|
952
|
+
rb_raise(rb_eArgError,
|
|
953
|
+
"target data_type is not representable via MemoryView");
|
|
954
|
+
}
|
|
955
|
+
ssize_t target_item_size = (ssize_t) ca_sizeof[explicit_data_type];
|
|
956
|
+
if (view->byte_size % target_item_size != 0) {
|
|
957
|
+
ssize_t bs = view->byte_size;
|
|
958
|
+
rb_memory_view_release(view);
|
|
959
|
+
rb_raise(rb_eArgError,
|
|
960
|
+
"byte_size %ld is not a multiple of target item_size %ld",
|
|
961
|
+
(long) bs, (long) target_item_size);
|
|
962
|
+
}
|
|
963
|
+
data_type = explicit_data_type;
|
|
964
|
+
*out_ndim = 1;
|
|
965
|
+
out_dim[0] = (ca_size_t) (view->byte_size / target_item_size);
|
|
966
|
+
*out_data_type = data_type;
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/* Typed producer: derive data_type from format. */
|
|
971
|
+
data_type = ca_mv_data_type_from_format(view->format, view->item_size);
|
|
972
|
+
if (data_type < 0) {
|
|
973
|
+
char fmt_buf[64];
|
|
974
|
+
snprintf(fmt_buf, sizeof(fmt_buf), "%s", view->format);
|
|
975
|
+
rb_memory_view_release(view);
|
|
976
|
+
rb_raise(rb_eArgError,
|
|
977
|
+
"unsupported MemoryView format: %s (item_size=%ld)",
|
|
978
|
+
fmt_buf, (long) view->item_size);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/* If the caller specified a data_type too, it must match. */
|
|
982
|
+
if (explicit_data_type >= 0 && explicit_data_type != data_type) {
|
|
983
|
+
rb_memory_view_release(view);
|
|
984
|
+
rb_raise(rb_eArgError,
|
|
985
|
+
"explicit data_type does not match producer format "
|
|
986
|
+
"(producer: %s, requested data_type %d)",
|
|
987
|
+
view->format, (int) explicit_data_type);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
if (view->ndim == 0) {
|
|
991
|
+
*out_ndim = 1;
|
|
992
|
+
out_dim[0] = 1;
|
|
993
|
+
}
|
|
994
|
+
else if (view->ndim > CA_RANK_MAX) {
|
|
995
|
+
long got = (long) view->ndim;
|
|
996
|
+
rb_memory_view_release(view);
|
|
997
|
+
rb_raise(rb_eArgError,
|
|
998
|
+
"MemoryView ndim (%ld) exceeds CArray CA_RANK_MAX (%d)",
|
|
999
|
+
got, CA_RANK_MAX);
|
|
1000
|
+
}
|
|
1001
|
+
else if (view->shape == NULL) {
|
|
1002
|
+
/* SIMPLE form: a typed producer may advertise a format but omit the
|
|
1003
|
+
shape array for a contiguous 1-D buffer (red-arrow's primitive
|
|
1004
|
+
arrays do this). Only 1-D is interpretable without a shape --
|
|
1005
|
+
length = byte_size / item_size. A missing shape at ndim > 1 is
|
|
1006
|
+
unrecoverable. */
|
|
1007
|
+
if (view->ndim > 1) {
|
|
1008
|
+
long got = (long) view->ndim;
|
|
1009
|
+
rb_memory_view_release(view);
|
|
1010
|
+
rb_raise(rb_eArgError,
|
|
1011
|
+
"MemoryView reports ndim %ld but provides no shape array",
|
|
1012
|
+
got);
|
|
1013
|
+
}
|
|
1014
|
+
*out_ndim = 1;
|
|
1015
|
+
out_dim[0] = (ca_size_t) (view->item_size ? view->byte_size / view->item_size : 0);
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
*out_ndim = (int8_t) view->ndim;
|
|
1019
|
+
for (i = 0; i < *out_ndim; i++) {
|
|
1020
|
+
out_dim[i] = (ca_size_t) view->shape[i];
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
*out_data_type = data_type;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/* Extract the optional data_type: and mask: keywords from argv.
|
|
1027
|
+
Returns the resolved data_type (-1 if not specified) and fills
|
|
1028
|
+
*out_obj / *out_mask. data_type: accepts symbols (:int32), integer
|
|
1029
|
+
constants (CA_INT32), and data_type marker classes (CArray::Int32
|
|
1030
|
+
etc.); marker classes are resolved via their `DataType` constant (set
|
|
1031
|
+
up by lib/carray/construct.rb), since rb_ca_guess_type does not
|
|
1032
|
+
natively recognise them. */
|
|
1033
|
+
static int8_t
|
|
1034
|
+
ca_mv_extract_type_and_mask_kwargs (int argc, VALUE *argv,
|
|
1035
|
+
VALUE *out_obj, VALUE *out_mask)
|
|
1036
|
+
{
|
|
1037
|
+
volatile VALUE robj = Qnil, ropt = Qnil, rtype = Qnil, rmask = Qnil;
|
|
1038
|
+
int8_t data_type = -1;
|
|
1039
|
+
ca_size_t bytes;
|
|
1040
|
+
rb_scan_args(argc, argv, "1:", (VALUE *) &robj, (VALUE *) &ropt);
|
|
1041
|
+
if (! NIL_P(ropt)) {
|
|
1042
|
+
rb_scan_options(ropt, "data_type,mask", &rtype, &rmask);
|
|
1043
|
+
}
|
|
1044
|
+
*out_obj = robj;
|
|
1045
|
+
*out_mask = rmask;
|
|
1046
|
+
if (! NIL_P(rtype)) {
|
|
1047
|
+
if (RB_TYPE_P(rtype, T_CLASS) &&
|
|
1048
|
+
rb_const_defined(rtype, rb_intern("DataType"))) {
|
|
1049
|
+
rtype = rb_const_get(rtype, rb_intern("DataType"));
|
|
1050
|
+
}
|
|
1051
|
+
rb_ca_guess_type_and_bytes(rtype, INT2NUM(0), &data_type, &bytes);
|
|
1052
|
+
}
|
|
1053
|
+
return data_type;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/* Acquire and validate a mask MemoryView against the data side's shape.
|
|
1057
|
+
Returns an allocated holder (caller owns; release via the imported
|
|
1058
|
+
holder TypedData dfree path) with view active on success.
|
|
1059
|
+
|
|
1060
|
+
On validation failure: releases the mask view, frees the mask holder,
|
|
1061
|
+
AND ALSO releases the supplied `data_view_to_release_on_fail` + frees
|
|
1062
|
+
the supplied `data_holder_to_free_on_fail` before raising. This
|
|
1063
|
+
avoids leaking the caller's already-acquired data side when mask
|
|
1064
|
+
validation aborts. Pass NULL for the data cleanup args if not
|
|
1065
|
+
applicable (e.g. mask validated for a stack-allocated data view in
|
|
1066
|
+
the copy path). */
|
|
1067
|
+
static ca_mv_imported_holder_t *
|
|
1068
|
+
ca_mv_acquire_mask_view (VALUE mask_src, int8_t data_ndim,
|
|
1069
|
+
const ca_size_t *data_dim,
|
|
1070
|
+
ca_mv_imported_holder_t *data_holder_to_free_on_fail,
|
|
1071
|
+
rb_memory_view_t *data_view_to_release_on_fail)
|
|
1072
|
+
{
|
|
1073
|
+
ca_mv_imported_holder_t *holder;
|
|
1074
|
+
rb_memory_view_t *mv;
|
|
1075
|
+
int i;
|
|
1076
|
+
|
|
1077
|
+
# define MV_MASK_CLEANUP_AND_RAISE(msg, ...) do { \
|
|
1078
|
+
if (holder) { \
|
|
1079
|
+
if (holder->valid) { rb_memory_view_release(&holder->view); } \
|
|
1080
|
+
xfree(holder); \
|
|
1081
|
+
} \
|
|
1082
|
+
if (data_view_to_release_on_fail) { \
|
|
1083
|
+
rb_memory_view_release(data_view_to_release_on_fail); \
|
|
1084
|
+
} \
|
|
1085
|
+
if (data_holder_to_free_on_fail) { \
|
|
1086
|
+
data_holder_to_free_on_fail->valid = false; \
|
|
1087
|
+
xfree(data_holder_to_free_on_fail); \
|
|
1088
|
+
} \
|
|
1089
|
+
rb_raise(rb_eArgError, msg, ##__VA_ARGS__); \
|
|
1090
|
+
} while (0)
|
|
1091
|
+
|
|
1092
|
+
holder = ALLOC(ca_mv_imported_holder_t);
|
|
1093
|
+
holder->valid = false;
|
|
1094
|
+
|
|
1095
|
+
/* Zero-init before get: same apache/arrow#45187 defense as the data
|
|
1096
|
+
path (release frees view->item_desc.components unconditionally). */
|
|
1097
|
+
memset(&holder->view, 0, sizeof(holder->view));
|
|
1098
|
+
|
|
1099
|
+
if (! rb_memory_view_get(mask_src, &holder->view, RUBY_MEMORY_VIEW_STRIDES)) {
|
|
1100
|
+
if (! rb_memory_view_get(mask_src, &holder->view, RUBY_MEMORY_VIEW_SIMPLE)) {
|
|
1101
|
+
MV_MASK_CLEANUP_AND_RAISE("mask: object does not support MemoryView");
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
holder->valid = true;
|
|
1105
|
+
mv = &holder->view;
|
|
1106
|
+
|
|
1107
|
+
if (mv->format != NULL) {
|
|
1108
|
+
char c = mv->format[0];
|
|
1109
|
+
/* skip a leading byte-order/native prefix '<>=!@' */
|
|
1110
|
+
if (c == '<' || c == '>' || c == '=' || c == '!' || c == '@') {
|
|
1111
|
+
c = mv->format[1];
|
|
1112
|
+
}
|
|
1113
|
+
if (c != '?' && c != 'B' && c != 'b') {
|
|
1114
|
+
char fmt_buf[64];
|
|
1115
|
+
snprintf(fmt_buf, sizeof(fmt_buf), "%s", mv->format);
|
|
1116
|
+
MV_MASK_CLEANUP_AND_RAISE("mask: format must be bool/uint8/int8 (got %s)",
|
|
1117
|
+
fmt_buf);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (mv->item_size != 1) {
|
|
1121
|
+
ssize_t got = mv->item_size;
|
|
1122
|
+
MV_MASK_CLEANUP_AND_RAISE("mask: item_size must be 1 (got %ld)",
|
|
1123
|
+
(long) got);
|
|
1124
|
+
}
|
|
1125
|
+
if (mv->ndim != data_ndim) {
|
|
1126
|
+
int got = (int) mv->ndim;
|
|
1127
|
+
MV_MASK_CLEANUP_AND_RAISE("mask: ndim (%d) does not match data ndim (%d)",
|
|
1128
|
+
got, (int) data_ndim);
|
|
1129
|
+
}
|
|
1130
|
+
if (mv->shape == NULL) {
|
|
1131
|
+
/* SIMPLE-form mask (contiguous 1-D, shape omitted). item_size is 1
|
|
1132
|
+
(checked above), so byte_size is the element count. */
|
|
1133
|
+
ca_size_t mlen = (ca_size_t) mv->byte_size;
|
|
1134
|
+
if (data_ndim != 1 || mlen != data_dim[0]) {
|
|
1135
|
+
long ms = (long) mlen, ds = (long) (data_ndim == 1 ? data_dim[0] : -1);
|
|
1136
|
+
MV_MASK_CLEANUP_AND_RAISE(
|
|
1137
|
+
"mask: length (%ld) does not match data shape (%ld)", ms, ds);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
else {
|
|
1141
|
+
for (i = 0; i < data_ndim; i++) {
|
|
1142
|
+
if ((ca_size_t) mv->shape[i] != data_dim[i]) {
|
|
1143
|
+
long ms = (long) mv->shape[i], ds = (long) data_dim[i];
|
|
1144
|
+
MV_MASK_CLEANUP_AND_RAISE(
|
|
1145
|
+
"mask: shape[%d] (%ld) does not match data shape (%ld)",
|
|
1146
|
+
i, ms, ds);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (! ca_mv_view_is_contiguous(mv)) {
|
|
1151
|
+
MV_MASK_CLEANUP_AND_RAISE(
|
|
1152
|
+
"mask: strided masks are not supported in this release; "
|
|
1153
|
+
"pass a row-major contiguous mask buffer");
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
# undef MV_MASK_CLEANUP_AND_RAISE
|
|
1157
|
+
return holder;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/* Copy a strided MemoryView into a contiguous row-major destination buffer.
|
|
1161
|
+
Walks indices in row-major order and reads from view->data using
|
|
1162
|
+
view->strides at each step. Assumes view->ndim >= 1, view->strides
|
|
1163
|
+
non-NULL, dst sized to hold elements * item_size. */
|
|
1164
|
+
static void
|
|
1165
|
+
ca_mv_copy_strided_to_contiguous (char *dst, const rb_memory_view_t *view,
|
|
1166
|
+
ca_size_t elements)
|
|
1167
|
+
{
|
|
1168
|
+
int8_t ndim = (int8_t) view->ndim;
|
|
1169
|
+
ssize_t item_size = view->item_size;
|
|
1170
|
+
ca_size_t idx[CA_RANK_MAX];
|
|
1171
|
+
ca_size_t n;
|
|
1172
|
+
int8_t k;
|
|
1173
|
+
|
|
1174
|
+
for (k = 0; k < ndim; k++) idx[k] = 0;
|
|
1175
|
+
|
|
1176
|
+
for (n = 0; n < elements; n++) {
|
|
1177
|
+
ssize_t src_off = 0;
|
|
1178
|
+
for (k = 0; k < ndim; k++) {
|
|
1179
|
+
src_off += (ssize_t) idx[k] * view->strides[k];
|
|
1180
|
+
}
|
|
1181
|
+
memcpy(dst, (const char *) view->data + src_off, (size_t) item_size);
|
|
1182
|
+
dst += item_size;
|
|
1183
|
+
/* increment row-major: last dim varies fastest */
|
|
1184
|
+
for (k = ndim - 1; k >= 0; k--) {
|
|
1185
|
+
idx[k]++;
|
|
1186
|
+
if (idx[k] < (ca_size_t) view->shape[k]) break;
|
|
1187
|
+
idx[k] = 0;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/* CArray.wrap_memory_view(obj, data_type: nil) -- zero-copy CAWrap.
|
|
1193
|
+
Rejects non-row-major-contiguous sources with a helpful pointer to
|
|
1194
|
+
from_memory_view; the contiguous-only contract keeps the boundary
|
|
1195
|
+
between Numo/CArray internal invariants clean (see the design note
|
|
1196
|
+
on strided wrap rejection).
|
|
1197
|
+
|
|
1198
|
+
When the producer is typeless (format=NULL), `data_type:` must be
|
|
1199
|
+
supplied; the byte buffer is reinterpreted as that data_type, ndim=1.
|
|
1200
|
+
|
|
1201
|
+
The receiver picks the class of the result. Called on CArray itself
|
|
1202
|
+
it builds a CAWrap, as it always has; called on a subclass of CAWrap
|
|
1203
|
+
it builds that subclass, so a gem bridging a foreign buffer can name
|
|
1204
|
+
where the array came from without writing a C extension of its own.
|
|
1205
|
+
The class marks the provenance of this object only -- a view derived
|
|
1206
|
+
from it is a CABlock or a CAStride like any other. */
|
|
1207
|
+
static VALUE
|
|
1208
|
+
rb_ca_s_wrap_memory_view (int argc, VALUE *argv, VALUE klass)
|
|
1209
|
+
{
|
|
1210
|
+
ca_mv_imported_holder_t *holder;
|
|
1211
|
+
ca_mv_imported_holder_t *mask_holder = NULL;
|
|
1212
|
+
rb_memory_view_t *v;
|
|
1213
|
+
int8_t data_type, ndim;
|
|
1214
|
+
ca_size_t dim[CA_RANK_MAX];
|
|
1215
|
+
VALUE wrap, holder_obj, obj, mask_obj, target_class;
|
|
1216
|
+
int8_t explicit_data_type =
|
|
1217
|
+
ca_mv_extract_type_and_mask_kwargs(argc, argv,
|
|
1218
|
+
(VALUE *) &obj, (VALUE *) &mask_obj);
|
|
1219
|
+
|
|
1220
|
+
/* Resolve the class before anything is acquired, so this rejection
|
|
1221
|
+
needs no cleanup. CArray means CAWrap; any other receiver has to
|
|
1222
|
+
be a CAWrap the caller can actually be handed. */
|
|
1223
|
+
target_class = ( klass == rb_cCArray ) ? rb_cCAWrap : klass;
|
|
1224
|
+
if ( ! RTEST(rb_class_inherited_p(target_class, rb_cCAWrap)) ) {
|
|
1225
|
+
rb_raise(rb_eTypeError,
|
|
1226
|
+
"wrap_memory_view builds a CAWrap; %s is not a subclass of it",
|
|
1227
|
+
rb_class2name(target_class));
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
holder = ALLOC(ca_mv_imported_holder_t);
|
|
1231
|
+
holder->valid = false;
|
|
1232
|
+
|
|
1233
|
+
ca_mv_acquire_and_validate(obj, &holder->view, explicit_data_type,
|
|
1234
|
+
&data_type, &ndim, dim);
|
|
1235
|
+
holder->valid = true;
|
|
1236
|
+
v = &holder->view;
|
|
1237
|
+
|
|
1238
|
+
/* Whether the producer is strided is only knowable once the view is
|
|
1239
|
+
acquired, and a strided one is returned as a CAStride wrapping an
|
|
1240
|
+
inner CAWrap -- so a caller who asked for a class would get back
|
|
1241
|
+
something that is not it. Refuse instead, and release first: this
|
|
1242
|
+
runs before the mask is acquired, so the holder is all there is to
|
|
1243
|
+
free. */
|
|
1244
|
+
if ( target_class != rb_cCAWrap &&
|
|
1245
|
+
v->ndim >= 1 && v->format != NULL && ! ca_mv_view_is_contiguous(v) ) {
|
|
1246
|
+
rb_memory_view_release(v);
|
|
1247
|
+
holder->valid = false;
|
|
1248
|
+
xfree(holder);
|
|
1249
|
+
rb_raise(rb_eArgError,
|
|
1250
|
+
"%s was requested, but this producer is strided, so the result "
|
|
1251
|
+
"would be a CAStride; pass a row-major contiguous producer, or "
|
|
1252
|
+
"use CArray.from_memory_view for a copy",
|
|
1253
|
+
rb_class2name(target_class));
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/* mask: paired buffer. Acquire + validate now (before building the
|
|
1257
|
+
wrap). On failure the helper releases both the mask view AND the
|
|
1258
|
+
already-acquired data view + holder, then raises. */
|
|
1259
|
+
if (! NIL_P(mask_obj)) {
|
|
1260
|
+
mask_holder = ca_mv_acquire_mask_view(mask_obj, ndim, dim,
|
|
1261
|
+
holder, &holder->view);
|
|
1262
|
+
/* Strided data side combined with mask: is not supported in this
|
|
1263
|
+
release. Catch this AFTER mask acquisition so the cleanup path can release
|
|
1264
|
+
both views uniformly. */
|
|
1265
|
+
if (v->ndim >= 1 && v->format != NULL &&
|
|
1266
|
+
! ca_mv_view_is_contiguous(v)) {
|
|
1267
|
+
rb_memory_view_release(&mask_holder->view);
|
|
1268
|
+
mask_holder->valid = false;
|
|
1269
|
+
xfree(mask_holder);
|
|
1270
|
+
rb_memory_view_release(v);
|
|
1271
|
+
holder->valid = false;
|
|
1272
|
+
xfree(holder);
|
|
1273
|
+
rb_raise(rb_eArgError,
|
|
1274
|
+
"mask: combining mask: with a strided data source is not "
|
|
1275
|
+
"supported in this release; pass a row-major contiguous "
|
|
1276
|
+
"data source");
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/* Strided producer: build a CAStride on top of an inner CAWrap that
|
|
1281
|
+
anchors the foreign memory. The inner CAWrap holds the MemoryView
|
|
1282
|
+
lifecycle (memory_view_source + memory_view_holder ivars); the
|
|
1283
|
+
outer CAStride is what the user sees, with full strides
|
|
1284
|
+
(including negative) preserved. */
|
|
1285
|
+
if (v->ndim >= 1 && v->format != NULL &&
|
|
1286
|
+
! ca_mv_view_is_contiguous(v)) {
|
|
1287
|
+
ca_size_t inner_dim[1] = { 1 };
|
|
1288
|
+
volatile VALUE inner_wrap;
|
|
1289
|
+
ca_size_t strides_arr[CA_RANK_MAX];
|
|
1290
|
+
int i;
|
|
1291
|
+
|
|
1292
|
+
inner_wrap = rb_ca_wrap_new(data_type, 1, inner_dim,
|
|
1293
|
+
ca_mv_carray_bytes_for(data_type, v->item_size),
|
|
1294
|
+
NULL, (char *) v->data);
|
|
1295
|
+
rb_ivar_set(inner_wrap, rb_intern("memory_view_source"), obj);
|
|
1296
|
+
holder_obj = TypedData_Wrap_Struct(rb_cObject,
|
|
1297
|
+
&ca_mv_imported_holder_data_type,
|
|
1298
|
+
holder);
|
|
1299
|
+
rb_ivar_set(inner_wrap, rb_intern("memory_view_holder"), holder_obj);
|
|
1300
|
+
if (v->readonly) {
|
|
1301
|
+
CArray *iw;
|
|
1302
|
+
if (ca_mv_extract(inner_wrap, &iw)) {
|
|
1303
|
+
ca_set_flag(iw, CA_FLAG_READ_ONLY);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
for (i = 0; i < ndim; i++) {
|
|
1307
|
+
strides_arr[i] = (ca_size_t) v->strides[i];
|
|
1308
|
+
}
|
|
1309
|
+
{
|
|
1310
|
+
VALUE result =
|
|
1311
|
+
rb_ca_stride_new(inner_wrap, data_type,
|
|
1312
|
+
ca_mv_carray_bytes_for(data_type, v->item_size),
|
|
1313
|
+
ndim, dim, strides_arr, /* base_offset */ 0);
|
|
1314
|
+
if (v->readonly) {
|
|
1315
|
+
CArray *rs;
|
|
1316
|
+
if (ca_mv_extract(result, &rs)) {
|
|
1317
|
+
ca_set_flag(rs, CA_FLAG_READ_ONLY);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return result;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/* Same struct rb_ca_wrap_new would build, wrapped in the class the
|
|
1325
|
+
receiver asked for. Everything downstream -- the holder and source
|
|
1326
|
+
ivars, the read-only flag, the borrowed mask -- is untouched, which
|
|
1327
|
+
is the point: those are what a gem building its own wrap by hand
|
|
1328
|
+
has to give up. */
|
|
1329
|
+
wrap = ca_wrap_struct_as(
|
|
1330
|
+
ca_wrap_new(data_type, ndim, dim,
|
|
1331
|
+
ca_mv_carray_bytes_for(data_type, v->item_size),
|
|
1332
|
+
NULL, (char *) v->data),
|
|
1333
|
+
target_class);
|
|
1334
|
+
|
|
1335
|
+
rb_ivar_set(wrap, rb_intern("memory_view_source"), obj);
|
|
1336
|
+
holder_obj = TypedData_Wrap_Struct(rb_cObject,
|
|
1337
|
+
&ca_mv_imported_holder_data_type,
|
|
1338
|
+
holder);
|
|
1339
|
+
rb_ivar_set(wrap, rb_intern("memory_view_holder"), holder_obj);
|
|
1340
|
+
|
|
1341
|
+
if (v->readonly) {
|
|
1342
|
+
CArray *ca;
|
|
1343
|
+
if (ca_mv_extract(wrap, &ca)) {
|
|
1344
|
+
ca_set_flag(ca, CA_FLAG_READ_ONLY);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/* Attach borrowed mask: build a CAWrap over the mask buffer and assign
|
|
1349
|
+
it to wrap->mask. Anchor the mask MV's source + holder on the same
|
|
1350
|
+
wrap object so the lifecycle is co-terminal with the data side.
|
|
1351
|
+
When `wrap` is collected: free_ca_wrap recurses into ca->mask (frees
|
|
1352
|
+
the mask wrap struct, leaves its borrowed ptr alone); the holder
|
|
1353
|
+
ivars become unreachable and are GC'd separately, each holder.dfree
|
|
1354
|
+
calling rb_memory_view_release. */
|
|
1355
|
+
if (mask_holder != NULL) {
|
|
1356
|
+
CArray *data_ca, *mask_ca;
|
|
1357
|
+
rb_memory_view_t *mv = &mask_holder->view;
|
|
1358
|
+
VALUE mask_holder_obj;
|
|
1359
|
+
if (! ca_mv_extract(wrap, &data_ca)) {
|
|
1360
|
+
rb_memory_view_release(mv);
|
|
1361
|
+
mask_holder->valid = false;
|
|
1362
|
+
xfree(mask_holder);
|
|
1363
|
+
rb_raise(rb_eRuntimeError,
|
|
1364
|
+
"internal: rb_ca_wrap_new did not return a CArray");
|
|
1365
|
+
}
|
|
1366
|
+
mask_ca = (CArray *) ca_wrap_new(CA_BOOLEAN, ndim, dim, 1, NULL,
|
|
1367
|
+
(char *) mv->data);
|
|
1368
|
+
ca_set_flag(mask_ca, CA_FLAG_MASK_ARRAY);
|
|
1369
|
+
if (mv->readonly) {
|
|
1370
|
+
ca_set_flag(mask_ca, CA_FLAG_READ_ONLY);
|
|
1371
|
+
}
|
|
1372
|
+
data_ca->mask = mask_ca;
|
|
1373
|
+
|
|
1374
|
+
rb_ivar_set(wrap, rb_intern("mask_memory_view_source"), mask_obj);
|
|
1375
|
+
mask_holder_obj = TypedData_Wrap_Struct(rb_cObject,
|
|
1376
|
+
&ca_mv_imported_holder_data_type,
|
|
1377
|
+
mask_holder);
|
|
1378
|
+
rb_ivar_set(wrap, rb_intern("mask_memory_view_holder"), mask_holder_obj);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
return wrap;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/* CArray.from_memory_view(obj, data_type: nil) -- copy into an independent CArray.
|
|
1385
|
+
Accepts strided typed sources (gather copy) and typeless sources
|
|
1386
|
+
(must be 1D contiguous; bytes reinterpreted as `data_type:`). */
|
|
1387
|
+
static VALUE
|
|
1388
|
+
rb_ca_s_from_memory_view (int argc, VALUE *argv, VALUE klass)
|
|
1389
|
+
{
|
|
1390
|
+
rb_memory_view_t view;
|
|
1391
|
+
ca_mv_imported_holder_t *mask_holder = NULL;
|
|
1392
|
+
int8_t data_type, ndim;
|
|
1393
|
+
ca_size_t dim[CA_RANK_MAX];
|
|
1394
|
+
VALUE result, obj, mask_obj;
|
|
1395
|
+
CArray *ca;
|
|
1396
|
+
bool contig;
|
|
1397
|
+
int8_t explicit_data_type =
|
|
1398
|
+
ca_mv_extract_type_and_mask_kwargs(argc, argv,
|
|
1399
|
+
(VALUE *) &obj, (VALUE *) &mask_obj);
|
|
1400
|
+
|
|
1401
|
+
ca_mv_acquire_and_validate(obj, &view, explicit_data_type,
|
|
1402
|
+
&data_type, &ndim, dim);
|
|
1403
|
+
|
|
1404
|
+
/* mask: acquire + validate now so failures clean up the data view too. */
|
|
1405
|
+
if (! NIL_P(mask_obj)) {
|
|
1406
|
+
mask_holder = ca_mv_acquire_mask_view(mask_obj, ndim, dim,
|
|
1407
|
+
NULL, &view);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
result = rb_carray_new(data_type, ndim, dim,
|
|
1411
|
+
ca_mv_carray_bytes_for(data_type, view.item_size),
|
|
1412
|
+
NULL);
|
|
1413
|
+
if (! ca_mv_extract(result, &ca)) {
|
|
1414
|
+
rb_memory_view_release(&view);
|
|
1415
|
+
rb_raise(rb_eRuntimeError, "internal: rb_carray_new did not return a CArray");
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/* Typeless producers (format == NULL) and SIMPLE-form producers (shape
|
|
1419
|
+
omitted) are 1-D contiguous by construction; ca_mv_view_is_contiguous
|
|
1420
|
+
handles the NULL shape that would crash the raw contiguity helper. */
|
|
1421
|
+
contig = (view.format == NULL) || ca_mv_view_is_contiguous(&view);
|
|
1422
|
+
|
|
1423
|
+
if (contig) {
|
|
1424
|
+
size_t total_bytes = (size_t) ca_length(ca);
|
|
1425
|
+
/* For typeless producers byte_size and total_bytes are equal by
|
|
1426
|
+
construction; for typed producers we already validated alignment. */
|
|
1427
|
+
if (view.byte_size < (ssize_t) total_bytes) {
|
|
1428
|
+
rb_memory_view_release(&view);
|
|
1429
|
+
if (mask_holder) {
|
|
1430
|
+
rb_memory_view_release(&mask_holder->view);
|
|
1431
|
+
mask_holder->valid = false;
|
|
1432
|
+
xfree(mask_holder);
|
|
1433
|
+
}
|
|
1434
|
+
rb_raise(rb_eRuntimeError,
|
|
1435
|
+
"internal: byte_size mismatch (carray=%lu, view=%ld)",
|
|
1436
|
+
(unsigned long) total_bytes, (long) view.byte_size);
|
|
1437
|
+
}
|
|
1438
|
+
memcpy(ca->ptr, view.data, total_bytes);
|
|
1439
|
+
}
|
|
1440
|
+
else {
|
|
1441
|
+
/* Strided typed source: walk indices in row-major order and gather. */
|
|
1442
|
+
ca_mv_copy_strided_to_contiguous(ca->ptr, &view, ca->elements);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
rb_memory_view_release(&view);
|
|
1446
|
+
|
|
1447
|
+
/* mask: materialise the canonical CA_BOOLEAN mask slot via ca_create_mask,
|
|
1448
|
+
then memcpy bytes from the (validated row-major contig 1-byte/elem) mask
|
|
1449
|
+
buffer. Source format ? / B / b all use byte values where any non-zero
|
|
1450
|
+
means masked; that semantic matches CArray's mask convention directly
|
|
1451
|
+
when we coerce non-zero to 1. */
|
|
1452
|
+
if (mask_holder) {
|
|
1453
|
+
rb_memory_view_t *mv = &mask_holder->view;
|
|
1454
|
+
size_t n = (size_t) ca->elements;
|
|
1455
|
+
size_t i;
|
|
1456
|
+
const uint8_t *src = (const uint8_t *) mv->data;
|
|
1457
|
+
boolean8_t *dst;
|
|
1458
|
+
ca_create_mask(ca);
|
|
1459
|
+
dst = (boolean8_t *) ca->mask->ptr;
|
|
1460
|
+
for (i = 0; i < n; i++) {
|
|
1461
|
+
dst[i] = (src[i] != 0) ? 1 : 0;
|
|
1462
|
+
}
|
|
1463
|
+
rb_memory_view_release(mv);
|
|
1464
|
+
mask_holder->valid = false;
|
|
1465
|
+
xfree(mask_holder);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
return result;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/* ---------------- Ruby method ---------------- */
|
|
1472
|
+
|
|
1473
|
+
static VALUE
|
|
1474
|
+
rb_ca_s_memory_view_available_p (VALUE klass, VALUE obj)
|
|
1475
|
+
{
|
|
1476
|
+
return rb_memory_view_available_p(obj) ? Qtrue : Qfalse;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/* Diagnostic: if memory_view_available? returns false on a CArray,
|
|
1480
|
+
why? Returns nil (no problem detected) or a String explaining
|
|
1481
|
+
the offending link in the parent chain. Useful for users hitting
|
|
1482
|
+
the alias-chain reject who can't tell from the generic
|
|
1483
|
+
"memory view not available" error what specifically went wrong. */
|
|
1484
|
+
static VALUE
|
|
1485
|
+
rb_ca_s_memory_view_reject_reason (VALUE klass, VALUE obj)
|
|
1486
|
+
{
|
|
1487
|
+
CArray *ca;
|
|
1488
|
+
ca_mv_reject_t rej;
|
|
1489
|
+
if (! ca_mv_extract(obj, &ca)) {
|
|
1490
|
+
return rb_str_new_cstr("not a CArray");
|
|
1491
|
+
}
|
|
1492
|
+
if (! ca_mv_ca_exportable(ca, obj)) {
|
|
1493
|
+
if (ca->data_type == CA_FIXLEN && RTEST(rb_ca_has_data_class(obj))) {
|
|
1494
|
+
return rb_str_new_cstr("struct data_class contains a member type the MV "
|
|
1495
|
+
"producer cannot express yet (nested struct, "
|
|
1496
|
+
"CArray template, :fixlen, or :object)");
|
|
1497
|
+
}
|
|
1498
|
+
return rb_sprintf("element data_type (%d) is not exportable via memory_view "
|
|
1499
|
+
"(CA_FIXLEN without data_class / CA_OBJECT)",
|
|
1500
|
+
ca->data_type);
|
|
1501
|
+
}
|
|
1502
|
+
if (ca_has_mask(ca)) {
|
|
1503
|
+
return rb_str_new_cstr("array has a mask; use arr.value (mask-ignoring CARefer) "
|
|
1504
|
+
"or arr.strip_mask(fill) first");
|
|
1505
|
+
}
|
|
1506
|
+
if (! ca_mv_check_alias_chain(ca, &rej)) {
|
|
1507
|
+
const char *where;
|
|
1508
|
+
if (rej.depth == 0) where = "this view";
|
|
1509
|
+
else if (rej.depth == 1) where = "parent";
|
|
1510
|
+
else where = NULL; /* ancestor[N] */
|
|
1511
|
+
if (where) {
|
|
1512
|
+
return rb_sprintf("%s is %s (%s); zero-copy wrap not possible. "
|
|
1513
|
+
"Use CArray.from_memory_view(arr) or arr.to_ca for a snapshot.",
|
|
1514
|
+
where, rej.class_name, rej.reason);
|
|
1515
|
+
} else {
|
|
1516
|
+
return rb_sprintf("ancestor[%d] is %s (%s); zero-copy wrap not possible. "
|
|
1517
|
+
"Use CArray.from_memory_view(arr) or arr.to_ca for a snapshot.",
|
|
1518
|
+
rej.depth - 1, rej.class_name, rej.reason);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return Qnil;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/* Test hook: expose the format-string parser directly so the consumer
|
|
1525
|
+
table (see docs/interop/MemoryViewFormat.md) can be exercised without needing
|
|
1526
|
+
a producer that emits every synonym. Returns the CArray data_type
|
|
1527
|
+
integer on accept, or nil on reject. */
|
|
1528
|
+
static VALUE
|
|
1529
|
+
rb_ca_s_parse_memory_view_format (VALUE klass, VALUE fmt_v, VALUE item_size_v)
|
|
1530
|
+
{
|
|
1531
|
+
const char *fmt = NIL_P(fmt_v) ? NULL : StringValueCStr(fmt_v);
|
|
1532
|
+
ssize_t item_size = (ssize_t) NUM2LL(item_size_v);
|
|
1533
|
+
int8_t dt;
|
|
1534
|
+
if (item_size <= 0) return Qnil;
|
|
1535
|
+
dt = ca_mv_data_type_from_format(fmt, item_size);
|
|
1536
|
+
if (dt < 0) return Qnil;
|
|
1537
|
+
/* Return Symbol so the test hook output compares directly against
|
|
1538
|
+
CA_* (Symbol) constants. */
|
|
1539
|
+
return rb_ca_data_type_to_sym(dt);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/* Test hook: return the MV format string the producer would emit
|
|
1543
|
+
for `obj`, without actually building the rb_memory_view_t. Used
|
|
1544
|
+
by spec_ai to verify PEP 3118 struct format emission for CAStruct
|
|
1545
|
+
arrays. Returns nil if obj isn't exportable. */
|
|
1546
|
+
static VALUE
|
|
1547
|
+
rb_ca_s_memory_view_format (VALUE klass, VALUE obj)
|
|
1548
|
+
{
|
|
1549
|
+
CArray *ca;
|
|
1550
|
+
if (! ca_mv_extract(obj, &ca)) return Qnil;
|
|
1551
|
+
if (! ca_mv_ca_exportable(ca, obj)) return Qnil;
|
|
1552
|
+
if (ca->data_type == CA_FIXLEN && RTEST(rb_ca_has_data_class(obj))) {
|
|
1553
|
+
return ca_mv_struct_format_for_data_class(rb_ca_data_class(obj));
|
|
1554
|
+
}
|
|
1555
|
+
if (ca->data_type == CA_FIXLEN) {
|
|
1556
|
+
/* Plain CA_FIXLEN: emit "Ns" via the same caching scheme used by
|
|
1557
|
+
the real producer (ca_mv_get). Reusing the cache keeps the
|
|
1558
|
+
returned String identity-equal to the one the producer hands out
|
|
1559
|
+
via view->format, which test code may rely on. */
|
|
1560
|
+
VALUE fmt_str = rb_attr_get(obj, rb_intern("__mv_fixlen_format__"));
|
|
1561
|
+
if (NIL_P(fmt_str)) {
|
|
1562
|
+
char fmt_buf[32];
|
|
1563
|
+
snprintf(fmt_buf, sizeof(fmt_buf), "%lds", (long) ca->bytes);
|
|
1564
|
+
fmt_str = rb_str_new_cstr(fmt_buf);
|
|
1565
|
+
rb_obj_freeze(fmt_str);
|
|
1566
|
+
rb_ivar_set(obj, rb_intern("__mv_fixlen_format__"), fmt_str);
|
|
1567
|
+
}
|
|
1568
|
+
return fmt_str;
|
|
1569
|
+
}
|
|
1570
|
+
const char *fmt = ca_mv_format_for(ca->data_type);
|
|
1571
|
+
return fmt ? rb_str_new_cstr(fmt) : Qnil;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/* ---------------- Init ---------------- */
|
|
1575
|
+
|
|
1576
|
+
static const rb_memory_view_entry_t ca_memory_view_entry = {
|
|
1577
|
+
.get_func = ca_mv_get,
|
|
1578
|
+
.release_func = ca_mv_release,
|
|
1579
|
+
.available_p_func = ca_mv_available_p,
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
void
|
|
1583
|
+
Init_carray_memory_view (void)
|
|
1584
|
+
{
|
|
1585
|
+
/* Statically known classes. */
|
|
1586
|
+
rb_memory_view_register(rb_cCArray, &ca_memory_view_entry);
|
|
1587
|
+
rb_memory_view_register(rb_cCAWrap, &ca_memory_view_entry);
|
|
1588
|
+
rb_memory_view_register(rb_cCScalar, &ca_memory_view_entry);
|
|
1589
|
+
rb_memory_view_register(rb_cCARefer, &ca_memory_view_entry);
|
|
1590
|
+
rb_memory_view_register(rb_cCABlock, &ca_memory_view_entry);
|
|
1591
|
+
rb_memory_view_register(rb_cCASelect, &ca_memory_view_entry);
|
|
1592
|
+
rb_memory_view_register(rb_cCARepeat, &ca_memory_view_entry);
|
|
1593
|
+
|
|
1594
|
+
/* Runtime-assigned classes: resolve via Ruby constants. */
|
|
1595
|
+
for (int i = 0; i < CA_MV_NUM_RUNTIME_OBJ_TYPES; i++) {
|
|
1596
|
+
ca_mv_runtime_type_t *rt = &ca_mv_runtime_types[i];
|
|
1597
|
+
ID class_id = rb_intern(rt->class_name);
|
|
1598
|
+
ID const_id = rb_intern(rt->const_name);
|
|
1599
|
+
if (rb_const_defined(rb_cObject, class_id)) {
|
|
1600
|
+
rt->klass = rb_const_get(rb_cObject, class_id);
|
|
1601
|
+
if (rt->strategy != CA_MV_REJECT) {
|
|
1602
|
+
rb_memory_view_register(rt->klass, &ca_memory_view_entry);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (rb_const_defined(rb_cObject, const_id)) {
|
|
1606
|
+
rt->id = NUM2INT(rb_const_get(rb_cObject, const_id));
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
rb_define_singleton_method(rb_cCArray, "memory_view_available?",
|
|
1611
|
+
rb_ca_s_memory_view_available_p, 1);
|
|
1612
|
+
rb_define_singleton_method(rb_cCArray, "memory_view_reject_reason",
|
|
1613
|
+
rb_ca_s_memory_view_reject_reason, 1);
|
|
1614
|
+
rb_define_singleton_method(rb_cCArray, "from_memory_view",
|
|
1615
|
+
rb_ca_s_from_memory_view, -1);
|
|
1616
|
+
rb_define_singleton_method(rb_cCArray, "wrap_memory_view",
|
|
1617
|
+
rb_ca_s_wrap_memory_view, -1);
|
|
1618
|
+
/* Test hooks (used by spec_ai/test_format_parser.rb and
|
|
1619
|
+
spec_ai/test_mv_struct_format.rb). Private API; names start
|
|
1620
|
+
with __ to discourage external use. */
|
|
1621
|
+
rb_define_singleton_method(rb_cCArray, "__memory_view_parse_format__",
|
|
1622
|
+
rb_ca_s_parse_memory_view_format, 2);
|
|
1623
|
+
rb_define_singleton_method(rb_cCArray, "__memory_view_format__",
|
|
1624
|
+
rb_ca_s_memory_view_format, 1);
|
|
1625
|
+
}
|