Skip to content

Commit 5c08104

Browse files
authored
feat: added SharedArrayBuffer api (#169)
1 parent 7c10c9d commit 5c08104

File tree

9 files changed

+266
-10
lines changed

9 files changed

+266
-10
lines changed

packages/emnapi/include/node/js_native_api.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,14 @@ napi_get_dataview_info(napi_env env,
480480
napi_value* arraybuffer,
481481
size_t* byte_offset);
482482

483+
#ifdef NAPI_EXPERIMENTAL
484+
#define NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
485+
NAPI_EXTERN napi_status NAPI_CDECL
486+
node_api_is_sharedarraybuffer(napi_env env, napi_value value, bool* result);
487+
NAPI_EXTERN napi_status NAPI_CDECL node_api_create_sharedarraybuffer(
488+
napi_env env, size_t byte_length, void** data, napi_value* result);
489+
#endif // NAPI_EXPERIMENTAL
490+
483491
// version management
484492
NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_basic_env env,
485493
uint32_t* result);

packages/emnapi/src/emnapi.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export function $emnapiSyncMemory<T extends ArrayBuffer | ArrayBufferView> (
158158
offset = offset ?? 0
159159
offset = offset >>> 0
160160
let view: Uint8Array
161-
if (arrayBufferOrView instanceof ArrayBuffer) {
161+
if (arrayBufferOrView instanceof ArrayBuffer || emnapiExternalMemory.isSharedArrayBuffer(arrayBufferOrView)) {
162162
const pointer = emnapiExternalMemory.getArrayBufferPointer(arrayBufferOrView, false).address
163163
if (!pointer) throw new Error('Unknown ArrayBuffer address')
164164
if (typeof len !== 'number' || len === -1) {
@@ -217,11 +217,11 @@ export function emnapi_sync_memory (env: napi_env, js_to_wasm: bool, arraybuffer
217217

218218
const id = makeGetValue('arraybuffer_or_view', 0, '*')
219219

220-
const jsValue = emnapiCtx.jsValueFromNapiValue<ArrayBuffer | ArrayBufferView>(id)!
220+
const jsValue = emnapiCtx.jsValueFromNapiValue<ArrayBufferLike | ArrayBufferView>(id)!
221221
const isArrayBuffer = jsValue instanceof ArrayBuffer
222222
const isDataView = jsValue instanceof DataView
223223
const isTypedArray = ArrayBuffer.isView(jsValue) && !isDataView
224-
if (!isArrayBuffer && !isTypedArray && !isDataView) {
224+
if (!isArrayBuffer && !isTypedArray && !isDataView && !emnapiExternalMemory.isSharedArrayBuffer(jsValue)) {
225225
return envObject.setLastError(napi_status.napi_invalid_arg)
226226
}
227227
const ret = $emnapiSyncMemory(Boolean(js_to_wasm), jsValue, offset, len)
@@ -240,7 +240,7 @@ export function $emnapiGetMemoryAddress (arrayBufferOrView: ArrayBuffer | ArrayB
240240
const isArrayBuffer = arrayBufferOrView instanceof ArrayBuffer
241241
const isDataView = arrayBufferOrView instanceof DataView
242242
const isTypedArray = ArrayBuffer.isView(arrayBufferOrView) && !isDataView
243-
if (!isArrayBuffer && !isTypedArray && !isDataView) {
243+
if (!isArrayBuffer && !isTypedArray && !isDataView && !emnapiExternalMemory.isSharedArrayBuffer(arrayBufferOrView)) {
244244
throw new TypeError('emnapiGetMemoryAddress expect ArrayBuffer or ArrayBufferView as first parameter')
245245
}
246246

packages/emnapi/src/memory.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ export const emnapiExternalMemory: {
3232
table: WeakMap<ArrayBuffer, ArrayBufferPointer>
3333
wasmMemoryViewTable: WeakMap<ArrayBufferView, MemoryViewDescriptor>
3434
init: () => void
35+
isSharedArrayBuffer: (value: any) => value is SharedArrayBuffer
3536
isDetachedArrayBuffer: (arrayBuffer: ArrayBufferLike) => boolean
3637
getOrUpdateMemoryView: <T extends ArrayBufferView>(view: T) => T
37-
getArrayBufferPointer: (arrayBuffer: ArrayBuffer, shouldCopy: boolean) => ArrayBufferPointer
38+
getArrayBufferPointer: (arrayBuffer: ArrayBufferLike, shouldCopy: boolean) => ArrayBufferPointer
3839
getViewPointer: <T extends ArrayBufferView>(view: T, shouldCopy: boolean) => ViewPointer<T>
3940
} = {
4041
registry: typeof FinalizationRegistry === 'function' ? new FinalizationRegistry(function (_pointer) { _free(to64('_pointer') as number) }) : undefined,
@@ -47,6 +48,13 @@ export const emnapiExternalMemory: {
4748
emnapiExternalMemory.wasmMemoryViewTable = new WeakMap()
4849
},
4950

51+
isSharedArrayBuffer (value: any): value is SharedArrayBuffer {
52+
return (
53+
(typeof SharedArrayBuffer === 'function' && value instanceof SharedArrayBuffer) ||
54+
(Object.prototype.toString.call(value) === '[object SharedArrayBuffer]')
55+
)
56+
},
57+
5058
isDetachedArrayBuffer: function (arrayBuffer: ArrayBufferLike): boolean {
5159
if (arrayBuffer.byteLength === 0) {
5260
try {

packages/emnapi/src/value-operation.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,24 @@ export function napi_is_arraybuffer (env: napi_env, value: napi_value, result: P
166166
return envObject.clearLastError()
167167
}
168168

169+
/** @__sig ippp */
170+
export function node_api_is_sharedarraybuffer (env: napi_env, value: napi_value, result: Pointer<bool>): napi_status {
171+
const envObject: Env = $CHECK_ENV_NOT_IN_GC!(env)
172+
$CHECK_ARG!(envObject, value)
173+
$CHECK_ARG!(envObject, result)
174+
const h = emnapiCtx.jsValueFromNapiValue(value)!
175+
from64('result')
176+
177+
const r = (
178+
(typeof SharedArrayBuffer === 'function' && h instanceof SharedArrayBuffer) ||
179+
(Object.prototype.toString.call(h) === '[object SharedArrayBuffer]')
180+
)
181+
? 1
182+
: 0
183+
makeSetValue('result', 0, 'r', 'i8')
184+
return envObject.clearLastError()
185+
}
186+
169187
/** @__sig ippp */
170188
export function napi_is_date (env: napi_env, value: napi_value, result: Pointer<bool>): napi_status {
171189
const envObject: Env = $CHECK_ENV_NOT_IN_GC!(env)

packages/emnapi/src/value/convert2c.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function napi_get_arraybuffer_info (env: napi_env, arraybuffer: napi_valu
3131
const envObject: Env = $CHECK_ENV_NOT_IN_GC!(env)
3232
$CHECK_ARG!(envObject, arraybuffer)
3333
const jsValue = emnapiCtx.jsValueFromNapiValue(arraybuffer)!
34-
if (!(jsValue instanceof ArrayBuffer)) {
34+
if (!(jsValue instanceof ArrayBuffer) && !emnapiExternalMemory.isSharedArrayBuffer(jsValue)) {
3535
return envObject.setLastError(napi_status.napi_invalid_arg)
3636
}
3737
if (data) {

packages/emnapi/src/value/create.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ export function napi_create_array_with_length (env: napi_env, length: size_t, re
3535
return envObject.clearLastError()
3636
}
3737

38-
function emnapiCreateArrayBuffer (byte_length: size_t, data: void_pp): ArrayBuffer {
38+
function emnapiCreateArrayBuffer (byte_length: size_t, data: void_pp, shared: boolean): ArrayBuffer {
3939
from64('byte_length')
4040
byte_length = byte_length >>> 0
41-
const arrayBuffer = new ArrayBuffer(byte_length)
41+
const arrayBuffer = shared ? new SharedArrayBuffer(byte_length) : new ArrayBuffer(byte_length)
4242

4343
if (data) {
4444
from64('data')
@@ -59,7 +59,23 @@ export function napi_create_arraybuffer (env: napi_env, byte_length: size_t, dat
5959
return $PREAMBLE!(env, (envObject) => {
6060
$CHECK_ARG!(envObject, result)
6161
from64('result')
62-
const arrayBuffer = emnapiCreateArrayBuffer(byte_length, data)
62+
const arrayBuffer = emnapiCreateArrayBuffer(byte_length, data, false)
63+
value = emnapiCtx.napiValueFromJsValue(arrayBuffer)
64+
makeSetValue('result', 0, 'value', '*')
65+
return $GET_RETURN_STATUS!(envObject)
66+
})
67+
}
68+
69+
/**
70+
* @__sig ipppp
71+
*/
72+
export function node_api_create_sharedarraybuffer (env: napi_env, byte_length: size_t, data: void_pp, result: Pointer<napi_value>): napi_status {
73+
let value: napi_value
74+
75+
return $PREAMBLE!(env, (envObject) => {
76+
$CHECK_ARG!(envObject, result)
77+
from64('result')
78+
const arrayBuffer = emnapiCreateArrayBuffer(byte_length, data, true)
6379
value = emnapiCtx.napiValueFromJsValue(arrayBuffer)
6480
makeSetValue('result', 0, 'value', '*')
6581
return $GET_RETURN_STATUS!(envObject)
@@ -368,7 +384,7 @@ export function napi_create_buffer_copy (
368384
if (!Buffer) {
369385
throw emnapiCtx.createNotSupportBufferError('napi_create_buffer_copy', '')
370386
}
371-
const arrayBuffer = emnapiCreateArrayBuffer(length, result_data)
387+
const arrayBuffer = emnapiCreateArrayBuffer(length, result_data, false)
372388
const buffer = Buffer.from(arrayBuffer)
373389
from64('data')
374390
from64('length')

packages/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ add_test("newtarget" "./newtarget/binding.c" OFF)
327327
add_test("number" "./number/binding.c;./number/test_null.c" OFF)
328328
add_test("symbol" "./symbol/binding.c" OFF)
329329
add_test("typedarray" "./typedarray/binding.c" OFF)
330+
add_test("sharedarraybuffer" "./sharedarraybuffer/binding.c" OFF)
330331
add_test("buffer" "./buffer/binding.c" OFF)
331332
target_compile_definitions("buffer" PRIVATE "NAPI_VERSION=10")
332333
add_test("buffer_finalizer" "./buffer_finalizer/binding.c" OFF)
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#define NAPI_EXPERIMENTAL
2+
#include <js_native_api.h>
3+
#include "../common.h"
4+
#include "../entry_point.h"
5+
6+
#ifdef __wasm__
7+
#include "emnapi.h"
8+
#endif
9+
10+
static napi_value TestIsSharedArrayBuffer(napi_env env,
11+
napi_callback_info info) {
12+
size_t argc = 1;
13+
napi_value args[1];
14+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
15+
16+
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
17+
18+
bool is_sharedarraybuffer;
19+
NODE_API_CALL(
20+
env, node_api_is_sharedarraybuffer(env, args[0], &is_sharedarraybuffer));
21+
22+
napi_value ret;
23+
NODE_API_CALL(env, napi_get_boolean(env, is_sharedarraybuffer, &ret));
24+
25+
return ret;
26+
}
27+
28+
static napi_value TestCreateSharedArrayBuffer(napi_env env,
29+
napi_callback_info info) {
30+
size_t argc = 1;
31+
napi_value args[1];
32+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
33+
34+
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
35+
36+
napi_valuetype valuetype0;
37+
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0));
38+
39+
NODE_API_ASSERT(
40+
env,
41+
valuetype0 == napi_number,
42+
"Wrong type of arguments. Expects a number as first argument.");
43+
44+
int32_t byte_length;
45+
NODE_API_CALL(env, napi_get_value_int32(env, args[0], &byte_length));
46+
47+
NODE_API_ASSERT(env,
48+
byte_length >= 0,
49+
"Invalid byte length. Expects a non-negative integer.");
50+
51+
napi_value ret;
52+
void* data;
53+
NODE_API_CALL(
54+
env, node_api_create_sharedarraybuffer(env, byte_length, &data, &ret));
55+
56+
return ret;
57+
}
58+
59+
static napi_value TestGetSharedArrayBufferInfo(napi_env env,
60+
napi_callback_info info) {
61+
size_t argc = 1;
62+
napi_value args[1];
63+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
64+
65+
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
66+
67+
void* data;
68+
size_t byte_length;
69+
NODE_API_CALL(env,
70+
napi_get_arraybuffer_info(env, args[0], &data, &byte_length));
71+
72+
napi_value ret;
73+
NODE_API_CALL(env, napi_create_uint32(env, byte_length, &ret));
74+
75+
return ret;
76+
}
77+
78+
static void WriteTestDataToBuffer(void* data, size_t byte_length) {
79+
if (byte_length > 0 && data != NULL) {
80+
uint8_t* bytes = (uint8_t*)data;
81+
for (size_t i = 0; i < byte_length; i++) {
82+
bytes[i] = i % 256;
83+
}
84+
}
85+
}
86+
87+
static napi_value TestSharedArrayBufferData(napi_env env,
88+
napi_callback_info info) {
89+
size_t argc = 1;
90+
napi_value args[1];
91+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
92+
93+
NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");
94+
95+
void* data;
96+
size_t byte_length;
97+
NODE_API_CALL(env,
98+
napi_get_arraybuffer_info(env, args[0], &data, &byte_length));
99+
100+
WriteTestDataToBuffer(data, byte_length);
101+
#ifdef __wasm__
102+
emnapi_sync_memory(env, false, &args[0], 0, NAPI_AUTO_LENGTH);
103+
#endif
104+
105+
// Return the same data pointer validity
106+
bool data_valid = (data != NULL) && (byte_length > 0);
107+
108+
napi_value ret;
109+
NODE_API_CALL(env, napi_get_boolean(env, data_valid, &ret));
110+
111+
return ret;
112+
}
113+
114+
EXTERN_C_START
115+
napi_value Init(napi_env env, napi_value exports) {
116+
napi_property_descriptor descriptors[] = {
117+
DECLARE_NODE_API_PROPERTY("TestIsSharedArrayBuffer",
118+
TestIsSharedArrayBuffer),
119+
DECLARE_NODE_API_PROPERTY("TestCreateSharedArrayBuffer",
120+
TestCreateSharedArrayBuffer),
121+
DECLARE_NODE_API_PROPERTY("TestGetSharedArrayBufferInfo",
122+
TestGetSharedArrayBufferInfo),
123+
DECLARE_NODE_API_PROPERTY("TestSharedArrayBufferData",
124+
TestSharedArrayBufferData),
125+
};
126+
127+
NODE_API_CALL(
128+
env,
129+
napi_define_properties(env,
130+
exports,
131+
sizeof(descriptors) / sizeof(*descriptors),
132+
descriptors));
133+
134+
return exports;
135+
}
136+
EXTERN_C_END
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* eslint-disable camelcase */
2+
'use strict'
3+
const assert = require('assert')
4+
const { load } = require('../util')
5+
6+
// eslint-disable-next-line camelcase
7+
module.exports = load('sharedarraybuffer').then(test_sharedarraybuffer => {
8+
{
9+
const sab = new SharedArrayBuffer(16)
10+
const ab = new ArrayBuffer(16)
11+
const obj = {}
12+
const arr = []
13+
14+
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(sab), true)
15+
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(ab), false)
16+
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(obj), false)
17+
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(arr), false)
18+
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(null), false)
19+
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(undefined), false)
20+
}
21+
22+
// Test node_api_create_sharedarraybuffer
23+
{
24+
const sab = test_sharedarraybuffer.TestCreateSharedArrayBuffer(16)
25+
assert(sab instanceof SharedArrayBuffer)
26+
assert.strictEqual(sab.byteLength, 16)
27+
}
28+
29+
// Test node_api_create_get_sharedarraybuffer_info
30+
{
31+
const sab = new SharedArrayBuffer(32)
32+
const byteLength = test_sharedarraybuffer.TestGetSharedArrayBufferInfo(sab)
33+
assert.strictEqual(byteLength, 32)
34+
}
35+
36+
// Test data access
37+
{
38+
const sab = new SharedArrayBuffer(8)
39+
const result = test_sharedarraybuffer.TestSharedArrayBufferData(sab)
40+
assert.strictEqual(result, true)
41+
42+
// Check if data was written correctly
43+
const view = new Uint8Array(sab)
44+
for (let i = 0; i < 8; i++) {
45+
assert.strictEqual(view[i], i % 256)
46+
}
47+
}
48+
49+
// Test data pointer from existing SharedArrayBuffer
50+
{
51+
const sab = new SharedArrayBuffer(16)
52+
const result = test_sharedarraybuffer.TestSharedArrayBufferData(sab)
53+
assert.strictEqual(result, true)
54+
}
55+
56+
// Test zero-length SharedArrayBuffer
57+
{
58+
const sab = test_sharedarraybuffer.TestCreateSharedArrayBuffer(0)
59+
assert(sab instanceof SharedArrayBuffer)
60+
assert.strictEqual(sab.byteLength, 0)
61+
}
62+
63+
// Test invalid arguments
64+
{
65+
assert.throws(() => {
66+
test_sharedarraybuffer.TestGetSharedArrayBufferInfo({})
67+
}, { name: 'Error', message: 'Invalid argument' })
68+
}
69+
})

0 commit comments

Comments
 (0)