napi: add bigint support

PR-URL: https://github.com/nodejs/node/pull/21226
Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com>
Reviewed-By: Kyle Farnung <kfarnung@microsoft.com>
This commit is contained in:
Gus Caplan 2018-06-08 22:41:20 -05:00
parent d85449dcdf
commit 1849a2b2e9
No known key found for this signature in database
GPG Key ID: F00BD11880E82F0E
8 changed files with 523 additions and 6 deletions

View File

@ -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,
@ -1225,6 +1224,7 @@ typedef enum {
napi_object,
napi_function,
napi_external,
napi_bigint,
} napi_valuetype;
```
@ -1250,6 +1250,8 @@ typedef enum {
napi_uint32_array,
napi_float32_array,
napi_float64_array,
napi_bigint64_array,
napi_biguint64_array,
} napi_typedarray_type;
```
@ -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.
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
@ -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 `false`.
#### 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 `false`.
#### 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.
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

View File

@ -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) {
@ -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);
@ -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");
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);
@ -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()) {
@ -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) {
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);
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.
@ -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);
}
@ -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;
}
}

View File

@ -671,6 +671,30 @@ napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func);
NAPI_EXTERN napi_status
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
NAPI_EXTERN napi_status napi_create_bigint_int64(napi_env env,
int64_t value,
napi_value* result);
NAPI_EXTERN napi_status napi_create_bigint_uint64(napi_env env,
uint64_t value,
napi_value* result);
NAPI_EXTERN napi_status napi_create_bigint_words(napi_env env,
int sign_bit,
size_t word_count,
const uint64_t* words,
napi_value* result);
NAPI_EXTERN napi_status napi_get_value_bigint_int64(napi_env env,
napi_value value,
int64_t* result,
bool* lossless);
NAPI_EXTERN napi_status napi_get_value_bigint_uint64(napi_env env,
napi_value value,
uint64_t* result,
bool* lossless);
NAPI_EXTERN napi_status napi_get_value_bigint_words(napi_env env,
napi_value value,
int* sign_bit,
size_t* word_count,
uint64_t* words);
#endif // NAPI_EXPERIMENTAL
EXTERN_C_END

View File

@ -46,6 +46,7 @@ typedef enum {
napi_object,
napi_function,
napi_external,
napi_bigint,
} napi_valuetype;
typedef enum {
@ -58,6 +59,8 @@ typedef enum {
napi_uint32_array,
napi_float32_array,
napi_float64_array,
napi_bigint64_array,
napi_biguint64_array,
} napi_typedarray_type;
typedef enum {
@ -78,6 +81,7 @@ typedef enum {
napi_callback_scope_mismatch,
napi_queue_full,
napi_closing,
napi_bigint_expected,
} napi_status;
#ifdef NAPI_EXPERIMENTAL

View File

@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_bigint",
"sources": [ "test_bigint.c" ]
}
]
}

View File

@ -0,0 +1,45 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const {
IsLossless,
TestInt64,
TestUint64,
TestWords,
CreateTooBigBigInt,
} = require(`./build/${common.buildType}/test_bigint`);
[
0n,
-0n,
1n,
-1n,
100n,
2121n,
-1233n,
986583n,
-976675n,
98765432213456789876546896323445679887645323232436587988766545658n,
-4350987086545760976737453646576078997096876957864353245245769809n,
].forEach((num) => {
if (num > -(2n ** 63n) && num < 2n ** 63n) {
assert.strictEqual(TestInt64(num), num);
assert.strictEqual(IsLossless(num, true), true);
} else {
assert.strictEqual(IsLossless(num, true), false);
}
if (num >= 0 && num < 2n ** 64n) {
assert.strictEqual(TestUint64(num), num);
assert.strictEqual(IsLossless(num, false), true);
} else {
assert.strictEqual(IsLossless(num, false), false);
}
assert.strictEqual(num, TestWords(num));
});
assert.throws(CreateTooBigBigInt, {
name: 'RangeError',
message: 'Maximum BigInt size exceeded',
});

View File

@ -0,0 +1,142 @@
#define NAPI_EXPERIMENTAL
#include <inttypes.h>
#include <stdio.h>
#include <node_api.h>
#include "../common.h"
static napi_value IsLossless(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &args, NULL, NULL));
bool is_signed;
NAPI_CALL(env, napi_get_value_bool(env, args[1], &is_signed));
bool lossless;
if (is_signed) {
int64_t input;
NAPI_CALL(env, napi_get_value_bigint_int64(env, args[0], &input, &lossless));
} else {
uint64_t input;
NAPI_CALL(env, napi_get_value_bigint_uint64(env, args[0], &input, &lossless));
}
napi_value output;
NAPI_CALL(env, napi_get_boolean(env, lossless, &output));
return output;
}
static napi_value TestInt64(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_bigint,
"Wrong type of arguments. Expects a bigint as first argument.");
int64_t input;
bool lossless;
NAPI_CALL(env, napi_get_value_bigint_int64(env, args[0], &input, &lossless));
napi_value output;
NAPI_CALL(env, napi_create_bigint_int64(env, input, &output));
return output;
}
static napi_value TestUint64(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_bigint,
"Wrong type of arguments. Expects a bigint as first argument.");
uint64_t input;
bool lossless;
NAPI_CALL(env, napi_get_value_bigint_uint64(
env, args[0], &input, &lossless));
napi_value output;
NAPI_CALL(env, napi_create_bigint_uint64(env, input, &output));
return output;
}
static napi_value TestWords(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_bigint,
"Wrong type of arguments. Expects a bigint as first argument.");
size_t expected_word_count;
NAPI_CALL(env, napi_get_value_bigint_words(
env, args[0], NULL, &expected_word_count, NULL));
int sign_bit;
size_t word_count = 10;
uint64_t words[10];
NAPI_CALL(env, napi_get_value_bigint_words(
env, args[0], &sign_bit, &word_count, &words));
NAPI_ASSERT(env, word_count == expected_word_count,
"word counts do not match");
napi_value output;
NAPI_CALL(env, napi_create_bigint_words(
env, sign_bit, word_count, words, &output));
return output;
}
// throws RangeError
static napi_value CreateTooBigBigInt(napi_env env, napi_callback_info info) {
int sign_bit = 0;
size_t word_count = SIZE_MAX;
uint64_t words[10];
napi_value output;
NAPI_CALL(env, napi_create_bigint_words(
env, sign_bit, word_count, words, &output));
return output;
}
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
DECLARE_NAPI_PROPERTY("IsLossless", IsLossless),
DECLARE_NAPI_PROPERTY("TestInt64", TestInt64),
DECLARE_NAPI_PROPERTY("TestUint64", TestUint64),
DECLARE_NAPI_PROPERTY("TestWords", TestWords),
DECLARE_NAPI_PROPERTY("CreateTooBigBigInt", CreateTooBigBigInt),
};
NAPI_CALL(env, napi_define_properties(
env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors));
return exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

View File

@ -42,7 +42,7 @@ assert.strictEqual(externalResult[2], 2);
const buffer = new ArrayBuffer(128);
const arrayTypes = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array,
Uint16Array, Int32Array, Uint32Array, Float32Array,
Float64Array ];
Float64Array, BigInt64Array, BigUint64Array ];
arrayTypes.forEach((currentType) => {
const template = Reflect.construct(currentType, buffer);
@ -64,7 +64,8 @@ arrayTypes.forEach((currentType) => {
});
const nonByteArrayTypes = [ Int16Array, Uint16Array, Int32Array, Uint32Array,
Float32Array, Float64Array ];
Float32Array, Float64Array,
BigInt64Array, BigUint64Array ];
nonByteArrayTypes.forEach((currentType) => {
const template = Reflect.construct(currentType, buffer);
assert.throws(() => {