diff --git a/doc/bigint.md b/doc/bigint.md
new file mode 100644
index 000000000..6d3b0afc0
--- /dev/null
+++ b/doc/bigint.md
@@ -0,0 +1,94 @@
+# BigInt
+
+A JavaScript BigInt value.
+
+## Methods
+
+### New
+
+```cpp
+static BigInt New(Napi::Env env, int64_t value);
+static BigInt New(Napi::Env env, uint64_t value);
+```
+
+ - `[in] env`: The environment in which to construct the `BigInt` object.
+ - `[in] value`: The value the JavaScript `BigInt` will contain
+
+These APIs convert the C `int64_t` and `uint64_t` types to the JavaScript
+`BigInt` type.
+
+```cpp
+static BigInt New(Napi::Env env,
+ int sign_bit,
+ size_t word_count,
+ const uint64_t* words);
+```
+
+ - `[in] env`: The environment in which to construct the `BigInt` object.
+ - `[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.
+
+This API converts an array of unsigned 64-bit words into a single `BigInt`
+value.
+
+The resulting `BigInt` is calculated as: (–1)`sign_bit` (`words[0]`
+× (264)0 + `words[1]` × (264)1 + …)
+
+Returns a new JavaScript `BigInt`.
+
+### Constructor
+
+```cpp
+Napi::BigInt();
+```
+
+Returns a new empty JavaScript `BigInt`.
+
+### Int64Value
+
+```cpp
+int64_t Int64Value(bool* lossless) const;
+```
+
+ - `[out] lossless`: Indicates whether the `BigInt` value was converted
+ losslessly.
+
+Returns the C `int64_t` primitive equivalent of the given JavaScript
+`BigInt`. If needed it will truncate the value, setting lossless to false.
+
+### Uint64Value
+
+```cpp
+uint64_t Uint64Value(bool* lossless) const;
+```
+
+ - `[out] lossless`: Indicates whether the `BigInt` value was converted
+ losslessly.
+
+Returns the C `uint64_t` primitive equivalent of the given JavaScript
+`BigInt`. If needed it will truncate the value, setting lossless to false.
+
+### WordCount
+
+```cpp
+size_t WordCount() const;
+```
+
+Returns the number of words needed to store this `BigInt` value.
+
+### ToWords
+
+```cpp
+void ToWords(size_t* word_count, int* sign_bit, uint64_t* words);
+```
+
+ - `[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 a single `BigInt` value into a sign bit, 64-bit little-endian array,
+and the number of elements in the array.
diff --git a/napi-inl.h b/napi-inl.h
index 59362e704..e2319ac04 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -298,6 +298,12 @@ inline bool Value::IsNumber() const {
return Type() == napi_number;
}
+#ifdef NAPI_EXPERIMENTAL
+inline bool Value::IsBigInt() const {
+ return Type() == napi_bigint;
+}
+#endif // NAPI_EXPERIMENTAL
+
inline bool Value::IsString() const {
return Type() == napi_string;
}
@@ -516,6 +522,69 @@ inline double Number::DoubleValue() const {
return result;
}
+#ifdef NAPI_EXPERIMENTAL
+////////////////////////////////////////////////////////////////////////////////
+// BigInt Class
+////////////////////////////////////////////////////////////////////////////////
+
+inline BigInt BigInt::New(napi_env env, int64_t val) {
+ napi_value value;
+ napi_status status = napi_create_bigint_int64(env, val, &value);
+ NAPI_THROW_IF_FAILED(env, status, BigInt());
+ return BigInt(env, value);
+}
+
+inline BigInt BigInt::New(napi_env env, uint64_t val) {
+ napi_value value;
+ napi_status status = napi_create_bigint_uint64(env, val, &value);
+ NAPI_THROW_IF_FAILED(env, status, BigInt());
+ return BigInt(env, value);
+}
+
+inline BigInt BigInt::New(napi_env env, int sign_bit, size_t word_count, const uint64_t* words) {
+ napi_value value;
+ napi_status status = napi_create_bigint_words(env, sign_bit, word_count, words, &value);
+ NAPI_THROW_IF_FAILED(env, status, BigInt());
+ return BigInt(env, value);
+}
+
+inline BigInt::BigInt() : Value() {
+}
+
+inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) {
+}
+
+inline int64_t BigInt::Int64Value(bool* lossless) const {
+ int64_t result;
+ napi_status status = napi_get_value_bigint_int64(
+ _env, _value, &result, lossless);
+ NAPI_THROW_IF_FAILED(_env, status, 0);
+ return result;
+}
+
+inline uint64_t BigInt::Uint64Value(bool* lossless) const {
+ uint64_t result;
+ napi_status status = napi_get_value_bigint_uint64(
+ _env, _value, &result, lossless);
+ NAPI_THROW_IF_FAILED(_env, status, 0);
+ return result;
+}
+
+inline size_t BigInt::WordCount() const {
+ size_t word_count;
+ napi_status status = napi_get_value_bigint_words(
+ _env, _value, nullptr, &word_count, nullptr);
+ NAPI_THROW_IF_FAILED(_env, status, 0);
+ return word_count;
+}
+
+inline void BigInt::ToWords(int* sign_bit, size_t* word_count, uint64_t* words) {
+ napi_status status = napi_get_value_bigint_words(
+ _env, _value, sign_bit, word_count, words);
+ NAPI_THROW_IF_FAILED(_env, status);
+}
+#endif // NAPI_EXPERIMENTAL
+
////////////////////////////////////////////////////////////////////////////////
// Name class
////////////////////////////////////////////////////////////////////////////////
diff --git a/napi.h b/napi.h
index 531d1c358..b2dce0ec5 100644
--- a/napi.h
+++ b/napi.h
@@ -52,6 +52,9 @@ namespace Napi {
class Value;
class Boolean;
class Number;
+#ifdef NAPI_EXPERIMENTAL
+ class BigInt;
+#endif // NAPI_EXPERIMENTAL
class String;
class Object;
class Array;
@@ -72,6 +75,10 @@ namespace Napi {
typedef TypedArrayOf Uint32Array; ///< Typed-array of unsigned 32-bit integers
typedef TypedArrayOf Float32Array; ///< Typed-array of 32-bit floating-point values
typedef TypedArrayOf Float64Array; ///< Typed-array of 64-bit floating-point values
+#ifdef NAPI_EXPERIMENTAL
+ typedef TypedArrayOf BigInt64Array; ///< Typed array of signed 64-bit integers
+ typedef TypedArrayOf BigUint64Array; ///< Typed array of unsigned 64-bit integers
+#endif // NAPI_EXPERIMENTAL
/// Defines the signature of a N-API C++ module's registration callback (init) function.
typedef Object (*ModuleRegisterCallback)(Env env, Object exports);
@@ -171,6 +178,9 @@ namespace Napi {
bool IsNull() const; ///< Tests if a value is a null JavaScript value.
bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean.
bool IsNumber() const; ///< Tests if a value is a JavaScript number.
+#ifdef NAPI_EXPERIMENTAL
+ bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint.
+#endif // NAPI_EXPERIMENTAL
bool IsString() const; ///< Tests if a value is a JavaScript string.
bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol.
bool IsArray() const; ///< Tests if a value is a JavaScript array.
@@ -242,6 +252,47 @@ namespace Napi {
double DoubleValue() const; ///< Converts a Number value to a 64-bit floating-point value.
};
+#ifdef NAPI_EXPERIMENTAL
+ /// A JavaScript bigint value.
+ class BigInt : public Value {
+ public:
+ static BigInt New(
+ napi_env env, ///< N-API environment
+ int64_t value ///< Number value
+ );
+ static BigInt New(
+ napi_env env, ///< N-API environment
+ uint64_t value ///< Number value
+ );
+
+ /// Creates a new BigInt object using a specified sign bit and a
+ /// specified list of digits/words.
+ /// The resulting number is calculated as:
+ /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...)
+ static BigInt New(
+ napi_env env, ///< N-API environment
+ int sign_bit, ///< Sign bit. 1 if negative.
+ size_t word_count, ///< Number of words in array
+ const uint64_t* words ///< Array of words
+ );
+
+ BigInt(); ///< Creates a new _empty_ BigInt instance.
+ BigInt(napi_env env, napi_value value); ///< Wraps a N-API value primitive.
+
+ int64_t Int64Value(bool* lossless) const; ///< Converts a BigInt value to a 64-bit signed integer value.
+ uint64_t Uint64Value(bool* lossless) const; ///< Converts a BigInt value to a 64-bit unsigned integer value.
+
+ size_t WordCount() const; ///< The number of 64-bit words needed to store the result of ToWords().
+
+ /// Writes the contents of this BigInt to a specified memory location.
+ /// `sign_bit` must be provided and will be set to 1 if this BigInt is negative.
+ /// `*word_count` has to 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 (i.e. the return value of `WordCount()`).
+ void ToWords(int* sign_bit, size_t* word_count, uint64_t* words);
+ };
+#endif // NAPI_EXPERIMENTAL
+
/// A JavaScript string or symbol value (that can be used as a property name).
class Name : public Value {
public:
@@ -705,6 +756,10 @@ namespace Napi {
: std::is_same::value ? napi_uint32_array
: std::is_same::value ? napi_float32_array
: std::is_same::value ? napi_float64_array
+#ifdef NAPI_EXPERIMENTAL
+ : std::is_same::value ? napi_bigint64_array
+ : std::is_same::value ? napi_biguint64_array
+#endif // NAPI_EXPERIMENTAL
: unknown_array_type;
}
/// !endcond
@@ -1552,9 +1607,9 @@ namespace Napi {
std::string _error;
};
- // Memory management.
+ // Memory management.
class MemoryManagement {
- public:
+ public:
static int64_t AdjustExternalMemory(Env env, int64_t change_in_bytes);
};
diff --git a/test/bigint.cc b/test/bigint.cc
new file mode 100644
index 000000000..48e44ac18
--- /dev/null
+++ b/test/bigint.cc
@@ -0,0 +1,76 @@
+#define NAPI_EXPERIMENTAL
+#include "napi.h"
+
+using namespace Napi;
+
+namespace {
+
+Value IsLossless(const CallbackInfo& info) {
+ Env env = info.Env();
+
+ BigInt big = info[0].As();
+ bool is_signed = info[1].ToBoolean().Value();
+
+ bool lossless;
+ if (is_signed) {
+ big.Int64Value(&lossless);
+ } else {
+ big.Uint64Value(&lossless);
+ }
+
+ return Boolean::New(env, lossless);
+}
+
+Value TestInt64(const CallbackInfo& info) {
+ bool lossless;
+ int64_t input = info[0].As().Int64Value(&lossless);
+
+ return BigInt::New(info.Env(), input);
+}
+
+Value TestUint64(const CallbackInfo& info) {
+ bool lossless;
+ uint64_t input = info[0].As().Uint64Value(&lossless);
+
+ return BigInt::New(info.Env(), input);
+}
+
+Value TestWords(const CallbackInfo& info) {
+ BigInt big = info[0].As();
+
+ size_t expected_word_count = big.WordCount();
+
+ int sign_bit;
+ size_t word_count = 10;
+ uint64_t words[10];
+
+ big.ToWords(&sign_bit, &word_count, words);
+
+ if (word_count != expected_word_count) {
+ Error::New(info.Env(), "word count did not match").ThrowAsJavaScriptException();
+ return BigInt();
+ }
+
+ return BigInt::New(info.Env(), sign_bit, word_count, words);
+}
+
+Value TestTooBigBigInt(const CallbackInfo& info) {
+ int sign_bit = 0;
+ size_t word_count = SIZE_MAX;
+ uint64_t words[10];
+
+ return BigInt::New(info.Env(), sign_bit, word_count, words);
+}
+
+} // anonymous namespace
+
+Object InitBigInt(Env env) {
+ Object exports = Object::New(env);
+ exports["IsLossless"] = Function::New(env, IsLossless);
+ exports["TestInt64"] = Function::New(env, TestInt64);
+ exports["TestUint64"] = Function::New(env, TestUint64);
+ exports["TestWords"] = Function::New(env, TestWords);
+ exports["TestTooBigBigInt"] = Function::New(env, TestTooBigBigInt);
+
+ return exports;
+}
diff --git a/test/bigint.js b/test/bigint.js
new file mode 100644
index 000000000..e4255172c
--- /dev/null
+++ b/test/bigint.js
@@ -0,0 +1,52 @@
+'use strict';
+
+const buildType = process.config.target_defaults.default_configuration;
+const assert = require('assert');
+
+test(require(`./build/${buildType}/binding.node`));
+test(require(`./build/${buildType}/binding_noexcept.node`));
+
+function test(binding) {
+ const {
+ TestInt64,
+ TestUint64,
+ TestWords,
+ IsLossless,
+ TestTooBigBigInt,
+ } = binding.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(TestTooBigBigInt, {
+ name: 'RangeError',
+ message: 'Maximum BigInt size exceeded',
+ });
+}
diff --git a/test/binding.cc b/test/binding.cc
index 3b4bb7b0a..6142ea0a6 100644
--- a/test/binding.cc
+++ b/test/binding.cc
@@ -6,6 +6,7 @@ Object InitArrayBuffer(Env env);
Object InitAsyncWorker(Env env);
Object InitBasicTypesNumber(Env env);
Object InitBasicTypesValue(Env env);
+Object InitBigInt(Env env);
Object InitBuffer(Env env);
Object InitDataView(Env env);
Object InitDataViewReadWrite(Env env);
@@ -26,6 +27,7 @@ Object Init(Env env, Object exports) {
exports.Set("asyncworker", InitAsyncWorker(env));
exports.Set("basic_types_number", InitBasicTypesNumber(env));
exports.Set("basic_types_value", InitBasicTypesValue(env));
+ exports.Set("bigint", InitBigInt(env));
exports.Set("buffer", InitBuffer(env));
exports.Set("dataview", InitDataView(env));
exports.Set("dataview_read_write", InitDataView(env));
diff --git a/test/binding.gyp b/test/binding.gyp
index 52cb7c225..3ca18ea87 100644
--- a/test/binding.gyp
+++ b/test/binding.gyp
@@ -5,6 +5,7 @@
'asyncworker.cc',
'basic_types/number.cc',
'basic_types/value.cc',
+ 'bigint.cc',
'binding.cc',
'buffer.cc',
'dataview/dataview.cc',
diff --git a/test/index.js b/test/index.js
index 5b6bc2cd9..580625008 100644
--- a/test/index.js
+++ b/test/index.js
@@ -12,6 +12,7 @@ let testModules = [
'asyncworker',
'basic_types/number',
'basic_types/value',
+ 'bigint',
'buffer',
'dataview/dataview',
'dataview/dataview_read_write',
diff --git a/test/typedarray.cc b/test/typedarray.cc
index b556a80a6..d231f69f6 100644
--- a/test/typedarray.cc
+++ b/test/typedarray.cc
@@ -1,3 +1,4 @@
+#define NAPI_EXPERIMENTAL
#include "napi.h"
using namespace Napi;
@@ -64,6 +65,16 @@ Value CreateTypedArray(const CallbackInfo& info) {
NAPI_TYPEDARRAY_NEW(Float64Array, info.Env(), length, napi_float64_array) :
NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, info.Env(), length, buffer, bufferOffset,
napi_float64_array);
+ } else if (arrayType == "bigint64") {
+ return buffer.IsUndefined() ?
+ NAPI_TYPEDARRAY_NEW(BigInt64Array, info.Env(), length, napi_bigint64_array) :
+ NAPI_TYPEDARRAY_NEW_BUFFER(BigInt64Array, info.Env(), length, buffer, bufferOffset,
+ napi_bigint64_array);
+ } else if (arrayType == "biguint64") {
+ return buffer.IsUndefined() ?
+ NAPI_TYPEDARRAY_NEW(BigUint64Array, info.Env(), length, napi_biguint64_array) :
+ NAPI_TYPEDARRAY_NEW_BUFFER(BigUint64Array, info.Env(), length, buffer, bufferOffset,
+ napi_biguint64_array);
} else {
Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException();
return Value();
@@ -86,6 +97,8 @@ Value GetTypedArrayType(const CallbackInfo& info) {
case napi_uint32_array: return String::New(info.Env(), "uint32");
case napi_float32_array: return String::New(info.Env(), "float32");
case napi_float64_array: return String::New(info.Env(), "float64");
+ case napi_bigint64_array: return String::New(info.Env(), "bigint64");
+ case napi_biguint64_array: return String::New(info.Env(), "biguint64");
default: return String::New(info.Env(), "invalid");
}
}
@@ -122,6 +135,10 @@ Value GetTypedArrayElement(const CallbackInfo& info) {
return Number::New(info.Env(), array.As()[index]);
case napi_float64_array:
return Number::New(info.Env(), array.As()[index]);
+ case napi_bigint64_array:
+ return BigInt::New(info.Env(), array.As()[index]);
+ case napi_biguint64_array:
+ return BigInt::New(info.Env(), array.As()[index]);
default:
Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException();
return Value();
@@ -160,6 +177,16 @@ void SetTypedArrayElement(const CallbackInfo& info) {
case napi_float64_array:
array.As()[index] = value.DoubleValue();
break;
+ case napi_bigint64_array: {
+ bool lossless;
+ array.As()[index] = value.As().Int64Value(&lossless);
+ break;
+ }
+ case napi_biguint64_array: {
+ bool lossless;
+ array.As()[index] = value.As().Uint64Value(&lossless);
+ break;
+ }
default:
Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException();
}
diff --git a/test/typedarray.js b/test/typedarray.js
index 9aa880c16..680cf856e 100644
--- a/test/typedarray.js
+++ b/test/typedarray.js
@@ -64,6 +64,53 @@ function test(binding) {
}
});
+ [
+ ['bigint64', BigInt64Array],
+ ['biguint64', BigUint64Array],
+ ].forEach(([type, Constructor]) => {
+ try {
+ const length = 4;
+ const t = binding.typedarray.createTypedArray(type, length);
+ assert.ok(t instanceof Constructor);
+ assert.strictEqual(binding.typedarray.getTypedArrayType(t), type);
+ assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length);
+
+ t[3] = 11n;
+ assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n);
+ binding.typedarray.setTypedArrayElement(t, 3, 22n);
+ assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n);
+ assert.strictEqual(t[3], 22n);
+
+ const b = binding.typedarray.getTypedArrayBuffer(t);
+ assert.ok(b instanceof ArrayBuffer);
+ } catch (e) {
+ console.log(type, Constructor);
+ throw e;
+ }
+
+ try {
+ const length = 4;
+ const offset = 8;
+ const b = new ArrayBuffer(offset + 64 * 4);
+
+ const t = binding.typedarray.createTypedArray(type, length, b, offset);
+ assert.ok(t instanceof Constructor);
+ assert.strictEqual(binding.typedarray.getTypedArrayType(t), type);
+ assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length);
+
+ t[3] = 11n;
+ assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n);
+ binding.typedarray.setTypedArrayElement(t, 3, 22n);
+ assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n);
+ assert.strictEqual(t[3], 22n);
+
+ assert.strictEqual(binding.typedarray.getTypedArrayBuffer(t), b);
+ } catch (e) {
+ console.log(type, Constructor);
+ throw e;
+ }
+ });
+
assert.throws(() => {
binding.typedarray.createInvalidTypedArray();
}, /Invalid (pointer passed as )?argument/);