BigInt on Tiny Hardware with XS
The JavaScript language is preparing to add built-in support for integers of arbitrary size with a new feature named BigInt
. The built-in Number
type is accurate to 53-bits of precision, which is insufficient for many tasks. By standardizing BigInt
into the language, developers benefit from having a single API available in all environments and JavaScript engines can more easily optimize BigInt
performance.
Moddable's XS JavaScript engine is focused on providing full JavaScript support on the smallest of devices, where memory and performance are limited. At first, BigInt
might seem a poor fit for our goals with XS. However, the Moddable SDK has long provided equivalent functionality to BigInt
in our Arith
modules, which are part of our the cryptography libraries. We decided it would be worthwhile to explore implementing these using the new BigInt
type.
Introducing BigInt
If you aren't yet familiar with BigInt
, "BigInt: arbitrary-precision integers in JavaScript" by Mathias Bynens is excellent introduction. The ECMA-262 specification draft text for BigInt
and an in-depth explainer by Daniel Ehrenberg are also available. At this writing, BigInt
is at Stage 3 in the JavaScript language standardization process which means it is considered stable, but changes may be still be made prior to it becoming part of the JavaScript standard based on the experience of engine implementations.
Here's a simple example of using BigInt
. BigInt
literals have an "n" at the end to distinguish them from Number
. That is because BigInt
values cannot be mixed with the built-in JavaScript Number
type. This restriction is in place to avoid problems that occur when performing operations that mix numeric types. That means the following code fails:
let big = 1000000000000000000000n;
big += 1;
The following succeeds because big
is incremented by another BigInt
.
let big = 1000000000000000000000n;
big += 1n;
The BigInt
specification also extends TypedArray
support with BigInt64Array
and BigUint64Array
. These allow efficient storage of arrays of 64-bit values. Values in the array are returned as BigInt
values.
let a = new BigInt64Array(2);
a[0] = 123456781234567812345678n
a[1] = a[0] + 1n;
BigInt in XS
With today's release, XS fully implements the current draft of the BigInt
specification. The implementation passes 100% of the BigInt
tests in the test262 test suite used to measure conformance with the JavaScript language specification.
BigInt
works on all targets. We've tested it on macOS, Windows, and Linux computers as well as ESP8266 and ESP32 microcontrollers. The only limit on the size of integers is available free memory.
The XS implementation of BigInt
stores the BigInt
values in the XS chunk heap, just as it stores strings. By storing BigInt
values in the chunk heap, instead of in system memory, the BigInt
values are automatically compacted during garbage collection which avoids memory fragmentation. This is important on microcontrollers with limited memory and no MMU.
BigInt
support is useful in certain specialized scenarios. Still, many projects will not use it. The XS JavaScript engine's strip feature automatically eliminates many unused language features from the engine at build time. This means that the BigInt
support in XS does not increase the size of your project unless you actually use it.
BigInt
is supported in xsbug, so properties with BigInt
values may be inspected. The values are displayed in hex notation rather than decimal.
Because BigInt
is a new primitive type in JavaScript, the xsTypeOf
macro in BigInt
now can return xsBigIntType
and xsBigIntXType
(the latter is for BigInt
values stored in read-only memory).
XS adds three functions to its BigInt
implementation that are not part of the standard. These are not strictly necessary as they could be implemented from a script. To achieve better performance, it is beneficial to provide them in the engine.
BigInt.bitLength(value)
-- a static function to return the bit length of a BigInt value. This functionality was considered for inclusion in the standard but deferred to a later release.
BigInt.fromArrayBuffer(buffer)
-- a static function to convert an ArrayBuffer
to a BigInt
. This is used to deserialize BigInt
values.
ArrayBuffer.fromBigInt(value)
- a static function to convert a BigInt
to an ArrayBuffer
. This is used to serialize BigInt
values.
The Moddable XS Test Engine (xst
) binaries are up-to-date with the latest version of XS, and so now include BigInt
support. The jsvu
tool uses the xst
binaries. Using jsvu
you can compare the results of BigInt
operations in XS, V8, and SpiderMonkey.
Use of BigInt in Moddable SDK
BigInt
has fully replaced the previous "big number" support in the Moddable SDK used to implement the cryptography libraries. The result has been smaller and simpler code.
BigInt
manages memory using the XS chunk heap, where as the "big number" module it replaces manages memory using a stack in the system heap. Both implementations are fast, but the BigInt
approach is able to more aggressively garbage collect unused values, which can lower the overall memory requirements of some operations. For example, the TLS handshake, which is the most memory intensive part of establishing a TLS connection, has reduced its peak memory use by between 6 and 8 KB. That's a huge saving when you recall that this code is running on devices with as little as 45 KB of memory available to the JavaScript engine.
Looking Ahead
This initial implementation of BigInt
has language conformance and size as priorities. The performance is as good or better than the Big Number implementation in the Moddable SDK that it replaces. Opportunities remain to optimize performance further.
The XS in C interface that bridges between JavaScript and native C code does not fully support BigInt
yet. The primitives are in place and are used by the cryptographic implementation. The usual xs*
macros in xs.h
remain.
Conclusion
BigInt
is another example of the JavaScript language expanding to standardize common functionality previously provided by scripts. Developers benefit from being able to use the same APIs across engines and environments, and engines have the potential to optimize the performance of these functions further. The XS engine continues to take on the challenge of providing these leading-edge JavaScript features on the smallest device. This gives developers working in embedded JavaScript a more powerful host to build their products on.