Skip to content

Commit 96d67bf

Browse files
committed
feat: support SharedArrayBuffer in napi_create_typedarray (#212)
1 parent 3802768 commit 96d67bf

4 files changed

Lines changed: 251 additions & 33 deletions

File tree

packages/emnapi/src/value/create.ts

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -361,15 +361,12 @@ export function napi_create_typedarray (
361361
$CHECK_ARG!(envObject, result)
362362

363363
const handle = emnapiCtx.handleStore.get(arraybuffer)!
364-
if (!handle.isArrayBuffer()) {
365-
return envObject.setLastError(napi_status.napi_invalid_arg)
366-
}
367364
const buffer = handle.value
368365

369366
from64('byte_offset')
370367
from64('length')
371368

372-
const createTypedArray = function (envObject: Env, Type: { new (...args: any[]): ArrayBufferView; name?: string }, size_of_element: number, buffer: ArrayBuffer, byte_offset: size_t, length: size_t): napi_status {
369+
const createTypedArray = function (envObject: Env, Type: { new (...args: any[]): ArrayBufferView; name?: string }, size_of_element: number, buffer: ArrayBuffer | SharedArrayBuffer, byte_offset: size_t, length: size_t): napi_status {
373370
byte_offset = byte_offset >>> 0
374371
length = length >>> 0
375372
if (size_of_element > 1) {
@@ -408,36 +405,40 @@ export function napi_create_typedarray (
408405
return envObject.getReturnStatus()
409406
}
410407

411-
switch (type) {
412-
case napi_typedarray_type.napi_int8_array:
413-
return createTypedArray(envObject, Int8Array, 1, buffer, byte_offset, length)
414-
case napi_typedarray_type.napi_uint8_array:
415-
return createTypedArray(envObject, Uint8Array, 1, buffer, byte_offset, length)
416-
case napi_typedarray_type.napi_uint8_clamped_array:
417-
return createTypedArray(envObject, Uint8ClampedArray, 1, buffer, byte_offset, length)
418-
case napi_typedarray_type.napi_int16_array:
419-
return createTypedArray(envObject, Int16Array, 2, buffer, byte_offset, length)
420-
case napi_typedarray_type.napi_uint16_array:
421-
return createTypedArray(envObject, Uint16Array, 2, buffer, byte_offset, length)
422-
case napi_typedarray_type.napi_int32_array:
423-
return createTypedArray(envObject, Int32Array, 4, buffer, byte_offset, length)
424-
case napi_typedarray_type.napi_uint32_array:
425-
return createTypedArray(envObject, Uint32Array, 4, buffer, byte_offset, length)
426-
case napi_typedarray_type.napi_float32_array:
427-
return createTypedArray(envObject, Float32Array, 4, buffer, byte_offset, length)
428-
case napi_typedarray_type.napi_float64_array:
429-
return createTypedArray(envObject, Float64Array, 8, buffer, byte_offset, length)
430-
case napi_typedarray_type.napi_bigint64_array:
431-
return createTypedArray(envObject, BigInt64Array, 8, buffer, byte_offset, length)
432-
case napi_typedarray_type.napi_biguint64_array:
433-
return createTypedArray(envObject, BigUint64Array, 8, buffer, byte_offset, length)
434-
case napi_typedarray_type.napi_float16_array:
435-
if (typeof Float16Array !== 'function') {
408+
if (buffer instanceof ArrayBuffer || emnapiExternalMemory.isSharedArrayBuffer(buffer)) {
409+
switch (type) {
410+
case napi_typedarray_type.napi_int8_array:
411+
return createTypedArray(envObject, Int8Array, 1, buffer, byte_offset, length)
412+
case napi_typedarray_type.napi_uint8_array:
413+
return createTypedArray(envObject, Uint8Array, 1, buffer, byte_offset, length)
414+
case napi_typedarray_type.napi_uint8_clamped_array:
415+
return createTypedArray(envObject, Uint8ClampedArray, 1, buffer, byte_offset, length)
416+
case napi_typedarray_type.napi_int16_array:
417+
return createTypedArray(envObject, Int16Array, 2, buffer, byte_offset, length)
418+
case napi_typedarray_type.napi_uint16_array:
419+
return createTypedArray(envObject, Uint16Array, 2, buffer, byte_offset, length)
420+
case napi_typedarray_type.napi_int32_array:
421+
return createTypedArray(envObject, Int32Array, 4, buffer, byte_offset, length)
422+
case napi_typedarray_type.napi_uint32_array:
423+
return createTypedArray(envObject, Uint32Array, 4, buffer, byte_offset, length)
424+
case napi_typedarray_type.napi_float32_array:
425+
return createTypedArray(envObject, Float32Array, 4, buffer, byte_offset, length)
426+
case napi_typedarray_type.napi_float64_array:
427+
return createTypedArray(envObject, Float64Array, 8, buffer, byte_offset, length)
428+
case napi_typedarray_type.napi_bigint64_array:
429+
return createTypedArray(envObject, BigInt64Array, 8, buffer, byte_offset, length)
430+
case napi_typedarray_type.napi_biguint64_array:
431+
return createTypedArray(envObject, BigUint64Array, 8, buffer, byte_offset, length)
432+
case napi_typedarray_type.napi_float16_array:
433+
if (typeof Float16Array !== 'function') {
434+
return envObject.setLastError(napi_status.napi_invalid_arg)
435+
}
436+
return createTypedArray(envObject, Float16Array, 2, buffer, byte_offset, length)
437+
default:
436438
return envObject.setLastError(napi_status.napi_invalid_arg)
437-
}
438-
return createTypedArray(envObject, Float16Array, 2, buffer, byte_offset, length)
439-
default:
440-
return envObject.setLastError(napi_status.napi_invalid_arg)
439+
}
440+
} else {
441+
return envObject.setLastError(napi_status.napi_invalid_arg)
441442
}
442443
})
443444
}

packages/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ add_test("newtarget" "./newtarget/binding.c" OFF)
323323
add_test("number" "./number/binding.c;./number/test_null.c" OFF)
324324
add_test("symbol" "./symbol/binding.c" OFF)
325325
add_test("typedarray" "./typedarray/binding.c" OFF)
326+
add_test("typedarray_sharedarraybuffer" "./typedarray/test_typedarray_sharedarraybuffer.c" OFF)
326327
add_test("sharedarraybuffer" "./sharedarraybuffer/binding.c" OFF)
327328
if(IS_EMSCRIPTEN)
328329
target_link_options("sharedarraybuffer" PRIVATE "-sEXPORTED_RUNTIME_METHODS=['emnapiInit','ExitStatus','wasmMemory','growMemory','emnapiAcquireExternalSharedArrayBuffer']")
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Verify napi_create_typedarray() accepts SharedArrayBuffer-backed views
2+
// without changing its existing error handling.
3+
4+
#include <js_native_api.h>
5+
#include "../common.h"
6+
#include "../entry_point.h"
7+
8+
static napi_value CreateTypedArray(napi_env env, napi_callback_info info) {
9+
size_t argc = 4;
10+
napi_value args[4];
11+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
12+
13+
NODE_API_ASSERT(env, argc == 2 || argc == 4, "Wrong number of arguments");
14+
15+
bool is_typedarray;
16+
NODE_API_CALL(env, napi_is_typedarray(env, args[0], &is_typedarray));
17+
NODE_API_ASSERT(env,
18+
is_typedarray,
19+
"Wrong type of arguments. Expects a typed array as first "
20+
"argument.");
21+
22+
napi_typedarray_type type;
23+
size_t length;
24+
size_t byte_offset;
25+
NODE_API_CALL(env,
26+
napi_get_typedarray_info(
27+
env, args[0], &type, &length, NULL, NULL, &byte_offset));
28+
29+
if (argc == 4) {
30+
uint32_t uint32_length;
31+
NODE_API_CALL(env, napi_get_value_uint32(env, args[2], &uint32_length));
32+
length = uint32_length;
33+
34+
uint32_t uint32_byte_offset;
35+
NODE_API_CALL(env,
36+
napi_get_value_uint32(env, args[3], &uint32_byte_offset));
37+
byte_offset = uint32_byte_offset;
38+
}
39+
40+
napi_value typedarray;
41+
NODE_API_CALL(env,
42+
napi_create_typedarray(
43+
env, type, length, args[1], byte_offset, &typedarray));
44+
45+
return typedarray;
46+
}
47+
48+
static napi_value GetArrayBuffer(napi_env env, napi_callback_info info) {
49+
size_t argc = 1;
50+
napi_value args[1];
51+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
52+
53+
NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments");
54+
55+
napi_value arraybuffer;
56+
NODE_API_CALL(env,
57+
napi_get_typedarray_info(
58+
env, args[0], NULL, NULL, NULL, &arraybuffer, NULL));
59+
60+
return arraybuffer;
61+
}
62+
63+
EXTERN_C_START
64+
napi_value Init(napi_env env, napi_value exports) {
65+
napi_property_descriptor descriptors[] = {
66+
DECLARE_NODE_API_PROPERTY("CreateTypedArray", CreateTypedArray),
67+
DECLARE_NODE_API_PROPERTY("GetArrayBuffer", GetArrayBuffer),
68+
};
69+
70+
NODE_API_CALL(
71+
env,
72+
napi_define_properties(env,
73+
exports,
74+
sizeof(descriptors) / sizeof(*descriptors),
75+
descriptors));
76+
77+
return exports;
78+
}
79+
EXTERN_C_END
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/* eslint-disable camelcase */
2+
'use strict'
3+
4+
// Verify SharedArrayBuffer-backed typed arrays can be created through
5+
// napi_create_typedarray() while preserving existing ArrayBuffer behavior.
6+
7+
const assert = require('assert')
8+
const { load } = require('../util')
9+
10+
module.exports = load('typedarray_sharedarraybuffer').then(
11+
(test_typedarray_sharedarraybuffer) => {
12+
const typedArrayCases = [
13+
{ type: Int8Array, values: [-1, 0, 127] },
14+
{ type: Uint8Array, values: [1, 2, 255] },
15+
{ type: Uint8ClampedArray, values: [0, 128, 255] },
16+
{ type: Int16Array, values: [-1, 0, 32767] },
17+
{ type: Uint16Array, values: [1, 2, 65535] },
18+
{ type: Int32Array, values: [-1, 0, 123456789] },
19+
{ type: Uint32Array, values: [1, 2, 4294967295] },
20+
{ type: Float32Array, values: [0.5, -1.5, 42.25] },
21+
{ type: Float64Array, values: [0.5, -1.5, 42.25] },
22+
{ type: BigInt64Array, values: [1n, -2n, 123456789n] },
23+
{ type: BigUint64Array, values: [1n, 2n, 123456789n] }
24+
]
25+
26+
if (typeof Float16Array === 'function') {
27+
typedArrayCases.push({ type: Float16Array, values: [0.5, -1.5, 42.25] })
28+
}
29+
30+
function createBuffer (Type, BufferType, length) {
31+
const byteOffset = Type.BYTES_PER_ELEMENT
32+
const byteLength = byteOffset + (length * Type.BYTES_PER_ELEMENT)
33+
return {
34+
buffer: new BufferType(byteLength),
35+
byteOffset
36+
}
37+
}
38+
39+
function createTypedArray (Type, buffer, byteOffset, length) {
40+
const template = new Type(buffer, byteOffset, length)
41+
return test_typedarray_sharedarraybuffer.CreateTypedArray(
42+
template,
43+
buffer
44+
)
45+
}
46+
47+
function verifyTypedArray (Type, buffer, byteOffset, values) {
48+
const theArray = createTypedArray(Type, buffer, byteOffset, values.length)
49+
const theArrayBuffer =
50+
test_typedarray_sharedarraybuffer.GetArrayBuffer(theArray)
51+
52+
assert.ok(theArray instanceof Type)
53+
assert.strictEqual(theArray.buffer, buffer)
54+
assert.strictEqual(theArrayBuffer, buffer)
55+
assert.strictEqual(theArray.byteOffset, byteOffset)
56+
assert.strictEqual(theArray.length, values.length)
57+
58+
theArray.set(values)
59+
assert.deepStrictEqual(
60+
Array.from(new Type(buffer, byteOffset, values.length)),
61+
values
62+
)
63+
}
64+
65+
// Keep the existing ArrayBuffer behavior covered while focusing this test
66+
// on SharedArrayBuffer-backed TypedArray creation.
67+
{
68+
const { buffer, byteOffset } = createBuffer(Uint8Array, ArrayBuffer, 3)
69+
verifyTypedArray(Uint8Array, buffer, byteOffset, [1, 2, 3])
70+
}
71+
72+
// Verify all TypedArray variants can be created from SharedArrayBuffer.
73+
typedArrayCases.forEach(({ type, values }) => {
74+
const { buffer, byteOffset } = createBuffer(
75+
type,
76+
SharedArrayBuffer,
77+
values.length
78+
)
79+
verifyTypedArray(type, buffer, byteOffset, values)
80+
})
81+
82+
// Test for creating TypedArrays with SharedArrayBuffer and invalid range.
83+
for (const { type, values } of typedArrayCases) {
84+
const { buffer, byteOffset } = createBuffer(
85+
type,
86+
SharedArrayBuffer,
87+
values.length
88+
)
89+
const template = new type(buffer, byteOffset, values.length)
90+
91+
assert.throws(() => {
92+
test_typedarray_sharedarraybuffer.CreateTypedArray(
93+
template,
94+
buffer,
95+
values.length + 1,
96+
byteOffset
97+
)
98+
}, RangeError)
99+
}
100+
101+
// Test for creating TypedArrays with SharedArrayBuffer and invalid alignment.
102+
for (const { type, values } of typedArrayCases) {
103+
if (type.BYTES_PER_ELEMENT <= 1) {
104+
continue
105+
}
106+
107+
const { buffer, byteOffset } = createBuffer(
108+
type,
109+
SharedArrayBuffer,
110+
values.length
111+
)
112+
const template = new type(buffer, byteOffset, values.length)
113+
114+
assert.throws(() => {
115+
test_typedarray_sharedarraybuffer.CreateTypedArray(
116+
template,
117+
buffer,
118+
1,
119+
byteOffset + 1
120+
)
121+
}, RangeError)
122+
}
123+
124+
// Test invalid arguments.
125+
{
126+
const template = new Uint8Array(1)
127+
128+
assert.throws(() => {
129+
test_typedarray_sharedarraybuffer.CreateTypedArray(template, {})
130+
}, { name: 'Error', message: 'Invalid argument' })
131+
132+
assert.throws(() => {
133+
test_typedarray_sharedarraybuffer.CreateTypedArray(template, 1)
134+
}, { name: 'Error', message: 'Invalid argument' })
135+
}
136+
}
137+
)

0 commit comments

Comments
 (0)