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

napi: add bigint support #21226

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 162 additions & 2 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,9 @@ typedef enum {
napi_escape_called_twice,
napi_handle_scope_mismatch,
napi_callback_scope_mismatch,
#ifdef NAPI_EXPERIMENTAL
napi_queue_full,
napi_closing,
#endif // NAPI_EXPERIMENTAL
napi_bigint_expected,
} napi_status;
```
If additional information is required upon an API returning a failed status,
Expand Down Expand Up @@ -1225,6 +1224,7 @@ typedef enum {
napi_object,
napi_function,
napi_external,
napi_bigint,
} napi_valuetype;
```

Expand All @@ -1250,6 +1250,8 @@ typedef enum {
napi_uint32_array,
napi_float32_array,
napi_float64_array,
napi_bigint64_array,
napi_biguint64_array,
} napi_typedarray_type;
```

Expand Down Expand Up @@ -1691,6 +1693,78 @@ This API is used to convert from the C `double` type to the JavaScript
The JavaScript `Number` type is described in
[Section 6.1.6][] of the ECMAScript Language Specification.

#### napi_create_bigint_int64
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
napi_status napi_create_bigint_int64(napi_env env,
int64_t value,
napi_value* result);
```

- `[in] env`: The environment that the API is invoked under.
- `[in] value`: Integer value to be represented in JavaScript.
- `[out] result`: A `napi_value` representing a JavaScript `BigInt`.

Returns `napi_ok` if the API succeeded.

This API converts the C `int64_t` type to the JavaScript `BigInt` type.

#### napi_create_bigint_uint64
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
napi_status napi_create_bigint_uint64(napi_env env,
uint64_t vaue,
napi_value* result);
```

- `[in] env`: The environment that the API is invoked under.
- `[in] value`: Unsigned integer value to be represented in JavaScript.
- `[out] result`: A `napi_value` representing a JavaScript `BigInt`.

Returns `napi_ok` if the API succeeded.

This API converts the C `uint64_t` type to the JavaScript `BigInt` type.

#### napi_create_bigint_words
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
napi_status napi_create_bigint_words(napi_env env,
int sign_bit,
size_t word_count,
const uint64_t* words,
napi_value* result);
```

- `[in] env`: The environment that the API is invoked under.
- `[in] sign_bit`: Determines if the resulting `BigInt` will be positive or
negative.
- `[in] word_count`: The length of the `words` array.
- `[in] words`: An array of `uint64_t` little-endian 64-bit words.
- `[out] result`: A `napi_value` representing a JavaScript `BigInt`.

Returns `napi_ok` if the API succeeded.

Copy link
Member

Choose a reason for hiding this comment

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

Missing a small blurb of what the function does, like the other functions do.

This API converts an array of unsigned 64-bit words into a single BigInt value.

This API converts an array of unsigned 64-bit words into a single `BigInt`
value.

The resulting `BigInt` is calculated as: (–1)<sup>`sign_bit`</sup> (`words[0]`
× (2<sup>64</sup>)<sup>0</sup> + `words[1]` × (2<sup>64</sup>)<sup>1</sup> + …)

#### napi_create_string_latin1
<!-- YAML
added: v8.0.0
Expand Down Expand Up @@ -1975,6 +2049,92 @@ in it returns `napi_number_expected`.
This API returns the C double primitive equivalent of the given JavaScript
`Number`.

#### napi_get_value_bigint_int64
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
napi_status napi_get_value_bigint_int64(napi_env env,
napi_value value,
int64_t* result,
bool* lossless);
```

- `[in] env`: The environment that the API is invoked under
- `[in] value`: `napi_value` representing JavaScript `BigInt`.
- `[out] result`: C `int64_t` primitive equivalent of the given JavaScript
`BigInt`.
- `[out] lossless`: Indicates whether the `BigInt` value was converted
losslessly.

Returns `napi_ok` if the API succeeded. If a non-`BigInt` is passed in it
returns `napi_bigint_expected`.

This API returns the C `int64_t` primitive equivalent of the given JavaScript
`BigInt`. If needed it will truncate the value, setting `lossless` to `true`.
Copy link
Member

Choose a reason for hiding this comment

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

This behavior sounds a bit counterintuitive to me: doesn't truncation lose some value? Why is lossless set to true then?

Copy link
Member Author

Choose a reason for hiding this comment

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

typo :P



#### napi_get_value_bigint_uint64
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
napi_status napi_get_value_bigint_uint64(napi_env env,
napi_value value,
uint64_t* result,
bool* lossless);
```

- `[in] env`: The environment that the API is invoked under.
- `[in] value`: `napi_value` representing JavaScript `BigInt`.
- `[out] result`: C `uint64_t` primitive equivalent of the given JavaScript
`BigInt`.
- `[out] lossless`: Indicates whether the `BigInt` value was converted
losslessly.

Returns `napi_ok` if the API succeeded. If a non-`BigInt` is passed in it
returns `napi_bigint_expected`.

This API returns the C `uint64_t` primitive equivalent of the given JavaScript
`BigInt`. If needed it will truncate the value, setting `lossless` to `true`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this sentence is backwards, it should set it to false in this case, right?



#### napi_get_value_bigint_words
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```C
napi_status napi_get_value_bigint_words(napi_env env,
napi_value value,
size_t* word_count,
int* sign_bit,
uint64_t* words);
```

- `[in] env`: The environment that the API is invoked under.
- `[in] value`: `napi_value` representing JavaScript `BigInt`.
- `[out] sign_bit`: Integer representing if the JavaScript `BigInt` is positive
or negative.
- `[in/out] word_count`: Must be initialized to the length of the `words`
array. Upon return, it will be set to the actual number of words that
would be needed to store this `BigInt`.
- `[out] words`: Pointer to a pre-allocated 64-bit word array.

Returns `napi_ok` if the API succeeded.

Copy link
Member

Choose a reason for hiding this comment

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

Also missing a sentence describing what the function does. I'd rather the longer descriptions in the parameter list be moved down here.

This API converts a single `BigInt` value into a sign bit, 64-bit little-endian
array, and the number of elements in the array. `sign_bit` and `words` may be
both set to `NULL`, in order to get only `word_count`.

#### napi_get_value_external
<!-- YAML
added: v8.0.0
Expand Down
137 changes: 135 additions & 2 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,8 @@ const char* error_messages[] = {nullptr,
"Invalid handle scope usage",
"Invalid callback scope usage",
"Thread-safe function queue is full",
"Thread-safe function handle is closing"
"Thread-safe function handle is closing",
"A bigint was expected",
};

static inline napi_status napi_clear_last_error(napi_env env) {
Expand Down Expand Up @@ -958,7 +959,7 @@ napi_status napi_get_last_error_info(napi_env env,
// We don't have a napi_status_last as this would result in an ABI
// change each time a message was added.
static_assert(
node::arraysize(error_messages) == napi_closing + 1,
node::arraysize(error_messages) == napi_bigint_expected + 1,
"Count of error messages must match count of error values");
CHECK_LE(env->last_error.error_code, napi_callback_scope_mismatch);

Expand Down Expand Up @@ -1713,6 +1714,58 @@ napi_status napi_create_int64(napi_env env,
return napi_clear_last_error(env);
}

napi_status napi_create_bigint_int64(napi_env env,
int64_t value,
napi_value* result) {
CHECK_ENV(env);
CHECK_ARG(env, result);

*result = v8impl::JsValueFromV8LocalValue(
v8::BigInt::New(env->isolate, value));

return napi_clear_last_error(env);
}

napi_status napi_create_bigint_uint64(napi_env env,
uint64_t value,
napi_value* result) {
CHECK_ENV(env);
CHECK_ARG(env, result);

*result = v8impl::JsValueFromV8LocalValue(
v8::BigInt::NewFromUnsigned(env->isolate, value));

return napi_clear_last_error(env);
}

napi_status napi_create_bigint_words(napi_env env,
int sign_bit,
size_t word_count,
const uint64_t* words,
napi_value* result) {
NAPI_PREAMBLE(env);
CHECK_ARG(env, words);
CHECK_ARG(env, result);

v8::Local<v8::Context> context = env->isolate->GetCurrentContext();

if (word_count > INT_MAX) {
napi_throw_range_error(env, nullptr, "Maximum BigInt size exceeded");
Copy link
Member

Choose a reason for hiding this comment

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

Is this tested?

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, this should probably just return napi_invalid_arg.

If we do throw an error, we should introduce a new error code.

Copy link
Member

@TimothyGu TimothyGu Jul 9, 2018

Choose a reason for hiding this comment

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

See #21226 (comment). The rationale is that there should be no difference in how INT_MAX + 1ULL and INT_MAX are treated.

return napi_set_last_error(env, napi_pending_exception);
}

v8::MaybeLocal<v8::BigInt> b = v8::BigInt::NewFromWords(
context, sign_bit, word_count, words);

if (try_catch.HasCaught()) {
return napi_set_last_error(env, napi_pending_exception);
} else {
CHECK_MAYBE_EMPTY(env, b, napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(b.ToLocalChecked());
return napi_clear_last_error(env);
}
}

napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) {
CHECK_ENV(env);
CHECK_ARG(env, result);
Expand Down Expand Up @@ -1878,6 +1931,8 @@ napi_status napi_typeof(napi_env env,

if (v->IsNumber()) {
*result = napi_number;
} else if (v->IsBigInt()) {
*result = napi_bigint;
} else if (v->IsString()) {
*result = napi_string;
} else if (v->IsFunction()) {
Expand Down Expand Up @@ -2201,6 +2256,72 @@ napi_status napi_get_value_int64(napi_env env,
return napi_clear_last_error(env);
}

napi_status napi_get_value_bigint_int64(napi_env env,
napi_value value,
int64_t* result,
bool* lossless) {
Copy link
Member

Choose a reason for hiding this comment

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

@nodejs/n-api Side note … did we make a conscious decision to allow bool in our API? That makes N-API incompatible with older C versions, which is a shame … and this does not seem to be just theoretical 🙁

Copy link
Member Author

Choose a reason for hiding this comment

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

what did C use before bool?

Copy link
Member

Choose a reason for hiding this comment

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

@devsnek Any integral type, and macros representing true (usually 1) and false (0).

CHECK_ENV(env);
CHECK_ARG(env, value);
CHECK_ARG(env, result);
CHECK_ARG(env, lossless);

v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected);

*result = val.As<v8::BigInt>()->Int64Value(lossless);

return napi_clear_last_error(env);
}

napi_status napi_get_value_bigint_uint64(napi_env env,
napi_value value,
uint64_t* result,
bool* lossless) {
CHECK_ENV(env);
CHECK_ARG(env, value);
CHECK_ARG(env, result);
CHECK_ARG(env, lossless);

v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected);

*result = val.As<v8::BigInt>()->Uint64Value(lossless);

return napi_clear_last_error(env);
}

napi_status napi_get_value_bigint_words(napi_env env,
napi_value value,
int* sign_bit,
size_t* word_count,
uint64_t* words) {
CHECK_ENV(env);
CHECK_ARG(env, value);
CHECK_ARG(env, word_count);

Copy link
Contributor

Choose a reason for hiding this comment

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

word_count is not optional, so please add CHECK_ARGS(env, word_count).

v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);

RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected);

v8::Local<v8::BigInt> big = val.As<v8::BigInt>();

int word_count_int = *word_count;

if (sign_bit == nullptr && words == nullptr) {
word_count_int = big->WordCount();
} else {
CHECK_ARG(env, sign_bit);
CHECK_ARG(env, words);
big->ToWordsArray(sign_bit, &word_count_int, words);
}

*word_count = word_count_int;

return napi_clear_last_error(env);
}

napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) {
// Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
// JS exceptions.
Expand Down Expand Up @@ -3139,6 +3260,14 @@ napi_status napi_create_typedarray(napi_env env,
CREATE_TYPED_ARRAY(
env, Float64Array, 8, buffer, byte_offset, length, typedArray);
break;
case napi_bigint64_array:
CREATE_TYPED_ARRAY(
env, BigInt64Array, 8, buffer, byte_offset, length, typedArray);
break;
case napi_biguint64_array:
CREATE_TYPED_ARRAY(
env, BigUint64Array, 8, buffer, byte_offset, length, typedArray);
break;
default:
return napi_set_last_error(env, napi_invalid_arg);
}
Expand Down Expand Up @@ -3181,6 +3310,10 @@ napi_status napi_get_typedarray_info(napi_env env,
*type = napi_float32_array;
} else if (value->IsFloat64Array()) {
*type = napi_float64_array;
} else if (value->IsBigInt64Array()) {
*type = napi_bigint64_array;
} else if (value->IsBigUint64Array()) {
*type = napi_biguint64_array;
}
}

Expand Down
Loading