Skip to content

Commit 220bee2

Browse files
authored
feat: add SharedArrayBuffer (#1688)
1 parent 23ce3b1 commit 220bee2

File tree

11 files changed

+345
-1
lines changed

11 files changed

+345
-1
lines changed

doc/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ The following is the documentation for node-addon-api.
6060
- [ClassPropertyDescriptor](class_property_descriptor.md)
6161
- [Buffer](buffer.md)
6262
- [ArrayBuffer](array_buffer.md)
63+
- [SharedArrayBuffer](shared_array_buffer.md)
6364
- [TypedArray](typed_array.md)
6465
- [TypedArrayOf](typed_array_of.md)
6566
- [DataView](dataview.md)

doc/shared_array_buffer.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# SharedArrayBuffer
2+
3+
Class `Napi::SharedArrayBuffer` inherits from class [`Napi::Object`][].
4+
5+
The `Napi::SharedArrayBuffer` class corresponds to the
6+
[JavaScript `SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)
7+
class.
8+
9+
**NOTE**: The support for `Napi::SharedArrayBuffer` is only available when using
10+
`NAPI_EXPERIMENTAL` and building against Node.js headers that support this
11+
feature.
12+
13+
## Methods
14+
15+
### New
16+
17+
Allocates a new `Napi::SharedArrayBuffer` instance with a given length.
18+
19+
```cpp
20+
static Napi::SharedArrayBuffer Napi::SharedArrayBuffer::New(napi_env env, size_t byteLength);
21+
```
22+
23+
- `[in] env`: The environment in which to create the `Napi::SharedArrayBuffer`
24+
instance.
25+
- `[in] byteLength`: The length to be allocated, in bytes.
26+
27+
Returns a new `Napi::SharedArrayBuffer` instance.
28+
29+
### Constructor
30+
31+
Initializes an empty instance of the `Napi::SharedArrayBuffer` class.
32+
33+
```cpp
34+
Napi::SharedArrayBuffer::SharedArrayBuffer();
35+
```
36+
37+
### Constructor
38+
39+
Initializes a wrapper instance of an existing `Napi::SharedArrayBuffer` object.
40+
41+
```cpp
42+
Napi::SharedArrayBuffer::SharedArrayBuffer(napi_env env, napi_value value);
43+
```
44+
45+
- `[in] env`: The environment in which to create the `Napi::SharedArrayBuffer`
46+
instance.
47+
- `[in] value`: The `Napi::SharedArrayBuffer` reference to wrap.
48+
49+
### ByteLength
50+
51+
```cpp
52+
size_t Napi::SharedArrayBuffer::ByteLength() const;
53+
```
54+
55+
Returns the length of the wrapped data, in bytes.
56+
57+
### Data
58+
59+
```cpp
60+
void* Napi::SharedArrayBuffer::Data() const;
61+
```
62+
63+
Returns a pointer the wrapped data.
64+
65+
[`Napi::Object`]: ./object.md

doc/value.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,19 @@ bool Napi::Value::IsPromise() const;
268268
Returns `true` if the underlying value is a JavaScript `Napi::Promise` or
269269
`false` otherwise.
270270

271+
### IsSharedArrayBuffer
272+
273+
```cpp
274+
bool Napi::Value::IsSharedArrayBuffer() const;
275+
```
276+
277+
Returns `true` if the underlying value is a JavaScript
278+
`Napi::IsSharedArrayBuffer` or `false` otherwise.
279+
280+
**NOTE**: The support for `Napi::SharedArrayBuffer` is only available when using
281+
`NAPI_EXPERIMENTAL` and building against Node.js headers that support this
282+
feature.
283+
271284
### IsString
272285

273286
```cpp

napi-inl.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,19 @@ inline bool Value::IsExternal() const {
934934
return Type() == napi_external;
935935
}
936936

937+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
938+
inline bool Value::IsSharedArrayBuffer() const {
939+
if (IsEmpty()) {
940+
return false;
941+
}
942+
943+
bool result;
944+
napi_status status = node_api_is_sharedarraybuffer(_env, _value, &result);
945+
NAPI_THROW_IF_FAILED(_env, status, false);
946+
return result;
947+
}
948+
#endif
949+
937950
template <typename T>
938951
inline T Value::As() const {
939952
#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS
@@ -2068,6 +2081,55 @@ inline uint32_t Array::Length() const {
20682081
return result;
20692082
}
20702083

2084+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
2085+
////////////////////////////////////////////////////////////////////////////////
2086+
// SharedArrayBuffer class
2087+
////////////////////////////////////////////////////////////////////////////////
2088+
2089+
inline SharedArrayBuffer::SharedArrayBuffer() : Object() {}
2090+
2091+
inline SharedArrayBuffer::SharedArrayBuffer(napi_env env, napi_value value)
2092+
: Object(env, value) {}
2093+
2094+
inline void SharedArrayBuffer::CheckCast(napi_env env, napi_value value) {
2095+
NAPI_CHECK(value != nullptr, "SharedArrayBuffer::CheckCast", "empty value");
2096+
2097+
bool result;
2098+
napi_status status = node_api_is_sharedarraybuffer(env, value, &result);
2099+
NAPI_CHECK(status == napi_ok,
2100+
"SharedArrayBuffer::CheckCast",
2101+
"node_api_is_sharedarraybuffer failed");
2102+
NAPI_CHECK(
2103+
result, "SharedArrayBuffer::CheckCast", "value is not sharedarraybuffer");
2104+
}
2105+
2106+
inline SharedArrayBuffer SharedArrayBuffer::New(napi_env env,
2107+
size_t byteLength) {
2108+
napi_value value;
2109+
void* data;
2110+
napi_status status =
2111+
node_api_create_sharedarraybuffer(env, byteLength, &data, &value);
2112+
NAPI_THROW_IF_FAILED(env, status, SharedArrayBuffer());
2113+
2114+
return SharedArrayBuffer(env, value);
2115+
}
2116+
2117+
inline void* SharedArrayBuffer::Data() {
2118+
void* data;
2119+
napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr);
2120+
NAPI_THROW_IF_FAILED(_env, status, nullptr);
2121+
return data;
2122+
}
2123+
2124+
inline size_t SharedArrayBuffer::ByteLength() {
2125+
size_t length;
2126+
napi_status status =
2127+
napi_get_arraybuffer_info(_env, _value, nullptr, &length);
2128+
NAPI_THROW_IF_FAILED(_env, status, 0);
2129+
return length;
2130+
}
2131+
#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
2132+
20712133
////////////////////////////////////////////////////////////////////////////////
20722134
// ArrayBuffer class
20732135
////////////////////////////////////////////////////////////////////////////////

napi.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,9 @@ class Value {
543543
bool IsDataView() const; ///< Tests if a value is a JavaScript data view.
544544
bool IsBuffer() const; ///< Tests if a value is a Node buffer.
545545
bool IsExternal() const; ///< Tests if a value is a pointer to external data.
546+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
547+
bool IsSharedArrayBuffer() const;
548+
#endif
546549

547550
/// Casts to another type of `Napi::Value`, when the actual type is known or
548551
/// assumed.
@@ -1202,6 +1205,21 @@ class Object::iterator {
12021205
};
12031206
#endif // NODE_ADDON_API_CPP_EXCEPTIONS
12041207

1208+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
1209+
class SharedArrayBuffer : public Object {
1210+
public:
1211+
SharedArrayBuffer();
1212+
SharedArrayBuffer(napi_env env, napi_value value);
1213+
1214+
static SharedArrayBuffer New(napi_env env, size_t byteLength);
1215+
1216+
static void CheckCast(napi_env env, napi_value value);
1217+
1218+
void* Data();
1219+
size_t ByteLength();
1220+
};
1221+
#endif
1222+
12051223
/// A JavaScript array buffer value.
12061224
class ArrayBuffer : public Object {
12071225
public:

test/binding.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Object InitTypedThreadSafeFunctionSum(Env env);
6464
Object InitTypedThreadSafeFunctionUnref(Env env);
6565
Object InitTypedThreadSafeFunction(Env env);
6666
#endif
67+
Object InitSharedArrayBuffer(Env env);
6768
Object InitSymbol(Env env);
6869
Object InitTypedArray(Env env);
6970
Object InitGlobalObject(Env env);
@@ -140,6 +141,7 @@ Object Init(Env env, Object exports) {
140141
exports.Set("promise", InitPromise(env));
141142
exports.Set("run_script", InitRunScript(env));
142143
exports.Set("symbol", InitSymbol(env));
144+
exports.Set("sharedarraybuffer", InitSharedArrayBuffer(env));
143145
#if (NAPI_VERSION > 3)
144146
exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env));
145147
exports.Set("threadsafe_function_exception",
@@ -194,6 +196,12 @@ Object Init(Env env, Object exports) {
194196
"isExperimental",
195197
Napi::Boolean::New(env, NAPI_VERSION == NAPI_VERSION_EXPERIMENTAL));
196198

199+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
200+
exports.Set("hasSharedArrayBuffer", Napi::Boolean::New(env, true));
201+
#else
202+
exports.Set("hasSharedArrayBuffer", Napi::Boolean::New(env, false));
203+
#endif
204+
197205
return exports;
198206
}
199207

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
'object/subscript_operator.cc',
5555
'promise.cc',
5656
'run_script.cc',
57+
'shared_array_buffer.cc',
5758
'symbol.cc',
5859
'threadsafe_function/threadsafe_function_ctx.cc',
5960
'threadsafe_function/threadsafe_function_exception.cc',

test/shared_array_buffer.cc

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include "napi.h"
2+
3+
using namespace Napi;
4+
5+
namespace {
6+
7+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
8+
Value TestIsSharedArrayBuffer(const CallbackInfo& info) {
9+
if (info.Length() < 1) {
10+
Error::New(info.Env(), "Wrong number of arguments")
11+
.ThrowAsJavaScriptException();
12+
return Value();
13+
}
14+
15+
return Boolean::New(info.Env(), info[0].IsSharedArrayBuffer());
16+
}
17+
18+
Value TestCreateSharedArrayBuffer(const CallbackInfo& info) {
19+
if (info.Length() < 1) {
20+
Error::New(info.Env(), "Wrong number of arguments")
21+
.ThrowAsJavaScriptException();
22+
return Value();
23+
} else if (!info[0].IsNumber()) {
24+
Error::New(info.Env(),
25+
"Wrong type of arguments. Expects a number as first argument.")
26+
.ThrowAsJavaScriptException();
27+
return Value();
28+
}
29+
30+
auto byte_length = info[0].As<Number>().Uint32Value();
31+
if (byte_length == 0) {
32+
Error::New(info.Env(),
33+
"Invalid byte length. Expects a non-negative integer.")
34+
.ThrowAsJavaScriptException();
35+
return Value();
36+
}
37+
38+
return SharedArrayBuffer::New(info.Env(), byte_length);
39+
}
40+
41+
Value TestGetSharedArrayBufferInfo(const CallbackInfo& info) {
42+
if (info.Length() < 1) {
43+
Error::New(info.Env(), "Wrong number of arguments")
44+
.ThrowAsJavaScriptException();
45+
return Value();
46+
} else if (!info[0].IsSharedArrayBuffer()) {
47+
Error::New(info.Env(),
48+
"Wrong type of arguments. Expects a SharedArrayBuffer as first "
49+
"argument.")
50+
.ThrowAsJavaScriptException();
51+
return Value();
52+
}
53+
54+
auto byte_length = info[0].As<SharedArrayBuffer>().ByteLength();
55+
56+
return Number::New(info.Env(), byte_length);
57+
}
58+
59+
Value TestSharedArrayBufferData(const CallbackInfo& info) {
60+
if (info.Length() < 1) {
61+
Error::New(info.Env(), "Wrong number of arguments")
62+
.ThrowAsJavaScriptException();
63+
return Value();
64+
} else if (!info[0].IsSharedArrayBuffer()) {
65+
Error::New(info.Env(),
66+
"Wrong type of arguments. Expects a SharedArrayBuffer as first "
67+
"argument.")
68+
.ThrowAsJavaScriptException();
69+
return Value();
70+
}
71+
72+
auto byte_length = info[0].As<SharedArrayBuffer>().ByteLength();
73+
void* data = info[0].As<SharedArrayBuffer>().Data();
74+
75+
if (byte_length > 0 && data != nullptr) {
76+
uint8_t* bytes = static_cast<uint8_t*>(data);
77+
for (size_t i = 0; i < byte_length; i++) {
78+
bytes[i] = i % 256;
79+
}
80+
81+
return Boolean::New(info.Env(), true);
82+
}
83+
84+
return Boolean::New(info.Env(), false);
85+
}
86+
#endif
87+
} // end anonymous namespace
88+
89+
Object InitSharedArrayBuffer(Env env) {
90+
Object exports = Object::New(env);
91+
92+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
93+
exports["testIsSharedArrayBuffer"] =
94+
Function::New(env, TestIsSharedArrayBuffer);
95+
exports["testCreateSharedArrayBuffer"] =
96+
Function::New(env, TestCreateSharedArrayBuffer);
97+
exports["testGetSharedArrayBufferInfo"] =
98+
Function::New(env, TestGetSharedArrayBufferInfo);
99+
exports["testSharedArrayBufferData"] =
100+
Function::New(env, TestSharedArrayBufferData);
101+
#endif
102+
103+
return exports;
104+
}

test/shared_array_buffer.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
5+
module.exports = require('./common').runTest(test);
6+
7+
let skippedMessageShown = false;
8+
9+
function test ({ hasSharedArrayBuffer, sharedarraybuffer }) {
10+
if (!hasSharedArrayBuffer) {
11+
if (!skippedMessageShown) {
12+
console.log(' >Skipped (no SharedArrayBuffer support)');
13+
skippedMessageShown = true;
14+
}
15+
return;
16+
}
17+
18+
{
19+
const sab = new SharedArrayBuffer(16);
20+
const ab = new ArrayBuffer(16);
21+
const obj = {};
22+
const arr = [];
23+
24+
assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(sab), true);
25+
assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(ab), false);
26+
assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(obj), false);
27+
assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(arr), false);
28+
assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(null), false);
29+
assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(undefined), false);
30+
}
31+
32+
{
33+
const sab = sharedarraybuffer.testCreateSharedArrayBuffer(16);
34+
assert(sab instanceof SharedArrayBuffer);
35+
assert.strictEqual(sab.byteLength, 16);
36+
}
37+
38+
{
39+
const sab = new SharedArrayBuffer(32);
40+
const byteLength = sharedarraybuffer.testGetSharedArrayBufferInfo(sab);
41+
assert.strictEqual(byteLength, 32);
42+
}
43+
44+
{
45+
const sab = new SharedArrayBuffer(8);
46+
const result = sharedarraybuffer.testSharedArrayBufferData(sab);
47+
assert.strictEqual(result, true);
48+
49+
// Check if data was written correctly
50+
const view = new Uint8Array(sab);
51+
for (let i = 0; i < 8; i++) {
52+
assert.strictEqual(view[i], i % 256);
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)