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 +4 -4
- data/CHANGELOG.md +11 -0
- data/ext/convolver/convolve_raw.c +79 -31
- data/ext/convolver/convolve_raw.h +3 -3
- data/ext/convolver/convolver.c +13 -11
- data/lib/convolver/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e886e4cc4f0c47a5c0e40209a20ac4b90f9c5fa349c76e9b04889fb8c010fa70
|
|
4
|
+
data.tar.gz: b656b786753b87ab68d89985262bedbbd3ad0929527a2953acde7af56cde643b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
6
|
-
|
|
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++ ) {
|
|
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,
|
|
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,
|
|
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(
|
|
32
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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,
|
|
50
|
-
int kernel_rank,
|
|
51
|
-
int out_rank,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 =
|
|
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] =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
22
|
-
int kernel_rank,
|
|
23
|
-
int out_rank,
|
|
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
|
data/ext/convolver/convolver.c
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
|
data/lib/convolver/version.rb
CHANGED