Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

48198 node api external strings #48339

Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bd8045b
node-api: unify string creation
gabrielschulhof Jun 2, 2023
50a2109
node-api: add string creation benchmark
gabrielschulhof Jun 3, 2023
1364872
node-api: implement external strings
gabrielschulhof Jun 3, 2023
a9e24b9
add test
gabrielschulhof Jun 3, 2023
e1127d1
rename to node_api_*
gabrielschulhof Jun 4, 2023
953304c
put back build type
gabrielschulhof Jun 5, 2023
09f7cdf
make linter work
gabrielschulhof Jun 5, 2023
902cb33
rename as requested
gabrielschulhof Jun 6, 2023
754864e
use std::basic_string_view to calc length in case of NAPI_AUTO_LENGTH
gabrielschulhof Jun 6, 2023
95a9971
test for auto-length
gabrielschulhof Jun 7, 2023
b4f383b
format
gabrielschulhof Jun 7, 2023
05317ac
correct length
gabrielschulhof Jun 7, 2023
20ab0fb
doc
gabrielschulhof Jun 7, 2023
ca4c08c
use right length indicator
gabrielschulhof Jun 8, 2023
5de34d4
final adjustment
gabrielschulhof Jun 8, 2023
64f2498
Apply suggestions from code review
gabrielschulhof Jun 8, 2023
cb9d95a
Apply suggestions from code review
gabrielschulhof Jun 8, 2023
ed83494
remove utf8
gabrielschulhof Jun 9, 2023
e063901
handle the case where we copy the string
gabrielschulhof Jun 10, 2023
4ff5e53
implement the non-copying case
gabrielschulhof Jun 10, 2023
402c93e
Unify the two external cases
gabrielschulhof Jun 11, 2023
32a5600
factor out tracking
gabrielschulhof Jun 11, 2023
0b72009
document
gabrielschulhof Jun 11, 2023
50dfa5d
debug ctor dtor
gabrielschulhof Jun 11, 2023
3a96b04
typo
gabrielschulhof Jun 11, 2023
caccb8e
Apply suggestions from code review
gabrielschulhof Jun 11, 2023
5c11c81
Apply suggestions from code review
gabrielschulhof Jun 11, 2023
b651fca
use common distructor
gabrielschulhof Jun 12, 2023
668fd4d
format
gabrielschulhof Jun 12, 2023
c721439
Apply suggestions from code review
gabrielschulhof Jun 12, 2023
7f67a2f
md lint
gabrielschulhof Jun 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmark/napi/string/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
56 changes: 56 additions & 0 deletions benchmark/napi/string/binding.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <assert.h>
#define NAPI_EXPERIMENTAL
#include <node_api.h>

#define NAPI_CALL(call) \
do { \
napi_status status = call; \
assert(status == napi_ok && #call " failed"); \
} while (0);

#define EXPORT_FUNC(env, exports, name, func) \
do { \
napi_value js_func; \
NAPI_CALL(napi_create_function( \
(env), (name), NAPI_AUTO_LENGTH, (func), NULL, &js_func)); \
NAPI_CALL(napi_set_named_property((env), (exports), (name), js_func)); \
} while (0);

const char* one_byte_string = "The Quick Brown Fox Jumped Over The Lazy Dog.";
const char16_t* two_byte_string =
u"The Quick Brown Fox Jumped Over The Lazy Dog.";

#define DECLARE_BINDING(CapName, lowercase_name, var_name) \
static napi_value CreateString##CapName(napi_env env, \
napi_callback_info info) { \
size_t argc = 4; \
napi_value argv[4]; \
uint32_t n; \
uint32_t index; \
napi_handle_scope scope; \
napi_value js_string; \
\
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); \
NAPI_CALL(napi_get_value_uint32(env, argv[0], &n)); \
NAPI_CALL(napi_open_handle_scope(env, &scope)); \
NAPI_CALL(napi_call_function(env, argv[1], argv[2], 0, NULL, NULL)); \
for (index = 0; index < n; index++) { \
NAPI_CALL(napi_create_string_##lowercase_name( \
env, (var_name), NAPI_AUTO_LENGTH, &js_string)); \
} \
NAPI_CALL(napi_call_function(env, argv[1], argv[3], 1, &argv[0], NULL)); \
NAPI_CALL(napi_close_handle_scope(env, scope)); \
\
return NULL; \
}

DECLARE_BINDING(Latin1, latin1, one_byte_string)
DECLARE_BINDING(Utf8, utf8, one_byte_string)
DECLARE_BINDING(Utf16, utf16, two_byte_string)

NAPI_MODULE_INIT() {
EXPORT_FUNC(env, exports, "createStringLatin1", CreateStringLatin1);
EXPORT_FUNC(env, exports, "createStringUtf8", CreateStringUtf8);
EXPORT_FUNC(env, exports, "createStringUtf16", CreateStringUtf16);
return exports;
}
8 changes: 8 additions & 0 deletions benchmark/napi/string/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.c' ]
}
]
}
19 changes: 19 additions & 0 deletions benchmark/napi/string/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';
const common = require('../../common.js');

let binding;
try {
binding = require(`./build/${common.buildType}/binding`);
} catch {
console.error(`${__filename}: Binding failed to load`);
process.exit(0);
}

const bench = common.createBenchmark(main, {
n: [1e5, 1e6, 1e7],
stringType: ['Latin1', 'Utf8', 'Utf16'],
});

function main({ n, stringType }) {
binding[`createString${stringType}`](n, bench, bench.start, bench.end);
}
90 changes: 90 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2886,6 +2886,36 @@ string. The native string is copied.
The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.

#### `node_api_create_external_string_latin1`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```c
napi_status node_api_create_string_latin1(napi_env env,
gabrielschulhof marked this conversation as resolved.
Show resolved Hide resolved
const char* str,
size_t length,
napi_value* result);
```

* `[in] env`: The environment that the API is invoked under.
* `[in] str`: Character buffer representing an ISO-8859-1-encoded string.
* `[in] length`: The length of the string in bytes, or `NAPI_AUTO_LENGTH` if it
is null-terminated.
* `[out] result`: A `napi_value` representing a JavaScript `string`.
gabrielschulhof marked this conversation as resolved.
Show resolved Hide resolved

Returns `napi_ok` if the API succeeded.

This API creates a JavaScript `string` value from an ISO-8859-1-encoded C
string. The native string may not be copied and must thus exist for the entire
life cycle of the JavaScript value.

The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.

#### `napi_create_string_utf16`

<!-- YAML
Expand Down Expand Up @@ -2914,6 +2944,36 @@ The native string is copied.
The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.

#### `node_api_create_external_string_utf16`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```c
napi_status node_api_create_string_utf16(napi_env env,
gabrielschulhof marked this conversation as resolved.
Show resolved Hide resolved
const char16_t* str,
size_t length,
napi_value* result)
```

* `[in] env`: The environment that the API is invoked under.
* `[in] str`: Character buffer representing a UTF16-LE-encoded string.
* `[in] length`: The length of the string in two-byte code units, or
`NAPI_AUTO_LENGTH` if it is null-terminated.
* `[out] result`: A `napi_value` representing a JavaScript `string`.
gabrielschulhof marked this conversation as resolved.
Show resolved Hide resolved

Returns `napi_ok` if the API succeeded.

This API creates a JavaScript `string` value from a UTF16-LE-encoded C string.
The native string may not be copied and must thus exist for the entire life
cycle of the JavaScript value.
gabrielschulhof marked this conversation as resolved.
Show resolved Hide resolved

The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.

#### `napi_create_string_utf8`

<!-- YAML
Expand Down Expand Up @@ -2942,6 +3002,36 @@ The native string is copied.
The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.

#### `node_api_create_external_string_utf8`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```c
napi_status node_api_create_string_utf8(napi_env env,
gabrielschulhof marked this conversation as resolved.
Show resolved Hide resolved
const char* str,
size_t length,
napi_value* result)
```

* `[in] env`: The environment that the API is invoked under.
* `[in] str`: Character buffer representing a UTF8-encoded string.
* `[in] length`: The length of the string in bytes, or `NAPI_AUTO_LENGTH` if it
is null-terminated.
* `[out] result`: A `napi_value` representing a JavaScript `string`.

Returns `napi_ok` if the API succeeded.

This API creates a JavaScript `string` value from a UTF8-encoded C string.
The native string may not be copied and must thus exist for the entire life
cycle of the JavaScript value.

The JavaScript `string` type is described in
[Section 6.1.4][] of the ECMAScript Language Specification.

### Functions to convert from Node-API to C types

#### `napi_get_array_length`
Expand Down
8 changes: 8 additions & 0 deletions src/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env,
const char16_t* str,
size_t length,
napi_value* result);
#ifdef NAPI_EXPERIMENTAL
NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_latin1(
napi_env env, const char* str, size_t length, napi_value* result);
NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_utf8(
napi_env env, const char* str, size_t length, napi_value* result);
NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_utf16(
napi_env env, const char16_t* str, size_t length, napi_value* result);
#endif // NAPI_EXPERIMENTAL
NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol(napi_env env,
napi_value description,
napi_value* result);
Expand Down
136 changes: 94 additions & 42 deletions src/js_native_api_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,50 @@ namespace v8impl {

namespace {

class ExternalOneByteStringResource
: public v8::String::ExternalOneByteStringResource {
public:
ExternalOneByteStringResource(const char* string, const size_t length)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it OK if lengh is NAPI_AUTO_LENGTH?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chose to assume that it is not, and used C++ string view to compute length.

: string_(string), length_(length) {}
const char* data() const { return string_; }
size_t length() const { return length_; }

private:
const char* string_;
const size_t length_;
};

class ExternalStringResource : public v8::String::ExternalStringResource {
public:
ExternalStringResource(const uint16_t* string, const size_t length)
: string_(string), length_(length) {}
const uint16_t* data() const { return string_; }
size_t length() const { return length_; }

private:
const uint16_t* string_;
const size_t length_;
};

template <typename CCharType, typename StringMaker>
napi_status NewString(napi_env env,
const CCharType* str,
size_t length,
napi_value* result,
StringMaker string_maker) {
CHECK_ENV(env);
if (length > 0) CHECK_ARG(env, str);
CHECK_ARG(env, result);
RETURN_STATUS_IF_FALSE(
env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, napi_invalid_arg);

auto isolate = env->isolate;
auto str_maybe = string_maker(isolate);
CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
return napi_clear_last_error(env);
}

inline napi_status V8NameFromPropertyDescriptor(
napi_env env,
const napi_property_descriptor* p,
Expand Down Expand Up @@ -1389,62 +1433,70 @@ napi_status NAPI_CDECL napi_create_string_latin1(napi_env env,
const char* str,
size_t length,
napi_value* result) {
CHECK_ENV(env);
if (length > 0) CHECK_ARG(env, str);
CHECK_ARG(env, result);
RETURN_STATUS_IF_FALSE(
env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, napi_invalid_arg);

auto isolate = env->isolate;
auto str_maybe =
v8::String::NewFromOneByte(isolate,
reinterpret_cast<const uint8_t*>(str),
v8::NewStringType::kNormal,
length);
CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);

*result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
return napi_clear_last_error(env);
return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
return v8::String::NewFromOneByte(isolate,
reinterpret_cast<const uint8_t*>(str),
v8::NewStringType::kNormal,
length);
});
}

napi_status NAPI_CDECL napi_create_string_utf8(napi_env env,
const char* str,
size_t length,
napi_value* result) {
CHECK_ENV(env);
if (length > 0) CHECK_ARG(env, str);
CHECK_ARG(env, result);
RETURN_STATUS_IF_FALSE(
env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, napi_invalid_arg);

auto isolate = env->isolate;
auto str_maybe = v8::String::NewFromUtf8(
isolate, str, v8::NewStringType::kNormal, static_cast<int>(length));
CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
return napi_clear_last_error(env);
return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
return v8::String::NewFromUtf8(
isolate, str, v8::NewStringType::kNormal, static_cast<int>(length));
});
}

napi_status NAPI_CDECL napi_create_string_utf16(napi_env env,
const char16_t* str,
size_t length,
napi_value* result) {
CHECK_ENV(env);
if (length > 0) CHECK_ARG(env, str);
CHECK_ARG(env, result);
RETURN_STATUS_IF_FALSE(
env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, napi_invalid_arg);
return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
return v8::String::NewFromTwoByte(isolate,
reinterpret_cast<const uint16_t*>(str),
v8::NewStringType::kNormal,
length);
});
}

napi_status NAPI_CDECL node_api_create_external_string_latin1(
napi_env env, const char* str, size_t length, napi_value* result) {
#if defined(V8_ENABLE_SANDBOX)
return napi_create_string_latin1(env, str, length, result);
#else
return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
if (length == NAPI_AUTO_LENGTH) {
length = (std::string_view(str)).length();
}
auto resource = new v8impl::ExternalOneByteStringResource(str, length);
return v8::String::NewExternalOneByte(isolate, resource);
});
#endif // V8_ENABLE_SANDBOX
}

auto isolate = env->isolate;
auto str_maybe =
v8::String::NewFromTwoByte(isolate,
reinterpret_cast<const uint16_t*>(str),
v8::NewStringType::kNormal,
length);
CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure);
napi_status NAPI_CDECL node_api_create_external_string_utf8(
napi_env env, const char* str, size_t length, napi_value* result) {
return napi_create_string_utf8(env, str, length, result);
}

*result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked());
return napi_clear_last_error(env);
napi_status NAPI_CDECL node_api_create_external_string_utf16(
napi_env env, const char16_t* str, size_t length, napi_value* result) {
#if defined(V8_ENABLE_SANDBOX)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check with Electron if this is needed for strings.

return napi_create_string_utf16(env, str, length, result);
#else
return v8impl::NewString(env, str, length, result, [&](v8::Isolate* isolate) {
if (length == NAPI_AUTO_LENGTH) {
length = (std::u16string_view(str)).length();
}
auto resource = new v8impl::ExternalStringResource(
reinterpret_cast<const uint16_t*>(str), length);
return v8::String::NewExternalTwoByte(isolate, resource);
});
#endif // V8_ENABLE_SANDBOX
}

napi_status NAPI_CDECL napi_create_double(napi_env env,
Expand Down
11 changes: 11 additions & 0 deletions test/js-native-api/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@
#define NODE_API_CALL_RETURN_VOID(env, the_call) \
NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING)

#define NODE_API_CHECK_STATUS(the_call) \
do { \
napi_status status = (the_call); \
if (status != napi_ok) { \
return status; \
} \
} while (0)

#define NODE_API_ASSERT_STATUS(env, assertion, message) \
NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure)

#define DECLARE_NODE_API_PROPERTY(name, func) \
{ (name), NULL, (func), NULL, NULL, NULL, napi_default, NULL }

Expand Down