convolver 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c27f2aa98b988627391d0a9812c6b8e0a59e65cf4a506592d95f43d1a02e3ae
4
- data.tar.gz: ede25a5203ed707bd3ca726eb513ceb44746bc5a04c294396d368ab3785b2c4c
3
+ metadata.gz: e886e4cc4f0c47a5c0e40209a20ac4b90f9c5fa349c76e9b04889fb8c010fa70
4
+ data.tar.gz: b656b786753b87ab68d89985262bedbbd3ad0929527a2953acde7af56cde643b
5
5
  SHA512:
6
- metadata.gz: aaf0fc6ab38eedbe06c2082b8ffaa2cdbfab45b3340aadf7c87839d28aa97dbb3be1e22a99c3aef25207b656741c69763bb177003d74e440ce7d6653be22aff8
7
- data.tar.gz: 20b127fa09a8c6b449e74183da3fa91a93603bc17701b4991543de6095ae64023ef217c166d5a7ef999a241de125c7278e6f1e2b3d895f5ec0d0fe2665188bd5
6
+ metadata.gz: 187f65c4daf5c25007348d0e3f21e44dda4bccb629f0dab067ac8193aac8977d22a441b79249d35ad4b7c2b17e43e3339db7045c0f83882009556bae6b446425
7
+ data.tar.gz: 4605ecab87c0027c02c30c82e78298766b311a4e359b178fec8b47427a5c6bfc57a049193173fb38fba3877874e679cf1d96ac41700a5cf090a31c5fa43c9635
data/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.1] - 2026-07-29
9
+
10
+ ### Fixed
11
+
12
+ - Made the direct native implementation handle non-contiguous Numo::NArray
13
+ views safely.
14
+ - Prevented an out-of-bounds native buffer write for rank-16 inputs.
15
+ - Replaced unchecked native size and offset arithmetic with overflow-checked
16
+ calculations.
17
+
8
18
  ## [1.0.0] - 2026-07-21
9
19
 
10
20
  ### Added
@@ -44,4 +54,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
44
54
  - Updated the native extension to use the maintained Numo C API and removed the
45
55
  legacy untyped-data compatibility code.
46
56
 
57
+ [1.0.1]: https://github.com/neilslater/convolver/compare/v1.0.0...v1.0.1
47
58
  [1.0.0]: https://github.com/neilslater/convolver/compare/v0.3.2...v1.0.0
@@ -2,22 +2,37 @@
2
2
 
3
3
  #include "convolve_raw.h"
4
4
 
5
- static inline int size_from_shape( int rank, int *shape ) {
6
- int size = 1;
5
+ static inline size_t checked_add( size_t left, size_t right, const char *description ) {
6
+ if ( right > SIZE_MAX - left ) {
7
+ rb_raise( rb_eRangeError, "%s exceeds native implementation limit", description );
8
+ }
9
+ return left + right;
10
+ }
11
+
12
+ static inline size_t checked_multiply( size_t left, size_t right, const char *description ) {
13
+ if ( left != 0 && right > SIZE_MAX / left ) {
14
+ rb_raise( rb_eRangeError, "%s exceeds native implementation limit", description );
15
+ }
16
+ return left * right;
17
+ }
18
+
19
+ static inline size_t size_from_shape( int rank, const size_t *shape, const char *description ) {
20
+ size_t size = 1;
7
21
  int i;
8
- for ( i = 0; i < rank; i++ ) { size *= shape[i]; }
22
+ for ( i = 0; i < rank; i++ ) {
23
+ size = checked_multiply( size, shape[i], description );
24
+ }
9
25
  return size;
10
26
  }
11
27
 
12
28
  // Sets reverse indices
13
- static inline void corner_reset( int rank, int *shape, int *rev_indices ) {
29
+ static inline void corner_reset( int rank, const size_t *shape, size_t *rev_indices ) {
14
30
  int i;
15
31
  for ( i = 0; i < rank; i++ ) { rev_indices[i] = shape[i] - 1; }
16
- return;
17
32
  }
18
33
 
19
34
  // Counts indices down, returns number of ranks that reset
20
- static inline int corner_dec( int rank, int *shape, int *rev_indices ) {
35
+ static inline int corner_dec( int rank, const size_t *shape, size_t *rev_indices ) {
21
36
  int i = 0;
22
37
  (void) rank;
23
38
  while ( ! rev_indices[i]-- ) {
@@ -28,16 +43,31 @@ static inline int corner_dec( int rank, int *shape, int *rev_indices ) {
28
43
  }
29
44
 
30
45
  // Generates co-increment steps by rank boundaries crossed, for the outer position as inner position is incremented by 1
31
- static inline void calc_co_increment( int rank, int *outer_shape, int *inner_shape, int *co_increment ) {
32
- int i, factor;
46
+ static inline void calc_co_increment(
47
+ int rank, const size_t *outer_shape, const size_t *inner_shape, size_t *co_increment ) {
48
+ size_t factor = 1;
49
+ int i;
33
50
  co_increment[0] = 1; // co-increment is always 1 in lowest rank
34
- factor = 1;
35
51
  for ( i = 0; i < rank; i++ ) {
36
- co_increment[i+1] = co_increment[i] + factor * ( outer_shape[i] - inner_shape[i] );
37
- factor *= outer_shape[i];
52
+ size_t skipped = checked_multiply( factor, outer_shape[i] - inner_shape[i], "array offset" );
53
+ co_increment[i+1] = checked_add( co_increment[i], skipped, "array offset" );
54
+ factor = checked_multiply( factor, outer_shape[i], "array size" );
38
55
  }
39
56
  }
40
57
 
58
+ static inline size_t maximum_offset(
59
+ int rank, const size_t *outer_shape, const size_t *inner_shape, const char *description ) {
60
+ size_t factor = 1;
61
+ size_t offset = 0;
62
+ int i;
63
+ for ( i = 0; i < rank; i++ ) {
64
+ size_t dimension_offset = checked_multiply( factor, inner_shape[i] - 1, description );
65
+ offset = checked_add( offset, dimension_offset, description );
66
+ factor = checked_multiply( factor, outer_shape[i], "array size" );
67
+ }
68
+ return offset;
69
+ }
70
+
41
71
  ////////////////////////////////////////////////////////////////////////////////////////////////////
42
72
  //
43
73
  // Convolve
@@ -46,33 +76,46 @@ static inline void calc_co_increment( int rank, int *outer_shape, int *inner_sha
46
76
  //
47
77
 
48
78
  void convolve_raw(
49
- int in_rank, int *in_shape, float *in_ptr,
50
- int kernel_rank, int *kernel_shape, float *kernel_ptr,
51
- int out_rank, int *out_shape, float *out_ptr ) {
52
- int i, j, kernel_size, kernel_aligned, out_size, offset;
53
- int out_co_incr[LARGEST_RANK], kernel_co_incr[LARGEST_RANK];
54
- int ker_q[LARGEST_RANK], out_q[LARGEST_RANK];
55
- int *kernel_co_incr_cache;
56
-
57
- kernel_size = size_from_shape( kernel_rank, kernel_shape );
58
- kernel_aligned = 4 * (kernel_size/4);
59
- out_size = size_from_shape( out_rank, out_shape );
79
+ int in_rank, const size_t *in_shape, const float *in_ptr,
80
+ int kernel_rank, const size_t *kernel_shape, const float *kernel_ptr,
81
+ int out_rank, const size_t *out_shape, float *out_ptr ) {
82
+ size_t i, j, input_size, kernel_size, kernel_aligned, out_size, offset;
83
+ size_t maximum_input_offset;
84
+ size_t out_co_incr[LARGEST_RANK + 1], kernel_co_incr[LARGEST_RANK + 1];
85
+ size_t ker_q[LARGEST_RANK], out_q[LARGEST_RANK];
86
+ size_t *kernel_co_incr_cache;
87
+ VALUE cache_storage = 0;
88
+
89
+ kernel_size = size_from_shape( kernel_rank, kernel_shape, "kernel size" );
90
+ kernel_aligned = kernel_size - kernel_size % 4;
91
+ out_size = size_from_shape( out_rank, out_shape, "output size" );
92
+ input_size = size_from_shape( in_rank, in_shape, "input size" );
60
93
 
61
94
  calc_co_increment( in_rank, in_shape, out_shape, out_co_incr );
62
95
  calc_co_increment( in_rank, in_shape, kernel_shape, kernel_co_incr );
96
+ maximum_input_offset = checked_add(
97
+ maximum_offset( in_rank, in_shape, out_shape, "input offset" ),
98
+ maximum_offset( in_rank, in_shape, kernel_shape, "input offset" ),
99
+ "input offset"
100
+ );
101
+ if ( maximum_input_offset >= input_size ) {
102
+ rb_raise( rb_eRangeError, "input offset exceeds native implementation limit" );
103
+ }
63
104
 
64
- kernel_co_incr_cache = ALLOC_N( int, kernel_size );
105
+ kernel_co_incr_cache = RB_ALLOCV_N( size_t, cache_storage, kernel_size );
65
106
  kernel_co_incr_cache[0] = 0;
66
107
 
67
108
  corner_reset( kernel_rank, kernel_shape, ker_q );
68
109
  for ( i = 1; i < kernel_size; i++ ) {
69
- kernel_co_incr_cache[i] = kernel_co_incr_cache[i-1] + kernel_co_incr[ corner_dec( kernel_rank, kernel_shape, ker_q ) ];
110
+ kernel_co_incr_cache[i] = checked_add(
111
+ kernel_co_incr_cache[i-1],
112
+ kernel_co_incr[ corner_dec( kernel_rank, kernel_shape, ker_q ) ],
113
+ "kernel offset"
114
+ );
70
115
  }
71
116
 
72
- // For convenience of flow, we set offset to -1 and adjust countdown 1 higher to compensate
73
- offset = -1;
117
+ offset = 0;
74
118
  corner_reset( out_rank, out_shape, out_q );
75
- out_q[0]++;
76
119
 
77
120
  // Main convolve loop
78
121
  for ( i = 0; i < out_size; i++ ) {
@@ -83,8 +126,6 @@ void convolve_raw(
83
126
  #endif
84
127
  float t = 0.0;
85
128
 
86
- offset += out_co_incr[ corner_dec( out_rank, out_shape, out_q ) ];
87
-
88
129
  #if CONVOLVER_USE_SSE
89
130
  // Use SIMD for all the aligned values in groups of 4
90
131
  for ( j = 0; j < kernel_aligned; j +=4 ) {
@@ -114,8 +155,15 @@ void convolve_raw(
114
155
  #else
115
156
  out_ptr[i] = t;
116
157
  #endif
158
+
159
+ if ( i + 1 < out_size ) {
160
+ offset = checked_add(
161
+ offset,
162
+ out_co_incr[ corner_dec( out_rank, out_shape, out_q ) ],
163
+ "input offset"
164
+ );
165
+ }
117
166
  }
118
167
 
119
- xfree( kernel_co_incr_cache );
120
- return;
168
+ RB_ALLOCV_END( cache_storage );
121
169
  }
@@ -18,8 +18,8 @@
18
18
  #define LARGEST_RANK 16
19
19
 
20
20
  void convolve_raw(
21
- int in_rank, int *in_shape, float *in_ptr,
22
- int kernel_rank, int *kernel_shape, float *kernel_ptr,
23
- int out_rank, int *out_shape, float *out_ptr );
21
+ int in_rank, const size_t *in_shape, const float *in_ptr,
22
+ int kernel_rank, const size_t *kernel_shape, const float *kernel_ptr,
23
+ int out_rank, const size_t *out_shape, float *out_ptr );
24
24
 
25
25
  #endif
@@ -1,4 +1,3 @@
1
- #include <limits.h>
2
1
  #include <ruby.h>
3
2
  #include <numo/narray.h>
4
3
  #include <numo/intern.h>
@@ -7,14 +6,11 @@
7
6
 
8
7
  static VALUE mConvolver;
9
8
 
10
- static void copy_shape(int rank, const size_t *source, int *target) {
9
+ static void copy_shape(int rank, const size_t *source, size_t *target) {
11
10
  int i;
12
11
 
13
12
  for (i = 0; i < rank; i++) {
14
- if (source[rank - i - 1] > INT_MAX) {
15
- rb_raise(rb_eArgError, "array dimension exceeds native implementation limit");
16
- }
17
- target[i] = (int)source[rank - i - 1];
13
+ target[i] = source[rank - i - 1];
18
14
  }
19
15
  }
20
16
 
@@ -34,9 +30,9 @@ static VALUE convolver_convolve_basic(VALUE self, VALUE signal, VALUE kernel) {
34
30
  narray_t *kernel_narray;
35
31
  int rank;
36
32
  int i;
37
- int signal_shape[LARGEST_RANK];
38
- int kernel_shape[LARGEST_RANK];
39
- int result_shape[LARGEST_RANK];
33
+ size_t signal_shape[LARGEST_RANK];
34
+ size_t kernel_shape[LARGEST_RANK];
35
+ size_t result_shape[LARGEST_RANK];
40
36
  size_t numo_result_shape[LARGEST_RANK];
41
37
 
42
38
  (void)self;
@@ -47,6 +43,12 @@ static VALUE convolver_convolve_basic(VALUE self, VALUE signal, VALUE kernel) {
47
43
 
48
44
  signal_value = rb_funcall(numo_cSFloat, rb_intern("cast"), 1, signal);
49
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);
51
+ }
50
52
  GetNArray(signal_value, signal_narray);
51
53
  GetNArray(kernel_value, kernel_narray);
52
54
 
@@ -66,10 +68,10 @@ static VALUE convolver_convolve_basic(VALUE self, VALUE signal, VALUE kernel) {
66
68
  copy_shape(rank, kernel_narray->shape, kernel_shape);
67
69
 
68
70
  for (i = 0; i < rank; i++) {
69
- result_shape[i] = signal_shape[i] - kernel_shape[i] + 1;
70
- if (result_shape[i] < 1) {
71
+ if (signal_shape[i] < kernel_shape[i]) {
71
72
  rb_raise(rb_eArgError, "kernel must not be larger than signal in any dimension");
72
73
  }
74
+ result_shape[i] = signal_shape[i] - kernel_shape[i] + 1;
73
75
  numo_result_shape[rank - i - 1] = (size_t)result_shape[i];
74
76
  }
75
77
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Convolver
4
4
  # Current gem version.
5
- VERSION = '1.0.0'
5
+ VERSION = '1.0.1'
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: convolver
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Slater