Skip to content

Commit 87ad657

Browse files
gabrielschulhofRafaelGSS
authored andcommitted
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 <[email protected]> Signed-off-by: Gabriel Schulhof <[email protected]>
1 parent 0500967 commit 87ad657

File tree

11 files changed

+713
-176
lines changed

11 files changed

+713
-176
lines changed

benchmark/napi/string/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/

benchmark/napi/string/binding.c

Lines changed: 56 additions & 0 deletions
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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 19 additions & 0 deletions
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

Lines changed: 108 additions & 1 deletion
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):
@@ -2886,6 +2891,56 @@ string. The native string is copied.
28862891
The JavaScript `string` type is described in
28872892
[Section 6.1.4][] of the ECMAScript Language Specification.
28882893

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

28912946
<!-- YAML
@@ -2914,6 +2969,56 @@ The native string is copied.
29142969
The JavaScript `string` type is described in
29152970
[Section 6.1.4][] of the ECMAScript Language Specification.
29162971

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

29193024
<!-- YAML
@@ -6476,6 +6581,8 @@ the add-on's file name during loading.
64766581
[`napi_wrap`]: #napi_wrap
64776582
[`node-addon-api`]: https://github.com/nodejs/node-addon-api
64786583
[`node_api.h`]: https://github.com/nodejs/node/blob/HEAD/src/node_api.h
6584+
[`node_api_create_external_string_latin1`]: #node_api_create_external_string_latin1
6585+
[`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16
64796586
[`node_api_create_syntax_error`]: #node_api_create_syntax_error
64806587
[`node_api_throw_syntax_error`]: #node_api_throw_syntax_error
64816588
[`process.release`]: process.md#processrelease

src/js_native_api.h

Lines changed: 18 additions & 0 deletions
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)