Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/emnapi/include/node/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,14 @@ napi_get_dataview_info(napi_env env,
napi_value* arraybuffer,
size_t* byte_offset);

#ifdef NAPI_EXPERIMENTAL
#define NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
NAPI_EXTERN napi_status NAPI_CDECL
node_api_is_sharedarraybuffer(napi_env env, napi_value value, bool* result);
NAPI_EXTERN napi_status NAPI_CDECL node_api_create_sharedarraybuffer(
napi_env env, size_t byte_length, void** data, napi_value* result);
#endif // NAPI_EXPERIMENTAL

// version management
NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_basic_env env,
uint32_t* result);
Expand Down
8 changes: 4 additions & 4 deletions packages/emnapi/src/emnapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export function $emnapiSyncMemory<T extends ArrayBuffer | ArrayBufferView> (
offset = offset ?? 0
offset = offset >>> 0
let view: Uint8Array
if (arrayBufferOrView instanceof ArrayBuffer) {
if (arrayBufferOrView instanceof ArrayBuffer || emnapiExternalMemory.isSharedArrayBuffer(arrayBufferOrView)) {
const pointer = emnapiExternalMemory.getArrayBufferPointer(arrayBufferOrView, false).address
if (!pointer) throw new Error('Unknown ArrayBuffer address')
if (typeof len !== 'number' || len === -1) {
Expand Down Expand Up @@ -217,11 +217,11 @@ export function emnapi_sync_memory (env: napi_env, js_to_wasm: bool, arraybuffer

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

const jsValue = emnapiCtx.jsValueFromNapiValue<ArrayBuffer | ArrayBufferView>(id)!
const jsValue = emnapiCtx.jsValueFromNapiValue<ArrayBufferLike | ArrayBufferView>(id)!
const isArrayBuffer = jsValue instanceof ArrayBuffer
const isDataView = jsValue instanceof DataView
const isTypedArray = ArrayBuffer.isView(jsValue) && !isDataView
if (!isArrayBuffer && !isTypedArray && !isDataView) {
if (!isArrayBuffer && !isTypedArray && !isDataView && !emnapiExternalMemory.isSharedArrayBuffer(jsValue)) {
return envObject.setLastError(napi_status.napi_invalid_arg)
}
const ret = $emnapiSyncMemory(Boolean(js_to_wasm), jsValue, offset, len)
Expand All @@ -240,7 +240,7 @@ export function $emnapiGetMemoryAddress (arrayBufferOrView: ArrayBuffer | ArrayB
const isArrayBuffer = arrayBufferOrView instanceof ArrayBuffer
const isDataView = arrayBufferOrView instanceof DataView
const isTypedArray = ArrayBuffer.isView(arrayBufferOrView) && !isDataView
if (!isArrayBuffer && !isTypedArray && !isDataView) {
if (!isArrayBuffer && !isTypedArray && !isDataView && !emnapiExternalMemory.isSharedArrayBuffer(arrayBufferOrView)) {
throw new TypeError('emnapiGetMemoryAddress expect ArrayBuffer or ArrayBufferView as first parameter')
}

Expand Down
10 changes: 9 additions & 1 deletion packages/emnapi/src/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ export const emnapiExternalMemory: {
table: WeakMap<ArrayBuffer, ArrayBufferPointer>
wasmMemoryViewTable: WeakMap<ArrayBufferView, MemoryViewDescriptor>
init: () => void
isSharedArrayBuffer: (value: any) => value is SharedArrayBuffer
isDetachedArrayBuffer: (arrayBuffer: ArrayBufferLike) => boolean
getOrUpdateMemoryView: <T extends ArrayBufferView>(view: T) => T
getArrayBufferPointer: (arrayBuffer: ArrayBuffer, shouldCopy: boolean) => ArrayBufferPointer
getArrayBufferPointer: (arrayBuffer: ArrayBufferLike, shouldCopy: boolean) => ArrayBufferPointer
getViewPointer: <T extends ArrayBufferView>(view: T, shouldCopy: boolean) => ViewPointer<T>
} = {
registry: typeof FinalizationRegistry === 'function' ? new FinalizationRegistry(function (_pointer) { _free(to64('_pointer') as number) }) : undefined,
Expand All @@ -47,6 +48,13 @@ export const emnapiExternalMemory: {
emnapiExternalMemory.wasmMemoryViewTable = new WeakMap()
},

isSharedArrayBuffer (value: any): value is SharedArrayBuffer {
return (
(typeof SharedArrayBuffer === 'function' && value instanceof SharedArrayBuffer) ||
(Object.prototype.toString.call(value) === '[object SharedArrayBuffer]')
)
},

isDetachedArrayBuffer: function (arrayBuffer: ArrayBufferLike): boolean {
if (arrayBuffer.byteLength === 0) {
try {
Expand Down
18 changes: 18 additions & 0 deletions packages/emnapi/src/value-operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,24 @@ export function napi_is_arraybuffer (env: napi_env, value: napi_value, result: P
return envObject.clearLastError()
}

/** @__sig ippp */
export function node_api_is_sharedarraybuffer (env: napi_env, value: napi_value, result: Pointer<bool>): napi_status {
const envObject: Env = $CHECK_ENV_NOT_IN_GC!(env)
$CHECK_ARG!(envObject, value)
$CHECK_ARG!(envObject, result)
const h = emnapiCtx.jsValueFromNapiValue(value)!
from64('result')

const r = (
(typeof SharedArrayBuffer === 'function' && h instanceof SharedArrayBuffer) ||
(Object.prototype.toString.call(h) === '[object SharedArrayBuffer]')
)
? 1
: 0
makeSetValue('result', 0, 'r', 'i8')
return envObject.clearLastError()
}

/** @__sig ippp */
export function napi_is_date (env: napi_env, value: napi_value, result: Pointer<bool>): napi_status {
const envObject: Env = $CHECK_ENV_NOT_IN_GC!(env)
Expand Down
2 changes: 1 addition & 1 deletion packages/emnapi/src/value/convert2c.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function napi_get_arraybuffer_info (env: napi_env, arraybuffer: napi_valu
const envObject: Env = $CHECK_ENV_NOT_IN_GC!(env)
$CHECK_ARG!(envObject, arraybuffer)
const jsValue = emnapiCtx.jsValueFromNapiValue(arraybuffer)!
if (!(jsValue instanceof ArrayBuffer)) {
if (!(jsValue instanceof ArrayBuffer) && !emnapiExternalMemory.isSharedArrayBuffer(jsValue)) {
return envObject.setLastError(napi_status.napi_invalid_arg)
}
if (data) {
Expand Down
24 changes: 20 additions & 4 deletions packages/emnapi/src/value/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ export function napi_create_array_with_length (env: napi_env, length: size_t, re
return envObject.clearLastError()
}

function emnapiCreateArrayBuffer (byte_length: size_t, data: void_pp): ArrayBuffer {
function emnapiCreateArrayBuffer (byte_length: size_t, data: void_pp, shared: boolean): ArrayBuffer {
from64('byte_length')
byte_length = byte_length >>> 0
const arrayBuffer = new ArrayBuffer(byte_length)
const arrayBuffer = shared ? new SharedArrayBuffer(byte_length) : new ArrayBuffer(byte_length)

if (data) {
from64('data')
Expand All @@ -59,7 +59,23 @@ export function napi_create_arraybuffer (env: napi_env, byte_length: size_t, dat
return $PREAMBLE!(env, (envObject) => {
$CHECK_ARG!(envObject, result)
from64('result')
const arrayBuffer = emnapiCreateArrayBuffer(byte_length, data)
const arrayBuffer = emnapiCreateArrayBuffer(byte_length, data, false)
value = emnapiCtx.napiValueFromJsValue(arrayBuffer)
makeSetValue('result', 0, 'value', '*')
return $GET_RETURN_STATUS!(envObject)
})
}

/**
* @__sig ipppp
*/
export function node_api_create_sharedarraybuffer (env: napi_env, byte_length: size_t, data: void_pp, result: Pointer<napi_value>): napi_status {
let value: napi_value

return $PREAMBLE!(env, (envObject) => {
$CHECK_ARG!(envObject, result)
from64('result')
const arrayBuffer = emnapiCreateArrayBuffer(byte_length, data, true)
value = emnapiCtx.napiValueFromJsValue(arrayBuffer)
makeSetValue('result', 0, 'value', '*')
return $GET_RETURN_STATUS!(envObject)
Expand Down Expand Up @@ -368,7 +384,7 @@ export function napi_create_buffer_copy (
if (!Buffer) {
throw emnapiCtx.createNotSupportBufferError('napi_create_buffer_copy', '')
}
const arrayBuffer = emnapiCreateArrayBuffer(length, result_data)
const arrayBuffer = emnapiCreateArrayBuffer(length, result_data, false)
const buffer = Buffer.from(arrayBuffer)
from64('data')
from64('length')
Expand Down
1 change: 1 addition & 0 deletions packages/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ add_test("newtarget" "./newtarget/binding.c" OFF)
add_test("number" "./number/binding.c;./number/test_null.c" OFF)
add_test("symbol" "./symbol/binding.c" OFF)
add_test("typedarray" "./typedarray/binding.c" OFF)
add_test("sharedarraybuffer" "./sharedarraybuffer/binding.c" OFF)
add_test("buffer" "./buffer/binding.c" OFF)
target_compile_definitions("buffer" PRIVATE "NAPI_VERSION=10")
add_test("buffer_finalizer" "./buffer_finalizer/binding.c" OFF)
Expand Down
136 changes: 136 additions & 0 deletions packages/test/sharedarraybuffer/binding.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#define NAPI_EXPERIMENTAL
#include <js_native_api.h>
#include "../common.h"
#include "../entry_point.h"

#ifdef __wasm__
#include "emnapi.h"
#endif

static napi_value TestIsSharedArrayBuffer(napi_env env,
napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));

NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");

bool is_sharedarraybuffer;
NODE_API_CALL(
env, node_api_is_sharedarraybuffer(env, args[0], &is_sharedarraybuffer));

napi_value ret;
NODE_API_CALL(env, napi_get_boolean(env, is_sharedarraybuffer, &ret));

return ret;
}

static napi_value TestCreateSharedArrayBuffer(napi_env env,
napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));

NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");

napi_valuetype valuetype0;
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0));

NODE_API_ASSERT(
env,
valuetype0 == napi_number,
"Wrong type of arguments. Expects a number as first argument.");

int32_t byte_length;
NODE_API_CALL(env, napi_get_value_int32(env, args[0], &byte_length));

NODE_API_ASSERT(env,
byte_length >= 0,
"Invalid byte length. Expects a non-negative integer.");

napi_value ret;
void* data;
NODE_API_CALL(
env, node_api_create_sharedarraybuffer(env, byte_length, &data, &ret));

return ret;
}

static napi_value TestGetSharedArrayBufferInfo(napi_env env,
napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));

NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");

void* data;
size_t byte_length;
NODE_API_CALL(env,
napi_get_arraybuffer_info(env, args[0], &data, &byte_length));

napi_value ret;
NODE_API_CALL(env, napi_create_uint32(env, byte_length, &ret));

return ret;
}

static void WriteTestDataToBuffer(void* data, size_t byte_length) {
if (byte_length > 0 && data != NULL) {
uint8_t* bytes = (uint8_t*)data;
for (size_t i = 0; i < byte_length; i++) {
bytes[i] = i % 256;
}
}
}

static napi_value TestSharedArrayBufferData(napi_env env,
napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));

NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments");

void* data;
size_t byte_length;
NODE_API_CALL(env,
napi_get_arraybuffer_info(env, args[0], &data, &byte_length));

WriteTestDataToBuffer(data, byte_length);
#ifdef __wasm__
emnapi_sync_memory(env, false, &args[0], 0, NAPI_AUTO_LENGTH);
#endif

// Return the same data pointer validity
bool data_valid = (data != NULL) && (byte_length > 0);

napi_value ret;
NODE_API_CALL(env, napi_get_boolean(env, data_valid, &ret));

return ret;
}

EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
DECLARE_NODE_API_PROPERTY("TestIsSharedArrayBuffer",
TestIsSharedArrayBuffer),
DECLARE_NODE_API_PROPERTY("TestCreateSharedArrayBuffer",
TestCreateSharedArrayBuffer),
DECLARE_NODE_API_PROPERTY("TestGetSharedArrayBufferInfo",
TestGetSharedArrayBufferInfo),
DECLARE_NODE_API_PROPERTY("TestSharedArrayBufferData",
TestSharedArrayBufferData),
};

NODE_API_CALL(
env,
napi_define_properties(env,
exports,
sizeof(descriptors) / sizeof(*descriptors),
descriptors));

return exports;
}
EXTERN_C_END
69 changes: 69 additions & 0 deletions packages/test/sharedarraybuffer/sharedarraybuffer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* eslint-disable camelcase */
'use strict'
const assert = require('assert')
const { load } = require('../util')

// eslint-disable-next-line camelcase
module.exports = load('sharedarraybuffer').then(test_sharedarraybuffer => {
{
const sab = new SharedArrayBuffer(16)
const ab = new ArrayBuffer(16)
const obj = {}
const arr = []

assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(sab), true)
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(ab), false)
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(obj), false)
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(arr), false)
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(null), false)
assert.strictEqual(test_sharedarraybuffer.TestIsSharedArrayBuffer(undefined), false)
}

// Test node_api_create_sharedarraybuffer
{
const sab = test_sharedarraybuffer.TestCreateSharedArrayBuffer(16)
assert(sab instanceof SharedArrayBuffer)
assert.strictEqual(sab.byteLength, 16)
}

// Test node_api_create_get_sharedarraybuffer_info
{
const sab = new SharedArrayBuffer(32)
const byteLength = test_sharedarraybuffer.TestGetSharedArrayBufferInfo(sab)
assert.strictEqual(byteLength, 32)
}

// Test data access
{
const sab = new SharedArrayBuffer(8)
const result = test_sharedarraybuffer.TestSharedArrayBufferData(sab)
assert.strictEqual(result, true)

// Check if data was written correctly
const view = new Uint8Array(sab)
for (let i = 0; i < 8; i++) {
assert.strictEqual(view[i], i % 256)
}
}

// Test data pointer from existing SharedArrayBuffer
{
const sab = new SharedArrayBuffer(16)
const result = test_sharedarraybuffer.TestSharedArrayBufferData(sab)
assert.strictEqual(result, true)
}

// Test zero-length SharedArrayBuffer
{
const sab = test_sharedarraybuffer.TestCreateSharedArrayBuffer(0)
assert(sab instanceof SharedArrayBuffer)
assert.strictEqual(sab.byteLength, 0)
}

// Test invalid arguments
{
assert.throws(() => {
test_sharedarraybuffer.TestGetSharedArrayBufferInfo({})
}, { name: 'Error', message: 'Invalid argument' })
}
})