@digitaldefiance/node-accelerate 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.
- package/LICENSE +21 -0
- package/README.md +540 -0
- package/accelerate.cc +657 -0
- package/binding.gyp +24 -0
- package/index.d.ts +320 -0
- package/index.js +392 -0
- package/package.json +71 -0
- package/scripts/check-platform.js +102 -0
package/accelerate.cc
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
// Apple Accelerate Framework Native Addon for Node.js
|
|
2
|
+
// Provides high-performance BLAS/vDSP operations for M4 Max
|
|
3
|
+
//
|
|
4
|
+
// This addon exposes Apple's Accelerate framework to JavaScript,
|
|
5
|
+
// giving you hardware-optimized:
|
|
6
|
+
// - Matrix multiplication (BLAS)
|
|
7
|
+
// - Vector operations (vDSP)
|
|
8
|
+
// - FFT (vDSP)
|
|
9
|
+
// - Convolution (vDSP)
|
|
10
|
+
|
|
11
|
+
#include <node_api.h>
|
|
12
|
+
#include <Accelerate/Accelerate.h>
|
|
13
|
+
#include <cstring>
|
|
14
|
+
#include <vector>
|
|
15
|
+
|
|
16
|
+
// Helper to throw JS errors
|
|
17
|
+
#define NAPI_THROW(env, msg) \
|
|
18
|
+
napi_throw_error(env, nullptr, msg); \
|
|
19
|
+
return nullptr;
|
|
20
|
+
|
|
21
|
+
#define NAPI_CHECK(call) \
|
|
22
|
+
if ((call) != napi_ok) { \
|
|
23
|
+
napi_throw_error(env, nullptr, "NAPI call failed"); \
|
|
24
|
+
return nullptr; \
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Get Float64Array data
|
|
28
|
+
static double* GetFloat64ArrayData(napi_env env, napi_value value, size_t* length) {
|
|
29
|
+
bool is_typedarray;
|
|
30
|
+
napi_is_typedarray(env, value, &is_typedarray);
|
|
31
|
+
if (!is_typedarray) return nullptr;
|
|
32
|
+
|
|
33
|
+
napi_typedarray_type type;
|
|
34
|
+
size_t byte_length;
|
|
35
|
+
void* data;
|
|
36
|
+
napi_value arraybuffer;
|
|
37
|
+
size_t byte_offset;
|
|
38
|
+
|
|
39
|
+
napi_get_typedarray_info(env, value, &type, length, &data, &arraybuffer, &byte_offset);
|
|
40
|
+
|
|
41
|
+
if (type != napi_float64_array) return nullptr;
|
|
42
|
+
return static_cast<double*>(data);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get Float32Array data
|
|
46
|
+
static float* GetFloat32ArrayData(napi_env env, napi_value value, size_t* length) {
|
|
47
|
+
bool is_typedarray;
|
|
48
|
+
napi_is_typedarray(env, value, &is_typedarray);
|
|
49
|
+
if (!is_typedarray) return nullptr;
|
|
50
|
+
|
|
51
|
+
napi_typedarray_type type;
|
|
52
|
+
size_t byte_length;
|
|
53
|
+
void* data;
|
|
54
|
+
napi_value arraybuffer;
|
|
55
|
+
size_t byte_offset;
|
|
56
|
+
|
|
57
|
+
napi_get_typedarray_info(env, value, &type, length, &data, &arraybuffer, &byte_offset);
|
|
58
|
+
|
|
59
|
+
if (type != napi_float32_array) return nullptr;
|
|
60
|
+
return static_cast<float*>(data);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Matrix multiplication using BLAS dgemm (double precision)
|
|
64
|
+
// C = alpha * A * B + beta * C
|
|
65
|
+
// A is MxK, B is KxN, C is MxN
|
|
66
|
+
static napi_value MatMulDouble(napi_env env, napi_callback_info info) {
|
|
67
|
+
size_t argc = 6;
|
|
68
|
+
napi_value args[6];
|
|
69
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
70
|
+
|
|
71
|
+
if (argc < 6) {
|
|
72
|
+
NAPI_THROW(env, "matmul requires 6 arguments: A, B, C, M, K, N");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
size_t len_a, len_b, len_c;
|
|
76
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
77
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
78
|
+
double* C = GetFloat64ArrayData(env, args[2], &len_c);
|
|
79
|
+
|
|
80
|
+
if (!A || !B || !C) {
|
|
81
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
int32_t M, K, N;
|
|
85
|
+
napi_get_value_int32(env, args[3], &M);
|
|
86
|
+
napi_get_value_int32(env, args[4], &K);
|
|
87
|
+
napi_get_value_int32(env, args[5], &N);
|
|
88
|
+
|
|
89
|
+
// BLAS dgemm: C = 1.0 * A * B + 0.0 * C
|
|
90
|
+
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
|
|
91
|
+
M, N, K,
|
|
92
|
+
1.0, A, K,
|
|
93
|
+
B, N,
|
|
94
|
+
0.0, C, N);
|
|
95
|
+
|
|
96
|
+
return args[2]; // Return C
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Matrix multiplication using BLAS sgemm (single precision)
|
|
100
|
+
static napi_value MatMulFloat(napi_env env, napi_callback_info info) {
|
|
101
|
+
size_t argc = 6;
|
|
102
|
+
napi_value args[6];
|
|
103
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
104
|
+
|
|
105
|
+
if (argc < 6) {
|
|
106
|
+
NAPI_THROW(env, "matmulFloat requires 6 arguments: A, B, C, M, K, N");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
size_t len_a, len_b, len_c;
|
|
110
|
+
float* A = GetFloat32ArrayData(env, args[0], &len_a);
|
|
111
|
+
float* B = GetFloat32ArrayData(env, args[1], &len_b);
|
|
112
|
+
float* C = GetFloat32ArrayData(env, args[2], &len_c);
|
|
113
|
+
|
|
114
|
+
if (!A || !B || !C) {
|
|
115
|
+
NAPI_THROW(env, "Arguments must be Float32Arrays");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
int32_t M, K, N;
|
|
119
|
+
napi_get_value_int32(env, args[3], &M);
|
|
120
|
+
napi_get_value_int32(env, args[4], &K);
|
|
121
|
+
napi_get_value_int32(env, args[5], &N);
|
|
122
|
+
|
|
123
|
+
cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
|
|
124
|
+
M, N, K,
|
|
125
|
+
1.0f, A, K,
|
|
126
|
+
B, N,
|
|
127
|
+
0.0f, C, N);
|
|
128
|
+
|
|
129
|
+
return args[2];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Vector dot product using vDSP
|
|
133
|
+
static napi_value DotProduct(napi_env env, napi_callback_info info) {
|
|
134
|
+
size_t argc = 2;
|
|
135
|
+
napi_value args[2];
|
|
136
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
137
|
+
|
|
138
|
+
size_t len_a, len_b;
|
|
139
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
140
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
141
|
+
|
|
142
|
+
if (!A || !B) {
|
|
143
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
147
|
+
double result;
|
|
148
|
+
vDSP_dotprD(A, 1, B, 1, &result, len);
|
|
149
|
+
|
|
150
|
+
napi_value js_result;
|
|
151
|
+
napi_create_double(env, result, &js_result);
|
|
152
|
+
return js_result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Vector add: C = A + B
|
|
156
|
+
static napi_value VectorAdd(napi_env env, napi_callback_info info) {
|
|
157
|
+
size_t argc = 3;
|
|
158
|
+
napi_value args[3];
|
|
159
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
160
|
+
|
|
161
|
+
size_t len_a, len_b, len_c;
|
|
162
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
163
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
164
|
+
double* C = GetFloat64ArrayData(env, args[2], &len_c);
|
|
165
|
+
|
|
166
|
+
if (!A || !B || !C) {
|
|
167
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
171
|
+
len = len < len_c ? len : len_c;
|
|
172
|
+
|
|
173
|
+
vDSP_vaddD(A, 1, B, 1, C, 1, len);
|
|
174
|
+
|
|
175
|
+
return args[2];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Vector subtract: C = A - B
|
|
179
|
+
static napi_value VectorSub(napi_env env, napi_callback_info info) {
|
|
180
|
+
size_t argc = 3;
|
|
181
|
+
napi_value args[3];
|
|
182
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
183
|
+
|
|
184
|
+
size_t len_a, len_b, len_c;
|
|
185
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
186
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
187
|
+
double* C = GetFloat64ArrayData(env, args[2], &len_c);
|
|
188
|
+
|
|
189
|
+
if (!A || !B || !C) {
|
|
190
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
194
|
+
len = len < len_c ? len : len_c;
|
|
195
|
+
|
|
196
|
+
vDSP_vsubD(B, 1, A, 1, C, 1, len); // Note: vDSP_vsubD computes C = A - B as C[i] = B[i] - A[i]
|
|
197
|
+
|
|
198
|
+
return args[2];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Vector multiply: C = A * B (element-wise)
|
|
202
|
+
static napi_value VectorMul(napi_env env, napi_callback_info info) {
|
|
203
|
+
size_t argc = 3;
|
|
204
|
+
napi_value args[3];
|
|
205
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
206
|
+
|
|
207
|
+
size_t len_a, len_b, len_c;
|
|
208
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
209
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
210
|
+
double* C = GetFloat64ArrayData(env, args[2], &len_c);
|
|
211
|
+
|
|
212
|
+
if (!A || !B || !C) {
|
|
213
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
217
|
+
len = len < len_c ? len : len_c;
|
|
218
|
+
|
|
219
|
+
vDSP_vmulD(A, 1, B, 1, C, 1, len);
|
|
220
|
+
|
|
221
|
+
return args[2];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Vector divide: C = A / B (element-wise)
|
|
225
|
+
static napi_value VectorDiv(napi_env env, napi_callback_info info) {
|
|
226
|
+
size_t argc = 3;
|
|
227
|
+
napi_value args[3];
|
|
228
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
229
|
+
|
|
230
|
+
size_t len_a, len_b, len_c;
|
|
231
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
232
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
233
|
+
double* C = GetFloat64ArrayData(env, args[2], &len_c);
|
|
234
|
+
|
|
235
|
+
if (!A || !B || !C) {
|
|
236
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
240
|
+
len = len < len_c ? len : len_c;
|
|
241
|
+
|
|
242
|
+
vDSP_vdivD(B, 1, A, 1, C, 1, len); // Note: vDSP_vdivD computes C = A / B as C[i] = B[i] / A[i]
|
|
243
|
+
|
|
244
|
+
return args[2];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Vector scale: B = A * scalar
|
|
248
|
+
static napi_value VectorScale(napi_env env, napi_callback_info info) {
|
|
249
|
+
size_t argc = 3;
|
|
250
|
+
napi_value args[3];
|
|
251
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
252
|
+
|
|
253
|
+
size_t len_a, len_b;
|
|
254
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
255
|
+
double scalar;
|
|
256
|
+
napi_get_value_double(env, args[1], &scalar);
|
|
257
|
+
double* B = GetFloat64ArrayData(env, args[2], &len_b);
|
|
258
|
+
|
|
259
|
+
if (!A || !B) {
|
|
260
|
+
NAPI_THROW(env, "First and third arguments must be Float64Arrays");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
264
|
+
vDSP_vsmulD(A, 1, &scalar, B, 1, len);
|
|
265
|
+
|
|
266
|
+
return args[2];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Sum of vector elements
|
|
270
|
+
static napi_value VectorSum(napi_env env, napi_callback_info info) {
|
|
271
|
+
size_t argc = 1;
|
|
272
|
+
napi_value args[1];
|
|
273
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
274
|
+
|
|
275
|
+
size_t len;
|
|
276
|
+
double* A = GetFloat64ArrayData(env, args[0], &len);
|
|
277
|
+
|
|
278
|
+
if (!A) {
|
|
279
|
+
NAPI_THROW(env, "Argument must be Float64Array");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
double result;
|
|
283
|
+
vDSP_sveD(A, 1, &result, len);
|
|
284
|
+
|
|
285
|
+
napi_value js_result;
|
|
286
|
+
napi_create_double(env, result, &js_result);
|
|
287
|
+
return js_result;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Mean of vector elements
|
|
291
|
+
static napi_value VectorMean(napi_env env, napi_callback_info info) {
|
|
292
|
+
size_t argc = 1;
|
|
293
|
+
napi_value args[1];
|
|
294
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
295
|
+
|
|
296
|
+
size_t len;
|
|
297
|
+
double* A = GetFloat64ArrayData(env, args[0], &len);
|
|
298
|
+
|
|
299
|
+
if (!A) {
|
|
300
|
+
NAPI_THROW(env, "Argument must be Float64Array");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
double result;
|
|
304
|
+
vDSP_meanvD(A, 1, &result, len);
|
|
305
|
+
|
|
306
|
+
napi_value js_result;
|
|
307
|
+
napi_create_double(env, result, &js_result);
|
|
308
|
+
return js_result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Max of vector elements
|
|
312
|
+
static napi_value VectorMax(napi_env env, napi_callback_info info) {
|
|
313
|
+
size_t argc = 1;
|
|
314
|
+
napi_value args[1];
|
|
315
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
316
|
+
|
|
317
|
+
size_t len;
|
|
318
|
+
double* A = GetFloat64ArrayData(env, args[0], &len);
|
|
319
|
+
|
|
320
|
+
if (!A) {
|
|
321
|
+
NAPI_THROW(env, "Argument must be Float64Array");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
double result;
|
|
325
|
+
vDSP_maxvD(A, 1, &result, len);
|
|
326
|
+
|
|
327
|
+
napi_value js_result;
|
|
328
|
+
napi_create_double(env, result, &js_result);
|
|
329
|
+
return js_result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Min of vector elements
|
|
333
|
+
static napi_value VectorMin(napi_env env, napi_callback_info info) {
|
|
334
|
+
size_t argc = 1;
|
|
335
|
+
napi_value args[1];
|
|
336
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
337
|
+
|
|
338
|
+
size_t len;
|
|
339
|
+
double* A = GetFloat64ArrayData(env, args[0], &len);
|
|
340
|
+
|
|
341
|
+
if (!A) {
|
|
342
|
+
NAPI_THROW(env, "Argument must be Float64Array");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
double result;
|
|
346
|
+
vDSP_minvD(A, 1, &result, len);
|
|
347
|
+
|
|
348
|
+
napi_value js_result;
|
|
349
|
+
napi_create_double(env, result, &js_result);
|
|
350
|
+
return js_result;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// FFT (real to complex)
|
|
354
|
+
static napi_value FFT(napi_env env, napi_callback_info info) {
|
|
355
|
+
size_t argc = 2;
|
|
356
|
+
napi_value args[2];
|
|
357
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
358
|
+
|
|
359
|
+
size_t len;
|
|
360
|
+
double* input = GetFloat64ArrayData(env, args[0], &len);
|
|
361
|
+
|
|
362
|
+
if (!input) {
|
|
363
|
+
NAPI_THROW(env, "First argument must be Float64Array");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Find log2 of length
|
|
367
|
+
vDSP_Length log2n = 0;
|
|
368
|
+
vDSP_Length n = len;
|
|
369
|
+
while (n > 1) {
|
|
370
|
+
n >>= 1;
|
|
371
|
+
log2n++;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if ((1UL << log2n) != len) {
|
|
375
|
+
NAPI_THROW(env, "Input length must be a power of 2");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Setup FFT
|
|
379
|
+
FFTSetupD setup = vDSP_create_fftsetupD(log2n, FFT_RADIX2);
|
|
380
|
+
if (!setup) {
|
|
381
|
+
NAPI_THROW(env, "Failed to create FFT setup");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Allocate split complex arrays
|
|
385
|
+
size_t half = len / 2;
|
|
386
|
+
std::vector<double> real(half);
|
|
387
|
+
std::vector<double> imag(half);
|
|
388
|
+
|
|
389
|
+
DSPDoubleSplitComplex split;
|
|
390
|
+
split.realp = real.data();
|
|
391
|
+
split.imagp = imag.data();
|
|
392
|
+
|
|
393
|
+
// Convert to split complex
|
|
394
|
+
vDSP_ctozD((DSPDoubleComplex*)input, 2, &split, 1, half);
|
|
395
|
+
|
|
396
|
+
// Perform FFT
|
|
397
|
+
vDSP_fft_zripD(setup, &split, 1, log2n, FFT_FORWARD);
|
|
398
|
+
|
|
399
|
+
// Scale
|
|
400
|
+
double scale = 0.5;
|
|
401
|
+
vDSP_vsmulD(split.realp, 1, &scale, split.realp, 1, half);
|
|
402
|
+
vDSP_vsmulD(split.imagp, 1, &scale, split.imagp, 1, half);
|
|
403
|
+
|
|
404
|
+
// Create output array (interleaved real/imag)
|
|
405
|
+
napi_value arraybuffer;
|
|
406
|
+
void* data;
|
|
407
|
+
napi_create_arraybuffer(env, len * sizeof(double), &data, &arraybuffer);
|
|
408
|
+
|
|
409
|
+
double* output = static_cast<double*>(data);
|
|
410
|
+
for (size_t i = 0; i < half; i++) {
|
|
411
|
+
output[i * 2] = real[i];
|
|
412
|
+
output[i * 2 + 1] = imag[i];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
napi_value result;
|
|
416
|
+
napi_create_typedarray(env, napi_float64_array, len, arraybuffer, 0, &result);
|
|
417
|
+
|
|
418
|
+
vDSP_destroy_fftsetupD(setup);
|
|
419
|
+
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Matrix-vector multiply: y = A * x
|
|
424
|
+
static napi_value MatVecMul(napi_env env, napi_callback_info info) {
|
|
425
|
+
size_t argc = 5;
|
|
426
|
+
napi_value args[5];
|
|
427
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
428
|
+
|
|
429
|
+
size_t len_a, len_x, len_y;
|
|
430
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
431
|
+
double* x = GetFloat64ArrayData(env, args[1], &len_x);
|
|
432
|
+
double* y = GetFloat64ArrayData(env, args[2], &len_y);
|
|
433
|
+
|
|
434
|
+
if (!A || !x || !y) {
|
|
435
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
int32_t M, N;
|
|
439
|
+
NAPI_CHECK(napi_get_value_int32(env, args[3], &M));
|
|
440
|
+
NAPI_CHECK(napi_get_value_int32(env, args[4], &N));
|
|
441
|
+
|
|
442
|
+
// y = A * x using GEMV
|
|
443
|
+
cblas_dgemv(CblasRowMajor, CblasNoTrans, M, N, 1.0, A, N, x, 1, 0.0, y, 1);
|
|
444
|
+
|
|
445
|
+
return args[2];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// AXPY: y = a*x + y
|
|
449
|
+
static napi_value AXPY(napi_env env, napi_callback_info info) {
|
|
450
|
+
size_t argc = 3;
|
|
451
|
+
napi_value args[3];
|
|
452
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
453
|
+
|
|
454
|
+
double alpha;
|
|
455
|
+
NAPI_CHECK(napi_get_value_double(env, args[0], &alpha));
|
|
456
|
+
|
|
457
|
+
size_t len_x, len_y;
|
|
458
|
+
double* x = GetFloat64ArrayData(env, args[1], &len_x);
|
|
459
|
+
double* y = GetFloat64ArrayData(env, args[2], &len_y);
|
|
460
|
+
|
|
461
|
+
if (!x || !y) {
|
|
462
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
size_t len = len_x < len_y ? len_x : len_y;
|
|
466
|
+
cblas_daxpy(len, alpha, x, 1, y, 1);
|
|
467
|
+
|
|
468
|
+
return args[2];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Vector absolute value
|
|
472
|
+
static napi_value VectorAbs(napi_env env, napi_callback_info info) {
|
|
473
|
+
size_t argc = 2;
|
|
474
|
+
napi_value args[2];
|
|
475
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
476
|
+
|
|
477
|
+
size_t len_a, len_b;
|
|
478
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
479
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
480
|
+
|
|
481
|
+
if (!A || !B) {
|
|
482
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
486
|
+
vDSP_vabsD(A, 1, B, 1, len);
|
|
487
|
+
|
|
488
|
+
return args[1];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Vector square: b = a^2
|
|
492
|
+
static napi_value VectorSquare(napi_env env, napi_callback_info info) {
|
|
493
|
+
size_t argc = 2;
|
|
494
|
+
napi_value args[2];
|
|
495
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
496
|
+
|
|
497
|
+
size_t len_a, len_b;
|
|
498
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
499
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
500
|
+
|
|
501
|
+
if (!A || !B) {
|
|
502
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
506
|
+
vDSP_vsqD(A, 1, B, 1, len);
|
|
507
|
+
|
|
508
|
+
return args[1];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Vector square root
|
|
512
|
+
static napi_value VectorSqrt(napi_env env, napi_callback_info info) {
|
|
513
|
+
size_t argc = 2;
|
|
514
|
+
napi_value args[2];
|
|
515
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
516
|
+
|
|
517
|
+
size_t len_a, len_b;
|
|
518
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
519
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
520
|
+
|
|
521
|
+
if (!A || !B) {
|
|
522
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
526
|
+
|
|
527
|
+
// vDSP doesn't have sqrt, use vForce
|
|
528
|
+
int n = (int)len;
|
|
529
|
+
vvsqrt(B, A, &n);
|
|
530
|
+
|
|
531
|
+
return args[1];
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Vector normalize (unit vector)
|
|
535
|
+
static napi_value VectorNormalize(napi_env env, napi_callback_info info) {
|
|
536
|
+
size_t argc = 2;
|
|
537
|
+
napi_value args[2];
|
|
538
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
539
|
+
|
|
540
|
+
size_t len_a, len_b;
|
|
541
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
542
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
543
|
+
|
|
544
|
+
if (!A || !B) {
|
|
545
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
549
|
+
|
|
550
|
+
// Compute magnitude
|
|
551
|
+
double magnitude;
|
|
552
|
+
vDSP_dotprD(A, 1, A, 1, &magnitude, len);
|
|
553
|
+
magnitude = sqrt(magnitude);
|
|
554
|
+
|
|
555
|
+
if (magnitude > 0) {
|
|
556
|
+
double inv_mag = 1.0 / magnitude;
|
|
557
|
+
vDSP_vsmulD(A, 1, &inv_mag, B, 1, len);
|
|
558
|
+
} else {
|
|
559
|
+
// Zero vector, just copy
|
|
560
|
+
memcpy(B, A, len * sizeof(double));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return args[1];
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Euclidean distance between two vectors
|
|
567
|
+
static napi_value EuclideanDistance(napi_env env, napi_callback_info info) {
|
|
568
|
+
size_t argc = 2;
|
|
569
|
+
napi_value args[2];
|
|
570
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
571
|
+
|
|
572
|
+
size_t len_a, len_b;
|
|
573
|
+
double* A = GetFloat64ArrayData(env, args[0], &len_a);
|
|
574
|
+
double* B = GetFloat64ArrayData(env, args[1], &len_b);
|
|
575
|
+
|
|
576
|
+
if (!A || !B) {
|
|
577
|
+
NAPI_THROW(env, "Arguments must be Float64Arrays");
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
size_t len = len_a < len_b ? len_a : len_b;
|
|
581
|
+
|
|
582
|
+
// Compute distance using vDSP
|
|
583
|
+
double result;
|
|
584
|
+
vDSP_distancesqD(A, 1, B, 1, &result, len);
|
|
585
|
+
result = sqrt(result);
|
|
586
|
+
|
|
587
|
+
napi_value js_result;
|
|
588
|
+
napi_create_double(env, result, &js_result);
|
|
589
|
+
return js_result;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// RMS (Root Mean Square)
|
|
593
|
+
static napi_value VectorRMS(napi_env env, napi_callback_info info) {
|
|
594
|
+
size_t argc = 1;
|
|
595
|
+
napi_value args[1];
|
|
596
|
+
NAPI_CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
|
597
|
+
|
|
598
|
+
size_t len;
|
|
599
|
+
double* A = GetFloat64ArrayData(env, args[0], &len);
|
|
600
|
+
|
|
601
|
+
if (!A) {
|
|
602
|
+
NAPI_THROW(env, "Argument must be Float64Array");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
double result;
|
|
606
|
+
vDSP_rmsqvD(A, 1, &result, len);
|
|
607
|
+
|
|
608
|
+
napi_value js_result;
|
|
609
|
+
napi_create_double(env, result, &js_result);
|
|
610
|
+
return js_result;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Module initialization
|
|
614
|
+
static napi_value Init(napi_env env, napi_value exports) {
|
|
615
|
+
napi_property_descriptor props[] = {
|
|
616
|
+
// Matrix operations
|
|
617
|
+
{"matmul", nullptr, MatMulDouble, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
618
|
+
{"matmulFloat", nullptr, MatMulFloat, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
619
|
+
{"matvec", nullptr, MatVecMul, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
620
|
+
|
|
621
|
+
// BLAS operations
|
|
622
|
+
{"axpy", nullptr, AXPY, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
623
|
+
|
|
624
|
+
// Vector arithmetic
|
|
625
|
+
{"dot", nullptr, DotProduct, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
626
|
+
{"vadd", nullptr, VectorAdd, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
627
|
+
{"vsub", nullptr, VectorSub, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
628
|
+
{"vmul", nullptr, VectorMul, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
629
|
+
{"vdiv", nullptr, VectorDiv, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
630
|
+
{"vscale", nullptr, VectorScale, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
631
|
+
|
|
632
|
+
// Vector functions
|
|
633
|
+
{"vabs", nullptr, VectorAbs, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
634
|
+
{"vsquare", nullptr, VectorSquare, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
635
|
+
{"vsqrt", nullptr, VectorSqrt, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
636
|
+
{"normalize", nullptr, VectorNormalize, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
637
|
+
|
|
638
|
+
// Reductions
|
|
639
|
+
{"sum", nullptr, VectorSum, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
640
|
+
{"mean", nullptr, VectorMean, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
641
|
+
{"max", nullptr, VectorMax, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
642
|
+
{"min", nullptr, VectorMin, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
643
|
+
{"rms", nullptr, VectorRMS, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
644
|
+
|
|
645
|
+
// Distance metrics
|
|
646
|
+
{"euclidean", nullptr, EuclideanDistance, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
647
|
+
|
|
648
|
+
// Signal processing
|
|
649
|
+
{"fft", nullptr, FFT, nullptr, nullptr, nullptr, napi_default, nullptr},
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
napi_define_properties(env, exports, sizeof(props) / sizeof(props[0]), props);
|
|
653
|
+
|
|
654
|
+
return exports;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "accelerate",
|
|
5
|
+
"sources": ["accelerate.cc"],
|
|
6
|
+
"conditions": [
|
|
7
|
+
["OS=='mac'", {
|
|
8
|
+
"link_settings": {
|
|
9
|
+
"libraries": [
|
|
10
|
+
"-framework Accelerate"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"xcode_settings": {
|
|
14
|
+
"OTHER_CFLAGS": [
|
|
15
|
+
"-mcpu=apple-m4",
|
|
16
|
+
"-mtune=apple-m4",
|
|
17
|
+
"-O3"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}]
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|