convolver 2.0.0 → 4.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.
@@ -2,10 +2,33 @@
2
2
  #include <numo/narray.h>
3
3
  #include <numo/intern.h>
4
4
 
5
+ #include "correlate_raw.h"
5
6
  #include "convolve_raw.h"
7
+ #include "fft_shape.h"
6
8
 
7
9
  static VALUE mConvolver;
8
10
 
11
+ /* Contiguous views share their backing storage but start at a byte offset. */
12
+ static const void *logical_pointer_for_read(VALUE value) {
13
+ return (const void *)(na_get_pointer_for_read(value) + na_get_offset(value));
14
+ }
15
+
16
+ static VALUE contiguous_float(VALUE value) {
17
+ narray_t *narray;
18
+
19
+ GetNArray(value, narray);
20
+ /* A scalar has no strides; Numo's contiguous check requires an axis. */
21
+ if (narray->ndim > 0 && !RTEST(na_check_contiguous(value))) {
22
+ value = na_copy(value);
23
+ }
24
+ return value;
25
+ }
26
+
27
+ enum convolver_operation {
28
+ CONVOLVER_CORRELATION,
29
+ CONVOLVER_CONVOLUTION
30
+ };
31
+
9
32
  static void copy_shape(int rank, const size_t *source, size_t *target) {
10
33
  int i;
11
34
 
@@ -14,15 +37,7 @@ static void copy_shape(int rank, const size_t *source, size_t *target) {
14
37
  }
15
38
  }
16
39
 
17
- /*
18
- * Calculates a valid cross-correlation using the direct native implementation.
19
- *
20
- * @overload convolve_basic_valid(signal, kernel)
21
- * @param signal [Numo::NArray] input values
22
- * @param kernel [Numo::NArray] correlation kernel
23
- * @return [Numo::SFloat] valid cross-correlation result
24
- */
25
- static VALUE convolver_convolve_basic_valid(VALUE self, VALUE signal, VALUE kernel) {
40
+ static VALUE convolver_basic_valid(VALUE signal, VALUE kernel, enum convolver_operation operation) {
26
41
  volatile VALUE signal_value;
27
42
  volatile VALUE kernel_value;
28
43
  volatile VALUE result_value;
@@ -35,20 +50,17 @@ static VALUE convolver_convolve_basic_valid(VALUE self, VALUE signal, VALUE kern
35
50
  size_t result_shape[LARGEST_RANK];
36
51
  size_t numo_result_shape[LARGEST_RANK];
37
52
 
38
- (void)self;
39
-
40
53
  if (!rb_obj_is_kind_of(signal, numo_cNArray) || !rb_obj_is_kind_of(kernel, numo_cNArray)) {
41
54
  rb_raise(rb_eArgError, "signal and kernel must be Numo::NArray values");
42
55
  }
43
56
 
44
- signal_value = rb_funcall(numo_cSFloat, rb_intern("cast"), 1, signal);
45
- kernel_value = rb_funcall(numo_cSFloat, rb_intern("cast"), 1, kernel);
46
- if (!RTEST(na_check_contiguous(signal_value))) {
47
- signal_value = na_copy(signal_value);
48
- }
49
- if (!RTEST(na_check_contiguous(kernel_value))) {
50
- kernel_value = na_copy(kernel_value);
57
+ VALUE dtype = CLASS_OF(signal);
58
+ if ((dtype != numo_cSFloat && dtype != numo_cDFloat) || CLASS_OF(kernel) != dtype) {
59
+ rb_raise(rb_eArgError, "native inputs must have matching SFloat or DFloat dtypes");
51
60
  }
61
+
62
+ signal_value = contiguous_float(signal);
63
+ kernel_value = contiguous_float(kernel);
52
64
  GetNArray(signal_value, signal_narray);
53
65
  GetNArray(kernel_value, kernel_narray);
54
66
 
@@ -75,17 +87,71 @@ static VALUE convolver_convolve_basic_valid(VALUE self, VALUE signal, VALUE kern
75
87
  numo_result_shape[rank - i - 1] = (size_t)result_shape[i];
76
88
  }
77
89
 
78
- result_value = nary_new(numo_cSFloat, rank, numo_result_shape);
79
- convolve_raw(
80
- rank, signal_shape, (float *)na_get_pointer_for_read(signal_value),
81
- rank, kernel_shape, (float *)na_get_pointer_for_read(kernel_value),
82
- rank, result_shape, (float *)na_get_pointer_for_write(result_value)
83
- );
90
+ result_value = nary_new(dtype, rank, numo_result_shape);
91
+ if (dtype == numo_cSFloat) {
92
+ if (operation == CONVOLVER_CORRELATION) {
93
+ correlate_raw(
94
+ rank, signal_shape, logical_pointer_for_read(signal_value),
95
+ rank, kernel_shape, logical_pointer_for_read(kernel_value),
96
+ rank, result_shape, (float *)na_get_pointer_for_write(result_value)
97
+ );
98
+ } else {
99
+ convolve_raw(
100
+ rank, signal_shape, logical_pointer_for_read(signal_value),
101
+ rank, kernel_shape, logical_pointer_for_read(kernel_value),
102
+ rank, result_shape, (float *)na_get_pointer_for_write(result_value)
103
+ );
104
+ }
105
+ } else {
106
+ if (operation == CONVOLVER_CORRELATION) {
107
+ correlate_raw_double(
108
+ rank, signal_shape, logical_pointer_for_read(signal_value),
109
+ rank, kernel_shape, logical_pointer_for_read(kernel_value),
110
+ rank, result_shape, (double *)na_get_pointer_for_write(result_value)
111
+ );
112
+ } else {
113
+ convolve_raw_double(
114
+ rank, signal_shape, logical_pointer_for_read(signal_value),
115
+ rank, kernel_shape, logical_pointer_for_read(kernel_value),
116
+ rank, result_shape, (double *)na_get_pointer_for_write(result_value)
117
+ );
118
+ }
119
+ }
84
120
 
85
121
  return result_value;
86
122
  }
87
123
 
124
+ /*
125
+ * Calculates a valid cross-correlation using the direct native implementation.
126
+ *
127
+ * @private
128
+ * @overload correlate_basic_valid(signal, kernel)
129
+ * @param signal [Numo::NArray] input values
130
+ * @param kernel [Numo::NArray] correlation kernel
131
+ * @return [Numo::SFloat, Numo::DFloat] valid cross-correlation result
132
+ */
133
+ static VALUE convolver_correlate_basic_valid(VALUE self, VALUE signal, VALUE kernel) {
134
+ (void)self;
135
+ return convolver_basic_valid(signal, kernel, CONVOLVER_CORRELATION);
136
+ }
137
+
138
+ /*
139
+ * Calculates a valid mathematical convolution using the direct native implementation.
140
+ *
141
+ * @private
142
+ * @overload convolve_basic_valid(signal, kernel)
143
+ * @param signal [Numo::NArray] input values
144
+ * @param kernel [Numo::NArray] convolution kernel
145
+ * @return [Numo::SFloat, Numo::DFloat] valid mathematical convolution result
146
+ */
147
+ static VALUE convolver_convolve_basic_valid(VALUE self, VALUE signal, VALUE kernel) {
148
+ (void)self;
149
+ return convolver_basic_valid(signal, kernel, CONVOLVER_CONVOLUTION);
150
+ }
151
+
88
152
  void Init_convolver(void) {
89
153
  mConvolver = rb_define_module("Convolver");
154
+ convolver_init_fft_shape(mConvolver);
155
+ rb_define_singleton_method(mConvolver, "correlate_basic_valid", convolver_correlate_basic_valid, 2);
90
156
  rb_define_singleton_method(mConvolver, "convolve_basic_valid", convolver_convolve_basic_valid, 2);
91
157
  }
@@ -0,0 +1,57 @@
1
+ #include "correlate_raw.h"
2
+ #include "raw_helpers.h"
3
+
4
+ #define RAW_INDEX(j) (j)
5
+ #if defined(__SSE__)
6
+ #define RAW_SIMD 1
7
+ #else
8
+ #define RAW_SIMD 0
9
+ #endif
10
+ #define RAW_NAME correlate_raw
11
+ #define RAW_TYPE float
12
+ #define RAW_LANES 4
13
+ #define RAW_VECTOR __m128
14
+ #define RAW_ZERO _mm_setzero_ps
15
+ #define RAW_LOAD _mm_loadu_ps
16
+ #define RAW_STORE _mm_storeu_ps
17
+ #define RAW_ADD _mm_add_ps
18
+ #define RAW_MUL _mm_mul_ps
19
+ #include "raw_loop.h"
20
+ #undef RAW_NAME
21
+ #undef RAW_TYPE
22
+ #undef RAW_LANES
23
+ #undef RAW_VECTOR
24
+ #undef RAW_ZERO
25
+ #undef RAW_LOAD
26
+ #undef RAW_STORE
27
+ #undef RAW_ADD
28
+ #undef RAW_MUL
29
+ #undef RAW_SIMD
30
+
31
+ #if defined(__SSE2__)
32
+ #define RAW_SIMD 1
33
+ #else
34
+ #define RAW_SIMD 0
35
+ #endif
36
+ #define RAW_NAME correlate_raw_double
37
+ #define RAW_TYPE double
38
+ #define RAW_LANES 2
39
+ #define RAW_VECTOR __m128d
40
+ #define RAW_ZERO _mm_setzero_pd
41
+ #define RAW_LOAD _mm_loadu_pd
42
+ #define RAW_STORE _mm_storeu_pd
43
+ #define RAW_ADD _mm_add_pd
44
+ #define RAW_MUL _mm_mul_pd
45
+ #include "raw_loop.h"
46
+ #undef RAW_NAME
47
+ #undef RAW_TYPE
48
+ #undef RAW_LANES
49
+ #undef RAW_VECTOR
50
+ #undef RAW_ZERO
51
+ #undef RAW_LOAD
52
+ #undef RAW_STORE
53
+ #undef RAW_ADD
54
+ #undef RAW_MUL
55
+ #undef RAW_SIMD
56
+
57
+ #undef RAW_INDEX
@@ -0,0 +1,14 @@
1
+ #ifndef CORRELATE_RAW_H
2
+ #define CORRELATE_RAW_H
3
+ #include "raw_config.h"
4
+
5
+ void correlate_raw(
6
+ int in_rank, const size_t *in_shape, const float *in_ptr,
7
+ int kernel_rank, const size_t *kernel_shape, const float *kernel_ptr,
8
+ int out_rank, const size_t *out_shape, float *out_ptr );
9
+
10
+ void correlate_raw_double(
11
+ int in_rank, const size_t *in_shape, const double *in_ptr,
12
+ int kernel_rank, const size_t *kernel_shape, const double *kernel_ptr,
13
+ int out_rank, const size_t *out_shape, double *out_ptr );
14
+ #endif
@@ -11,6 +11,7 @@ $LOAD_PATH.each do |load_path|
11
11
  end
12
12
 
13
13
  abort 'numo/narray.h not found' unless have_header('numo/narray.h')
14
+ abort 'log2 not found' unless have_func('log2', 'math.h') || have_library('m', 'log2', 'math.h')
14
15
 
15
16
  if RUBY_PLATFORM.match?(/mswin|cygwin|mingw/)
16
17
  $LOAD_PATH.each do |load_path|
@@ -0,0 +1,116 @@
1
+ #include "fft_shape.h"
2
+ #include "raw_config.h"
3
+ #include <limits.h>
4
+ #include <math.h>
5
+
6
+ static size_t positive_size(VALUE value) {
7
+ if (!RB_INTEGER_TYPE_P(value) || RTEST(rb_funcall(value, rb_intern("<="), 1, INT2FIX(0)))) {
8
+ rb_raise(rb_eArgError, "FFT dimensions and size limit must be positive integers");
9
+ }
10
+ return NUM2SIZET(value);
11
+ }
12
+
13
+ static long read_shape(VALUE shape, size_t *dimensions) {
14
+ Check_Type(shape, T_ARRAY);
15
+ long rank = RARRAY_LEN(shape);
16
+ if (rank < 1 || rank > LARGEST_RANK) {
17
+ rb_raise(rb_eArgError, "FFT shape rank must be between 1 and %d", LARGEST_RANK);
18
+ }
19
+ for (long axis = 0; axis < rank; axis++) {
20
+ dimensions[axis] = positive_size(rb_ary_entry(shape, axis));
21
+ }
22
+ return rank;
23
+ }
24
+
25
+ static size_t doubled_candidate(size_t initial, size_t target, int even, size_t maximum) {
26
+ size_t candidate = initial;
27
+ while (candidate < target || (even && candidate % 2 != 0)) {
28
+ if (candidate > maximum / 2) return 0;
29
+ candidate *= 2;
30
+ }
31
+ return candidate;
32
+ }
33
+
34
+ /* Enumerate 3/5 powers only while they can improve the best 2/3/5 candidate. */
35
+ static size_t next_fast_size(size_t target, int even, size_t maximum) {
36
+ size_t best = 0, five = 1;
37
+ for (;;) {
38
+ size_t three = five;
39
+ for (;;) {
40
+ size_t candidate = doubled_candidate(three, target, even, maximum);
41
+ if (candidate != 0 && (best == 0 || candidate < best)) best = candidate;
42
+ size_t bound = best != 0 ? best : maximum;
43
+ if (three > bound / 3) break;
44
+ three *= 3;
45
+ }
46
+ size_t bound = best != 0 ? best : maximum;
47
+ if (five > bound / 5) break;
48
+ five *= 5;
49
+ }
50
+ return best;
51
+ }
52
+
53
+ /*
54
+ * Generates optional smooth dimensions without allocating numeric buffers.
55
+ * @private
56
+ */
57
+ static VALUE convolver_fft_fast_shape(VALUE self, VALUE shape, VALUE maximum_value) {
58
+ (void)self;
59
+ size_t dimensions[LARGEST_RANK];
60
+ long rank = read_shape(shape, dimensions);
61
+ size_t maximum = positive_size(maximum_value);
62
+ for (long axis = 0; axis < rank; axis++) {
63
+ if (dimensions[axis] > maximum) return Qnil;
64
+ dimensions[axis] = next_fast_size(dimensions[axis], axis == rank - 1, maximum);
65
+ if (dimensions[axis] == 0) return Qnil;
66
+ }
67
+ VALUE result = rb_ary_new_capa(rank);
68
+ for (long axis = 0; axis < rank; axis++) {
69
+ rb_ary_push(result, SIZET2NUM(dimensions[axis]));
70
+ }
71
+ return result;
72
+ }
73
+
74
+ static double large_factor_penalty(size_t value) {
75
+ double penalty = 0.0;
76
+ size_t divisor = 2;
77
+ while (divisor <= value / divisor) {
78
+ while (value % divisor == 0) {
79
+ if (divisor > 7) penalty += 1.5 * (log2((double)divisor) - 3.0);
80
+ value /= divisor;
81
+ }
82
+ divisor += divisor == 2 ? 1 : 2;
83
+ }
84
+ if (value > 7) penalty += 1.5 * (log2((double)value) - 3.0);
85
+ return penalty;
86
+ }
87
+
88
+ /*
89
+ * Scores an admissible candidate; Ruby owns the complete FFT buffer policy.
90
+ * @private
91
+ */
92
+ static VALUE convolver_fft_shape_cost(VALUE self, VALUE shape) {
93
+ (void)self;
94
+ size_t dimensions[LARGEST_RANK], product = 1;
95
+ long rank = read_shape(shape, dimensions);
96
+ for (long axis = 0; axis < rank; axis++) {
97
+ /* Bound trial factorization even if this private primitive is called directly. */
98
+ if (dimensions[axis] > (size_t)INT_MAX / 16) {
99
+ rb_raise(rb_eRangeError, "FFT scoring axis exceeds native integer limit");
100
+ }
101
+ if (dimensions[axis] > SIZE_MAX / product) {
102
+ rb_raise(rb_eRangeError, "FFT scoring size exceeds native element limit");
103
+ }
104
+ product *= dimensions[axis];
105
+ }
106
+ double cost = 0.0;
107
+ for (long axis = 0; axis < rank; axis++) {
108
+ cost += log2((double)dimensions[axis]) + large_factor_penalty(dimensions[axis]);
109
+ }
110
+ return DBL2NUM((double)product * cost);
111
+ }
112
+
113
+ void convolver_init_fft_shape(VALUE convolver) {
114
+ rb_define_singleton_method(convolver, "fft_fast_shape", convolver_fft_fast_shape, 2);
115
+ rb_define_singleton_method(convolver, "fft_shape_cost", convolver_fft_shape_cost, 1);
116
+ }
@@ -0,0 +1,8 @@
1
+ #ifndef CONVOLVER_FFT_SHAPE_H
2
+ #define CONVOLVER_FFT_SHAPE_H
3
+
4
+ #include <ruby.h>
5
+
6
+ void convolver_init_fft_shape(VALUE convolver);
7
+
8
+ #endif
@@ -0,0 +1,11 @@
1
+ #ifndef CONVOLVER_RAW_CONFIG_H
2
+ #define CONVOLVER_RAW_CONFIG_H
3
+ #include <ruby.h>
4
+ #if defined(__SSE__)
5
+ #include <xmmintrin.h>
6
+ #endif
7
+ #if defined(__SSE2__)
8
+ #include <emmintrin.h>
9
+ #endif
10
+ #define LARGEST_RANK 16
11
+ #endif
@@ -0,0 +1,71 @@
1
+ #ifndef CONVOLVER_RAW_HELPERS_H
2
+ #define CONVOLVER_RAW_HELPERS_H
3
+
4
+ static inline size_t checked_add( size_t left, size_t right, const char *description ) {
5
+ if ( right > SIZE_MAX - left ) {
6
+ rb_raise( rb_eRangeError, "%s exceeds native implementation limit", description );
7
+ }
8
+ return left + right;
9
+ }
10
+
11
+ static inline size_t checked_multiply( size_t left, size_t right, const char *description ) {
12
+ if ( left != 0 && right > SIZE_MAX / left ) {
13
+ rb_raise( rb_eRangeError, "%s exceeds native implementation limit", description );
14
+ }
15
+ return left * right;
16
+ }
17
+
18
+ static inline size_t size_from_shape( int rank, const size_t *shape, const char *description ) {
19
+ size_t size = 1;
20
+ int i;
21
+ for ( i = 0; i < rank; i++ ) {
22
+ size = checked_multiply( size, shape[i], description );
23
+ }
24
+ return size;
25
+ }
26
+
27
+ // Sets reverse indices
28
+ static inline void corner_reset( int rank, const size_t *shape, size_t *rev_indices ) {
29
+ int i;
30
+ for ( i = 0; i < rank; i++ ) { rev_indices[i] = shape[i] - 1; }
31
+ }
32
+
33
+ // Counts indices down, returns number of ranks that reset
34
+ static inline int corner_dec( int rank, const size_t *shape, size_t *rev_indices ) {
35
+ int i = 0;
36
+ (void) rank;
37
+ while ( ! rev_indices[i]-- ) {
38
+ rev_indices[i] = shape[i] - 1;
39
+ i++;
40
+ }
41
+ return i;
42
+ }
43
+
44
+ // Generates co-increment steps by rank boundaries crossed, for the outer position as inner position is incremented by 1
45
+ static inline void calc_co_increment(
46
+ int rank, const size_t *outer_shape, const size_t *inner_shape, size_t *co_increment ) {
47
+ size_t factor = 1;
48
+ int i;
49
+ co_increment[0] = 1; // co-increment is always 1 in lowest rank
50
+ for ( i = 0; i < rank; i++ ) {
51
+ size_t skipped = checked_multiply( factor, outer_shape[i] - inner_shape[i], "array offset" );
52
+ co_increment[i+1] = checked_add( co_increment[i], skipped, "array offset" );
53
+ factor = checked_multiply( factor, outer_shape[i], "array size" );
54
+ }
55
+ }
56
+
57
+ static inline size_t maximum_offset(
58
+ int rank, const size_t *outer_shape, const size_t *inner_shape, const char *description ) {
59
+ size_t factor = 1;
60
+ size_t offset = 0;
61
+ int i;
62
+ for ( i = 0; i < rank; i++ ) {
63
+ size_t dimension_offset = checked_multiply( factor, inner_shape[i] - 1, description );
64
+ offset = checked_add( offset, dimension_offset, description );
65
+ factor = checked_multiply( factor, outer_shape[i], "array size" );
66
+ }
67
+ return offset;
68
+ }
69
+
70
+
71
+ #endif
@@ -0,0 +1,93 @@
1
+ /* Intentionally included once per dtype; operation/dtype dispatch is outside hot loops. */
2
+ void RAW_NAME(
3
+ int in_rank, const size_t *in_shape, const RAW_TYPE *in_ptr,
4
+ int kernel_rank, const size_t *kernel_shape, const RAW_TYPE *kernel_ptr,
5
+ int out_rank, const size_t *out_shape, RAW_TYPE *out_ptr ) {
6
+ size_t i, j, input_size, kernel_size, kernel_aligned, out_size, offset;
7
+ size_t maximum_input_offset;
8
+ size_t out_co_incr[LARGEST_RANK + 1], kernel_co_incr[LARGEST_RANK + 1];
9
+ size_t ker_q[LARGEST_RANK], out_q[LARGEST_RANK];
10
+ size_t *kernel_co_incr_cache;
11
+ VALUE cache_storage = 0;
12
+
13
+ kernel_size = size_from_shape( kernel_rank, kernel_shape, "kernel size" );
14
+ kernel_aligned = kernel_size - kernel_size % RAW_LANES;
15
+ out_size = size_from_shape( out_rank, out_shape, "output size" );
16
+ input_size = size_from_shape( in_rank, in_shape, "input size" );
17
+
18
+ calc_co_increment( in_rank, in_shape, out_shape, out_co_incr );
19
+ calc_co_increment( in_rank, in_shape, kernel_shape, kernel_co_incr );
20
+ maximum_input_offset = checked_add(
21
+ maximum_offset( in_rank, in_shape, out_shape, "input offset" ),
22
+ maximum_offset( in_rank, in_shape, kernel_shape, "input offset" ),
23
+ "input offset"
24
+ );
25
+ if ( maximum_input_offset >= input_size ) {
26
+ rb_raise( rb_eRangeError, "input offset exceeds native implementation limit" );
27
+ }
28
+
29
+ kernel_co_incr_cache = RB_ALLOCV_N( size_t, cache_storage, kernel_size );
30
+ kernel_co_incr_cache[0] = 0;
31
+
32
+ corner_reset( kernel_rank, kernel_shape, ker_q );
33
+ for ( i = 1; i < kernel_size; i++ ) {
34
+ kernel_co_incr_cache[i] = checked_add(
35
+ kernel_co_incr_cache[i-1],
36
+ kernel_co_incr[ corner_dec( kernel_rank, kernel_shape, ker_q ) ],
37
+ "kernel offset"
38
+ );
39
+ }
40
+
41
+ offset = 0;
42
+ corner_reset( out_rank, out_shape, out_q );
43
+
44
+ for ( i = 0; i < out_size; i++ ) {
45
+ RAW_TYPE t = 0;
46
+ #if RAW_SIMD
47
+ RAW_VECTOR simd_t = RAW_ZERO();
48
+ RAW_TYPE v[RAW_LANES];
49
+ for ( j = 0; j < kernel_aligned; j += RAW_LANES ) {
50
+ RAW_VECTOR simd_x = RAW_LOAD(kernel_ptr + j);
51
+ #if RAW_LANES == 4
52
+ RAW_VECTOR simd_y = _mm_set_ps(
53
+ in_ptr[offset + kernel_co_incr_cache[RAW_INDEX(j+3)]],
54
+ in_ptr[offset + kernel_co_incr_cache[RAW_INDEX(j+2)]],
55
+ in_ptr[offset + kernel_co_incr_cache[RAW_INDEX(j+1)]],
56
+ in_ptr[offset + kernel_co_incr_cache[RAW_INDEX(j)]]);
57
+ #else
58
+ RAW_VECTOR simd_y = _mm_set_pd(
59
+ in_ptr[offset + kernel_co_incr_cache[RAW_INDEX(j+1)]],
60
+ in_ptr[offset + kernel_co_incr_cache[RAW_INDEX(j)]]);
61
+ #endif
62
+ simd_t = RAW_ADD(RAW_MUL(simd_x, simd_y), simd_t);
63
+ }
64
+ RAW_STORE(v, simd_t);
65
+ #else
66
+ for ( j = 0; j < kernel_aligned; j++ ) {
67
+ t += in_ptr[offset + kernel_co_incr_cache[RAW_INDEX(j)]] * kernel_ptr[j];
68
+ }
69
+ #endif
70
+ for ( j = kernel_aligned; j < kernel_size; j++ ) {
71
+ t += in_ptr[offset + kernel_co_incr_cache[RAW_INDEX(j)]] * kernel_ptr[j];
72
+ }
73
+ #if RAW_SIMD
74
+ #if RAW_LANES == 4
75
+ out_ptr[i] = v[0] + v[1] + v[2] + v[3] + t;
76
+ #else
77
+ out_ptr[i] = v[0] + v[1] + t;
78
+ #endif
79
+ #else
80
+ out_ptr[i] = t;
81
+ #endif
82
+
83
+ if ( i + 1 < out_size ) {
84
+ offset = checked_add(
85
+ offset,
86
+ out_co_incr[ corner_dec( out_rank, out_shape, out_q ) ],
87
+ "input offset"
88
+ );
89
+ }
90
+ }
91
+
92
+ RB_ALLOCV_END( cache_storage );
93
+ }
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Convolver
4
+ # Checks individual allocations against native size and tagged-stride limits.
5
+ # @private
6
+ class BufferLimits
7
+ SIZE_MAX = (1 << ([0].pack('J').bytesize * 8)) - 1
8
+ # Numo tags signed strides with a left shift: floor(SSIZE_MAX / 2).
9
+ BYTE_MAX = SIZE_MAX / 4
10
+ INT_MAX = (1 << (([0].pack('i').bytesize * 8) - 1)) - 1
11
+ INDEX_BYTES = [0].pack('J').bytesize
12
+
13
+ attr_reader :size_max, :byte_max, :int_max
14
+
15
+ def initialize(size_max: SIZE_MAX, byte_max: BYTE_MAX, int_max: INT_MAX)
16
+ @size_max = size_max
17
+ @byte_max = byte_max
18
+ @int_max = int_max
19
+ end
20
+
21
+ def buffer!(count, width, description, error: RangeError)
22
+ return if count <= byte_max / width
23
+
24
+ raise error, "#{description} exceeds native byte/stride limit"
25
+ end
26
+
27
+ def product!(shape, description, error: RangeError)
28
+ shape.reduce(1) do |product, size|
29
+ raise error, "#{description} exceeds native element limit" if size > size_max / product
30
+
31
+ product * size
32
+ end
33
+ end
34
+
35
+ NATIVE = new.freeze
36
+ end
37
+
38
+ private_constant :BufferLimits
39
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'convolver/fft_limits'
4
+
5
+ module Convolver
6
+ # Preflights casts, extension, indexing and direct-path cache allocations.
7
+ # @private
8
+ class BufferPreparation
9
+ def initialize(plan, signal_shape, kernel_shape, limits: BufferLimits::NATIVE)
10
+ @plan = plan
11
+ @signal_size = limits.product!(signal_shape, 'signal size')
12
+ @kernel_size = limits.product!(kernel_shape, 'kernel size')
13
+ @limits = limits
14
+ @scalar = signal_shape.empty?
15
+ end
16
+
17
+ def basic!
18
+ common!
19
+ extension!
20
+ limits.buffer!(kernel_size, BufferLimits::INDEX_BYTES, 'direct kernel offset cache')
21
+ end
22
+
23
+ def fft!
24
+ common!
25
+ return if @scalar
26
+ return extension! unless plan.wrap?
27
+
28
+ limits.buffer!(signal_size, 8, 'circular signal preparation', error: FftUnavailable)
29
+ limits.buffer!(kernel_size, 8, 'circular kernel preparation', error: FftUnavailable)
30
+ limits.buffer!(kernel_size, BufferLimits::INDEX_BYTES, 'circular kernel index buffer', error: FftUnavailable)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :plan, :signal_size, :kernel_size, :limits
36
+
37
+ def common!
38
+ limits.buffer!(plan.result_size, plan.dtype::ELEMENT_BYTE_SIZE, 'result buffer')
39
+ limits.buffer!(signal_size, plan.dtype::ELEMENT_BYTE_SIZE, 'signal conversion buffer')
40
+ limits.buffer!(kernel_size, plan.dtype::ELEMENT_BYTE_SIZE, 'kernel conversion buffer')
41
+ end
42
+
43
+ def extension!
44
+ return if plan.valid?
45
+
46
+ limits.buffer!(plan.extended_size, plan.dtype::ELEMENT_BYTE_SIZE, 'extended signal buffer')
47
+ return if plan.boundary == :constant
48
+
49
+ plan.extended_shape.each do |length|
50
+ limits.buffer!(length, BufferLimits::INDEX_BYTES, 'boundary index buffer')
51
+ end
52
+ end
53
+ end
54
+
55
+ private_constant :BufferPreparation
56
+ end