diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 77257623157bdd..e8db0d307cace4 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -3267,6 +3267,10 @@ Specification. ```c @@ -3277,21 +3281,20 @@ napi_status napi_get_arraybuffer_info(napi_env env, ``` * `[in] env`: The environment that the API is invoked under. -* `[in] arraybuffer`: `napi_value` representing the `ArrayBuffer` being queried. -* `[out] data`: The underlying data buffer of the `ArrayBuffer`. If byte\_length +* `[in] arraybuffer`: `napi_value` representing the `ArrayBuffer` or `SharedArrayBuffer` being queried. +* `[out] data`: The underlying data buffer of the `ArrayBuffer` or `SharedArrayBuffer` is `0`, this may be `NULL` or any other pointer value. * `[out] byte_length`: Length in bytes of the underlying data buffer. Returns `napi_ok` if the API succeeded. -This API is used to retrieve the underlying data buffer of an `ArrayBuffer` and -its length. +This API is used to retrieve the underlying data buffer of an `ArrayBuffer` or `SharedArrayBuffer` and its length. _WARNING_: Use caution while using this API. The lifetime of the underlying data -buffer is managed by the `ArrayBuffer` even after it's returned. A +buffer is managed by the `ArrayBuffer` or `SharedArrayBuffer` even after it's returned. A possible safe way to use this API is in conjunction with [`napi_create_reference`][], which can be used to guarantee control over the -lifetime of the `ArrayBuffer`. It's also safe to use the returned data buffer +lifetime of the `ArrayBuffer` or `SharedArrayBuffer`. It's also safe to use the returned data buffer within the same callback as long as there are no calls to other APIs that might trigger a GC. @@ -4246,6 +4249,63 @@ This API represents the invocation of the `ArrayBuffer` `IsDetachedBuffer` operation as defined in [Section isDetachedBuffer][] of the ECMAScript Language Specification. +### `node_api_is_sharedarraybuffer` + + + +> Stability: 1 - Experimental + +```c +napi_status node_api_is_sharedarraybuffer(napi_env env, napi_value value, bool* result) +``` + +* `[in] env`: The environment that the API is invoked under. +* `[in] value`: The JavaScript value to check. +* `[out] result`: Whether the given `napi_value` represents a `SharedArrayBuffer`. + +Returns `napi_ok` if the API succeeded. + +This API checks if the Object passed in is a `SharedArrayBuffer`. + +### `node_api_create_sharedarraybuffer` + + + +> Stability: 1 - Experimental + +```c +napi_status node_api_create_sharedarraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result) +``` + +* `[in] env`: The environment that the API is invoked under. +* `[in] byte_length`: The length in bytes of the shared array buffer to create. +* `[out] data`: Pointer to the underlying byte buffer of the `SharedArrayBuffer`. + `data` can optionally be ignored by passing `NULL`. +* `[out] result`: A `napi_value` representing a JavaScript `SharedArrayBuffer`. + +Returns `napi_ok` if the API succeeded. + +This API returns a Node-API value corresponding to a JavaScript `SharedArrayBuffer`. +`SharedArrayBuffer`s are used to represent fixed-length binary data buffers that +can be shared across multiple workers. + +The `SharedArrayBuffer` allocated will have an underlying byte buffer whose size is +determined by the `byte_length` parameter that's passed in. +The underlying buffer is optionally returned back to the caller in case the +caller wants to directly manipulate the buffer. This buffer can only be +written to directly from native code. To write to this buffer from JavaScript, +a typed array or `DataView` object would need to be created. + +JavaScript `SharedArrayBuffer` objects are described in +[Section SharedArrayBuffer objects][] of the ECMAScript Language Specification. + ## Working with JavaScript properties Node-API exposes a set of APIs to get and set properties on JavaScript @@ -6759,6 +6819,7 @@ the add-on's file name during loading. [Section IsArray]: https://tc39.es/ecma262/#sec-isarray [Section IsStrctEqual]: https://tc39.es/ecma262/#sec-strict-equality-comparison [Section Promise objects]: https://tc39.es/ecma262/#sec-promise-objects +[Section SharedArrayBuffer objects]: https://tc39.es/ecma262/#sec-sharedarraybuffer-objects [Section ToBoolean]: https://tc39.es/ecma262/#sec-toboolean [Section ToNumber]: https://tc39.es/ecma262/#sec-tonumber [Section ToObject]: https://tc39.es/ecma262/#sec-toobject diff --git a/src/js_native_api.h b/src/js_native_api.h index 8177ace3acbb89..b8ce2631a80d38 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -486,6 +486,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); diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 260a572ce71a82..1278a0920c4262 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -3076,21 +3076,68 @@ napi_status NAPI_CDECL napi_get_arraybuffer_info(napi_env env, CHECK_ARG(env, arraybuffer); v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); - RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg); - v8::Local ab = value.As(); + if (value->IsArrayBuffer()) { + v8::Local ab = value.As(); - if (data != nullptr) { - *data = ab->Data(); - } + if (data != nullptr) { + *data = ab->Data(); + } - if (byte_length != nullptr) { - *byte_length = ab->ByteLength(); + if (byte_length != nullptr) { + *byte_length = ab->ByteLength(); + } + } else if (value->IsSharedArrayBuffer()) { + v8::Local sab = value.As(); + + if (data != nullptr) { + *data = sab->Data(); + } + + if (byte_length != nullptr) { + *byte_length = sab->ByteLength(); + } + } else { + return napi_set_last_error(env, napi_invalid_arg); } return napi_clear_last_error(env); } +napi_status NAPI_CDECL node_api_is_sharedarraybuffer(napi_env env, + napi_value value, + bool* result) { + CHECK_ENV_NOT_IN_GC(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsSharedArrayBuffer(); + + return napi_clear_last_error(env); +} + +napi_status NAPI_CDECL node_api_create_sharedarraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local buffer = + v8::SharedArrayBuffer::New(isolate, byte_length); + + // Optionally return a pointer to the buffer's data, to avoid another call to + // retrieve it. + if (data != nullptr) { + *data = buffer->Data(); + } + + *result = v8impl::JsValueFromV8LocalValue(buffer); + return GET_RETURN_STATUS(env); +} + napi_status NAPI_CDECL napi_is_typedarray(napi_env env, napi_value value, bool* result) { diff --git a/test/js-native-api/test_sharedarraybuffer/binding.gyp b/test/js-native-api/test_sharedarraybuffer/binding.gyp new file mode 100644 index 00000000000000..0020e6b4a9e917 --- /dev/null +++ b/test/js-native-api/test_sharedarraybuffer/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_sharedarraybuffer", + "sources": [ "test_sharedarraybuffer.c" ] + } + ] +} diff --git a/test/js-native-api/test_sharedarraybuffer/test.js b/test/js-native-api/test_sharedarraybuffer/test.js new file mode 100644 index 00000000000000..9c3066124b7c7b --- /dev/null +++ b/test/js-native-api/test_sharedarraybuffer/test.js @@ -0,0 +1,67 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const test_sharedarraybuffer = require(`./build/${common.buildType}/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' }); +} diff --git a/test/js-native-api/test_sharedarraybuffer/test_sharedarraybuffer.c b/test/js-native-api/test_sharedarraybuffer/test_sharedarraybuffer.c new file mode 100644 index 00000000000000..661e52c566f789 --- /dev/null +++ b/test/js-native-api/test_sharedarraybuffer/test_sharedarraybuffer.c @@ -0,0 +1,130 @@ +#define NAPI_EXPERIMENTAL +#include +#include +#include "../common.h" +#include "../entry_point.h" + +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); + + // 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