Skip to content

Commit

Permalink
implement additional APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
toyobayashi committed Jun 20, 2021
1 parent 214f135 commit b0b3752
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 11 deletions.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"request": "launch",
"name": "Launch Program",
"runtimeArgs": ["--expose-gc"],
"program": "${workspaceFolder}/test/function/function.test.js",
"args": ["--expose-gc"]
"program": "${workspaceFolder}/test/emnapi/emnapi.test.js",
"args": []
},
{
"name": "Windows",
Expand Down
63 changes: 62 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ npm test

These APIs always return `napi_generic_failure`.

- ~~napi_create_external_arraybuffer~~
- ~~napi_create_external_arraybuffer~~ (use [`emnapi_create_external_uint8array`][] instead)
- ~~napi_adjust_external_memory~~
- ~~napi_detach_arraybuffer~~
- ~~napi_is_detached_arraybuffer~~
Expand Down Expand Up @@ -364,3 +364,64 @@ These APIs always return `napi_generic_failure`.
- napi_object_seal
- napi_type_tag_object
- napi_check_object_type_tag

### Additional

These APIs are in `emnapi.h`.

#### emnapi_get_module_object

```c
napi_status emnapi_get_module_object(napi_env env,
napi_value* result);
```
* `[in] env`: The environment that the API is invoked under.
* `[out] result`: A `napi_value` representing the `Module` object of Emscripten.
Returns `napi_ok` if the API succeeded.
#### emnapi_get_module_property
```c
napi_status emnapi_get_module_property(napi_env env,
const char* utf8name,
napi_value* result);
```

* `[in] env`: The environment that the API is invoked under.
* `[in] utf8Name`: The name of the `Module` property to get.
* `[out] result`: The value of the property.

Returns `napi_ok` if the API succeeded.

#### emnapi_create_external_uint8array

```c
napi_status emnapi_create_external_uint8array(napi_env env,
void* external_data,
size_t byte_length,
napi_finalize finalize_cb,
void* finalize_hint,
napi_value* result);
```
* `[in] env`: The environment that the API is invoked under.
* `[in] external_data`: Pointer to the underlying byte buffer of the
`Uint8Array`.
* `[in] byte_length`: The length in bytes of the underlying buffer.
* `[in] finalize_cb`: Optional callback to call when the `Uint8Array` is being
collected.
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
collection.
* `[out] result`: A `napi_value` representing a JavaScript `Uint8Array`.
Returns `napi_ok` if the API succeeded.
Returns `napi_generic_failure` if `FinalizationRegistry` or `WeakRef` is not supported.
This API returns an N-API value corresponding to a JavaScript `Uint8Array`.
The underlying byte buffer of the `Uint8Array` is externally allocated and
managed. The caller must ensure that the byte buffer remains valid until the
finalize callback is called.
[`emnapi_create_external_uint8array`]: #emnapi_create_external_uint8array
63 changes: 62 additions & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ npm test

以下 API 不可实现,调用后将永远返回 `napi_generic_failure` 状态。

- ~~napi_create_external_arraybuffer~~
- ~~napi_create_external_arraybuffer~~ (使用 [`emnapi_create_external_uint8array`][] 代替)
- ~~napi_adjust_external_memory~~
- ~~napi_detach_arraybuffer~~
- ~~napi_is_detached_arraybuffer~~
Expand Down Expand Up @@ -361,3 +361,64 @@ npm test
- napi_object_seal
- napi_type_tag_object
- napi_check_object_type_tag

### 额外新增的 API

以下 API 在 `emnapi.h` 头文件中。

#### emnapi_get_module_object

```c
napi_status emnapi_get_module_object(napi_env env,
napi_value* result);
```
* `[in] env`: The environment that the API is invoked under.
* `[out] result`: A `napi_value` representing the `Module` object of Emscripten.
Returns `napi_ok` if the API succeeded.
#### emnapi_get_module_property
```c
napi_status emnapi_get_module_property(napi_env env,
const char* utf8name,
napi_value* result);
```

* `[in] env`: The environment that the API is invoked under.
* `[in] utf8Name`: The name of the `Module` property to get.
* `[out] result`: The value of the property.

Returns `napi_ok` if the API succeeded.

#### emnapi_create_external_uint8array

```c
napi_status emnapi_create_external_uint8array(napi_env env,
void* external_data,
size_t byte_length,
napi_finalize finalize_cb,
void* finalize_hint,
napi_value* result);
```
* `[in] env`: The environment that the API is invoked under.
* `[in] external_data`: Pointer to the underlying byte buffer of the
`Uint8Array`.
* `[in] byte_length`: The length in bytes of the underlying buffer.
* `[in] finalize_cb`: Optional callback to call when the `Uint8Array` is being
collected.
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
collection.
* `[out] result`: A `napi_value` representing a JavaScript `Uint8Array`.
Returns `napi_ok` if the API succeeded.
Returns `napi_generic_failure` if `FinalizationRegistry` or `WeakRef` is not supported.
This API returns an N-API value corresponding to a JavaScript `Uint8Array`.
The underlying byte buffer of the `Uint8Array` is externally allocated and
managed. The caller must ensure that the byte buffer remains valid until the
finalize callback is called.
[`emnapi_create_external_uint8array`]: #emnapi_create_external_uint8array
1 change: 1 addition & 0 deletions cgen.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ module.exports = function (_options, { isDebug, isEmscripten }) {
createTarget('number', ['./test/number/binding.c'], true),
createTarget('symbol', ['./test/symbol/binding.c'], true),
createTarget('typedarray', ['./test/typedarray/binding.c'], true),
createTarget('emnapi', ['./test/emnapi/binding.c'], true),

createNodeAddonApiTarget('n_hello', ['./test/node-addon-api/hello/binding.cc'])
]
Expand Down
4 changes: 4 additions & 0 deletions include/emnapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

EXTERN_C_START

NAPI_EXTERN
napi_status emnapi_get_module_object(napi_env env,
napi_value* result);

NAPI_EXTERN
napi_status emnapi_get_module_property(napi_env env,
const char* utf8name,
Expand Down
59 changes: 59 additions & 0 deletions lib/emnapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
function emnapi_get_module_object (env: napi_env, result: Pointer<napi_value>): emnapi.napi_status {
return emnapi.preamble(env, (envObject) => {
return emnapi.checkArgs(env, [result], () => {
HEAP32[result >> 2] = envObject.ensureHandleId(Module)
return emnapi.getReturnStatus(env)
})
})
}

function emnapi_get_module_property (env: napi_env, utf8name: const_char_p, result: Pointer<napi_value>): emnapi.napi_status {
return emnapi.preamble(env, (envObject) => {
return emnapi.checkArgs(env, [utf8name, result], () => {
HEAP32[result >> 2] = envObject.ensureHandleId(Module[UTF8ToString(utf8name)])
return emnapi.getReturnStatus(env)
})
})
}

function emnapi_create_external_uint8array (
env: napi_env,
external_data: void_p,
byte_length: size_t,
finalize_cb: napi_finalize,
finalize_hint: void_p,
result: Pointer<napi_value>
): emnapi.napi_status {
if (!emnapi.supportFinalizer) return emnapi.napi_set_last_error(env, emnapi.napi_status.napi_generic_failure)
return emnapi.preamble(env, (envObject) => {
return emnapi.checkArgs(env, [result], () => {
byte_length = byte_length >>> 0
if (external_data === emnapi.NULL) {
byte_length = 0
}

if (byte_length > 2147483647) {
throw new RangeError('Cannot create a Uint8Array larger than 2147483647 bytes')
}
if ((external_data + byte_length) > HEAPU8.buffer.byteLength) {
throw new RangeError('Memory out of range')
}
const u8arr = new Uint8Array(HEAPU8.buffer, external_data, byte_length)
const handle = envObject.getCurrentScope().add(u8arr)
if (finalize_cb !== emnapi.NULL) {
const status = emnapi.wrap(emnapi.WrapType.anonymous, env, handle.id, external_data, finalize_cb, finalize_hint, emnapi.NULL)
if (status === emnapi.napi_status.napi_pending_exception) {
throw envObject.tryCatch.extractException()
} else if (status !== emnapi.napi_status.napi_ok) {
return emnapi.napi_set_last_error(env, status)
}
}
HEAP32[result >> 2] = handle.id
return emnapi.getReturnStatus(env)
})
})
}

emnapiImplement('emnapi_get_module_object', emnapi_get_module_object)
emnapiImplement('emnapi_get_module_property', emnapi_get_module_property)
emnapiImplement('emnapi_create_external_uint8array', emnapi_create_external_uint8array)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tybys/emnapi",
"version": "0.1.4",
"version": "0.2.0",
"description": "Node-API implementation for Emscripten",
"main": "index.js",
"typings": "index.d.ts",
Expand Down
84 changes: 84 additions & 0 deletions test/emnapi/binding.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include <stdlib.h>
#include "js_native_api.h"
#include "emnapi.h"
#include "../common.h"

static napi_value getModuleObject(napi_env env, napi_callback_info info) {
napi_value result;
NAPI_CALL(env, emnapi_get_module_object(env, &result));

return result;
}

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

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

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

NAPI_ASSERT(env, valuetype0 == napi_string,
"Wrong type of arguments. Expects a string as first argument.");

char name[64] = { 0 };
NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], name, 64, NULL));

napi_value result;
NAPI_CALL(env, emnapi_get_module_property(env, name, &result));

return result;
}

static void FinalizeCallback(napi_env env,
void* finalize_data,
void* finalize_hint)
{
free(finalize_data);
}

static napi_value External(napi_env env, napi_callback_info info) {
const uint8_t nElem = 3;
int8_t* externalData = malloc(nElem*sizeof(int8_t));
externalData[0] = 0;
externalData[1] = 1;
externalData[2] = 2;

napi_value output_array;
NAPI_CALL(env, emnapi_create_external_uint8array(
env,
externalData,
nElem*sizeof(int8_t),
FinalizeCallback,
NULL, // finalize_hint
&output_array));

return output_array;
}


static napi_value NullArrayBuffer(napi_env env, napi_callback_info info) {
static void* data = NULL;
napi_value output_array;
NAPI_CALL(env,
emnapi_create_external_uint8array(env, data, 0, NULL, NULL, &output_array));
return output_array;
}

EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
DECLARE_NAPI_PROPERTY("getModuleObject", getModuleObject),
DECLARE_NAPI_PROPERTY("getModuleProperty", getModuleProperty),
DECLARE_NAPI_PROPERTY("External", External),
DECLARE_NAPI_PROPERTY("NullArrayBuffer", NullArrayBuffer)
};

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

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

module.exports = load('emnapi').then(test_typedarray => {
const mod = test_typedarray.getModuleObject()

const HEAPU8 = test_typedarray.getModuleProperty('HEAPU8')
const mem = HEAPU8.buffer
assert.ok(mem instanceof ArrayBuffer)
assert.strictEqual(mod.HEAPU8, HEAPU8)

const externalResult = test_typedarray.External()
assert.ok(externalResult instanceof Uint8Array)
assert.strictEqual(externalResult.length, 3)
assert.strictEqual(externalResult[0], 0)
assert.strictEqual(externalResult[1], 1)
assert.strictEqual(externalResult[2], 2)

const buffer = test_typedarray.NullArrayBuffer()
assert.ok(buffer instanceof Uint8Array)
assert.strictEqual(buffer.length, 0)
assert.strictEqual(buffer.buffer, mem)
assert.notStrictEqual(buffer.buffer.byteLength, 0)
})
3 changes: 0 additions & 3 deletions test/general/binding.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,13 @@ static napi_value createNapiError(napi_env env, napi_callback_info info) {
"Last error info code should match last status");
NAPI_ASSERT(env, error_info->error_message,
"Last error info message should not be null");
printf("create, code: %d, message: %s\n", error_info->error_code, error_info->error_message);
NAPI_CALL(env, napi_get_last_error_info(env, &error_info));
printf("create, code: %d, message: %s\n", error_info->error_code, error_info->error_message);
return NULL;
}

static napi_value testNapiErrorCleanup(napi_env env, napi_callback_info info) {
const napi_extended_error_info *error_info = 0;
NAPI_CALL(env, napi_get_last_error_info(env, &error_info));
printf("code: %d, message: %s\n", error_info->error_code, error_info->error_message);
napi_value result;
bool is_ok = error_info->error_code == napi_ok;
NAPI_CALL(env, napi_get_boolean(env, is_ok, &result));
Expand Down
4 changes: 1 addition & 3 deletions test/general/status.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,5 @@ const { load } = require('../util')

module.exports = load('general').then(addon => {
addon.createNapiError()
const r = addon.testNapiErrorCleanup()
console.log(r)
assert(r, 'napi_status cleaned up for second call')
assert(addon.testNapiErrorCleanup(), 'napi_status cleaned up for second call')
})

0 comments on commit b0b3752

Please sign in to comment.