Skip to content

Commit feb6a54

Browse files
gabrielschulhofruyadorno
authored andcommittedSep 12, 2023
node-api: implement external strings
Introduce APIs that allow for the creation of JavaScript strings without copying the underlying native string into the engine. The APIs fall back to regular string creation if the engine's external string APIs are unavailable. In this case, an optional boolean out-parameter indicates that the string was copied, and the optional finalizer is called if given. PR-URL: #48339 Fixes: #48198 Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com> Signed-off-by: Gabriel Schulhof <gabrielschulhof@gmail.com>
1 parent a30f2fb commit feb6a54

File tree

11 files changed

+713
-176
lines changed

11 files changed

+713
-176
lines changed
 

‎benchmark/napi/string/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/

‎benchmark/napi/string/binding.c

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include <assert.h>
2+
#define NAPI_EXPERIMENTAL
3+
#include <node_api.h>
4+
5+
#define NAPI_CALL(call) \
6+
do { \
7+
napi_status status = call; \
8+
assert(status == napi_ok && #call " failed"); \
9+
} while (0);
10+
11+
#define EXPORT_FUNC(env, exports, name, func) \
12+
do { \
13+
napi_value js_func; \
14+
NAPI_CALL(napi_create_function( \
15+
(env), (name), NAPI_AUTO_LENGTH, (func), NULL, &js_func)); \
16+
NAPI_CALL(napi_set_named_property((env), (exports), (name), js_func)); \
17+
} while (0);
18+
19+
const char* one_byte_string = "The Quick Brown Fox Jumped Over The Lazy Dog.";
20+
const char16_t* two_byte_string =
21+
u"The Quick Brown Fox Jumped Over The Lazy Dog.";
22+
23+
#define DECLARE_BINDING(CapName, lowercase_name, var_name) \
24+
static napi_value CreateString##CapName(napi_env env, \
25+
napi_callback_info info) { \
26+
size_t argc = 4; \
27+
napi_value argv[4]; \
28+
uint32_t n; \
29+
uint32_t index; \
30+
napi_handle_scope scope; \
31+
napi_value js_string; \
32+
\
33+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); \
34+
NAPI_CALL(napi_get_value_uint32(env, argv[0], &n)); \
35+
NAPI_CALL(napi_open_handle_scope(env, &scope)); \
36+
NAPI_CALL(napi_call_function(env, argv[1], argv[2], 0, NULL, NULL)); \
37+
for (index = 0; index < n; index++) { \
38+
NAPI_CALL(napi_create_string_##lowercase_name( \
39+
env, (var_name), NAPI_AUTO_LENGTH, &js_string)); \
40+
} \
41+
NAPI_CALL(napi_call_function(env, argv[1], argv[3], 1, &argv[0], NULL)); \
42+
NAPI_CALL(napi_close_handle_scope(env, scope)); \
43+
\
44+
return NULL; \
45+
}
46+
47+
DECLARE_BINDING(Latin1, latin1, one_byte_string)
48+
DECLARE_BINDING(Utf8, utf8, one_byte_string)
49+
DECLARE_BINDING(Utf16, utf16, two_byte_string)
50+
51+
NAPI_MODULE_INIT() {
52+
EXPORT_FUNC(env, exports, "createStringLatin1", CreateStringLatin1);
53+
EXPORT_FUNC(env, exports, "createStringUtf8", CreateStringUtf8);
54+
EXPORT_FUNC(env, exports, "createStringUtf16", CreateStringUtf16);
55+
return exports;
56+
}

‎benchmark/napi/string/binding.gyp

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.c' ]
6+
}
7+
]
8+
}

‎benchmark/napi/string/index.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
const common = require('../../common.js');
3+
4+
let binding;
5+
try {
6+
binding = require(`./build/${common.buildType}/binding`);
7+
} catch {
8+
console.error(`${__filename}: Binding failed to load`);
9+
process.exit(0);
10+
}
11+
12+
const bench = common.createBenchmark(main, {
13+
n: [1e5, 1e6, 1e7],
14+
stringType: ['Latin1', 'Utf8', 'Utf16'],
15+
});
16+
17+
function main({ n, stringType }) {
18+
binding[`createString${stringType}`](n, bench, bench.start, bench.end);
19+
}

‎doc/api/n-api.md

+108-1
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ napiVersion: 1
801801

802802
Function pointer type for add-on provided functions that allow the user to be
803803
notified when externally-owned data is ready to be cleaned up because the
804-
object with which it was associated with, has been garbage-collected. The user
804+
object with which it was associated with has been garbage-collected. The user
805805
must provide a function satisfying the following signature which would get
806806
called upon the object's collection. Currently, `napi_finalize` can be used for
807807
finding out when objects that have external data are collected.
@@ -819,6 +819,11 @@ Since these functions may be called while the JavaScript engine is in a state
819819
where it cannot execute JavaScript code, some Node-API calls may return
820820
`napi_pending_exception` even when there is no exception pending.
821821

822+
In the case of [`node_api_create_external_string_latin1`][] and
823+
[`node_api_create_external_string_utf16`][] the `env` parameter may be null,
824+
because external strings can be collected during the latter part of environment
825+
shutdown.
826+
822827
Change History:
823828

824829
* experimental (`NAPI_EXPERIMENTAL` is defined):
@@ -2882,6 +2887,56 @@ string. The native string is copied.
28822887
The JavaScript `string` type is described in
28832888
[Section 6.1.4][] of the ECMAScript Language Specification.
28842889

2890+
#### `node_api_create_external_string_latin1`
2891+
2892+
<!-- YAML
2893+
added: REPLACEME
2894+
-->
2895+
2896+
> Stability: 1 - Experimental
2897+
2898+
```c
2899+
napi_status
2900+
node_api_create_external_string_latin1(napi_env env,
2901+
char* str,
2902+
size_t length,
2903+
napi_finalize finalize_callback,
2904+
void* finalize_hint,
2905+
napi_value* result,
2906+
bool* copied);
2907+
```
2908+
2909+
* `[in] env`: The environment that the API is invoked under.
2910+
* `[in] str`: Character buffer representing an ISO-8859-1-encoded string.
2911+
* `[in] length`: The length of the string in bytes, or `NAPI_AUTO_LENGTH` if it
2912+
is null-terminated.
2913+
* `[in] finalize_callback`: The function to call when the string is being
2914+
collected. The function will be called with the following parameters:
2915+
* `[in] env`: The environment in which the add-on is running. This value
2916+
may be null if the string is being collected as part of the termination
2917+
of the worker or the main Node.js instance.
2918+
* `[in] data`: This is the value `str` as a `void*` pointer.
2919+
* `[in] finalize_hint`: This is the value `finalize_hint` that was given
2920+
to the API.
2921+
[`napi_finalize`][] provides more details.
2922+
This parameter is optional. Passing a null value means that the add-on
2923+
doesn't need to be notified when the corresponding JavaScript string is
2924+
collected.
2925+
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
2926+
collection.
2927+
* `[out] result`: A `napi_value` representing a JavaScript `string`.
2928+
* `[out] copied`: Whether the string was copied. If it was, the finalizer will
2929+
already have been invoked to destroy `str`.
2930+
2931+
Returns `napi_ok` if the API succeeded.
2932+
2933+
This API creates a JavaScript `string` value from an ISO-8859-1-encoded C
2934+
string. The native string may not be copied and must thus exist for the entire
2935+
life cycle of the JavaScript value.
2936+
2937+
The JavaScript `string` type is described in
2938+
[Section 6.1.4][] of the ECMAScript Language Specification.
2939+
28852940
#### `napi_create_string_utf16`
28862941

28872942
<!-- YAML
@@ -2910,6 +2965,56 @@ The native string is copied.
29102965
The JavaScript `string` type is described in
29112966
[Section 6.1.4][] of the ECMAScript Language Specification.
29122967

2968+
#### `node_api_create_external_string_utf16`
2969+
2970+
<!-- YAML
2971+
added: REPLACEME
2972+
-->
2973+
2974+
> Stability: 1 - Experimental
2975+
2976+
```c
2977+
napi_status
2978+
node_api_create_external_string_utf16(napi_env env,
2979+
char16_t* str,
2980+
size_t length,
2981+
napi_finalize finalize_callback,
2982+
void* finalize_hint,
2983+
napi_value* result,
2984+
bool* copied);
2985+
```
2986+
2987+
* `[in] env`: The environment that the API is invoked under.
2988+
* `[in] str`: Character buffer representing a UTF16-LE-encoded string.
2989+
* `[in] length`: The length of the string in two-byte code units, or
2990+
`NAPI_AUTO_LENGTH` if it is null-terminated.
2991+
* `[in] finalize_callback`: The function to call when the string is being
2992+
collected. The function will be called with the following parameters:
2993+
* `[in] env`: The environment in which the add-on is running. This value
2994+
may be null if the string is being collected as part of the termination
2995+
of the worker or the main Node.js instance.
2996+
* `[in] data`: This is the value `str` as a `void*` pointer.
2997+
* `[in] finalize_hint`: This is the value `finalize_hint` that was given
2998+
to the API.
2999+
[`napi_finalize`][] provides more details.
3000+
This parameter is optional. Passing a null value means that the add-on
3001+
doesn't need to be notified when the corresponding JavaScript string is
3002+
collected.
3003+
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
3004+
collection.
3005+
* `[out] result`: A `napi_value` representing a JavaScript `string`.
3006+
* `[out] copied`: Whether the string was copied. If it was, the finalizer will
3007+
already have been invoked to destroy `str`.
3008+
3009+
Returns `napi_ok` if the API succeeded.
3010+
3011+
This API creates a JavaScript `string` value from a UTF16-LE-encoded C string.
3012+
The native string may not be copied and must thus exist for the entire life
3013+
cycle of the JavaScript value.
3014+
3015+
The JavaScript `string` type is described in
3016+
[Section 6.1.4][] of the ECMAScript Language Specification.
3017+
29133018
#### `napi_create_string_utf8`
29143019

29153020
<!-- YAML
@@ -6472,6 +6577,8 @@ the add-on's file name during loading.
64726577
[`napi_wrap`]: #napi_wrap
64736578
[`node-addon-api`]: https://github.com/nodejs/node-addon-api
64746579
[`node_api.h`]: https://github.com/nodejs/node/blob/HEAD/src/node_api.h
6580+
[`node_api_create_external_string_latin1`]: #node_api_create_external_string_latin1
6581+
[`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16
64756582
[`node_api_create_syntax_error`]: #node_api_create_syntax_error
64766583
[`node_api_throw_syntax_error`]: #node_api_throw_syntax_error
64776584
[`process.release`]: process.md#processrelease

‎src/js_native_api.h

+18
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,24 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env,
9292
const char16_t* str,
9393
size_t length,
9494
napi_value* result);
95+
#ifdef NAPI_EXPERIMENTAL
96+
NAPI_EXTERN napi_status NAPI_CDECL
97+
node_api_create_external_string_latin1(napi_env env,
98+
char* str,
99+
size_t length,
100+
napi_finalize finalize_callback,
101+
void* finalize_hint,
102+
napi_value* result,
103+
bool* copied);
104+
NAPI_EXTERN napi_status NAPI_CDECL
105+
node_api_create_external_string_utf16(napi_env env,
106+
char16_t* str,
107+
size_t length,
108+
napi_finalize finalize_callback,
109+
void* finalize_hint,
110+
napi_value* result,
111+
bool* copied);
112+
#endif // NAPI_EXPERIMENTAL
95113
NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol(napi_env env,
96114
napi_value description,
97115
napi_value* result);

0 commit comments

Comments
 (0)
Please sign in to comment.