diff options
author | Moonchild <moonchild@palemoon.org> | 2023-07-23 23:38:53 +0000 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2023-07-23 23:38:53 +0000 |
commit | b4ddadd8b98a83e833eb01223dbb3858955e868d (patch) | |
tree | 57d4bc112d682b2e8125c6b6bea14660038523a5 | |
parent | 7175868124c789ae6607be6244038dd6d50fe771 (diff) | |
parent | f359533b82ec4131d5af7955685e9178887ba9cd (diff) | |
download | uxp-b4ddadd8b98a83e833eb01223dbb3858955e868d.tar.gz |
Merge pull request 'Add BigInt primitive type.' (#2276) from dbsoft/UXP:bigint-merged into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/2276
130 files changed, 6238 insertions, 441 deletions
diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index d55be9eb89..467b134864 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -1037,11 +1037,17 @@ MaybeWrapValue(JSContext* cx, JS::MutableHandle<JS::Value> rval) return MaybeWrapStringValue(cx, rval); } - if (!rval.isObject()) { - return true; + if (rval.isObject()) { + return MaybeWrapObjectValue(cx, rval); + } + // This could be optimized by checking the zone first, similar to + // the way strings are handled. At present, this is used primarily + // for structured cloning, so avoiding the overhead of JS_WrapValue + // calls is less important than for other types. + if (rval.isBigInt()) { + return JS_WrapValue(cx, rval); } - - return MaybeWrapObjectValue(cx, rval); + return true; } namespace binding_detail { diff --git a/js/public/Class.h b/js/public/Class.h index f1d7739718..8aeacdc541 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -1100,6 +1100,7 @@ enum class ESClass { SetIterator, Arguments, Error, + BigInt, /** None of the above. */ Other diff --git a/js/public/Conversions.h b/js/public/Conversions.h index 4978583eca..200fc030fb 100644 --- a/js/public/Conversions.h +++ b/js/public/Conversions.h @@ -11,6 +11,7 @@ #include "mozilla/Casting.h" #include "mozilla/FloatingPoint.h" #include "mozilla/TypeTraits.h" +#include "mozilla/WrappingOperations.h" #include <math.h> @@ -120,7 +121,7 @@ ToBoolean(HandleValue v) if (v.isSymbol()) return true; - /* The slow path handles strings and objects. */ + /* The slow path handles strings, BigInts and objects. */ return js::ToBooleanSlow(v); } diff --git a/js/public/GCPolicyAPI.h b/js/public/GCPolicyAPI.h index 151ed4e048..cffaee7140 100644 --- a/js/public/GCPolicyAPI.h +++ b/js/public/GCPolicyAPI.h @@ -47,6 +47,7 @@ // Expand the given macro D for each public GC pointer. #define FOR_EACH_PUBLIC_GC_POINTER_TYPE(D) \ D(JS::Symbol*) \ + D(JS::BigInt*) \ D(JSAtom*) \ D(JSFunction*) \ D(JSObject*) \ @@ -125,6 +126,7 @@ struct GCPointerPolicy } }; template <> struct GCPolicy<JS::Symbol*> : public GCPointerPolicy<JS::Symbol*> {}; +template <> struct GCPolicy<JS::BigInt*> : public GCPointerPolicy<JS::BigInt*> {}; template <> struct GCPolicy<JSAtom*> : public GCPointerPolicy<JSAtom*> {}; template <> struct GCPolicy<JSFunction*> : public GCPointerPolicy<JSFunction*> {}; template <> struct GCPolicy<JSObject*> : public GCPointerPolicy<JSObject*> {}; diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 72764aec0e..d9660a7cf8 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -580,6 +580,7 @@ struct UnusedGCThingSizes macro(Other, GCHeapUnused, objectGroup) \ macro(Other, GCHeapUnused, string) \ macro(Other, GCHeapUnused, symbol) \ + macro(Other, GCHeapUnused, bigInt) \ macro(Other, GCHeapUnused, jitcode) \ macro(Other, GCHeapUnused, scope) \ macro(Other, GCHeapUnused, regExpShared) @@ -599,6 +600,7 @@ struct UnusedGCThingSizes case JS::TraceKind::Object: object += n; break; case JS::TraceKind::String: string += n; break; case JS::TraceKind::Symbol: symbol += n; break; + case JS::TraceKind::BigInt: bigInt += n; break; case JS::TraceKind::Script: script += n; break; case JS::TraceKind::Shape: shape += n; break; case JS::TraceKind::BaseShape: baseShape += n; break; @@ -640,6 +642,8 @@ struct ZoneStats { #define FOR_EACH_SIZE(macro) \ macro(Other, GCHeapUsed, symbolsGCHeap) \ + macro(Other, GCHeapUsed, bigIntsGCHeap) \ + macro(Other, MallocHeap, bigIntsMallocHeap) \ macro(Other, GCHeapAdmin, gcHeapArenaAdmin) \ macro(Other, GCHeapUsed, lazyScriptsGCHeap) \ macro(Other, MallocHeap, lazyScriptsMallocHeap) \ diff --git a/js/public/TraceKind.h b/js/public/TraceKind.h index 13228a9612..78cf20f3a1 100644 --- a/js/public/TraceKind.h +++ b/js/public/TraceKind.h @@ -60,7 +60,8 @@ enum class TraceKind JitCode = 0x1F, LazyScript = 0x2F, Scope = 0x3F, - RegExpShared = 0x4F + RegExpShared = 0x4F, + BigInt = 0x5F }; const static uintptr_t OutOfLineTraceKindMask = 0x07; static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set"); @@ -91,6 +92,7 @@ struct MapTypeToTraceKind { D(Shape, js::Shape, true) \ D(String, JSString, false) \ D(Symbol, JS::Symbol, false) \ + D(BigInt, JS::BigInt, false) \ D(RegExpShared, js::RegExpShared, true) // Map from all public types to their trace kind. diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index 01d28d93c3..cc03a54609 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -141,6 +141,7 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer virtual void onObjectEdge(JSObject** objp) { onChild(JS::GCCellPtr(*objp)); } virtual void onStringEdge(JSString** strp) { onChild(JS::GCCellPtr(*strp)); } virtual void onSymbolEdge(JS::Symbol** symp) { onChild(JS::GCCellPtr(*symp)); } + virtual void onBigIntEdge(JS::BigInt** bip) { onChild(JS::GCCellPtr(*bip)); } virtual void onScriptEdge(JSScript** scriptp) { onChild(JS::GCCellPtr(*scriptp)); } virtual void onShapeEdge(js::Shape** shapep) { onChild(JS::GCCellPtr(*shapep, JS::TraceKind::Shape)); @@ -226,6 +227,7 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer void dispatchToOnEdge(JSObject** objp) { onObjectEdge(objp); } void dispatchToOnEdge(JSString** strp) { onStringEdge(strp); } void dispatchToOnEdge(JS::Symbol** symp) { onSymbolEdge(symp); } + void dispatchToOnEdge(JS::BigInt** bip) { onBigIntEdge(bip); } void dispatchToOnEdge(JSScript** scriptp) { onScriptEdge(scriptp); } void dispatchToOnEdge(js::Shape** shapep) { onShapeEdge(shapep); } void dispatchToOnEdge(js::ObjectGroup** groupp) { onObjectGroupEdge(groupp); } diff --git a/js/public/TypeDecls.h b/js/public/TypeDecls.h index 2b36ed95b9..27f652f04f 100644 --- a/js/public/TypeDecls.h +++ b/js/public/TypeDecls.h @@ -39,6 +39,7 @@ namespace JS { typedef unsigned char Latin1Char; class Symbol; +class BigInt; class Value; template <typename T> class Handle; template <typename T> class MutableHandle; @@ -53,6 +54,7 @@ typedef Handle<JSObject*> HandleObject; typedef Handle<JSScript*> HandleScript; typedef Handle<JSString*> HandleString; typedef Handle<JS::Symbol*> HandleSymbol; +typedef Handle<JS::BigInt*> HandleBigInt; typedef Handle<Value> HandleValue; typedef Handle<StackGCVector<Value>> HandleValueVector; @@ -62,6 +64,7 @@ typedef MutableHandle<JSObject*> MutableHandleObject; typedef MutableHandle<JSScript*> MutableHandleScript; typedef MutableHandle<JSString*> MutableHandleString; typedef MutableHandle<JS::Symbol*> MutableHandleSymbol; +typedef MutableHandle<JS::BigInt*> MutableHandleBigInt; typedef MutableHandle<Value> MutableHandleValue; typedef MutableHandle<StackGCVector<Value>> MutableHandleValueVector; @@ -70,6 +73,7 @@ typedef Rooted<JSFunction*> RootedFunction; typedef Rooted<JSScript*> RootedScript; typedef Rooted<JSString*> RootedString; typedef Rooted<JS::Symbol*> RootedSymbol; +typedef Rooted<JS::BigInt*> RootedBigInt; typedef Rooted<jsid> RootedId; typedef Rooted<JS::Value> RootedValue; @@ -81,6 +85,7 @@ typedef PersistentRooted<JSObject*> PersistentRootedObject; typedef PersistentRooted<JSScript*> PersistentRootedScript; typedef PersistentRooted<JSString*> PersistentRootedString; typedef PersistentRooted<JS::Symbol*> PersistentRootedSymbol; +typedef PersistentRooted<JS::BigInt*> PersistentRootedBigInt; typedef PersistentRooted<Value> PersistentRootedValue; diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h index a4ed0dff49..3df3a4840b 100644 --- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -1057,6 +1057,22 @@ class JS_PUBLIC_API(Concrete<JS::Symbol>) : TracerConcrete<JS::Symbol> { }; template<> +class JS_PUBLIC_API(Concrete<JS::BigInt>) : TracerConcrete<JS::BigInt> { + protected: + explicit Concrete(JS::BigInt* ptr) : TracerConcrete(ptr) {} + + public: + static void construct(void* storage, JS::BigInt* ptr) { + new (storage) Concrete(ptr); + } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +template<> class JS_PUBLIC_API(Concrete<JSScript>) : TracerConcreteWithCompartment<JSScript> { protected: explicit Concrete(JSScript *ptr) : TracerConcreteWithCompartment<JSScript>(ptr) { } diff --git a/js/public/Utility.h b/js/public/Utility.h index cadcef7000..dbe69e18c0 100644 --- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -14,6 +14,7 @@ #include "mozilla/Scoped.h" #include "mozilla/TemplateLib.h" #include "mozilla/UniquePtr.h" +#include "mozilla/WrappingOperations.h" #include <stdlib.h> #include <string.h> @@ -539,7 +540,7 @@ ScrambleHashCode(HashNumber h) * are stored in a hash table; see Knuth for details. */ static const HashNumber goldenRatio = 0x9E3779B9U; - return h * goldenRatio; + return mozilla::WrappingMultiply(h, goldenRatio); } } /* namespace detail */ diff --git a/js/public/Value.h b/js/public/Value.h index c645f07733..30f4670049 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -56,6 +56,7 @@ JS_ENUM_HEADER(JSValueType, uint8_t) JSVAL_TYPE_STRING = 0x06, JSVAL_TYPE_SYMBOL = 0x07, JSVAL_TYPE_PRIVATE_GCTHING = 0x08, + JSVAL_TYPE_BIGINT = 0x09, JSVAL_TYPE_OBJECT = 0x0c, /* These never appear in a jsval; they are only provided as an out-of-band value. */ @@ -80,6 +81,7 @@ JS_ENUM_HEADER(JSValueTag, uint32_t) JSVAL_TAG_BOOLEAN = JSVAL_TAG_CLEAR | JSVAL_TYPE_BOOLEAN, JSVAL_TAG_MAGIC = JSVAL_TAG_CLEAR | JSVAL_TYPE_MAGIC, JSVAL_TAG_OBJECT = JSVAL_TAG_CLEAR | JSVAL_TYPE_OBJECT, + JSVAL_TAG_BIGINT = JSVAL_TAG_CLEAR | JSVAL_TYPE_BIGINT, JSVAL_TAG_PRIVATE_GCTHING = JSVAL_TAG_CLEAR | JSVAL_TYPE_PRIVATE_GCTHING } JS_ENUM_FOOTER(JSValueTag); @@ -100,6 +102,7 @@ JS_ENUM_HEADER(JSValueTag, uint32_t) JSVAL_TAG_BOOLEAN = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_BOOLEAN, JSVAL_TAG_MAGIC = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_MAGIC, JSVAL_TAG_OBJECT = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_OBJECT, + JSVAL_TAG_BIGINT = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_BIGINT, JSVAL_TAG_PRIVATE_GCTHING = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_PRIVATE_GCTHING } JS_ENUM_FOOTER(JSValueTag); @@ -117,6 +120,7 @@ JS_ENUM_HEADER(JSValueShiftedTag, uint64_t) JSVAL_SHIFTED_TAG_BOOLEAN = (((uint64_t)JSVAL_TAG_BOOLEAN) << JSVAL_TAG_SHIFT), JSVAL_SHIFTED_TAG_MAGIC = (((uint64_t)JSVAL_TAG_MAGIC) << JSVAL_TAG_SHIFT), JSVAL_SHIFTED_TAG_OBJECT = (((uint64_t)JSVAL_TAG_OBJECT) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_BIGINT = (((uint64_t)JSVAL_TAG_BIGINT) << JSVAL_TAG_SHIFT), JSVAL_SHIFTED_TAG_PRIVATE_GCTHING = (((uint64_t)JSVAL_TAG_PRIVATE_GCTHING) << JSVAL_TAG_SHIFT) } JS_ENUM_FOOTER(JSValueShiftedTag); @@ -275,7 +279,7 @@ CanonicalizeNaN(double d) * * - JS::Value has setX() and isX() members for X in * - * { Int32, Double, String, Symbol, Boolean, Undefined, Null, Object, Magic } + * { Int32, Double, String, Symbol, BigInt, Boolean, Undefined, Null, Object, Magic } * * JS::Value also contains toX() for each of the non-singleton types. * @@ -370,6 +374,11 @@ class MOZ_NON_PARAM alignas(8) Value data.asBits = bitsFromTagAndPayload(JSVAL_TAG_SYMBOL, PayloadType(sym)); } + void setBigInt(JS::BigInt* bi) { + MOZ_ASSERT(uintptr_t(bi) > 0x1000); + data.asBits = bitsFromTagAndPayload(JSVAL_TAG_BIGINT, PayloadType(bi)); + } + void setObject(JSObject& obj) { MOZ_ASSERT(uintptr_t(&obj) > 0x1000 || uintptr_t(&obj) == 0x48); #if defined(JS_PUNBOX64) @@ -498,7 +507,7 @@ class MOZ_NON_PARAM alignas(8) Value #if defined(JS_NUNBOX32) return uint32_t(toTag()) <= uint32_t(JSVAL_TAG_CLEAR); #elif defined(JS_PUNBOX64) - return (data.asBits | mozilla::DoubleTypeTraits::kSignBit) <= JSVAL_SHIFTED_TAG_MAX_DOUBLE; + return (data.asBits | mozilla::FloatingPoint<double>::kSignBit) <= JSVAL_SHIFTED_TAG_MAX_DOUBLE; #endif } @@ -519,6 +528,10 @@ class MOZ_NON_PARAM alignas(8) Value return toTag() == JSVAL_TAG_SYMBOL; } + bool isBigInt() const { + return toTag() == JSVAL_TAG_BIGINT; + } + bool isObject() const { #if defined(JS_NUNBOX32) return toTag() == JSVAL_TAG_OBJECT; @@ -583,6 +596,8 @@ class MOZ_NON_PARAM alignas(8) Value "Value type tags must correspond with JS::TraceKinds."); if (MOZ_UNLIKELY(isPrivateGCThing())) return JS::GCThingTraceKind(toGCThing()); + if (MOZ_UNLIKELY(isBigInt())) + return JS::TraceKind::BigInt; return JS::TraceKind(toTag() & 0x03); } @@ -647,6 +662,15 @@ class MOZ_NON_PARAM alignas(8) Value #endif } + JS::BigInt* toBigInt() const { + MOZ_ASSERT(isBigInt()); +#if defined(JS_NUNBOX32) + return data.s.payload.bi; +#elif defined(JS_PUNBOX64) + return reinterpret_cast<JS::BigInt*>(data.asBits & JSVAL_PAYLOAD_MASK); +#endif + } + JSObject& toObject() const { MOZ_ASSERT(isObject()); #if defined(JS_NUNBOX32) @@ -759,6 +783,8 @@ class MOZ_NON_PARAM alignas(8) Value "Private GC thing Values must not be strings. Make a StringValue instead."); MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::Symbol, "Private GC thing Values must not be symbols. Make a SymbolValue instead."); + MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::BigInt, + "Private GC thing Values must not be BigInts. Make a BigIntValue instead."); MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::Object, "Private GC thing Values must not be objects. Make an ObjectValue instead."); @@ -811,6 +837,7 @@ class MOZ_NON_PARAM alignas(8) Value uint32_t boo; // Don't use |bool| -- it must be four bytes. JSString* str; JS::Symbol* sym; + JS::BigInt* bi; JSObject* obj; js::gc::Cell* cell; void* ptr; @@ -866,6 +893,7 @@ class MOZ_NON_PARAM alignas(8) Value uint32_t boo; // Don't use |bool| -- it must be four bytes. JSString* str; JS::Symbol* sym; + JS::BigInt* bi; JSObject* obj; js::gc::Cell* cell; void* ptr; @@ -1062,7 +1090,7 @@ IsCanonicalized(double d) uint64_t bits; mozilla::BitwiseCast<uint64_t>(d, &bits); - return (bits & ~mozilla::DoubleTypeTraits::kSignBit) == detail::CanonicalizedNaNBits; + return (bits & ~mozilla::FloatingPoint<double>::kSignBit) == detail::CanonicalizedNaNBits; } static inline Value @@ -1098,6 +1126,14 @@ SymbolValue(JS::Symbol* sym) } static inline Value +BigIntValue(JS::BigInt* bi) +{ + Value v; + v.setBigInt(bi); + return v; +} + +static inline Value BooleanValue(bool boo) { Value v; @@ -1365,6 +1401,7 @@ class WrappedPtrOperations<JS::Value, Wrapper> bool isDouble() const { return value().isDouble(); } bool isString() const { return value().isString(); } bool isSymbol() const { return value().isSymbol(); } + bool isBigInt() const { return value().isBigInt(); } bool isObject() const { return value().isObject(); } bool isMagic() const { return value().isMagic(); } bool isMagic(JSWhyMagic why) const { return value().isMagic(why); } @@ -1380,6 +1417,7 @@ class WrappedPtrOperations<JS::Value, Wrapper> double toDouble() const { return value().toDouble(); } JSString* toString() const { return value().toString(); } JS::Symbol* toSymbol() const { return value().toSymbol(); } + JS::BigInt* toBigInt() const { return value().toBigInt(); } JSObject& toObject() const { return value().toObject(); } JSObject* toObjectOrNull() const { return value().toObjectOrNull(); } gc::Cell* toGCThing() const { return value().toGCThing(); } @@ -1421,6 +1459,7 @@ class MutableWrappedPtrOperations<JS::Value, Wrapper> : public WrappedPtrOperati void setNumber(double d) { set(JS::NumberValue(d)); } void setString(JSString* str) { set(JS::StringValue(str)); } void setSymbol(JS::Symbol* sym) { set(JS::SymbolValue(sym)); } + void setBigInt(JS::BigInt* bi) { set(JS::BigIntValue(bi)); } void setObject(JSObject& obj) { set(JS::ObjectValue(obj)); } void setObjectOrNull(JSObject* arg) { set(JS::ObjectOrNullValue(arg)); } void setPrivate(void* ptr) { set(JS::PrivateValue(ptr)); } @@ -1469,6 +1508,8 @@ DispatchTyped(F f, const JS::Value& val, Args&&... args) return f(&val.toObject(), mozilla::Forward<Args>(args)...); if (val.isSymbol()) return f(val.toSymbol(), mozilla::Forward<Args>(args)...); + if (val.isBigInt()) + return f(val.toBigInt(), mozilla::Forward<Args>(args)...); if (MOZ_UNLIKELY(val.isPrivateGCThing())) return DispatchTyped(f, val.toGCCellPtr(), mozilla::Forward<Args>(args)...); MOZ_ASSERT(!val.isGCThing()); diff --git a/js/src/NamespaceImports.h b/js/src/NamespaceImports.h index a1d8bca1c3..dd940dc808 100644 --- a/js/src/NamespaceImports.h +++ b/js/src/NamespaceImports.h @@ -50,6 +50,7 @@ class PropertyResult; class Symbol; enum class SymbolCode: uint32_t; +class BigInt; } // namespace JS // Do the importing. @@ -122,6 +123,7 @@ using JS::RootedObject; using JS::RootedScript; using JS::RootedString; using JS::RootedSymbol; +using JS::RootedBigInt; using JS::RootedValue; using JS::PersistentRooted; @@ -131,6 +133,7 @@ using JS::PersistentRootedObject; using JS::PersistentRootedScript; using JS::PersistentRootedString; using JS::PersistentRootedSymbol; +using JS::PersistentRootedBigInt; using JS::PersistentRootedValue; using JS::Handle; @@ -140,6 +143,7 @@ using JS::HandleObject; using JS::HandleScript; using JS::HandleString; using JS::HandleSymbol; +using JS::HandleBigInt; using JS::HandleValue; using JS::MutableHandle; @@ -149,6 +153,7 @@ using JS::MutableHandleObject; using JS::MutableHandleScript; using JS::MutableHandleString; using JS::MutableHandleSymbol; +using JS::MutableHandleBigInt; using JS::MutableHandleValue; using JS::NullHandleValue; @@ -166,6 +171,7 @@ using JS::Zone; using JS::Symbol; using JS::SymbolCode; +using JS::BigInt; } /* namespace js */ #endif /* NamespaceImports_h */ diff --git a/js/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp new file mode 100644 index 0000000000..6c78970d74 --- /dev/null +++ b/js/src/builtin/BigInt.cpp @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "builtin/BigInt.h" + +#include "jsapi.h" + +#include "builtin/TypedObject.h" +#include "gc/Tracer.h" +#include "js/TracingAPI.h" +#include "vm/ArrayBufferObject.h" +#include "vm/BigIntType.h" +#include "vm/SelfHosting.h" +#include "vm/TaggedProto.h" + +#include "vm/NativeObject-inl.h" + +using namespace js; + +static MOZ_ALWAYS_INLINE bool +IsBigInt(HandleValue v) +{ + return v.isBigInt() || (v.isObject() && v.toObject().is<BigIntObject>()); +} + +static JSObject* +CreateBigIntPrototype(JSContext* cx, JSProtoKey key) +{ + return GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global()); +} + +// BigInt proposal section 5.1.3 +static bool +BigIntConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (args.isConstructing()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR, "BigInt"); + return false; + } + + // Step 2. + RootedValue v(cx, args.get(0)); + if (!ToPrimitive(cx, JSTYPE_NUMBER, &v)) + return false; + + // Steps 3-4. + BigInt* bi = v.isNumber() + ? NumberToBigInt(cx, v.toNumber()) + : ToBigInt(cx, v); + if (!bi) + return false; + + args.rval().setBigInt(bi); + return true; +} + +JSObject* +BigIntObject::create(JSContext* cx, HandleBigInt bigInt) +{ + RootedObject obj(cx, NewBuiltinClassInstance(cx, &class_)); + if (!obj) + return nullptr; + BigIntObject& bn = obj->as<BigIntObject>(); + bn.setFixedSlot(PRIMITIVE_VALUE_SLOT, BigIntValue(bigInt)); + return &bn; +} + +BigInt* +BigIntObject::unbox() const +{ + return getFixedSlot(PRIMITIVE_VALUE_SLOT).toBigInt(); +} + +// BigInt proposal section 5.3.4 +bool +BigIntObject::valueOf_impl(JSContext* cx, const CallArgs& args) +{ + // Step 1. + HandleValue thisv = args.thisv(); + MOZ_ASSERT(IsBigInt(thisv)); + RootedBigInt bi(cx, thisv.isBigInt() + ? thisv.toBigInt() + : thisv.toObject().as<BigIntObject>().unbox()); + + args.rval().setBigInt(bi); + return true; +} + +bool +BigIntObject::valueOf(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsBigInt, valueOf_impl>(cx, args); +} + +// BigInt proposal section 5.3.3 +bool +BigIntObject::toString_impl(JSContext* cx, const CallArgs& args) +{ + // Step 1. + HandleValue thisv = args.thisv(); + MOZ_ASSERT(IsBigInt(thisv)); + RootedBigInt bi(cx, thisv.isBigInt() + ? thisv.toBigInt() + : thisv.toObject().as<BigIntObject>().unbox()); + + // Steps 2-3. + uint8_t radix = 10; + + // Steps 4-5. + if (args.hasDefined(0)) { + double d; + if (!ToInteger(cx, args.get(0), &d)) + return false; + if (d < 2 || d > 36) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_RADIX); + return false; + } + radix = d; + } + + // Steps 6-7. + JSLinearString* str = BigInt::toString(cx, bi, radix); + if (!str) + return false; + args.rval().setString(str); + return true; +} + +bool +BigIntObject::toString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsBigInt, toString_impl>(cx, args); +} + +// BigInt proposal section 5.3.2. "This function is +// implementation-dependent, and it is permissible, but not encouraged, +// for it to return the same thing as toString." +bool +BigIntObject::toLocaleString_impl(JSContext* cx, const CallArgs& args) +{ + HandleValue thisv = args.thisv(); + MOZ_ASSERT(IsBigInt(thisv)); + RootedBigInt bi(cx, thisv.isBigInt() + ? thisv.toBigInt() + : thisv.toObject().as<BigIntObject>().unbox()); + + RootedString str(cx, BigInt::toString(cx, bi, 10)); + if (!str) + return false; + args.rval().setString(str); + return true; +} + +bool +BigIntObject::toLocaleString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsBigInt, toLocaleString_impl>(cx, args); +} + +// BigInt proposal section 5.2.1. BigInt.asUintN ( bits, bigint ) +bool +BigIntObject::asUintN(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + uint64_t bits; + if (!ToIndex(cx, args.get(0), &bits)) { + return false; + } + + // Step 2. + RootedBigInt bi(cx, ToBigInt(cx, args.get(1))); + if (!bi) { + return false; + } + + // Step 3. + BigInt* res = BigInt::asUintN(cx, bi, bits); + if (!res) { + return false; + } + + args.rval().setBigInt(res); + return true; +} + +// BigInt proposal section 5.2.2. BigInt.asIntN ( bits, bigint ) +bool +BigIntObject::asIntN(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + uint64_t bits; + if (!ToIndex(cx, args.get(0), &bits)) { + return false; + } + + // Step 2. + RootedBigInt bi(cx, ToBigInt(cx, args.get(1))); + if (!bi) { + return false; + } + + // Step 3. + BigInt* res = BigInt::asIntN(cx, bi, bits); + if (!res) { + return false; + } + + args.rval().setBigInt(res); + return true; +} + +const ClassSpec BigIntObject::classSpec_ = { + GenericCreateConstructor<BigIntConstructor, 1, gc::AllocKind::FUNCTION>, + CreateBigIntPrototype, + BigIntObject::staticMethods, + nullptr, + BigIntObject::methods, + BigIntObject::properties +}; + +const Class BigIntObject::class_ = { + "BigInt", + JSCLASS_HAS_CACHED_PROTO(JSProto_BigInt) | + JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS), + JS_NULL_CLASS_OPS, + &BigIntObject::classSpec_ +}; + +const JSPropertySpec BigIntObject::properties[] = { + // BigInt proposal section 5.3.5 + JS_STRING_SYM_PS(toStringTag, "BigInt", JSPROP_READONLY), + JS_PS_END +}; + +const JSFunctionSpec BigIntObject::methods[] = { + JS_FN("valueOf", valueOf, 0, 0), + JS_FN("toString", toString, 0, 0), + JS_FN("toLocaleString", toLocaleString, 0, 0), + JS_FS_END +}; + +const JSFunctionSpec BigIntObject::staticMethods[] = { + JS_FN("asUintN", asUintN, 2, 0), + JS_FN("asIntN", asIntN, 2, 0), + JS_FS_END +}; diff --git a/js/src/builtin/BigInt.h b/js/src/builtin/BigInt.h new file mode 100644 index 0000000000..6971549fc3 --- /dev/null +++ b/js/src/builtin/BigInt.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef builtin_BigInt_h +#define builtin_BigInt_h + +#include "js/Class.h" +#include "js/RootingAPI.h" +#include "vm/BigIntType.h" +#include "vm/NativeObject.h" + +namespace js { + +class GlobalObject; + +class BigIntObject : public NativeObject +{ + static const unsigned PRIMITIVE_VALUE_SLOT = 0; + static const unsigned RESERVED_SLOTS = 1; + + public: + static const ClassSpec classSpec_; + static const Class class_; + + static JSObject* create(JSContext* cx, JS::Handle<JS::BigInt*> bi); + + // Methods defined on BigInt.prototype. + static bool valueOf_impl(JSContext* cx, const CallArgs& args); + static bool valueOf(JSContext* cx, unsigned argc, JS::Value* vp); + static bool toString_impl(JSContext* cx, const CallArgs& args); + static bool toString(JSContext* cx, unsigned argc, JS::Value* vp); + static bool toLocaleString_impl(JSContext* cx, const CallArgs& args); + static bool toLocaleString(JSContext* cx, unsigned argc, JS::Value* vp); + static bool asUintN(JSContext* cx, unsigned argc, JS::Value* vp); + static bool asIntN(JSContext* cx, unsigned argc, JS::Value* vp); + + JS::BigInt* unbox() const; + + private: + static const JSPropertySpec properties[]; + static const JSFunctionSpec methods[]; + static const JSFunctionSpec staticMethods[]; +}; + +extern JSObject* +InitBigIntClass(JSContext* cx, Handle<GlobalObject*> global); + +} // namespace js + +#endif diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index 893e0448a4..fe748a6bde 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -61,7 +61,7 @@ HashableValue::setValue(JSContext* cx, HandleValue v) } MOZ_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() || value.isNumber() || - value.isString() || value.isSymbol() || value.isObject()); + value.isString() || value.isSymbol() || value.isObject() || value.isBigInt()); return true; } @@ -81,6 +81,8 @@ HashValue(const Value& v, const mozilla::HashCodeScrambler& hcs) return v.toString()->asAtom().hash(); if (v.isSymbol()) return v.toSymbol()->hash(); + if (v.isBigInt()) + return MaybeForwarded(v.toBigInt())->hash(); if (v.isObject()) return hcs.scramble(v.asRawBits()); @@ -100,6 +102,12 @@ HashableValue::operator==(const HashableValue& other) const // Two HashableValues are equal if they have equal bits. bool b = (value.asRawBits() == other.value.asRawBits()); + // BigInt values are considered equal if they represent the same + // mathematical value. + if (!b && (value.isBigInt() && other.value.isBigInt())) { + b = BigInt::equal(value.toBigInt(), other.value.toBigInt()); + } + #ifdef DEBUG bool same; JS::RootingContext* rcx = GetJSContextFromMainThread(); @@ -378,8 +386,9 @@ MarkKey(Range& r, const HashableValue& key, JSTracer* trc) HashableValue newKey = key.mark(trc); if (newKey.get() != key.get()) { - // The hash function only uses the bits of the Value, so it is safe to - // rekey even when the object or string has been modified by the GC. + // The hash function must take account of the fact that the thing being + // hashed may have been moved by GC. This is only an issue for BigInt as for + // other types the hash function only uses the bits of the Value. r.rekeyFront(newKey); } } diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 5221afb617..388af28f26 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -7,6 +7,8 @@ #include "mozilla/ArrayUtils.h" +#include "builtin/BigInt.h" + #include "jscntxt.h" #include "jsstr.h" @@ -471,6 +473,9 @@ js::obj_toString(JSContext* cx, unsigned argc, Value* vp) case ESClass::RegExp: builtinTag = cx->names().objectRegExp; break; + case ESClass::BigInt: + builtinTag = cx->names().objectBigInt; + break; default: if (obj->isCallable()) { // Non-standard: Prevent <object> from showing up as Function. diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index f0b2001422..a8dd93aab2 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -21,6 +21,7 @@ #include "frontend/TokenStream.h" #include "js/CharacterEncoding.h" #include "vm/RegExpObject.h" +#include "vm/BigIntType.h" #include "jsobjinlines.h" @@ -3434,6 +3435,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_STRING: case PNK_REGEXP: case PNK_NUMBER: + case PNK_BIGINT: case PNK_TRUE: case PNK_FALSE: case PNK_NULL: @@ -3604,7 +3606,7 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst) case PNK_REGEXP: { - RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object); + RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object()); LOCAL_ASSERT(re1 && re1->is<RegExpObject>()); RootedObject re2(cx, CloneRegExpObject(cx, re1)); @@ -3619,6 +3621,13 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst) val.setNumber(pn->as<NumericLiteral>().value()); break; + case PNK_BIGINT: + { + BigInt* x = pn->as<BigIntLiteral>().box()->value(); + val.setBigInt(x); + break; + } + case PNK_NULL: val.setNull(); break; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index e5a47c496a..cb249c254c 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1093,6 +1093,11 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) *answer = false; return true; + case PNK_BIGINT: + MOZ_ASSERT(pn->is<BigIntLiteral>()); + *answer = false; + return true; + // |this| can throw in derived class constructors, including nested arrow // functions or eval. case PNK_THIS: @@ -4225,6 +4230,9 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje case PNK_NUMBER: vp.setNumber(as<NumericLiteral>().value()); return true; + case PNK_BIGINT: + vp.setBigInt(as<BigIntLiteral>().box()->value()); + return true; case PNK_TEMPLATE_STRING: case PNK_STRING: vp.setString(as<NameNode>().atom()); @@ -4835,6 +4843,15 @@ BytecodeEmitter::emitCopyDataProperties(CopyOption option) } bool +BytecodeEmitter::emitBigIntOp(BigInt* bigint) +{ + if (!constList.append(BigIntValue(bigint))) { + return false; + } + return emitIndex32(JSOP_BIGINT, constList.length() - 1); +} + +bool BytecodeEmitter::emitIterator() { // Convert iterable to iterator. @@ -9579,6 +9596,12 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: return false; break; + case PNK_BIGINT: + if (!emitBigIntOp(pn->as<BigIntLiteral>().box()->value())) { + return false; + } + break; + case PNK_REGEXP: if (!emitRegExp(objectList.add(pn->as<RegExpLiteral>().objbox()))) return false; @@ -10317,7 +10340,7 @@ CGConstList::finish(ConstArray* array) MOZ_ASSERT(length() == array->length); for (unsigned i = 0; i < length(); i++) - array->vector[i] = list[i]; + array->vector[i] = vector[i]; } /* @@ -10331,6 +10354,7 @@ CGConstList::finish(ConstArray* array) unsigned CGObjectList::add(ObjectBox* objbox) { + MOZ_ASSERT(objbox->isObjectBox()); MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; @@ -10342,7 +10366,7 @@ CGObjectList::indexOf(JSObject* obj) { MOZ_ASSERT(length > 0); unsigned index = length - 1; - for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink) + for (ObjectBox* box = lastbox; box->object() != obj; box = box->emitLink) index--; return index; } @@ -10358,8 +10382,8 @@ CGObjectList::finish(ObjectArray* array) do { --cursor; MOZ_ASSERT(!*cursor); - MOZ_ASSERT(objbox->object->isTenured()); - *cursor = objbox->object; + MOZ_ASSERT(objbox->object()->isTenured()); + *cursor = objbox->object(); } while ((objbox = objbox->emitLink) != nullptr); MOZ_ASSERT(cursor == array->vector); } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 732a1f24f8..9ca4856cf5 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -35,20 +35,21 @@ class SharedContext; class TokenStream; class CGConstList { - Vector<Value> list; + Rooted<ValueVector> vector; public: - explicit CGConstList(ExclusiveContext* cx) : list(cx) {} + explicit CGConstList(ExclusiveContext* cx) + : vector(cx, ValueVector(cx)) + { } MOZ_MUST_USE bool append(const Value& v) { - MOZ_ASSERT_IF(v.isString(), v.toString()->isAtom()); - return list.append(v); + return vector.append(v); } - size_t length() const { return list.length(); } + size_t length() const { return vector.length(); } void finish(ConstArray* array); }; struct CGObjectList { uint32_t length; /* number of emitted so far objects */ - ObjectBox* lastbox; /* last emitted object */ + ObjectBox* lastbox; /* last emitted object */ CGObjectList() : length(0), lastbox(nullptr) {} @@ -198,7 +199,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter return innermostEmitterScope_; } - CGConstList constList; /* constants to be included with the script */ + CGConstList constList; /* double and bigint values used by script */ CGObjectList objectList; /* list of emitted objects */ CGScopeList scopeList; /* list of emitted scopes */ CGTryNoteList tryNoteList; /* list of emitted try notes */ @@ -478,6 +479,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitNumberOp(double dval); + MOZ_MUST_USE bool emitBigIntOp(BigInt* bigint); + MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn); MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn); MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 9fb963f63e..b457c43942 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -395,6 +395,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_THIS: case PNK_ELISION: case PNK_NUMBER: + case PNK_BIGINT: case PNK_NEW: case PNK_GENERATOR: case PNK_GENEXP: @@ -485,6 +486,7 @@ IsEffectless(ParseNode* node) node->isKind(PNK_FALSE) || node->isKind(PNK_STRING) || node->isKind(PNK_TEMPLATE_STRING) || + node->isKind(PNK_BIGINT) || node->isKind(PNK_NUMBER) || node->isKind(PNK_NULL) || node->isKind(PNK_RAW_UNDEFINED) || @@ -503,6 +505,9 @@ Boolish(ParseNode* pn, bool isNullish = false) return (isNullish || isNonZeroNumber) ? Truthy : Falsy; } + case PNK_BIGINT: + return (pn->as<BigIntLiteral>().box()->value()->isZero()) ? Falsy : Truthy; + case PNK_STRING: case PNK_TEMPLATE_STRING: { bool isNonZeroLengthString = (pn->as<NameNode>().atom()->length() > 0); @@ -591,6 +596,8 @@ FoldTypeOfExpr(ExclusiveContext* cx, UnaryNode* node, Parser<FullParseHandler>& result = cx->names().string; else if (expr->isKind(PNK_NUMBER)) result = cx->names().number; + else if (expr->isKind(PNK_BIGINT)) + result = cx->names().bigint; else if (expr->isKind(PNK_NULL)) result = cx->names().object; else if (expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) @@ -1699,6 +1706,10 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo MOZ_ASSERT(pn->is<NumericLiteral>()); return true; + case PNK_BIGINT: + MOZ_ASSERT(pn->is<BigIntLiteral>()); + return true; + case PNK_SUPERBASE: case PNK_TYPEOFNAME: { #ifdef DEBUG diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 14733d74f2..e27b9f540a 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -151,6 +151,18 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return new_<NumericLiteral>(value, decimalPoint, pos); } + // The Boxer object here is any object that can allocate BigIntBoxes. + // Specifically, a Boxer has a .newBigIntBox(T) method that accepts a + // BigInt* argument and returns a BigIntBox*. + template <class Boxer> + BigIntLiteralType newBigInt(BigInt* bi, const TokenPos& pos, Boxer& boxer) { + BigIntBox* box = boxer.newBigIntBox(bi); + if (!box) { + return null(); + } + return new_<BigIntLiteral>(box, pos); + } + BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return new_<BooleanLiteral>(cond, pos); } diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index 6ddb554ad7..3fbdd3fc8a 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -420,6 +420,9 @@ class NameResolver MOZ_ASSERT(cur->is<NumericLiteral>()); break; + case PNK_BIGINT: + MOZ_ASSERT(cur->is<BigIntLiteral>()); + break; case PNK_TYPEOFNAME: case PNK_SUPERBASE: diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 7ad470865f..065efa8380 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -218,6 +218,10 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) MOZ_ASSERT(pn->is<NumericLiteral>()); return PushResult::Recyclable; + case PNK_BIGINT: + MOZ_ASSERT(pn->is<BigIntLiteral>()); + return PushResult::Recyclable; + // Nodes with a single non-null child. case PNK_TYPEOFNAME: case PNK_TYPEOFEXPR: @@ -716,6 +720,9 @@ ParseNode::dump(int indent) case PN_NUMBER: as<NumericLiteral>().dump(indent); return; + case PN_BIGINT: + as<BigIntLiteral>().dump(indent); + return; case PN_REGEXP: as<RegExpLiteral>().dump(indent); return; @@ -760,6 +767,12 @@ NumericLiteral::dump(int indent) } void +BigIntLiteral::dump(int indent) +{ + fprintf(stderr, "(%s)", parseNodeNames[size_t(getKind())]); +} + +void RegExpLiteral::dump(int indent) { fprintf(stderr, "(%s)", parseNodeNames[size_t(getKind())]); @@ -962,23 +975,45 @@ LexicalScopeNode::dump(int indent) } #endif -ObjectBox::ObjectBox(JSObject* object, ObjectBox* traceLink) - : object(object), - traceLink(traceLink), +TraceListNode::TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink) + : gcThing(gcThing), + traceLink(traceLink) +{ + MOZ_ASSERT(gcThing->isTenured()); +} + +BigIntBox* +TraceListNode::asBigIntBox() +{ + MOZ_ASSERT(isBigIntBox()); + return static_cast<BigIntBox*>(this); +} + +ObjectBox* +TraceListNode::asObjectBox() +{ + MOZ_ASSERT(isObjectBox()); + return static_cast<ObjectBox*>(this); +} + +BigIntBox::BigIntBox(BigInt* bi, TraceListNode* traceLink) + : TraceListNode(bi, traceLink) +{ +} + +ObjectBox::ObjectBox(JSObject* obj, TraceListNode* traceLink) + : TraceListNode(obj, traceLink), emitLink(nullptr) { - MOZ_ASSERT(!object->is<JSFunction>()); - MOZ_ASSERT(object->isTenured()); + MOZ_ASSERT(!object()->is<JSFunction>()); } -ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink) - : object(function), - traceLink(traceLink), +ObjectBox::ObjectBox(JSFunction* function, TraceListNode* traceLink) + : TraceListNode(function, traceLink), emitLink(nullptr) { - MOZ_ASSERT(object->is<JSFunction>()); + MOZ_ASSERT(object()->is<JSFunction>()); MOZ_ASSERT(asFunctionBox()->function() == function); - MOZ_ASSERT(object->isTenured()); } FunctionBox* @@ -989,16 +1024,17 @@ ObjectBox::asFunctionBox() } /* static */ void -ObjectBox::TraceList(JSTracer* trc, ObjectBox* listHead) +TraceListNode::TraceList(JSTracer* trc, TraceListNode* listHead) { - for (ObjectBox* box = listHead; box; box = box->traceLink) - box->trace(trc); + for (TraceListNode* node = listHead; node; node = node->traceLink) { + node->trace(trc); + } } void -ObjectBox::trace(JSTracer* trc) +TraceListNode::trace(JSTracer* trc) { - TraceRoot(trc, &object, "parser.object"); + TraceGenericPointerRoot(trc, &gcThing, "parser.traceListNode"); } void diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 46977ee253..b1fcab0735 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -12,6 +12,7 @@ #include "builtin/ModuleObject.h" #include "frontend/TokenStream.h" +#include "vm/BigIntType.h" namespace js { namespace frontend { @@ -20,6 +21,7 @@ class ParseContext; class FullParseHandler; class FunctionBox; class ObjectBox; +class BigIntBox; #define FOR_EACH_PARSE_NODE_KIND(F) \ F(NOP) \ @@ -53,6 +55,7 @@ class ObjectBox; F(OBJECT_PROPERTY_NAME) \ F(COMPUTED_NAME) \ F(NUMBER) \ + F(BIGINT) \ F(STRING) \ F(TEMPLATE_STRING_LIST) \ F(TEMPLATE_STRING) \ @@ -524,6 +527,8 @@ IsTypeofKind(ParseNodeKind kind) * regexp: RegExp model object * PNK_NUMBER (NumericLiteral) * value: double value of numeric literal + * PNK_BIGINT (BigIntLiteral) + * box: BigIntBox holding BigInt* value * PNK_TRUE, PNK_FALSE (BooleanLiteral) * pn_op: JSOp bytecode * PNK_NULL (NullLiteral) @@ -571,6 +576,7 @@ enum ParseNodeArity PN_LIST, /* generic singly linked list */ PN_NAME, /* name, label, string */ PN_NUMBER, /* numeric literal */ + PN_BIGINT, /* BigInt literal */ PN_REGEXP, /* regexp literal */ PN_LOOP, /* loop control (break/continue) */ PN_SCOPE /* lexical scope */ @@ -613,6 +619,7 @@ enum ParseNodeArity macro(RawUndefinedLiteral, RawUndefinedLiteralType, asRawUndefinedLiteral) \ \ macro(NumericLiteral, NumericLiteralType, asNumericLiteral) \ + macro(BigIntLiteral, BigIntLiteralType, asBigIntLiteral) \ \ macro(RegExpLiteral, RegExpLiteralType, asRegExpLiteral) \ \ @@ -828,6 +835,11 @@ class ParseNode double value; /* aligned numeric literal value */ DecimalPoint decimalPoint; /* Whether the number has a decimal point */ } number; + struct { + private: + friend class BigIntLiteral; + BigIntBox* box; + } bigint; class { private: friend class LoopControlStatement; @@ -849,6 +861,7 @@ class ParseNode /* True if pn is a parsenode representing a literal constant. */ bool isLiteral() const { return isKind(PNK_NUMBER) || + isKind(PNK_BIGINT) || isKind(PNK_STRING) || isKind(PNK_TRUE) || isKind(PNK_FALSE) || @@ -1631,6 +1644,30 @@ class NumericLiteral : public ParseNode } }; +class BigIntLiteral : public ParseNode +{ + public: + BigIntLiteral(BigIntBox* bibox, const TokenPos& pos) + : ParseNode(PNK_BIGINT, JSOP_NOP, PN_BIGINT, pos) + { + pn_u.bigint.box = bibox; + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_BIGINT); + MOZ_ASSERT_IF(match, node.isArity(PN_BIGINT)); + return match; + } + +#ifdef DEBUG + void dump(int indent); +#endif + + BigIntBox* box() const { + return pn_u.bigint.box; + } +}; + class LexicalScopeNode : public ParseNode { public: @@ -2350,25 +2387,48 @@ ParseNode::isConstant() } } -class ObjectBox +class TraceListNode { - public: - JSObject* object; + protected: + js::gc::Cell* gcThing; + TraceListNode* traceLink; + + TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink); + + bool isBigIntBox() const { return gcThing->is<BigInt>(); } + bool isObjectBox() const { return gcThing->is<JSObject>(); } + + BigIntBox* asBigIntBox(); + ObjectBox* asObjectBox(); - ObjectBox(JSObject* object, ObjectBox* traceLink); - bool isFunctionBox() { return object->is<JSFunction>(); } - FunctionBox* asFunctionBox(); virtual void trace(JSTracer* trc); - static void TraceList(JSTracer* trc, ObjectBox* listHead); + public: + static void TraceList(JSTracer* trc, TraceListNode* listHead); +}; + +class BigIntBox : public TraceListNode +{ + public: + BigIntBox(BigInt* bi, TraceListNode* link); + BigInt* value() const { return gcThing->as<BigInt>(); } +}; +class ObjectBox : public TraceListNode +{ protected: friend struct CGObjectList; - - ObjectBox* traceLink; ObjectBox* emitLink; + + ObjectBox(JSFunction* function, TraceListNode* link); - ObjectBox(JSFunction* function, ObjectBox* traceLink); + public: + ObjectBox(JSObject* obj, TraceListNode* link); + + JSObject* object() const { return gcThing->as<JSObject>(); } + + bool isFunctionBox() const { return object()->is<JSFunction>(); } + FunctionBox* asFunctionBox(); }; enum ParseReportKind diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index a1b669bf91..9da893f94f 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -438,7 +438,7 @@ UsedNameTracker::rewind(RewindToken token) r.front().value().resetToScope(token.scriptId, token.scopeId); } -FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, +FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, TraceListNode* traceListHead, JSFunction* fun, uint32_t toStringStart, Directives directives, bool extraWarnings, GeneratorKind generatorKind, FunctionAsyncKind asyncKind) @@ -882,11 +882,11 @@ Parser<FullParseHandler>::setAwaitHandling(AwaitHandling awaitHandling) parser->setAwaitHandling(awaitHandling); } -template <typename ParseHandler> -ObjectBox* -Parser<ParseHandler>::newObjectBox(JSObject* obj) +template <typename BoxT, typename ArgT> +BoxT* +ParserBase::newTraceListNode(ArgT* arg) { - MOZ_ASSERT(obj); + MOZ_ASSERT(arg); /* * We use JSContext.tempLifoAlloc to allocate parsed objects and place them @@ -896,15 +896,27 @@ Parser<ParseHandler>::newObjectBox(JSObject* obj) * function. */ - ObjectBox* objbox = alloc.new_<ObjectBox>(obj, traceListHead); - if (!objbox) { + BoxT* box = alloc.template new_<BoxT>(arg, traceListHead); + if (!box) { ReportOutOfMemory(context); return nullptr; } - traceListHead = objbox; + traceListHead = box; + + return box; +} + +ObjectBox* +ParserBase::newObjectBox(JSObject* obj) +{ + return newTraceListNode<ObjectBox, JSObject>(obj); +} - return objbox; +BigIntBox* +ParserBase::newBigIntBox(BigInt* val) +{ + return newTraceListNode<BigIntBox, BigInt>(val); } template <typename ParseHandler> @@ -10579,6 +10591,37 @@ Parser<ParseHandler>::newRegExp() return handler.newRegExp(reobj, pos(), *this); } +template <> +BigIntLiteral* +Parser<FullParseHandler>::newBigInt() +{ + // The token's charBuffer contains the DecimalIntegerLiteral or + // NumericLiteralBase production, and as such does not include the + // BigIntLiteralSuffix (the trailing "n"). Note that NumericLiteralBase + // productions may start with 0[bBoOxX], indicating binary/octal/hex. + const auto& chars = tokenStream.getTokenbuf(); + mozilla::Range<const char16_t> source(chars.begin(), chars.length()); + + BigInt* b = js::ParseBigIntLiteral(context, source); + if (!b) { + return null(); + } + + // newBigInt immediately puts "b" in a BigIntBox, which is allocated using + // tempLifoAlloc, avoiding any potential GC. Therefore it's OK to pass a + // raw pointer. + return handler.newBigInt(b, pos(), *this); +} + +template <> +SyntaxParseHandler::BigIntLiteralType +Parser<SyntaxParseHandler>::newBigInt() +{ + // The tokenizer has already checked the syntax of the bigint. + + return handler.newBigInt(); +} + template <typename ParseHandler> void Parser<ParseHandler>::checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos, @@ -11474,6 +11517,9 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling case TOK_NUMBER: return newNumber(tokenStream.currentToken()); + case TOK_BIGINT: + return newBigInt(); + case TOK_TRUE: return handler.newBooleanLiteral(true, pos()); case TOK_FALSE: diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index fd1bd034c4..4a8e038d6e 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -779,8 +779,8 @@ class ParserBase : public StrictModeGetter TokenStream tokenStream; LifoAlloc::Mark tempPoolMark; - /* list of parsed objects for GC tracing */ - ObjectBox* traceListHead; + /* list of parsed objects and BigInts for GC tracing */ + TraceListNode* traceListHead; /* innermost parse context (stack-allocated) */ ParseContext* pc; @@ -915,6 +915,13 @@ class ParserBase : public StrictModeGetter bool warnOnceAboutExprClosure(); bool warnOnceAboutForEach(); + ObjectBox* newObjectBox(JSObject* obj); + BigIntBox* newBigIntBox(BigInt* val); + +private: + template <typename BoxT, typename ArgT> + BoxT* newTraceListNode(ArgT* arg); + protected: enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true }; enum ForInitLocation { InForInit, NotInForInit }; @@ -1085,7 +1092,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) { friend class Parser; LifoAlloc::Mark mark; - ObjectBox* traceListHead; + TraceListNode* traceListHead; }; Mark mark() const { Mark m; @@ -1174,7 +1181,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) * Allocate a new parsed object or function container from * cx->tempLifoAlloc. */ - ObjectBox* newObjectBox(JSObject* obj); + public: FunctionBox* newFunctionBox(FunctionNodeType funNode, JSFunction* fun, uint32_t toStringStart, Directives directives, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, @@ -1660,6 +1667,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) const mozilla::Maybe<DeclarationKind>& maybeDecl, ListNodeType literal); ListNodeType arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError); RegExpLiteralType newRegExp(); + BigIntLiteralType newBigInt(); ListNodeType objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index a241907482..bc0212054b 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -443,7 +443,7 @@ class FunctionBox : public ObjectBox, public SharedContext FunctionContextFlags funCxFlags; - FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, JSFunction* fun, + FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, TraceListNode* traceListHead, JSFunction* fun, uint32_t toStringStart, Directives directives, bool extraWarnings, GeneratorKind generatorKind, FunctionAsyncKind asyncKind); @@ -467,7 +467,8 @@ class FunctionBox : public ObjectBox, public SharedContext void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); ObjectBox* toObjectBox() override { return this; } - JSFunction* function() const { return &object->as<JSFunction>(); } + JSFunction* function() const { return &object()->as<JSFunction>(); } + void clobberFunction(JSFunction* function) { gcThing = function; } Scope* compilationEnclosingScope() const override { // This method is used to distinguish the outermost SharedContext. If diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 130a5da61d..818530cfe9 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -224,6 +224,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) } NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { return NodeGeneric; } + BigIntLiteralType newBigInt() { return NodeGeneric; } BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return NodeGeneric; } NameNodeType newStringLiteral(JSAtom* atom, const TokenPos& pos) { diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index 232e373dcf..3e1ff68bbd 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -74,6 +74,7 @@ macro(PRIVATE_NAME, "private identifier") \ macro(NUMBER, "numeric literal") \ macro(STRING, "string literal") \ + macro(BIGINT, "bigint literal") \ \ /* start of template literal with substitutions */ \ macro(TEMPLATE_HEAD, "'${'") \ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index a201e42a52..b11c7df584 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1382,6 +1382,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) const char16_t* identStart; NameVisibility identVisibility; bool hadUnicodeEscape; + bool isBigInt = false; // Check if in the middle of a template string. Have to get this out of // the way first. @@ -1619,6 +1620,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } } while (true); } + if (c == 'n') { + isBigInt = true; + c = getCharIgnoreEOL(); + } ungetCharIgnoreEOL(c); if (c != EOF) { @@ -1638,6 +1643,19 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } } + if (isBigInt) { + size_t length = userbuf.addressOfNextRawChar() - numStart - 1; + tokenbuf.clear(); + if(!tokenbuf.reserve(length > 0 ? length : 1)) + goto error; + if(length > 0) + tokenbuf.infallibleAppend(numStart, length); + else + tokenbuf.infallibleAppend("0", 1); + tp->type = TOK_BIGINT; + goto out; + } + // Unlike identifiers and strings, numbers cannot contain escaped // chars, so we don't need to use tokenbuf. Instead we can just // convert the char16_t characters in userbuf to the numeric value. @@ -1677,7 +1695,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) // if (c1kind == BasePrefix) { tp = newToken(-1); - int radix; + int radix = 10; c = getCharIgnoreEOL(); if (c == 'x' || c == 'X') { radix = 16; @@ -1777,6 +1795,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) hasExp = false; goto decimal_rest; } + if (c == 'n') { + isBigInt = true; + c = getCharIgnoreEOL(); + } ungetCharIgnoreEOL(c); if (c != EOF) { @@ -1796,6 +1818,28 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } } + if (isBigInt) { + size_t length = userbuf.addressOfNextRawChar() - numStart - 1; + tokenbuf.clear(); + if(!tokenbuf.reserve(radix == 10 ? length : (length + 2))) + goto error; + switch(radix) + { + case 2: + tokenbuf.infallibleAppend("0b", 2); + break; + case 8: + tokenbuf.infallibleAppend("0o", 2); + break; + case 16: + tokenbuf.infallibleAppend("0x", 2); + break; + } + tokenbuf.infallibleAppend(numStart, length); + tp->type = TOK_BIGINT; + goto out; + } + double dval; const char16_t* dummy; if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix, diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 5c51540351..c0e980a7c1 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -14,6 +14,7 @@ #include "gc/Zone.h" #include "vm/HelperThreads.h" #include "vm/Runtime.h" +#include "vm/BigIntType.h" namespace js { namespace gc { @@ -71,6 +72,7 @@ struct MovingTracer : JS::CallbackTracer void onObjectEdge(JSObject** objp) override; void onShapeEdge(Shape** shapep) override; void onStringEdge(JSString** stringp) override; + void onBigIntEdge(JS::BigInt** bip) override; void onScriptEdge(JSScript** scriptp) override; void onLazyScriptEdge(LazyScript** lazyp) override; void onBaseShapeEdge(BaseShape** basep) override; diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 371e4119f9..3e14ed906f 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -111,6 +111,7 @@ enum class AllocKind : uint8_t { FAT_INLINE_ATOM, ATOM, SYMBOL, + BIGINT, JITCODE, SCOPE, REGEXP_SHARED, @@ -151,6 +152,7 @@ enum class AllocKind : uint8_t { D(FAT_INLINE_ATOM, String, js::FatInlineAtom, js::FatInlineAtom) \ D(ATOM, String, js::NormalAtom, js::NormalAtom) \ D(SYMBOL, Symbol, JS::Symbol, JS::Symbol) \ + D(BIGINT, BigInt, JS::BigInt, JS::BigInt) \ D(JITCODE, JitCode, js::jit::JitCode, js::jit::JitCode) \ D(SCOPE, Scope, js::Scope, js::Scope) \ D(REGEXP_SHARED, RegExpShared, js::RegExpShared, js::RegExpShared) diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 42c872a1da..13ec5b0c05 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -21,6 +21,7 @@ #include "js/SliceBudget.h" #include "vm/ArgumentsObject.h" #include "vm/ArrayObject.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/EnvironmentObject.h" #include "vm/Scope.h" @@ -875,6 +876,7 @@ js::GCMarker::markAndTraceChildren(T* thing) namespace js { template <> void GCMarker::traverse(BaseShape* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(JS::Symbol* thing) { markAndTraceChildren(thing); } +template <> void GCMarker::traverse(JS::BigInt* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(RegExpShared* thing) { markAndTraceChildren(thing); } } // namespace js @@ -1458,6 +1460,12 @@ js::GCMarker::lazilyMarkChildren(ObjectGroup* group) traverseEdge(group, static_cast<JSObject*>(fun)); } +void +JS::BigInt::traceChildren(JSTracer* trc) +{ + return; +} + struct TraverseObjectFunctor { template <typename T> @@ -1704,6 +1712,8 @@ GCMarker::processMarkStackTop(SliceBudget& budget) } } else if (v.isSymbol()) { traverseEdge(obj, v.toSymbol()); + } else if (v.isBigInt()) { + traverseEdge(obj, v.toBigInt()); } else if (v.isPrivateGCThing()) { traverseEdge(obj, v.toGCCellPtr()); } diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 40b331b311..414079f799 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -425,6 +425,7 @@ struct RewrapTaggedPointer{}; DECLARE_REWRAP(JS::Value, JSObject, JS::ObjectOrNullValue, ); DECLARE_REWRAP(JS::Value, JSString, JS::StringValue, ); DECLARE_REWRAP(JS::Value, JS::Symbol, JS::SymbolValue, ); +DECLARE_REWRAP(JS::Value, JS::BigInt, JS::BigIntValue, ); DECLARE_REWRAP(jsid, JSString, NON_INTEGER_ATOM_TO_JSID, (JSAtom*)); DECLARE_REWRAP(jsid, JS::Symbol, SYMBOL_TO_JSID, ); DECLARE_REWRAP(js::TaggedProto, JSObject, js::TaggedProto, ); @@ -435,7 +436,8 @@ struct IsPrivateGCThingInValue : public mozilla::EnableIf<mozilla::IsBaseOf<Cell, T>::value && !mozilla::IsBaseOf<JSObject, T>::value && !mozilla::IsBaseOf<JSString, T>::value && - !mozilla::IsBaseOf<JS::Symbol, T>::value, T> + !mozilla::IsBaseOf<JS::Symbol, T>::value && + !mozilla::IsBaseOf<JS::BigInt, T>::value, T> { static_assert(!mozilla::IsSame<Cell, T>::value && !mozilla::IsSame<TenuredCell, T>::value, "T must not be Cell or TenuredCell"); diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index 26a9e5ff78..ea7f613e38 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -20,6 +20,7 @@ #include "gc/Marking.h" #include "gc/Zone.h" +#include "vm/BigIntType.h" #include "vm/Shape.h" #include "vm/Symbol.h" @@ -319,6 +320,10 @@ JS_GetTraceThingInfo(char* buf, size_t bufsize, JSTracer* trc, void* thing, name = "symbol"; break; + case JS::TraceKind::BigInt: + name = "BigInt"; + break; + case JS::TraceKind::BaseShape: name = "base_shape"; break; diff --git a/js/src/gdb/mozilla/jsval.py b/js/src/gdb/mozilla/jsval.py index f5ae78b1c2..d6f5e01fdf 100644 --- a/js/src/gdb/mozilla/jsval.py +++ b/js/src/gdb/mozilla/jsval.py @@ -165,6 +165,15 @@ class jsvalTypeCache(object): self.NULL = d['JSVAL_TYPE_NULL'] self.OBJECT = d['JSVAL_TYPE_OBJECT'] + self.enable_bigint = False + try: + # Looking up the tag will throw an exception if BigInt is not + # enabled. + self.BIGINT = get('JSVAL_TYPE_BIGINT') + self.enable_bigint = True + except: + pass + # Let self.magic_names be an array whose i'th element is the name of # the i'th magic value. d = gdb.types.make_enum_dict(gdb.lookup_type('JSWhyMagic')) @@ -206,6 +215,8 @@ class jsval_layout(object): value = self.box.as_address().cast(self.cache.JSString_ptr_t) elif tag == self.jtc.SYMBOL: value = self.box.as_address().cast(self.cache.JSSymbol_ptr_t) + elif self.jtc.enable_bigint and tag == self.jtc.BIGINT: + return '$JS::BigIntValue()' elif tag == self.jtc.NULL: return 'JSVAL_NULL' elif tag == self.jtc.OBJECT: diff --git a/js/src/gdb/tests/test-jsval.cpp b/js/src/gdb/tests/test-jsval.cpp index f3c3247e2c..2007464cbe 100644 --- a/js/src/gdb/tests/test-jsval.cpp +++ b/js/src/gdb/tests/test-jsval.cpp @@ -1,6 +1,8 @@ #include "gdb-tests.h" #include "jsapi.h" +#include "vm/BigIntType.h" + FRAGMENT(jsval, simple) { using namespace JS; @@ -17,6 +19,7 @@ FRAGMENT(jsval, simple) { RootedString hello(cx, JS_NewStringCopyZ(cx, "Hello!")); RootedValue friendly_string(cx, StringValue(hello)); RootedValue symbol(cx, SymbolValue(GetSymbolFor(cx, hello))); + RootedValue bi(cx, BigIntValue(BigInt::zero(cx))); RootedValue global(cx); global.setObject(*CurrentGlobalOrNull(cx)); @@ -36,5 +39,6 @@ FRAGMENT(jsval, simple) { (void) empty_string; (void) friendly_string; (void) symbol; + (void) bi; (void) global; } diff --git a/js/src/gdb/tests/test-jsval.py b/js/src/gdb/tests/test-jsval.py index f39a6591fd..38edb28197 100644 --- a/js/src/gdb/tests/test-jsval.py +++ b/js/src/gdb/tests/test-jsval.py @@ -14,5 +14,7 @@ assert_pretty('elements_hole', '$jsmagic(JS_ELEMENTS_HOLE)') assert_pretty('empty_string', '$jsval("")') assert_pretty('friendly_string', '$jsval("Hello!")') assert_pretty('symbol', '$jsval(Symbol.for("Hello!"))') +if enable_bigint: + assert_pretty('bi', '$JS::BigIntValue()') assert_pretty('global', '$jsval((JSObject *) [object global] delegate)') assert_pretty('onehundredthirtysevenonehundredtwentyeighths', '$jsval(1.0703125)') diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 30c83a5042..ce27e4de19 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -1958,6 +1958,7 @@ jit::FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo) case Bailout_NonObjectInput: case Bailout_NonStringInput: case Bailout_NonSymbolInput: + case Bailout_NonBigIntInput: case Bailout_UnexpectedSimdInput: case Bailout_NonSharedTypedArrayInput: case Bailout_Debugger: diff --git a/js/src/jit/BaselineCacheIR.cpp b/js/src/jit/BaselineCacheIR.cpp index 9bea352ae7..5317f0e4e5 100644 --- a/js/src/jit/BaselineCacheIR.cpp +++ b/js/src/jit/BaselineCacheIR.cpp @@ -710,6 +710,9 @@ BaselineCacheIRCompiler::emitGuardType() case JSVAL_TYPE_SYMBOL: masm.branchTestSymbol(Assembler::NotEqual, input, failure->label()); break; + case JSVAL_TYPE_BIGINT: + masm.branchTestBigInt(Assembler::NotEqual, input, failure->label()); + break; case JSVAL_TYPE_DOUBLE: masm.branchTestNumber(Assembler::NotEqual, input, failure->label()); break; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 74cfb82e43..7dbd076c6b 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1621,6 +1621,13 @@ BaselineCompiler::emit_JSOP_DOUBLE() } bool +BaselineCompiler::emit_JSOP_BIGINT() +{ + frame.push(script->getConst(GET_UINT32_INDEX(pc))); + return true; +} + +bool BaselineCompiler::emit_JSOP_STRING() { frame.push(StringValue(script->getAtom(pc))); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 24a97f20b6..8e9976b17f 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -65,6 +65,7 @@ namespace jit { _(JSOP_UINT16) \ _(JSOP_UINT24) \ _(JSOP_DOUBLE) \ + _(JSOP_BIGINT) \ _(JSOP_STRING) \ _(JSOP_SYMBOL) \ _(JSOP_OBJECT) \ diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 61d77adc2e..2827f4b1d5 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -8093,6 +8093,10 @@ ICTypeOf_Typed::Compiler::generateStubCode(MacroAssembler& masm) masm.branchTestSymbol(Assembler::NotEqual, R0, &failure); break; + case JSTYPE_BIGINT: + masm.branchTestBigInt(Assembler::NotEqual, R0, &failure); + break; + default: MOZ_CRASH("Unexpected type"); } diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index 9168a344e5..4da9b7539f 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -446,6 +446,9 @@ GetPropIRGenerator::tryAttachPrimitive(CacheIRWriter& writer, ValOperandId valId } else if (val_.isSymbol()) { primitiveType = JSVAL_TYPE_SYMBOL; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_Symbol)); + } else if (val_.isBigInt()) { + primitiveType = JSVAL_TYPE_BIGINT; + proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_BigInt)); } else { MOZ_ASSERT(val_.isNullOrUndefined() || val_.isMagic()); return true; diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 1f7d292f29..0459592448 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -526,9 +526,11 @@ CodeGenerator::testValueTruthyKernel(const ValueOperand& value, bool mightBeString = valueMIR->mightBeType(MIRType::String); bool mightBeSymbol = valueMIR->mightBeType(MIRType::Symbol); bool mightBeDouble = valueMIR->mightBeType(MIRType::Double); + bool mightBeBigInt = valueMIR->mightBeType(MIRType::BigInt); int tagCount = int(mightBeUndefined) + int(mightBeNull) + int(mightBeBoolean) + int(mightBeInt32) + int(mightBeObject) + - int(mightBeString) + int(mightBeSymbol) + int(mightBeDouble); + int(mightBeString) + int(mightBeSymbol) + int(mightBeDouble) + + int(mightBeBigInt);; MOZ_ASSERT_IF(!valueMIR->emptyResultTypeSet(), tagCount > 0); @@ -618,6 +620,20 @@ CodeGenerator::testValueTruthyKernel(const ValueOperand& value, --tagCount; } + if (mightBeBigInt) { + MOZ_ASSERT(tagCount != 0); + Label notBigInt; + if (tagCount != 1) { + masm.branchTestBigInt(Assembler::NotEqual, tag, ¬BigInt); + } + masm.branchTestBigIntTruthy(false, value, ifFalsy); + if (tagCount != 1) { + masm.jump(ifTruthy); + } + masm.bind(¬BigInt); + --tagCount; + } + if (mightBeSymbol) { // All symbols are truthy. MOZ_ASSERT(tagCount != 0); @@ -954,8 +970,15 @@ CodeGenerator::visitValueToString(LValueToString* lir) } // Symbol - if (lir->mir()->input()->mightBeType(MIRType::Symbol)) + if (lir->mir()->input()->mightBeType(MIRType::Symbol)) { masm.branchTestSymbol(Assembler::Equal, tag, ool->entry()); + } + + // BigInt + if (lir->mir()->input()->mightBeType(MIRType::BigInt)) { + // No fastpath currently implemented. + masm.branchTestBigInt(Assembler::Equal, tag, ool->entry()); + } #ifdef DEBUG masm.assumeUnreachable("Unexpected type for MValueToString."); @@ -4902,10 +4925,11 @@ CodeGenerator::branchIfInvalidated(Register temp, Label* invalidated) } void -CodeGenerator::emitAssertObjectOrStringResult(Register input, MIRType type, const TemporaryTypeSet* typeset) +CodeGenerator::emitAssertGCThingResult(Register input, MIRType type, const TemporaryTypeSet* typeset) { MOZ_ASSERT(type == MIRType::Object || type == MIRType::ObjectOrNull || - type == MIRType::String || type == MIRType::Symbol); + type == MIRType::String || type == MIRType::Symbol || + type == MIRType::BigInt); AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); regs.take(input); @@ -4960,6 +4984,9 @@ CodeGenerator::emitAssertObjectOrStringResult(Register input, MIRType type, cons case MIRType::Symbol: callee = JS_FUNC_TO_DATA_PTR(void*, AssertValidSymbolPtr); break; + case MIRType::BigInt: + callee = JS_FUNC_TO_DATA_PTR(void*, AssertValidBigIntPtr); + break; default: MOZ_CRASH(); } @@ -5029,7 +5056,7 @@ CodeGenerator::emitAssertResultV(const ValueOperand input, const TemporaryTypeSe #ifdef DEBUG void -CodeGenerator::emitObjectOrStringResultChecks(LInstruction* lir, MDefinition* mir) +CodeGenerator::emitGCThingResultChecks(LInstruction* lir, MDefinition* mir) { if (lir->numDefs() == 0) return; @@ -5037,7 +5064,7 @@ CodeGenerator::emitObjectOrStringResultChecks(LInstruction* lir, MDefinition* mi MOZ_ASSERT(lir->numDefs() == 1); Register output = ToRegister(lir->getDef(0)); - emitAssertObjectOrStringResult(output, mir->type(), mir->resultTypeSet()); + emitAssertGCThingResult(output, mir->type(), mir->resultTypeSet()); } void @@ -5069,7 +5096,8 @@ CodeGenerator::emitDebugResultChecks(LInstruction* ins) case MIRType::ObjectOrNull: case MIRType::String: case MIRType::Symbol: - emitObjectOrStringResultChecks(ins, mir); + case MIRType::BigInt: + emitGCThingResultChecks(ins, mir); break; case MIRType::Value: emitValueResultChecks(ins, mir); @@ -10306,7 +10334,7 @@ CodeGenerator::visitThrow(LThrow* lir) callVM(ThrowInfoCodeGen, lir); } -typedef bool (*BitNotFn)(JSContext*, HandleValue, int* p); +typedef bool (*BitNotFn)(JSContext*, MutableHandleValue, MutableHandleValue); static const VMFunction BitNotInfo = FunctionInfo<BitNotFn>(BitNot, "BitNot"); void @@ -10316,7 +10344,7 @@ CodeGenerator::visitBitNotV(LBitNotV* lir) callVM(BitNotInfo, lir); } -typedef bool (*BitopFn)(JSContext*, HandleValue, HandleValue, int* p); +typedef bool (*BitopFn)(JSContext*, MutableHandleValue, MutableHandleValue, MutableHandleValue); static const VMFunction BitAndInfo = FunctionInfo<BitopFn>(BitAnd, "BitAnd"); static const VMFunction BitOrInfo = FunctionInfo<BitopFn>(BitOr, "BitOr"); static const VMFunction BitXorInfo = FunctionInfo<BitopFn>(BitXor, "BitXor"); @@ -10386,9 +10414,11 @@ CodeGenerator::visitTypeOfV(LTypeOfV* lir) bool testNull = input->mightBeType(MIRType::Null); bool testString = input->mightBeType(MIRType::String); bool testSymbol = input->mightBeType(MIRType::Symbol); + bool testBigInt = input->mightBeType(MIRType::BigInt); unsigned numTests = unsigned(testObject) + unsigned(testNumber) + unsigned(testBoolean) + - unsigned(testUndefined) + unsigned(testNull) + unsigned(testString) + unsigned(testSymbol); + unsigned(testUndefined) + unsigned(testNull) + unsigned(testString) + unsigned(testSymbol) + + unsigned(testBigInt); MOZ_ASSERT_IF(!input->emptyResultTypeSet(), numTests > 0); @@ -10484,6 +10514,19 @@ CodeGenerator::visitTypeOfV(LTypeOfV* lir) numTests--; } + if (testBigInt) { + Label notBigInt; + if (numTests > 1) { + masm.branchTestBigInt(Assembler::NotEqual, tag, ¬BigInt); + } + masm.movePtr(ImmGCPtr(names.bigint), output); + if (numTests > 1) { + masm.jump(&done); + } + masm.bind(¬BigInt); + numTests--; + } + MOZ_ASSERT(numTests == 0); masm.bind(&done); @@ -11794,7 +11837,7 @@ CodeGenerator::visitAssertResultT(LAssertResultT* ins) Register input = ToRegister(ins->input()); MDefinition* mir = ins->mirRaw(); - emitAssertObjectOrStringResult(input, mir->type(), mir->resultTypeSet()); + emitAssertGCThingResult(input, mir->type(), mir->resultTypeSet()); } void diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 2749a68b05..64fe9378b8 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -431,7 +431,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitAssertResultV(LAssertResultV* ins); void visitAssertResultT(LAssertResultT* ins); void emitAssertResultV(const ValueOperand output, const TemporaryTypeSet* typeset); - void emitAssertObjectOrStringResult(Register input, MIRType type, const TemporaryTypeSet* typeset); + void emitAssertGCThingResult(Register input, MIRType type, const TemporaryTypeSet* typeset); void visitInterruptCheck(LInterruptCheck* lir); void visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit* ins); @@ -571,7 +571,7 @@ class CodeGenerator final : public CodeGeneratorSpecific #ifdef DEBUG void emitDebugResultChecks(LInstruction* ins); - void emitObjectOrStringResultChecks(LInstruction* lir, MDefinition* mir); + void emitGCThingResultChecks(LInstruction* lir, MDefinition* mir); void emitValueResultChecks(LInstruction* lir, MDefinition* mir); #endif diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 410769251c..aa8f8164b5 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -2611,6 +2611,7 @@ IsResumableMIRType(MIRType type) case MIRType::Float32: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: case MIRType::MagicOptimizedArguments: case MIRType::MagicOptimizedOut: diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 0a9734a335..a440bfa598 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -721,6 +721,9 @@ IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecod case JSOP_NEG: type = inspector->expectedResultType(last); break; + case JSOP_BIGINT: + type = MIRType::BigInt; + break; default: break; } @@ -1347,6 +1350,7 @@ IonBuilder::addOsrValueTypeBarrier(uint32_t slot, MInstruction** def_, case MIRType::Double: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: if (type != def->type()) { MUnbox* unbox = MUnbox::New(alloc(), def, type, MUnbox::Fallible); @@ -1765,6 +1769,7 @@ IonBuilder::inspectOpcode(JSOp op) return jsop_compare(op); case JSOP_DOUBLE: + case JSOP_BIGINT: pushConstant(info().getConst(pc)); return true; @@ -4760,8 +4765,10 @@ IonBuilder::bitnotTrySpecialized(bool* emitted, MDefinition* input) // Try to emit a specialized bitnot instruction based on the input type // of the operand. - if (input->mightBeType(MIRType::Object) || input->mightBeType(MIRType::Symbol)) + if (input->mightBeType(MIRType::Object) || input->mightBeType(MIRType::Symbol) || + input->mightBeType(MIRType::BigInt)) { return true; + } MBitNot* ins = MBitNot::New(alloc(), input); ins->setSpecialization(MIRType::Int32); @@ -7209,6 +7216,7 @@ ObjectOrSimplePrimitive(MDefinition* op) // Return true if op is either undefined/null/boolean/int32 or an object. return !op->mightBeType(MIRType::String) && !op->mightBeType(MIRType::Symbol) + && !op->mightBeType(MIRType::BigInt) && !op->mightBeType(MIRType::Double) && !op->mightBeType(MIRType::Float32) && !op->mightBeType(MIRType::MagicOptimizedArguments) @@ -8348,6 +8356,10 @@ IonBuilder::testSingletonPropertyTypes(MDefinition* obj, jsid id) key = JSProto_Symbol; break; + case MIRType::BigInt: + key = JSProto_BigInt; + break; + case MIRType::Int32: case MIRType::Double: key = JSProto_Number; diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index c7a3e282aa..c9714343dc 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -103,6 +103,7 @@ enum BailoutKind Bailout_NonObjectInput, Bailout_NonStringInput, Bailout_NonSymbolInput, + Bailout_NonBigIntInput, // SIMD Unbox expects a given type, bails out if it doesn't match. Bailout_UnexpectedSimdInput, @@ -212,6 +213,8 @@ BailoutKindString(BailoutKind kind) return "Bailout_NonStringInput"; case Bailout_NonSymbolInput: return "Bailout_NonSymbolInput"; + case Bailout_NonBigIntInput: + return "Bailout_NonBigIntInput"; case Bailout_UnexpectedSimdInput: return "Bailout_UnexpectedSimdInput"; case Bailout_NonSharedTypedArrayInput: @@ -412,6 +415,7 @@ enum class MIRType // Types above have trivial conversion to a number. String, Symbol, + BigInt, // Types above are primitive (including undefined and null). Object, MagicOptimizedArguments, // JS_OPTIMIZED_ARGUMENTS magic value. @@ -496,6 +500,8 @@ MIRTypeFromValueType(JSValueType type) return MIRType::String; case JSVAL_TYPE_SYMBOL: return MIRType::Symbol; + case JSVAL_TYPE_BIGINT: + return MIRType::BigInt; case JSVAL_TYPE_BOOLEAN: return MIRType::Boolean; case JSVAL_TYPE_NULL: @@ -528,6 +534,8 @@ ValueTypeFromMIRType(MIRType type) return JSVAL_TYPE_STRING; case MIRType::Symbol: return JSVAL_TYPE_SYMBOL; + case MIRType::BigInt: + return JSVAL_TYPE_BIGINT; case MIRType::MagicOptimizedArguments: case MIRType::MagicOptimizedOut: case MIRType::MagicHole: @@ -568,6 +576,8 @@ StringFromMIRType(MIRType type) return "String"; case MIRType::Symbol: return "Symbol"; + case MIRType::BigInt: + return "BigInt"; case MIRType::Object: return "Object"; case MIRType::MagicOptimizedArguments: diff --git a/js/src/jit/LIR.h b/js/src/jit/LIR.h index 4083f7f1bb..e9143a6f41 100644 --- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -599,6 +599,7 @@ class LDefinition return LDefinition::INT32; case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: case MIRType::ObjectOrNull: return LDefinition::OBJECT; diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 1c80c74716..d315c618e7 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -717,6 +717,10 @@ LIRGenerator::visitTest(MTest* test) // TestPolicy). MOZ_ASSERT(opd->type() != MIRType::String); + // BigInt is boxed in type analysis. + MOZ_ASSERT(opd->type() != MIRType::BigInt, + "BigInt should be boxed by TestPolicy"); + // Testing a constant. if (MConstant* constant = opd->maybeConstantValue()) { bool b; @@ -2149,9 +2153,11 @@ LIRGenerator::visitToInt32(MToInt32* convert) case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: case MIRType::Undefined: - // Objects might be effectful. Symbols throw. Undefined coerces to NaN, not int32. + // Objects might be effectful. Symbols and BigInts throw. Undefined + // coerces to NaN, not int32. MOZ_CRASH("ToInt32 invalid input type"); default: @@ -2939,6 +2945,8 @@ LIRGenerator::visitNot(MNot* ins) // String is converted to length of string in the type analysis phase (see // TestPolicy). MOZ_ASSERT(op->type() != MIRType::String); + MOZ_ASSERT(op->type() != MIRType::BigInt, + "BigInt should be boxed by TestPolicy"); // - boolean: x xor 1 // - int32: LCompare(x, 0) diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 1a98432ffa..064c7ee7d2 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -2894,6 +2894,7 @@ IonBuilder::inlineToInteger(CallInfo& callInfo) if (input->mightBeType(MIRType::Object) || input->mightBeType(MIRType::String) || input->mightBeType(MIRType::Symbol) || + input->mightBeType(MIRType::BigInt) || input->mightBeType(MIRType::Undefined) || input->mightBeMagicType()) { @@ -3021,12 +3022,16 @@ IonBuilder::inlineAtomicsCompareExchange(CallInfo& callInfo) // These guards are desirable here and in subsequent atomics to // avoid bad bailouts with MTruncateToInt32, see https://bugzilla.mozilla.org/show_bug.cgi?id=1141986#c20. MDefinition* oldval = callInfo.getArg(2); - if (oldval->mightBeType(MIRType::Object) || oldval->mightBeType(MIRType::Symbol)) + if (oldval->mightBeType(MIRType::Object) || oldval->mightBeType(MIRType::Symbol) || + oldval->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } MDefinition* newval = callInfo.getArg(3); - if (newval->mightBeType(MIRType::Object) || newval->mightBeType(MIRType::Symbol)) + if (newval->mightBeType(MIRType::Object) || newval->mightBeType(MIRType::Symbol) || + newval->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } Scalar::Type arrayType; bool requiresCheck = false; @@ -3063,8 +3068,10 @@ IonBuilder::inlineAtomicsExchange(CallInfo& callInfo) } MDefinition* value = callInfo.getArg(2); - if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol) || + value->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } Scalar::Type arrayType; bool requiresCheck = false; @@ -3151,8 +3158,10 @@ IonBuilder::inlineAtomicsStore(CallInfo& callInfo) return InliningStatus_NotInlined; } - if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol) || + value->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } Scalar::Type arrayType; bool requiresCheck = false; @@ -3194,8 +3203,10 @@ IonBuilder::inlineAtomicsBinop(CallInfo& callInfo, InlinableNative target) } MDefinition* value = callInfo.getArg(2); - if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol) || + value->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } Scalar::Type arrayType; bool requiresCheck = false; diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 6b89af32de..63ff8f7201 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -935,6 +935,9 @@ MConstant::MConstant(const js::Value& vp, CompilerConstraintList* constraints) case MIRType::Symbol: payload_.sym = vp.toSymbol(); break; + case MIRType::BigInt: + payload_.bi = vp.toBigInt(); + break; case MIRType::Object: payload_.obj = &vp.toObject(); // Create a singleton type set for the object. This isn't necessary for @@ -1014,7 +1017,12 @@ MConstant::assertInitializedPayload() const case MIRType::String: case MIRType::Object: case MIRType::Symbol: + case MIRType::BigInt: +#if MOZ_LITTLE_ENDIAN MOZ_ASSERT_IF(JS_BITS_PER_WORD == 32, (payload_.asBits >> 32) == 0); +#else + MOZ_ASSERT_IF(JS_BITS_PER_WORD == 32, (payload_.asBits << 32) == 0); +#endif break; default: MOZ_ASSERT(IsNullOrUndefined(type()) || IsMagicType(type())); @@ -1103,6 +1111,9 @@ MConstant::printOpcode(GenericPrinter& out) const case MIRType::Symbol: out.printf("symbol at %p", (void*)toSymbol()); break; + case MIRType::BigInt: + out.printf("BigInt at %p", (void*)toBigInt()); + break; case MIRType::String: out.printf("string %p", (void*)toString()); break; @@ -1164,6 +1175,8 @@ MConstant::toJSValue() const return StringValue(toString()); case MIRType::Symbol: return SymbolValue(toSymbol()); + case MIRType::BigInt: + return BigIntValue(toBigInt()); case MIRType::Object: return ObjectValue(toObject()); case MIRType::MagicOptimizedArguments: @@ -1207,6 +1220,9 @@ MConstant::valueToBoolean(bool* res) const case MIRType::Symbol: *res = true; return true; + case MIRType::BigInt: + *res = !toBigInt()->isZero(); + return true; case MIRType::String: *res = toString()->length() != 0; return true; @@ -2199,6 +2215,7 @@ MUnbox::printOpcode(GenericPrinter& out) const case MIRType::Boolean: out.printf("to Boolean"); break; case MIRType::String: out.printf("to String"); break; case MIRType::Symbol: out.printf("to Symbol"); break; + case MIRType::BigInt: out.printf("to BigInt"); break; case MIRType::Object: out.printf("to Object"); break; default: break; } @@ -2591,6 +2608,7 @@ jit::TypeSetIncludes(TypeSet* types, MIRType input, TypeSet* inputTypes) case MIRType::Float32: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::MagicOptimizedArguments: return types->hasType(TypeSet::PrimitiveType(ValueTypeFromMIRType(input))); @@ -2846,9 +2864,11 @@ void MBinaryBitwiseInstruction::infer(BaselineInspector*, jsbytecode*) { if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(0)->mightBeType(MIRType::Symbol) || - getOperand(1)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Symbol)) + getOperand(1)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Symbol) || + getOperand(1)->mightBeType(MIRType::BigInt)) { specialization_ = MIRType::None; + setResultType(MIRType::Value); } else { specializeAs(MIRType::Int32); } @@ -2858,9 +2878,10 @@ void MBinaryBitwiseInstruction::specializeAs(MIRType type) { MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64); - MOZ_ASSERT(this->type() == type); + MOZ_ASSERT(this->type() == MIRType::Value || this->type() == type); specialization_ = type; + setResultType(type); if (isBitOr() || isBitAnd() || isBitXor()) setCommutative(); @@ -2870,17 +2891,23 @@ void MShiftInstruction::infer(BaselineInspector*, jsbytecode*) { if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Object) || - getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol)) + getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol) || + getOperand(0)->mightBeType(MIRType::BigInt) || getOperand(1)->mightBeType(MIRType::BigInt)) + { specialization_ = MIRType::None; - else + setResultType(MIRType::Value); + } else { specialization_ = MIRType::Int32; + setResultType(MIRType::Int32); + } } void MUrsh::infer(BaselineInspector* inspector, jsbytecode* pc) { if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Object) || - getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol)) + getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol) || + getOperand(0)->mightBeType(MIRType::BigInt) || getOperand(1)->mightBeType(MIRType::BigInt)) { specialization_ = MIRType::None; setResultType(MIRType::Value); @@ -3828,7 +3855,7 @@ MBitNot::NewInt32(TempAllocator& alloc, MDefinition* input) { MBitNot* ins = new(alloc) MBitNot(input); ins->specialization_ = MIRType::Int32; - MOZ_ASSERT(ins->type() == MIRType::Int32); + ins->setResultType(MIRType::Int32); return ins; } @@ -3874,6 +3901,9 @@ MTypeOf::foldsTo(TempAllocator& alloc) case MIRType::Symbol: type = JSTYPE_SYMBOL; break; + case MIRType::BigInt: + type = JSTYPE_BIGINT; + break; case MIRType::Null: type = JSTYPE_OBJECT; break; @@ -4453,6 +4483,12 @@ MCompare::tryFoldTypeOf(bool* result) *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); return true; } + } + else if (constant->toString() == TypeName(JSTYPE_BIGINT, names)) { + if (!typeOf->input()->mightBeType(MIRType::BigInt)) { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } } else if (constant->toString() == TypeName(JSTYPE_OBJECT, names)) { if (!typeOf->input()->mightBeType(MIRType::Object) && !typeOf->input()->mightBeType(MIRType::Null)) @@ -5606,6 +5642,8 @@ MConstant::appendRoots(MRootList& roots) const return roots.append(toString()); case MIRType::Symbol: return roots.append(toSymbol()); + case MIRType::BigInt: + return roots.append(toBigInt()); case MIRType::Object: return roots.append(&toObject()); case MIRType::Undefined: diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 6c0616c71d..6a2adc9622 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -1548,6 +1548,7 @@ class MConstant : public MNullaryInstruction double d; JSString* str; JS::Symbol* sym; + BigInt* bi; JSObject* obj; uint64_t asBits; }; @@ -1672,6 +1673,10 @@ class MConstant : public MNullaryInstruction MOZ_ASSERT(type() == MIRType::Symbol); return payload_.sym; } + BigInt* toBigInt() const { + MOZ_ASSERT(type() == MIRType::BigInt); + return payload_.bi; + } JSObject& toObject() const { MOZ_ASSERT(type() == MIRType::Object); return *payload_.obj; @@ -4743,6 +4748,7 @@ class MUnbox final : public MUnaryInstruction, public BoxInputsPolicy::Data type == MIRType::Double || type == MIRType::String || type == MIRType::Symbol || + type == MIRType::BigInt || type == MIRType::Object); TemporaryTypeSet* resultSet = ins->resultTypeSet(); @@ -4781,6 +4787,9 @@ class MUnbox final : public MUnaryInstruction, public BoxInputsPolicy::Data case MIRType::Symbol: kind = Bailout_NonSymbolInput; break; + case MIRType::BigInt: + kind = Bailout_NonBigIntInput; + break; case MIRType::Object: kind = Bailout_NonObjectInput; break; @@ -5189,9 +5198,11 @@ class MToDouble setMovable(); // An object might have "valueOf", which means it is effectful. - // ToNumber(symbol) throws. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // ToNumber(symbol) and ToNumber(bigint) throw. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } public: @@ -5226,11 +5237,15 @@ class MToDouble MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override; bool canRecoverOnBailout() const override { - if (input()->type() == MIRType::Value) + if (input()->type() == MIRType::Value) { return false; - if (input()->type() == MIRType::Symbol) + } + if (input()->type() == MIRType::Symbol) { return false; - + } + if (input()->type() == MIRType::BigInt) { + return false; + } return true; } @@ -5253,9 +5268,11 @@ class MToFloat32 setMovable(); // An object might have "valueOf", which means it is effectful. - // ToNumber(symbol) throws. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // ToNumber(symbol) and ToNumber(BigInt) throw. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } explicit MToFloat32(MDefinition* def, bool mustPreserveNaN) @@ -5537,9 +5554,11 @@ class MToInt32 setMovable(); // An object might have "valueOf", which means it is effectful. - // ToNumber(symbol) throws. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // ToInt32(symbol) and ToInt32(BigInt) throw. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } public: @@ -5594,9 +5613,11 @@ class MTruncateToInt32 setMovable(); // An object might have "valueOf", which means it is effectful. - // ToInt32(symbol) throws. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // ToInt32(symbol) and ToInt32(BigInt) throw. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } public: @@ -5639,9 +5660,12 @@ class MToString : setResultType(MIRType::String); setMovable(); - // Objects might override toString and Symbols throw. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // Objects might override toString; Symbol and BigInts throw. We bailout in + // those cases and run side-effects in baseline instead. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } public: @@ -5701,7 +5725,7 @@ class MBitNot : MUnaryInstruction(input) { specialization_ = MIRType::None; - setResultType(MIRType::Int32); + setResultType(MIRType::Value); setMovable(); } @@ -5861,7 +5885,7 @@ class MBinaryBitwiseInstruction maskMatchesRightRange(false) { MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64); - setResultType(type); + setResultType(MIRType::Value); setMovable(); } diff --git a/js/src/jit/MacroAssembler-inl.h b/js/src/jit/MacroAssembler-inl.h index dd20f67317..336b9e2df8 100644 --- a/js/src/jit/MacroAssembler-inl.h +++ b/js/src/jit/MacroAssembler-inl.h @@ -545,6 +545,7 @@ MacroAssembler::branchTestMIRType(Condition cond, const Value& val, MIRType type case MIRType::Int32: return branchTestInt32(cond, val, label); case MIRType::String: return branchTestString(cond, val, label); case MIRType::Symbol: return branchTestSymbol(cond, val, label); + case MIRType::BigInt: return branchTestBigInt(cond, val, label); case MIRType::Object: return branchTestObject(cond, val, label); case MIRType::Double: return branchTestDouble(cond, val, label); case MIRType::MagicOptimizedArguments: // Fall through. diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 0425ac03a1..fdbcc9f23c 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -45,12 +45,13 @@ MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, Barrie MOZ_ASSERT(!types->unknown()); Label matched; - TypeSet::Type tests[8] = { + TypeSet::Type tests[9] = { TypeSet::Int32Type(), TypeSet::UndefinedType(), TypeSet::BooleanType(), TypeSet::StringType(), TypeSet::SymbolType(), + TypeSet::BigIntType(), TypeSet::NullType(), TypeSet::MagicArgType(), TypeSet::AnyObjectType() @@ -2736,6 +2737,9 @@ MacroAssembler::maybeBranchTestType(MIRType type, MDefinition* maybeDef, Registe case MIRType::Symbol: branchTestSymbol(Equal, tag, label); break; + case MIRType::BigInt: + branchTestBigInt(Equal, tag, label); + break; case MIRType::Object: branchTestObject(Equal, tag, label); break; diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 6d9888469e..173a39014c 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -1137,6 +1137,7 @@ class MacroAssembler : public MacroAssemblerSpecific inline void branchTestBoolean(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestString(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestSymbol(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; + inline void branchTestBigInt(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestNull(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestObject(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestPrimitive(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; @@ -1177,6 +1178,10 @@ class MacroAssembler : public MacroAssemblerSpecific inline void branchTestSymbol(Condition cond, const ValueOperand& value, Label* label) DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); + inline void branchTestBigInt(Condition cond, const BaseIndex& address, Label* label) PER_SHARED_ARCH; + inline void branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) + DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); + inline void branchTestNull(Condition cond, const Address& address, Label* label) PER_SHARED_ARCH; inline void branchTestNull(Condition cond, const BaseIndex& address, Label* label) PER_SHARED_ARCH; inline void branchTestNull(Condition cond, const ValueOperand& value, Label* label) @@ -1216,6 +1221,8 @@ class MacroAssembler : public MacroAssemblerSpecific inline void branchTestBooleanTruthy(bool truthy, const ValueOperand& value, Label* label) PER_ARCH; inline void branchTestStringTruthy(bool truthy, const ValueOperand& value, Label* label) DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); + inline void branchTestBigIntTruthy(bool truthy, const ValueOperand& value, Label* label) + DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); private: @@ -1256,6 +1263,9 @@ class MacroAssembler : public MacroAssemblerSpecific inline void branchTestSymbolImpl(Condition cond, const T& t, Label* label) DEFINED_ON(arm, arm64, x86_shared); template <typename T> + inline void branchTestBigIntImpl(Condition cond, const T& t, Label* label) + DEFINED_ON(arm, arm64, x86_shared); + template <typename T> inline void branchTestNullImpl(Condition cond, const T& t, Label* label) DEFINED_ON(arm, arm64, x86_shared); template <typename T> diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index 833ad871d0..0d6882f52c 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -164,13 +164,13 @@ bool RBitNot::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue operand(cx, iter.read()); + RootedValue result(cx); - int32_t result; - if (!js::BitNot(cx, operand, &result)) + if (!js::BitNot(cx, &operand, &result)) { return false; + } - RootedValue rootedResult(cx, js::Int32Value(result)); - iter.storeInstructionResult(rootedResult); + iter.storeInstructionResult(result); return true; } @@ -190,14 +190,14 @@ RBitAnd::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; + RootedValue result(cx); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitAnd(cx, lhs, rhs, &result)) + if (!js::BitAnd(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue rootedResult(cx, js::Int32Value(result)); - iter.storeInstructionResult(rootedResult); + iter.storeInstructionResult(result); return true; } @@ -217,14 +217,14 @@ RBitOr::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; + RootedValue result(cx); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitOr(cx, lhs, rhs, &result)) + if (!js::BitOr(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue asValue(cx, js::Int32Value(result)); - iter.storeInstructionResult(asValue); + iter.storeInstructionResult(result);; return true; } @@ -244,13 +244,13 @@ RBitXor::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); + RootedValue result(cx); - int32_t result; - if (!js::BitXor(cx, lhs, rhs, &result)) + if (!js::BitXor(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue rootedResult(cx, js::Int32Value(result)); - iter.storeInstructionResult(rootedResult); + iter.storeInstructionResult(result); return true; } @@ -270,14 +270,14 @@ RLsh::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; + RootedValue result(cx); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitLsh(cx, lhs, rhs, &result)) + if (!js::BitLsh(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue asValue(cx, js::Int32Value(result)); - iter.storeInstructionResult(asValue); + iter.storeInstructionResult(result); return true; } @@ -297,14 +297,14 @@ RRsh::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); + RootedValue result(cx); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - int32_t result; - if (!js::BitRsh(cx, lhs, rhs, &result)) + if (!js::BitRsh(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue rootedResult(cx, js::Int32Value(result)); - iter.storeInstructionResult(rootedResult); + iter.storeInstructionResult(result); return true; } @@ -327,8 +327,9 @@ RUrsh::recover(JSContext* cx, SnapshotIterator& iter) const MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); RootedValue result(cx); - if (!js::UrshOperation(cx, lhs, rhs, &result)) + if (!js::UrshOperation(cx, &lhs, &rhs, &result)) { return false; + } iter.storeInstructionResult(result); return true; @@ -778,7 +779,7 @@ RPow::recover(JSContext* cx, SnapshotIterator& iter) const RootedValue result(cx); MOZ_ASSERT(base.isNumber() && power.isNumber()); - if (!js::math_pow_handle(cx, base, power, &result)) + if (!js::PowValues(cx, &base, &power, &result)) return false; iter.storeInstructionResult(result); @@ -805,7 +806,7 @@ RPowHalf::recover(JSContext* cx, SnapshotIterator& iter) const power.setNumber(0.5); MOZ_ASSERT(base.isNumber()); - if (!js::math_pow_handle(cx, base, power, &result)) + if (!js::PowValues(cx, &base, &power, &result)) return false; iter.storeInstructionResult(result); diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index f8f4433af7..375edb1400 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -942,47 +942,43 @@ DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub return false; break; case JSOP_POW: - if (!math_pow_handle(cx, lhsCopy, rhsCopy, ret)) + if (!PowValues(cx, &lhsCopy, &rhsCopy, ret)) return false; break; case JSOP_BITOR: { - int32_t result; - if (!BitOr(cx, lhs, rhs, &result)) + if (!BitOr(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_BITXOR: { - int32_t result; - if (!BitXor(cx, lhs, rhs, &result)) + if (!BitXor(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_BITAND: { - int32_t result; - if (!BitAnd(cx, lhs, rhs, &result)) + if (!BitAnd(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_LSH: { - int32_t result; - if (!BitLsh(cx, lhs, rhs, &result)) + if (!BitLsh(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_RSH: { - int32_t result; - if (!BitRsh(cx, lhs, rhs, &result)) + if (!BitRsh(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_URSH: { - if (!UrshOperation(cx, lhs, rhs, ret)) + if (!UrshOperation(cx, &lhsCopy, &rhsCopy, ret)) { return false; + } break; } default: @@ -1475,16 +1471,19 @@ DoUnaryArithFallback(JSContext* cx, void* payload, ICUnaryArith_Fallback* stub_, switch (op) { case JSOP_BITNOT: { - int32_t result; - if (!BitNot(cx, val, &result)) + RootedValue valCopy(cx, val); + if (!BitNot(cx, &valCopy, res)) { return false; - res.setInt32(result); + } break; } - case JSOP_NEG: - if (!NegOperation(cx, script, pc, val, res)) + case JSOP_NEG: { + // We copy val here because the original value is needed below. + RootedValue valCopy(cx, val); + if (!NegOperation(cx, script, pc, &valCopy, res)) return false; break; + } default: MOZ_CRASH("Unexpected op"); } diff --git a/js/src/jit/Snapshots.cpp b/js/src/jit/Snapshots.cpp index 6d9a461412..3111941315 100644 --- a/js/src/jit/Snapshots.cpp +++ b/js/src/jit/Snapshots.cpp @@ -404,6 +404,8 @@ ValTypeToString(JSValueType type) { return "string"; case JSVAL_TYPE_SYMBOL: return "symbol"; + case JSVAL_TYPE_BIGINT: + return "BigInt"; case JSVAL_TYPE_BOOLEAN: return "boolean"; case JSVAL_TYPE_OBJECT: diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 396fdc57f8..1222cdd2b2 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -700,7 +700,8 @@ ToDoublePolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) case MIRType::Object: case MIRType::String: case MIRType::Symbol: - // Objects might be effectful. Symbols give TypeError. + case MIRType::BigInt: + // Objects might be effectful. Symbols and BigInts give TypeError. break; default: break; @@ -748,7 +749,8 @@ ToInt32Policy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) case MIRType::Object: case MIRType::String: case MIRType::Symbol: - // Objects might be effectful. Symbols give TypeError. + case MIRType::BigInt: + // Objects might be effectful. Symbols and BigInts give TypeError. break; default: break; @@ -765,7 +767,8 @@ ToStringPolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) MOZ_ASSERT(ins->isToString()); MIRType type = ins->getOperand(0)->type(); - if (type == MIRType::Object || type == MIRType::Symbol) { + if (type == MIRType::Object || type == MIRType::Symbol || + type == MIRType::BigInt) { ins->replaceOperand(0, BoxAt(alloc, ins, ins->getOperand(0))); return true; } @@ -955,6 +958,7 @@ StoreUnboxedScalarPolicy::adjustValueInput(TempAllocator& alloc, MInstruction* i case MIRType::Object: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: value = BoxAt(alloc, ins, value); break; default: diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 6e5676f153..8802d3582b 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1236,15 +1236,27 @@ AssertValidSymbolPtr(JSContext* cx, JS::Symbol* sym) MOZ_ASSERT(sym->getAllocKind() == gc::AllocKind::SYMBOL); } +void +AssertValidBigIntPtr(JSContext* cx, JS::BigInt* bi) { + // FIXME: check runtime? + MOZ_ASSERT(cx->zone() == bi->zone()); + MOZ_ASSERT(bi->isAligned()); + MOZ_ASSERT(bi->isTenured()); + MOZ_ASSERT(bi->getAllocKind() == gc::AllocKind::BIGINT); +} + void AssertValidValue(JSContext* cx, Value* v) { - if (v->isObject()) + if (v->isObject()) { AssertValidObjectPtr(cx, &v->toObject()); - else if (v->isString()) + } else if (v->isString()) { AssertValidStringPtr(cx, v->toString()); - else if (v->isSymbol()) + } else if (v->isSymbol()) { AssertValidSymbolPtr(cx, v->toSymbol()); + } else if (v->isBigInt()) { + AssertValidBigIntPtr(cx, v->toBigInt()); + } } bool diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 32830038d1..f4280f5800 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -751,6 +751,7 @@ void AssertValidObjectPtr(JSContext* cx, JSObject* obj); void AssertValidObjectOrNullPtr(JSContext* cx, JSObject* obj); void AssertValidStringPtr(JSContext* cx, JSString* str); void AssertValidSymbolPtr(JSContext* cx, JS::Symbol* sym); +void AssertValidBigIntPtr(JSContext* cx, JS::BigInt* bi); void AssertValidValue(JSContext* cx, Value* v); void MarkValueFromIon(JSRuntime* rt, Value* vp); diff --git a/js/src/jit/arm/MacroAssembler-arm-inl.h b/js/src/jit/arm/MacroAssembler-arm-inl.h index 2cc26b2242..3fc07e0de5 100644 --- a/js/src/jit/arm/MacroAssembler-arm-inl.h +++ b/js/src/jit/arm/MacroAssembler-arm-inl.h @@ -1880,6 +1880,29 @@ MacroAssembler::branchTestSymbolImpl(Condition cond, const T& t, Label* label) ma_b(label, c); } +void MacroAssembler::branchTestBigInt(Condition cond, Register tag, Label* label) { + branchTestBigIntImpl(cond, tag, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const BaseIndex& address, Label* label) { + branchTestBigIntImpl(cond, address, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) { + branchTestBigIntImpl(cond, value, label); +} + +template <typename T> +void MacroAssembler::branchTestBigIntImpl(Condition cond, const T& t, Label* label) { + Condition c = testBigInt(cond, t); + ma_b(label, c); +} + +void MacroAssembler::branchTestBigIntTruthy(bool truthy, const ValueOperand& value, Label* label) { + Condition c = testBigIntTruthy(truthy, value); + ma_b(label, c); +} + void MacroAssembler::branchTestNull(Condition cond, Register tag, Label* label) { diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index a50046d697..e099022c27 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -2717,6 +2717,11 @@ MacroAssemblerARMCompat::testSymbol(Assembler::Condition cond, const ValueOperan return testSymbol(cond, value.typeReg()); } +Assembler::Condition MacroAssemblerARMCompat::testBigInt(Assembler::Condition cond, const ValueOperand& value) +{ + return testBigInt(cond, value.typeReg()); +} + Assembler::Condition MacroAssemblerARMCompat::testObject(Assembler::Condition cond, const ValueOperand& value) { @@ -2790,6 +2795,13 @@ MacroAssemblerARMCompat::testSymbol(Assembler::Condition cond, Register tag) return cond; } +Assembler::Condition MacroAssemblerARMCompat::testBigInt(Assembler::Condition cond, Register tag) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_cmp(tag, ImmTag(JSVAL_TAG_BIGINT)); + return cond; +} + Assembler::Condition MacroAssemblerARMCompat::testObject(Assembler::Condition cond, Register tag) { @@ -2907,6 +2919,14 @@ MacroAssemblerARMCompat::testObject(Condition cond, const Address& address) return testObject(cond, scratch); } +Assembler::Condition MacroAssemblerARMCompat::testBigInt(Condition cond, const Address& address) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ScratchRegisterScope scratch(asMasm()); + Register tag = extractTag(address, scratch); + return testBigInt(cond, tag); +} + Assembler::Condition MacroAssemblerARMCompat::testNumber(Condition cond, const Address& address) { @@ -2993,6 +3013,16 @@ MacroAssemblerARMCompat::testInt32(Condition cond, const BaseIndex& src) return cond; } ++Assembler::Condition +MacroAssemblerARMCompat::testBigInt(Condition cond,const BaseIndex& src) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ScratchRegisterScope scratch(asMasm()); + Register tag = extractTag(src, scratch); + ma_cmp(tag, ImmTag(JSVAL_TAG_BIGINT)); + return cond; +} + Assembler::Condition MacroAssemblerARMCompat::testObject(Condition cond, const BaseIndex& src) { @@ -3736,6 +3766,19 @@ MacroAssemblerARMCompat::testStringTruthy(bool truthy, const ValueOperand& value return truthy ? Assembler::NotEqual : Assembler::Equal; } ++Assembler::Condition +MacroAssemblerARMCompat::testBigIntTruthy(bool truthy, const ValueOperand& value) +{ + Register bi = value.payloadReg(); + ScratchRegisterScope scratch(asMasm()); + SecondScratchRegisterScope scratch2(asMasm()); + + ma_dtr(IsLoad, bi, Imm32(BigInt::offsetOfLengthSignAndReservedBits()), + scratch, scratch2); + as_cmp(scratch, Imm8(0)); + return truthy ? Assembler::NotEqual : Assembler::Equal; +} + void MacroAssemblerARMCompat::floor(FloatRegister input, Register output, Label* bail) { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 2ed9a4f6e1..aaf92539a3 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -695,6 +695,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testUndefined(Condition cond, const ValueOperand& value); Condition testString(Condition cond, const ValueOperand& value); Condition testSymbol(Condition cond, const ValueOperand& value); + Condition testBigInt(Condition cond, const ValueOperand& value); Condition testObject(Condition cond, const ValueOperand& value); Condition testNumber(Condition cond, const ValueOperand& value); Condition testMagic(Condition cond, const ValueOperand& value); @@ -708,6 +709,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testUndefined(Condition cond, Register tag); Condition testString(Condition cond, Register tag); Condition testSymbol(Condition cond, Register tag); + Condition testBigInt(Condition cond, Register tag); Condition testObject(Condition cond, Register tag); Condition testDouble(Condition cond, Register tag); Condition testNumber(Condition cond, Register tag); @@ -723,6 +725,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testUndefined(Condition cond, const Address& address); Condition testString(Condition cond, const Address& address); Condition testSymbol(Condition cond, const Address& address); + Condition testBigInt(Condition cond, const Address& address); Condition testObject(Condition cond, const Address& address); Condition testNumber(Condition cond, const Address& address); @@ -731,6 +734,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testBoolean(Condition cond, const BaseIndex& src); Condition testString(Condition cond, const BaseIndex& src); Condition testSymbol(Condition cond, const BaseIndex& src); + Condition testBigInt(Condition cond, const BaseIndex& src); Condition testInt32(Condition cond, const BaseIndex& src); Condition testObject(Condition cond, const BaseIndex& src); Condition testDouble(Condition cond, const BaseIndex& src); @@ -749,6 +753,8 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void unboxString(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const Address& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const BaseIndex& src, Register dest) { unboxNonDouble(src, dest); } @@ -797,6 +803,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testBooleanTruthy(bool truthy, const ValueOperand& operand); Condition testDoubleTruthy(bool truthy, FloatRegister reg); Condition testStringTruthy(bool truthy, const ValueOperand& value); + Condition testBigIntTruthy(bool truthy, const ValueOperand& value); void boolValueToFloat32(const ValueOperand& operand, FloatRegister dest); void int32ValueToFloat32(const ValueOperand& operand, FloatRegister dest); diff --git a/js/src/jit/mips32/MacroAssembler-mips32-inl.h b/js/src/jit/mips32/MacroAssembler-mips32-inl.h index 8a2e8c6bfd..a805f7bdb9 100644 --- a/js/src/jit/mips32/MacroAssembler-mips32-inl.h +++ b/js/src/jit/mips32/MacroAssembler-mips32-inl.h @@ -963,6 +963,19 @@ MacroAssembler::branchTestSymbol(Condition cond, const ValueOperand& value, Labe branchTestSymbol(cond, value.typeReg(), label); } +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) +{ + branchTestBigInt(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestBigIntTruthy(bool b, const ValueOperand& value, Label* label) +{ + Register bi = value.payloadReg(); + SecondScratchRegisterScope scratch2(*this); + ma_lw(scratch2, Address(bi, BigInt::offsetOfLengthSignAndReservedBits())); + ma_b(scratch2, Imm32(0), label, b ? NotEqual : Equal); +} + void MacroAssembler::branchTestNull(Condition cond, const ValueOperand& value, Label* label) { diff --git a/js/src/jit/mips64/CodeGenerator-mips64.cpp b/js/src/jit/mips64/CodeGenerator-mips64.cpp index 7cd90fba7a..8e96171615 100644 --- a/js/src/jit/mips64/CodeGenerator-mips64.cpp +++ b/js/src/jit/mips64/CodeGenerator-mips64.cpp @@ -196,6 +196,9 @@ CodeGeneratorMIPS64::visitUnbox(LUnbox* unbox) case MIRType::Symbol: masm.unboxSymbol(inputReg, result); break; + case MIRType::BigInt: + masm.unboxBigInt(inputReg, result); + break; default: MOZ_CRASH("Given MIRType cannot be unboxed."); } @@ -219,6 +222,9 @@ CodeGeneratorMIPS64::visitUnbox(LUnbox* unbox) case MIRType::Symbol: masm.unboxSymbol(inputAddr, result); break; + case MIRType::BigInt: + masm.unboxBigInt(inputAddr, result); + break; default: MOZ_CRASH("Given MIRType cannot be unboxed."); } diff --git a/js/src/jit/mips64/MacroAssembler-mips64-inl.h b/js/src/jit/mips64/MacroAssembler-mips64-inl.h index 4c1f3e37f0..9ed2b35a36 100644 --- a/js/src/jit/mips64/MacroAssembler-mips64-inl.h +++ b/js/src/jit/mips64/MacroAssembler-mips64-inl.h @@ -622,6 +622,21 @@ MacroAssembler::branchTestSymbol(Condition cond, const ValueOperand& value, Labe branchTestSymbol(cond, scratch2, label); } +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) +{ + SecondScratchRegisterScope scratch2(*this); + splitTag(value, scratch2); + branchTestBigInt(cond, scratch2, label); +} + +void MacroAssembler::branchTestBigIntTruthy(bool b, const ValueOperand& value, Label* label) +{ + SecondScratchRegisterScope scratch2(*this); + unboxBigInt(value, scratch2); + loadPtr(Address(scratch2, BigInt::offsetOfLengthSignAndReservedBits()), scratch2); + ma_b(scratch2, ImmWord(0), label, b ? NotEqual : Equal); +} + void MacroAssembler::branchTestNull(Condition cond, const ValueOperand& value, Label* label) { diff --git a/js/src/jit/mips64/MacroAssembler-mips64.cpp b/js/src/jit/mips64/MacroAssembler-mips64.cpp index 96989e393d..df7ec624a9 100644 --- a/js/src/jit/mips64/MacroAssembler-mips64.cpp +++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp @@ -1495,6 +1495,21 @@ MacroAssemblerMIPS64Compat::unboxSymbol(Register src, Register dest) ma_dext(dest, src, Imm32(0), Imm32(JSVAL_TAG_SHIFT)); } +void MacroAssemblerMIPS64Compat::unboxBigInt(const ValueOperand& operand, Register dest) +{ + unboxNonDouble(operand, dest, JSVAL_TYPE_BIGINT); +} + +void MacroAssemblerMIPS64Compat::unboxBigInt(Register src, Register dest) +{ + unboxNonDouble(src, dest, JSVAL_TYPE_BIGINT); +} + +void MacroAssemblerMIPS64Compat::unboxBigInt(const Address& src, Register dest) +{ + unboxNonDouble(src, dest, JSVAL_TYPE_BIGINT); +} + void MacroAssemblerMIPS64Compat::unboxSymbol(const Address& src, Register dest) { diff --git a/js/src/jit/mips64/MacroAssembler-mips64.h b/js/src/jit/mips64/MacroAssembler-mips64.h index 04aa2ea702..3a84b7c0c9 100644 --- a/js/src/jit/mips64/MacroAssembler-mips64.h +++ b/js/src/jit/mips64/MacroAssembler-mips64.h @@ -361,6 +361,9 @@ class MacroAssemblerMIPS64Compat : public MacroAssemblerMIPS64 void unboxSymbol(const ValueOperand& src, Register dest); void unboxSymbol(Register src, Register dest); void unboxSymbol(const Address& src, Register dest); + void unboxBigInt(const ValueOperand& operand, Register dest); + void unboxBigInt(Register src, Register dest); + void unboxBigInt(const Address& src, Register dest); void unboxObject(const ValueOperand& src, Register dest); void unboxObject(Register src, Register dest); void unboxObject(const Address& src, Register dest); diff --git a/js/src/jit/none/MacroAssembler-none.h b/js/src/jit/none/MacroAssembler-none.h index 599fe75bfb..f71439a6cc 100644 --- a/js/src/jit/none/MacroAssembler-none.h +++ b/js/src/jit/none/MacroAssembler-none.h @@ -359,6 +359,7 @@ class MacroAssemblerNone : public Assembler template <typename T> void unboxBoolean(T, Register) { MOZ_CRASH(); } template <typename T> void unboxString(T, Register) { MOZ_CRASH(); } template <typename T> void unboxSymbol(T, Register) { MOZ_CRASH(); } + template <typename T> void unboxBigInt(T, Register) { MOZ_CRASH(); } template <typename T> void unboxObject(T, Register) { MOZ_CRASH(); } template <typename T> void unboxDouble(T, FloatRegister) { MOZ_CRASH(); } void unboxValue(const ValueOperand&, AnyRegister) { MOZ_CRASH(); } @@ -393,6 +394,7 @@ class MacroAssemblerNone : public Assembler void loadConstantFloat32(wasm::RawF32, FloatRegister) { MOZ_CRASH(); } Condition testInt32Truthy(bool, ValueOperand) { MOZ_CRASH(); } Condition testStringTruthy(bool, ValueOperand) { MOZ_CRASH(); } + Condition testBigIntTruthy(bool, ValueOperand) { MOZ_CRASH(); } template <typename T> void loadUnboxedValue(T, MIRType, AnyRegister) { MOZ_CRASH(); } template <typename T> void storeUnboxedValue(const ConstantOrRegister&, MIRType, T, MIRType) { MOZ_CRASH(); } diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp index 16e082745c..3701f24872 100644 --- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -431,6 +431,7 @@ CodeGeneratorShared::encodeAllocation(LSnapshot* snapshot, MDefinition* mir, case MIRType::Int32: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: case MIRType::ObjectOrNull: case MIRType::Boolean: diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 2d51a580bf..69782f8061 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -3211,7 +3211,7 @@ class LBitNotI : public LInstructionHelper<1, 1, 0> }; // Call a VM function to perform a BITNOT operation. -class LBitNotV : public LCallInstructionHelper<1, BOX_PIECES, 0> +class LBitNotV : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES, 0> { public: LIR_HEADER(BitNotV) @@ -3271,7 +3271,7 @@ class LBitOpI64 : public LInstructionHelper<INT64_PIECES, 2 * INT64_PIECES, 0> }; // Call a VM function to perform a bitwise operation. -class LBitOpV : public LCallInstructionHelper<1, 2 * BOX_PIECES, 0> +class LBitOpV : public LCallInstructionHelper<BOX_PIECES, 2 * BOX_PIECES, 0> { JSOp jsop_; diff --git a/js/src/jit/shared/Lowering-shared-inl.h b/js/src/jit/shared/Lowering-shared-inl.h index d17a9ba568..cdbba9ef84 100644 --- a/js/src/jit/shared/Lowering-shared-inl.h +++ b/js/src/jit/shared/Lowering-shared-inl.h @@ -402,7 +402,8 @@ LIRGeneratorShared::redefine(MDefinition* def, MDefinition* as) case MIRType::Object: case MIRType::ObjectOrNull: case MIRType::String: - case MIRType::Symbol: { + case MIRType::Symbol: + case MIRType::BigInt: { LAssertResultT* check = new(alloc()) LAssertResultT(useRegister(def)); add(check, def->toInstruction()); break; diff --git a/js/src/jit/shared/Lowering-shared.cpp b/js/src/jit/shared/Lowering-shared.cpp index 0acf34dfb4..f0f4e5eb85 100644 --- a/js/src/jit/shared/Lowering-shared.cpp +++ b/js/src/jit/shared/Lowering-shared.cpp @@ -100,6 +100,9 @@ LIRGeneratorShared::visitConstant(MConstant* ins) case MIRType::Symbol: define(new(alloc()) LPointer(ins->toSymbol()), ins); break; + case MIRType::BigInt: + define(new (alloc()) LPointer(ins->toBigInt()), ins); + break; case MIRType::Object: define(new(alloc()) LPointer(&ins->toObject()), ins); break; diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp index 61ce8a187c..9dea005abb 100644 --- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -121,6 +121,9 @@ CodeGeneratorX64::visitUnbox(LUnbox* unbox) case MIRType::Symbol: cond = masm.testSymbol(Assembler::NotEqual, value); break; + case MIRType::BigInt: + cond = masm.testBigInt(Assembler::NotEqual, value); + break; default: MOZ_CRASH("Given MIRType cannot be unboxed."); } @@ -145,6 +148,9 @@ CodeGeneratorX64::visitUnbox(LUnbox* unbox) case MIRType::Symbol: masm.unboxSymbol(input, result); break; + case MIRType::BigInt: + masm.unboxBigInt(input, result); + break; default: MOZ_CRASH("Given MIRType cannot be unboxed."); } diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index 3af87e1ef8..7acb2d34f6 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -231,6 +231,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared cmp32(tag, ImmTag(JSVAL_TAG_SYMBOL)); return cond; } + Condition testBigInt(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + cmp32(tag, ImmTag(JSVAL_TAG_BIGINT)); + return cond; + } Condition testObject(Condition cond, Register tag) { MOZ_ASSERT(cond == Equal || cond == NotEqual); cmp32(tag, ImmTag(JSVAL_TAG_OBJECT)); @@ -306,6 +311,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared splitTag(src, scratch); return testSymbol(cond, scratch); } + Condition testBigInt(Condition cond, const ValueOperand& src) { + ScratchRegisterScope scratch(asMasm()); + splitTag(src, scratch); + return testBigInt(cond, scratch); + } Condition testObject(Condition cond, const ValueOperand& src) { ScratchRegisterScope scratch(asMasm()); splitTag(src, scratch); @@ -359,6 +369,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared splitTag(src, scratch); return testSymbol(cond, scratch); } + Condition testBigInt(Condition cond, const Address& src) { + ScratchRegisterScope scratch(asMasm()); + splitTag(src, scratch); + return testBigInt(cond, scratch); + } Condition testObject(Condition cond, const Address& src) { ScratchRegisterScope scratch(asMasm()); splitTag(src, scratch); @@ -406,6 +421,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared splitTag(src, scratch); return testSymbol(cond, scratch); } + Condition testBigInt(Condition cond, const BaseIndex& src) { + ScratchRegisterScope scratch(asMasm()); + splitTag(src, scratch); + return testBigInt(cond, scratch); + } Condition testInt32(Condition cond, const BaseIndex& src) { ScratchRegisterScope scratch(asMasm()); splitTag(src, scratch); @@ -794,6 +814,9 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared void unboxSymbol(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const Operand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const Operand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxObject(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const Operand& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const Address& src, Register dest) { unboxNonDouble(Operand(src), dest); } @@ -894,6 +917,13 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared cmp32(Operand(scratch, JSString::offsetOfLength()), Imm32(0)); return truthy ? Assembler::NotEqual : Assembler::Equal; } + Condition testBigIntTruthy(bool truthy, const ValueOperand& value) { + ScratchRegisterScope scratch(asMasm()); + unboxBigInt(value, scratch); + cmpPtr(Operand(scratch, BigInt::offsetOfLengthSignAndReservedBits()), + ImmWord(0)); + return truthy ? Assembler::NotEqual : Assembler::Equal; + } template <typename T> inline void loadInt32OrDouble(const T& src, FloatRegister dest); diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h index e7f058c396..36f3a008a9 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h @@ -944,6 +944,30 @@ MacroAssembler::branchTestSymbolImpl(Condition cond, const T& t, Label* label) j(cond, label); } +void MacroAssembler::branchTestBigInt(Condition cond, Register tag, Label* label) { + branchTestBigIntImpl(cond, tag, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const BaseIndex& address, Label* label) { + branchTestBigIntImpl(cond, address, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) { + branchTestBigIntImpl(cond, value, label); +} + +template <typename T> +void MacroAssembler::branchTestBigIntImpl(Condition cond, const T& t, Label* label) { + cond = testBigInt(cond, t); + j(cond, label); +} + +void MacroAssembler::branchTestBigIntTruthy(bool truthy, const ValueOperand& value, Label* label) +{ + Condition cond = testBigIntTruthy(truthy, value); + j(cond, label); +} + void MacroAssembler::branchTestNull(Condition cond, Register tag, Label* label) { diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index 01efd007d3..cbe1d6da8c 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -294,6 +294,11 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cmp32(tag, ImmTag(JSVAL_TAG_SYMBOL)); return cond; } + Condition testBigInt(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + cmp32(tag, ImmTag(JSVAL_TAG_BIGINT)); + return cond; + } Condition testObject(Condition cond, Register tag) { MOZ_ASSERT(cond == Equal || cond == NotEqual); cmp32(tag, ImmTag(JSVAL_TAG_OBJECT)); @@ -410,6 +415,9 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared Condition testSymbol(Condition cond, const ValueOperand& value) { return testSymbol(cond, value.typeReg()); } + Condition testBigInt(Condition cond, const ValueOperand& value) { + return testBigInt(cond, value.typeReg()); + } Condition testObject(Condition cond, const ValueOperand& value) { return testObject(cond, value.typeReg()); } @@ -455,6 +463,11 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cmp32(tagOf(address), ImmTag(JSVAL_TAG_SYMBOL)); return cond; } + Condition testBigInt(Condition cond, const BaseIndex& address) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + cmp32(tagOf(address), ImmTag(JSVAL_TAG_BIGINT)); + return cond; + } Condition testInt32(Condition cond, const BaseIndex& address) { MOZ_ASSERT(cond == Equal || cond == NotEqual); cmp32(tagOf(address), ImmTag(JSVAL_TAG_INT32)); @@ -696,6 +709,8 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared void unboxString(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const Address& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const BaseIndex& src, Register dest) { unboxNonDouble(src, dest); } @@ -793,6 +808,12 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cmp32(Operand(string, JSString::offsetOfLength()), Imm32(0)); return truthy ? Assembler::NotEqual : Assembler::Equal; } + Condition testBigIntTruthy(bool truthy, const ValueOperand& value) { + Register bi = value.payloadReg(); + cmpPtr(Operand(bi, BigInt::offsetOfLengthSignAndReservedBits()), + ImmWord(0)); + return truthy ? Assembler::NotEqual : Assembler::Equal; + } template <typename T> inline void loadInt32OrDouble(const T& src, FloatRegister dest); diff --git a/js/src/js.msg b/js/src/js.msg index 242d81a5c8..1cb571d189 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -619,3 +619,13 @@ MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_TYPEERR, "'for await' loop shoul MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator") MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator") MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value") + +// BigInt +MSG_DEF(JSMSG_BIGINT_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert BigInt to number") +MSG_DEF(JSMSG_NUMBER_TO_BIGINT, 0, JSEXN_RANGEERR, "can't convert non-finite number to BigInt") +MSG_DEF(JSMSG_BIGINT_TOO_LARGE, 0, JSEXN_RANGEERR, "BigInt is too large to allocate") +MSG_DEF(JSMSG_BIGINT_DIVISION_BY_ZERO, 0, JSEXN_RANGEERR, "BigInt division by zero") +MSG_DEF(JSMSG_BIGINT_NEGATIVE_EXPONENT, 0, JSEXN_RANGEERR, "BigInt negative exponent") +MSG_DEF(JSMSG_BIGINT_INVALID_SYNTAX, 0, JSEXN_SYNTAXERR, "invalid BigInt syntax") +MSG_DEF(JSMSG_NOT_BIGINT, 0, JSEXN_TYPEERR, "not a BigInt") +MSG_DEF(JSMSG_BIGINT_NOT_SERIALIZABLE, 0, JSEXN_TYPEERR, "BigInt value can't be serialized in JSON") diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index b166a786ca..cc0fcbebd9 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -1099,6 +1099,12 @@ ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_ * with those as well. */ break; + } else if (elem.isBigInt()) { + // ToString(bigint) doesn't access bigint.toString or + // anything like that, so it can't mutate the array we're + // walking through, so it *could* be handled here. We don't + // do so yet for reasons of initial-implementation economy. + break; } else { MOZ_ASSERT(elem.isMagic(JS_ELEMENTS_HOLE) || elem.isNullOrUndefined()); } diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 02308314c9..2a72ac38a3 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -28,6 +28,8 @@ #include "vm/String-inl.h" +#include "vm/BigIntType.h" + using namespace js; using namespace js::gc; @@ -484,6 +486,13 @@ ToAtomSlow(ExclusiveContext* cx, typename MaybeRooted<Value, allowGC>::HandleTyp } return nullptr; } + if (v.isBigInt()) { + RootedBigInt i(cx, v.toBigInt()); + JSAtom* atom = BigIntToAtom(cx, i); + if (!allowGC && !atom) + cx->recoverFromOutOfMemory(); + return atom; + } MOZ_ASSERT(v.isUndefined()); return cx->names().undefined; } diff --git a/js/src/jsbool.cpp b/js/src/jsbool.cpp index 324868216b..0a70fe49f2 100644 --- a/js/src/jsbool.cpp +++ b/js/src/jsbool.cpp @@ -18,6 +18,7 @@ #include "vm/GlobalObject.h" #include "vm/ProxyObject.h" #include "vm/StringBuffer.h" +#include "vm/BigIntType.h" #include "vm/BooleanObject-inl.h" @@ -170,6 +171,8 @@ js::ToBooleanSlow(HandleValue v) { if (v.isString()) return v.toString()->length() != 0; + if (v.isBigInt()) + return !v.toBigInt()->isZero(); MOZ_ASSERT(v.isObject()); return !EmulatesUndefined(&v.toObject()); diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index c93dee510b..926c7e3c86 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -340,6 +340,21 @@ JSCompartment::wrap(JSContext* cx, MutableHandleString strp) } bool +JSCompartment::wrap(JSContext* cx, MutableHandleBigInt bi) +{ + MOZ_ASSERT(cx->compartment() == this); + + if (bi->zone() == cx->zone()) + return true; + + BigInt* copy = BigInt::copy(cx, bi); + if (!copy) + return false; + bi.set(copy); + return true; +} + +bool JSCompartment::getNonWrapperObjectForCurrentCompartment(JSContext* cx, MutableHandleObject obj) { // Ensure that we have entered a compartment. diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index dbada58439..a02b39301d 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -583,6 +583,7 @@ struct JSCompartment MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp); MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandleString strp); + MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandle<JS::BigInt*> bi); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandleObject obj); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<js::PropertyDescriptor> desc); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<JS::GCVector<JS::Value>> vec); diff --git a/js/src/jscompartmentinlines.h b/js/src/jscompartmentinlines.h index c17ba99de2..c092889e28 100644 --- a/js/src/jscompartmentinlines.h +++ b/js/src/jscompartmentinlines.h @@ -79,6 +79,14 @@ JSCompartment::wrap(JSContext* cx, JS::MutableHandleValue vp) return true; } + if (vp.isBigInt()) { + JS::RootedBigInt bi(cx, vp.toBigInt()); + if (!wrap(cx, &bi)) + return false; + vp.setBigInt(bi); + return true; + } + MOZ_ASSERT(vp.isObject()); /* diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 0cd57ab52b..f73cc5fd65 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -9,6 +9,8 @@ #include <stdint.h> +#include "builtin/BigInt.h" + #include "jscntxt.h" #include "jscompartment.h" #include "jsgc.h" @@ -299,6 +301,8 @@ js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClass* cls) *cls = ESClass::Arguments; else if (obj->is<ErrorObject>()) *cls = ESClass::Error; + else if (obj->is<BigIntObject>()) + *cls = ESClass::BigInt; else *cls = ESClass::Other; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index e172ea36c2..5c2835cca9 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -222,6 +222,7 @@ #include "jit/JitcodeMap.h" #include "js/SliceBudget.h" #include "proxy/DeadObjectProxy.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/ProxyObject.h" #include "vm/Shape.h" @@ -371,7 +372,8 @@ static const FinalizePhase BackgroundFinalizePhases[] = { AllocKind::STRING, AllocKind::FAT_INLINE_ATOM, AllocKind::ATOM, - AllocKind::SYMBOL + AllocKind::SYMBOL, + AllocKind::BIGINT } }, { @@ -1986,6 +1988,7 @@ MovingTracer::updateEdge(T** thingp) void MovingTracer::onObjectEdge(JSObject** objp) { updateEdge(objp); } void MovingTracer::onShapeEdge(Shape** shapep) { updateEdge(shapep); } void MovingTracer::onStringEdge(JSString** stringp) { updateEdge(stringp); } +void MovingTracer::onBigIntEdge(JS::BigInt** bip) { updateEdge(bip); } void MovingTracer::onScriptEdge(JSScript** scriptp) { updateEdge(scriptp); } void MovingTracer::onLazyScriptEdge(LazyScript** lazyp) { updateEdge(lazyp); } void MovingTracer::onBaseShapeEdge(BaseShape** basep) { updateEdge(basep); } @@ -6557,6 +6560,8 @@ JS::GCCellPtr::GCCellPtr(const Value& v) ptr = checkedCast(&v.toObject(), JS::TraceKind::Object); else if (v.isSymbol()) ptr = checkedCast(v.toSymbol(), JS::TraceKind::Symbol); + else if (v.isBigInt()) + ptr = checkedCast(v.toBigInt(), JS::TraceKind::BigInt); else if (v.isPrivateGCThing()) ptr = checkedCast(v.toGCThing(), v.toGCThing()->getTraceKind()); else diff --git a/js/src/jsgc.h b/js/src/jsgc.h index ffd6102747..521dea05c6 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -20,6 +20,7 @@ #include "threading/ConditionVariable.h" #include "threading/Thread.h" #include "vm/NativeObject.h" +#include "vm/BigIntType.h" namespace js { @@ -119,6 +120,7 @@ IsNurseryAllocable(AllocKind kind) false, /* AllocKind::FAT_INLINE_ATOM */ false, /* AllocKind::ATOM */ false, /* AllocKind::SYMBOL */ + false, /* AllocKind::BIGINT */ false, /* AllocKind::JITCODE */ false, /* AllocKind::SCOPE */ false, /* AllocKind::REGEXP_SHARED */ @@ -158,6 +160,7 @@ IsBackgroundFinalized(AllocKind kind) true, /* AllocKind::FAT_INLINE_ATOM */ true, /* AllocKind::ATOM */ true, /* AllocKind::SYMBOL */ + true, /* AllocKind::BIGINT */ false, /* AllocKind::JITCODE */ true, /* AllocKind::SCOPE */ true, /* AllocKind::REGEXP_SHARED */ diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 75ab8fcb29..8f17ff15ca 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -13,6 +13,7 @@ #include "mozilla/MathAlgorithms.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Unused.h" +#include "mozilla/WrappingOperations.h" #include <algorithm> // for std::max #include <fcntl.h> @@ -103,6 +104,7 @@ using mozilla::IsNegative; using mozilla::IsNegativeZero; using mozilla::PositiveInfinity; using mozilla::NegativeInfinity; +using mozilla::WrappingMultiply; using JS::ToNumber; using JS::GenericNaN; @@ -458,16 +460,13 @@ js::math_floor(JSContext* cx, unsigned argc, Value* vp) bool js::math_imul_handle(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { - uint32_t a = 0, b = 0; - if (!lhs.isUndefined() && !ToUint32(cx, lhs, &a)) + int32_t a = 0, b = 0; + if (!lhs.isUndefined() && !ToInt32(cx, lhs, &a)) return false; - if (!rhs.isUndefined() && !ToUint32(cx, rhs, &b)) + if (!rhs.isUndefined() && !ToInt32(cx, rhs, &b)) return false; - uint32_t product = a * b; - res.setInt32(product > INT32_MAX - ? int32_t(INT32_MIN + (product - INT32_MAX - 1)) - : int32_t(product)); + res.setInt32(WrappingMultiply(a, b)); return true; } @@ -686,29 +685,23 @@ js::ecmaPow(double x, double y) } bool -js::math_pow_handle(JSContext* cx, HandleValue base, HandleValue power, MutableHandleValue result) +js::math_pow(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + double x; - if (!ToNumber(cx, base, &x)) + if (!ToNumber(cx, args.get(0), &x)) return false; double y; - if (!ToNumber(cx, power, &y)) + if (!ToNumber(cx, args.get(1), &y)) return false; double z = ecmaPow(x, y); - result.setNumber(z); + args.rval().setNumber(z); return true; } -bool -js::math_pow(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - return math_pow_handle(cx, args.get(0), args.get(1), args.rval()); -} - uint64_t js::GenerateRandomSeed() { diff --git a/js/src/jsmath.h b/js/src/jsmath.h index e716edce14..558d3d356a 100644 --- a/js/src/jsmath.h +++ b/js/src/jsmath.h @@ -124,10 +124,6 @@ extern bool math_sqrt(JSContext* cx, unsigned argc, js::Value* vp); extern bool -math_pow_handle(JSContext* cx, js::HandleValue base, js::HandleValue power, - js::MutableHandleValue result); - -extern bool math_pow(JSContext* cx, unsigned argc, js::Value* vp); extern bool diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 549596e579..573b55cc85 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -524,15 +524,22 @@ Number(JSContext* cx, unsigned argc, Value* vp) bool isConstructing = args.isConstructing(); if (args.length() > 0) { - if (!ToNumber(cx, args[0])) + // BigInt proposal section 6.2, steps 2a-c. + if (!ToNumeric(cx, args[0])) return false; - args.rval().set(args[0]); - } else { - args.rval().setInt32(0); + if (args[0].isBigInt()) + args[0].setNumber(BigInt::numberValue(args[0].toBigInt())); + MOZ_ASSERT(args[0].isNumber()); } - if (!isConstructing) + if (!isConstructing) { + if (args.length() > 0) { + args.rval().set(args[0]); + } else { + args.rval().setInt32(0); + } return true; + } RootedObject newTarget(cx, &args.newTarget().toObject()); RootedObject proto(cx); @@ -1452,9 +1459,19 @@ js::ToNumberSlow(ExclusiveContext* cx, HandleValue v_, double* out) return false; } - MOZ_ASSERT(v.isUndefined()); - *out = GenericNaN(); - return true; + if (v.isUndefined()) { + *out = GenericNaN(); + return true; + } + + MOZ_ASSERT(v.isSymbol() || v.isBigInt()); + if (cx->isJSContext()) { + unsigned errnum = JSMSG_SYMBOL_TO_NUMBER; + if (v.isBigInt()) + errnum = JSMSG_BIGINT_TO_NUMBER; + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, errnum); + } + return false; } JS_PUBLIC_API(bool) @@ -1482,6 +1499,30 @@ js::ToInt8Slow(JSContext *cx, const HandleValue v, int8_t *out) return true; } +// BigInt proposal section 3.1.6 +bool +js::ToNumericSlow(ExclusiveContext* cx, MutableHandleValue vp) +{ + MOZ_ASSERT(!vp.isNumber()); + MOZ_ASSERT(!vp.isBigInt()); + + // Step 1. + if (!vp.isPrimitive()) { + if (!cx->isJSContext()) + return false; + if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, vp)) + return false; + } + + // Step 2. + if (vp.isBigInt()) { + return true; + } + + // Step 3. + return ToNumber(cx->asJSContext(), vp); +} + /* * Convert a value to an uint8_t, according to the ToUInt8() function in ES6 * ECMA-262, 7.1.10. Return converted value in *out on success, false on failure. @@ -1573,6 +1614,27 @@ js::ToInt32Slow(JSContext* cx, const HandleValue v, int32_t* out) return true; } +bool +js::ToInt32OrBigIntSlow(JSContext* cx, MutableHandleValue vp) +{ + MOZ_ASSERT(!vp.isInt32()); + if (vp.isDouble()) { + vp.setInt32(ToInt32(vp.toDouble())); + return true; + } + + if (!ToNumeric(cx, vp)) { + return false; + } + + if (vp.isBigInt()) { + return true; + } + + vp.setInt32(ToInt32(vp.toNumber())); + return true; +} + JS_PUBLIC_API(bool) js::ToUint32Slow(JSContext* cx, const HandleValue v, uint32_t* out) { @@ -1706,6 +1768,35 @@ js::ToIntegerIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) return true; } +// ES2017 draft 7.1.17 ToIndex +bool +js::ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) +{ + // Step 1. + if (v.isUndefined()) { + *index = 0; + return true; + } + + // Step 2.a. + double integerIndex; + if (!ToInteger(cx, v, &integerIndex)) + return false; + + // Inlined version of ToLength. + // 1. Already an integer. + // 2. Step eliminates < 0, +0 == -0 with SameValueZero. + // 3/4. Limit to <= 2^53-1, so everything above should fail. + if (integerIndex < 0 || integerIndex >= DOUBLE_INTEGRAL_PRECISION_LIMIT) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; + } + + // Step 3. + *index = uint64_t(integerIndex); + return true; +} + template <typename CharT> bool js_strtod(ExclusiveContext* cx, const CharT* begin, const CharT* end, const CharT** dEnd, diff --git a/js/src/jsnum.h b/js/src/jsnum.h index f169a9c38d..bd53fdc1a0 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -308,6 +308,15 @@ MOZ_MUST_USE bool ToLengthClamped(T* cx, HandleValue v, uint32_t* out, bool* ove */ MOZ_MUST_USE bool ToIntegerIndex(JSContext* cx, JS::HandleValue v, uint64_t* index); +/* ES2017 draft 7.1.17 ToIndex + * + * Return true and set |*index| to the integer value if |v| is a valid + * integer index value. Otherwise report a RangeError and return false. + * + * The returned index will always be in the range 0 <= *index <= 2^53-1. + */ +MOZ_MUST_USE bool ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index); + MOZ_MUST_USE inline bool SafeAdd(int32_t one, int32_t two, int32_t* res) { @@ -362,6 +371,32 @@ ToNumber(ExclusiveContext* cx, HandleValue v, double* out) return ToNumberSlow(cx, v, out); } +bool +ToNumericSlow(ExclusiveContext* cx, JS::MutableHandleValue vp); + +// BigInt proposal section 3.1.6 +MOZ_ALWAYS_INLINE MOZ_MUST_USE bool +ToNumeric(ExclusiveContext* cx, JS::MutableHandleValue vp) +{ + if (vp.isNumber()) + return true; + if (vp.isBigInt()) + return true; + return ToNumericSlow(cx, vp); +} + +bool +ToInt32OrBigIntSlow(JSContext* cx, JS::MutableHandleValue vp); + +MOZ_ALWAYS_INLINE MOZ_MUST_USE bool +ToInt32OrBigInt(JSContext* cx, JS::MutableHandleValue vp) +{ + if (vp.isInt32()) { + return true; + } + return ToInt32OrBigIntSlow(cx, vp); +} + void FIX_FPU(); } /* namespace js */ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index b0cf6bc69b..d0f9430bc8 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -35,6 +35,7 @@ #include "jswin.h" #include "jswrapper.h" +#include "builtin/BigInt.h" #include "builtin/Eval.h" #include "builtin/Object.h" #include "builtin/SymbolObject.h" @@ -3104,9 +3105,13 @@ js::PrimitiveToObject(JSContext* cx, const Value& v) return NumberObject::create(cx, v.toNumber()); if (v.isBoolean()) return BooleanObject::create(cx, v.toBoolean()); - MOZ_ASSERT(v.isSymbol()); - RootedSymbol symbol(cx, v.toSymbol()); - return SymbolObject::create(cx, symbol); + if (v.isSymbol()) { + RootedSymbol symbol(cx, v.toSymbol()); + return SymbolObject::create(cx, symbol); + } + MOZ_ASSERT(v.isBigInt()); + RootedBigInt bigInt(cx, v.toBigInt()); + return BigIntObject::create(cx, bigInt); } /* diff --git a/js/src/json.cpp b/js/src/json.cpp index f3cf22dac9..d426fc721a 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -9,6 +9,8 @@ #include "mozilla/Range.h" #include "mozilla/ScopeExit.h" +#include "builtin/BigInt.h" + #include "jsarray.h" #include "jsatom.h" #include "jscntxt.h" @@ -328,6 +330,8 @@ PreprocessValue(JSContext* cx, HandleObject holder, KeyType key, MutableHandleVa } else if (cls == ESClass::Boolean) { if (!Unbox(cx, obj, vp)) return false; + } else if (cls == ESClass::BigInt) { + vp.setBigInt(obj->as<BigIntObject>().unbox()); } } @@ -626,6 +630,12 @@ Str(JSContext* cx, const Value& v, StringifyContext* scx) return NumberValueToStringBuffer(cx, v, scx->sb); } + /* Step 10 in the BigInt proposal. */ + if (v.isBigInt()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BIGINT_NOT_SERIALIZABLE); + return false; + } + /* Step 10. */ MOZ_ASSERT(v.isObject()); RootedObject obj(cx, &v.toObject()); diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 5573d3d48b..c9ec240e7f 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -1039,6 +1039,9 @@ js::Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, break; } + case JOF_BIGINT: + // Fallthrough. + case JOF_DOUBLE: { RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc))); JSAutoByteString bytes; diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 11d0429298..70ae964b23 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -56,6 +56,7 @@ enum { JOF_ATOMOBJECT = 19, /* uint16_t constant index + object index */ JOF_SCOPE = 20, /* unsigned 32-bit scope index */ JOF_ENVCOORD = 21, /* embedded ScopeCoordinate immediate */ + JOF_BIGINT = 22, /* uint32_t index for BigInt value */ JOF_TYPEMASK = 0x001f, /* mask for above immediate types */ JOF_NAME = 1 << 5, /* name operation */ diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 7de6b0245a..75168d37ef 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -91,6 +91,7 @@ real(Float32Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Float32)) \ real(Float64Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Float64)) \ real(Uint8ClampedArray, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint8Clamped)) \ + real(BigInt, InitViaClassSpec, OCLASP(BigInt)) \ real(Proxy, InitProxyClass, js::ProxyClassPtr) \ real(WeakMap, InitWeakMapClass, OCLASP(WeakMap)) \ real(Map, InitMapClass, OCLASP(Map)) \ diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 01d3143023..4bc8b0649a 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -80,6 +80,7 @@ enum JSType { JSTYPE_BOOLEAN, /* boolean */ JSTYPE_NULL, /* null */ JSTYPE_SYMBOL, /* symbol */ + JSTYPE_BIGINT, /* BigInt */ JSTYPE_LIMIT }; diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 49a8a3ad04..5cf0d19195 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -83,7 +83,8 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) SCRIPT_NULL, SCRIPT_OBJECT, SCRIPT_VOID, - SCRIPT_HOLE + SCRIPT_HOLE, + SCRIPT_BIGINT }; ConstTag tag; @@ -104,6 +105,8 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) tag = SCRIPT_OBJECT; } else if (vp.isMagic(JS_ELEMENTS_HOLE)) { tag = SCRIPT_HOLE; + } else if (vp.isBigInt()) { + tag = SCRIPT_BIGINT; } else { MOZ_ASSERT(vp.isUndefined()); tag = SCRIPT_VOID; @@ -176,6 +179,20 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) if (mode == XDR_DECODE) vp.setMagic(JS_ELEMENTS_HOLE); break; + case SCRIPT_BIGINT: { + RootedBigInt bi(cx); + if (mode == XDR_ENCODE) { + bi = vp.toBigInt(); + } + + if(!XDRBigInt(xdr, &bi)) + return false; + + if (mode == XDR_DECODE) { + vp.setBigInt(bi); + } + break; + } default: // Fail in debug, but only soft-fail in release MOZ_ASSERT(false, "Bad XDR value kind"); @@ -3418,6 +3435,38 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, } } + /* Constants */ + + AutoValueVector consts(cx); + if (nconsts != 0) { + GCPtrValue* vector = src->consts()->vector; + RootedValue val(cx); + RootedValue clone(cx); + for (unsigned i = 0; i < nconsts; i++) { + val = vector[i]; + if (val.isDouble()) { + clone = val; + } else if (val.isBigInt()) { + if (cx->zone() == val.toBigInt()->zone()) { + clone.setBigInt(val.toBigInt()); + } else { + RootedBigInt b(cx, val.toBigInt()); + BigInt* copy = BigInt::copy(cx, b); + if (!copy) { + return false; + } + clone.setBigInt(copy); + } + } else { + MOZ_ASSERT_UNREACHABLE("bad script consts() element"); + } + + if (!consts.append(clone)) { + return false; + } + } + } + /* Objects */ AutoObjectVector objects(cx); @@ -3508,7 +3557,7 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, GCPtrValue* vector = Rebase<GCPtrValue>(dst, src, src->consts()->vector); dst->consts()->vector = vector; for (unsigned i = 0; i < nconsts; ++i) - MOZ_ASSERT_IF(vector[i].isGCThing(), vector[i].toString()->isAtom()); + vector[i].init(consts[i]); } if (nobjects != 0) { GCPtrObject* vector = Rebase<GCPtrObject>(dst, src, src->objects()->vector); diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index fdee274c32..593cf4d708 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -3734,6 +3734,11 @@ js::ToStringSlow(ExclusiveContext* cx, typename MaybeRooted<Value, allowGC>::Han JSMSG_SYMBOL_TO_STRING); } return nullptr; + } else if (v.isBigInt()) { + if (!allowGC) + return nullptr; + RootedBigInt i(cx, v.toBigInt()); + str = BigInt::toString(cx, i, 10); } else { MOZ_ASSERT(v.isUndefined()); str = cx->names().undefined; diff --git a/js/src/moz.build b/js/src/moz.build index cecb7ae32d..b75afc2628 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -384,6 +384,7 @@ main_deunified_sources = [ # instantiations may or may not be needed depending on what it gets bundled # with. SOURCES += [ + 'builtin/BigInt.cpp', 'builtin/RegExp.cpp', 'frontend/Parser.cpp', 'gc/StoreBuffer.cpp', @@ -392,6 +393,7 @@ SOURCES += [ 'jsdtoa.cpp', 'jsmath.cpp', 'jsutil.cpp', + 'vm/BigIntType.cpp', 'vm/Initialization.cpp', ] diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 5d355ada9d..3bed40af47 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -264,27 +264,24 @@ ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp) return true; } -/* - * new ArrayBuffer(byteLength) - */ + +// ES2017 draft 24.1.2.1 bool ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + // Step 1. if (!ThrowIfNotConstructing(cx, args, "ArrayBuffer")) return false; - int32_t nbytes = 0; - if (argc > 0 && !ToInt32(cx, args[0], &nbytes)) + // Step 2. + uint64_t byteLength; + if (!ToIndex(cx, args.get(0), &byteLength)) return false; - if (nbytes < 0) { - /* - * We're just not going to support arrays that are bigger than what will fit - * as an integer value; if someone actually ever complains (validly), then we - * can fix. - */ + // Non-standard: Refuse to allocate buffers larger than ~2 GiB. + if (byteLength > INT32_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } @@ -294,7 +291,7 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp) if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return false; - JSObject* bufobj = create(cx, uint32_t(nbytes), proto); + JSObject* bufobj = create(cx, uint32_t(byteLength), proto); if (!bufobj) return false; args.rval().setObject(*bufobj); diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp new file mode 100644 index 0000000000..7b8375526f --- /dev/null +++ b/js/src/vm/BigIntType.cpp @@ -0,0 +1,3206 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Portions of this code taken from WebKit, whose copyright is as follows: + * + * Copyright (C) 2017 Caio Lima <ticaiolima@gmail.com> + * Copyright (C) 2017-2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Portions of this code taken from V8, whose copyright notice is as follows: + * + * Copyright 2017 the V8 project authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Portions of this code taken from Dart, whose copyright notice is as follows: + * + * Copyright (c) 2014 the Dart project authors. Please see the AUTHORS file + * [1] for details. All rights reserved. Use of this source code is governed by + * a BSD-style license that can be found in the LICENSE file [2]. + * + * [1] https://github.com/dart-lang/sdk/blob/master/AUTHORS + * [2] https://github.com/dart-lang/sdk/blob/master/LICENSE + * + * Portions of this code taken from Go, whose copyright notice is as follows: + * + * Copyright 2009 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file [3]. + * + * [3] https://golang.org/LICENSE + */ + +#include "vm/BigIntType.h" + +#include "mozilla/Casting.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Maybe.h" +#include "mozilla/Range.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/WrappingOperations.h" + +#include <functional> +#include <math.h> +#include <memory> + +#include "jsapi.h" +#include "jsnum.h" +#include "jscntxt.h" + +#include "builtin/BigInt.h" +#include "gc/Allocator.h" +#include "js/Initialization.h" +#include "js/Utility.h" +#include "vm/SelfHosting.h" + +#include "vm/String.h" + +using namespace js; + +using mozilla::Abs; +using mozilla::AssertedCast; +using mozilla::BitwiseCast; +using mozilla::IsFinite; +using mozilla::Maybe; +using mozilla::NegativeInfinity; +using mozilla::Nothing; +using mozilla::PositiveInfinity; +using mozilla::Range; +using mozilla::RangedPtr; +using mozilla::Some; +using mozilla::WrapToSigned; + +static inline unsigned DigitLeadingZeroes(BigInt::Digit x) { + return sizeof(x) == 4 ? mozilla::CountLeadingZeroes32(x) + : mozilla::CountLeadingZeroes64(x); +} + +BigInt* BigInt::createUninitialized(ExclusiveContext* cx, size_t length, + bool isNegative) { + if (length > MaxDigitLength) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + + UniquePtr<Digit[], JS::FreePolicy> heapDigits; + if (length > InlineDigitsLength) { + heapDigits = cx->make_pod_array<Digit>(length); + if (!heapDigits) { + return nullptr; + } + } else { + heapDigits = nullptr; + } + + BigInt* x = Allocate<BigInt>(cx); + if (!x) { + return nullptr; + } + + x->lengthSignAndReservedBits_ = + (length << LengthShift) | (isNegative ? SignBit : 0); + MOZ_ASSERT(x->digitLength() == length); + MOZ_ASSERT(x->isNegative() == isNegative); + + if (heapDigits) { + x->heapDigits_ = heapDigits.release(); + } + + return x; +} + +void BigInt::initializeDigitsToZero() { + auto digs = digits(); + std::uninitialized_fill_n(digs.begin(), digs.Length(), 0); +} + +void BigInt::finalize(js::FreeOp* fop) { + if (hasHeapDigits()) { + fop->free_(heapDigits_); + } +} + +js::HashNumber BigInt::hash() { + js::HashNumber h = + mozilla::HashBytes(digits().data(), digitLength() * sizeof(Digit)); + return mozilla::AddToHash(h, isNegative()); +} + +size_t BigInt::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return hasInlineDigits() ? 0 : mallocSizeOf(heapDigits_); +} + +BigInt* BigInt::zero(ExclusiveContext* cx) { + return createUninitialized(cx, 0, false); +} + +BigInt* BigInt::one(ExclusiveContext* cx) { + BigInt* ret = createUninitialized(cx, 1, false); + + if (!ret) { + return nullptr; + } + + ret->setDigit(0, 1); + + return ret; +} + +BigInt* BigInt::neg(ExclusiveContext* cx, HandleBigInt x) { + if (x->isZero()) { + return x; + } + + BigInt* result = copy(cx, x); + if (!result) { + return nullptr; + } + result->lengthSignAndReservedBits_ ^= SignBit; + return result; +} + +#if !defined(JS_64BIT) +#define HAVE_TWO_DIGIT 1 +using TwoDigit = uint64_t; +#elif defined(HAVE_INT128_SUPPORT) +#define HAVE_TWO_DIGIT 1 +using TwoDigit = __uint128_t; +#endif + +inline BigInt::Digit BigInt::digitMul(Digit a, Digit b, Digit* high) { +#if defined(HAVE_TWO_DIGIT) + TwoDigit result = static_cast<TwoDigit>(a) * static_cast<TwoDigit>(b); + *high = result >> DigitBits; + + return static_cast<Digit>(result); +#else + // Multiply in half-pointer-sized chunks. + // For inputs [AH AL]*[BH BL], the result is: + // + // [AL*BL] // rLow + // + [AL*BH] // rMid1 + // + [AH*BL] // rMid2 + // + [AH*BH] // rHigh + // = [R4 R3 R2 R1] // high = [R4 R3], low = [R2 R1] + // + // Where of course we must be careful with carries between the columns. + Digit aLow = a & HalfDigitMask; + Digit aHigh = a >> HalfDigitBits; + Digit bLow = b & HalfDigitMask; + Digit bHigh = b >> HalfDigitBits; + + Digit rLow = aLow * bLow; + Digit rMid1 = aLow * bHigh; + Digit rMid2 = aHigh * bLow; + Digit rHigh = aHigh * bHigh; + + Digit carry = 0; + Digit low = digitAdd(rLow, rMid1 << HalfDigitBits, &carry); + low = digitAdd(low, rMid2 << HalfDigitBits, &carry); + + *high = (rMid1 >> HalfDigitBits) + (rMid2 >> HalfDigitBits) + rHigh + carry; + + return low; +#endif +} + +BigInt::Digit BigInt::digitDiv(Digit high, Digit low, Digit divisor, + Digit* remainder) { + MOZ_ASSERT(high < divisor, "division must not overflow"); +#if defined(__x86_64__) + Digit quotient; + Digit rem; + __asm__("divq %[divisor]" + // Outputs: `quotient` will be in rax, `rem` in rdx. + : "=a"(quotient), "=d"(rem) + // Inputs: put `high` into rdx, `low` into rax, and `divisor` into + // any register or stack slot. + : "d"(high), "a"(low), [divisor] "rm"(divisor)); + *remainder = rem; + return quotient; +#elif defined(__i386__) + Digit quotient; + Digit rem; + __asm__("divl %[divisor]" + // Outputs: `quotient` will be in eax, `rem` in edx. + : "=a"(quotient), "=d"(rem) + // Inputs: put `high` into edx, `low` into eax, and `divisor` into + // any register or stack slot. + : "d"(high), "a"(low), [divisor] "rm"(divisor)); + *remainder = rem; + return quotient; +#else + static constexpr Digit HalfDigitBase = 1ull << HalfDigitBits; + // Adapted from Warren, Hacker's Delight, p. 152. + unsigned s = DigitLeadingZeroes(divisor); + // If `s` is DigitBits here, it causes an undefined behavior. + // But `s` is never DigitBits since `divisor` is never zero here. + MOZ_ASSERT(s != DigitBits); + divisor <<= s; + + Digit vn1 = divisor >> HalfDigitBits; + Digit vn0 = divisor & HalfDigitMask; + + // `sZeroMask` which is 0 if s == 0 and all 1-bits otherwise. + // + // `s` can be 0. If `s` is 0, performing "low >> (DigitBits - s)" must not + // be done since it causes an undefined behavior since `>> DigitBits` is + // undefined in C++. Quoted from C++ spec, "The type of the result is that of + // the promoted left operand. + // + // The behavior is undefined if the right operand is negative, or greater + // than or equal to the length in bits of the promoted left operand". We + // mask the right operand of the shift by `shiftMask` (`DigitBits - 1`), + // which makes `DigitBits - 0` zero. + // + // This shifting produces a value which covers 0 < `s` <= (DigitBits - 1) + // cases. `s` == DigitBits never happen as we asserted. Since `sZeroMask` + // clears the value in the case of `s` == 0, `s` == 0 case is also covered. + static_assert(sizeof(intptr_t) == sizeof(Digit), + "unexpected size of BigInt::Digit"); + Digit sZeroMask = + static_cast<Digit>((-static_cast<intptr_t>(s)) >> (DigitBits - 1)); + static constexpr unsigned shiftMask = DigitBits - 1; + Digit un32 = + (high << s) | ((low >> ((DigitBits - s) & shiftMask)) & sZeroMask); + + Digit un10 = low << s; + Digit un1 = un10 >> HalfDigitBits; + Digit un0 = un10 & HalfDigitMask; + Digit q1 = un32 / vn1; + Digit rhat = un32 - q1 * vn1; + + while (q1 >= HalfDigitBase || q1 * vn0 > rhat * HalfDigitBase + un1) { + q1--; + rhat += vn1; + if (rhat >= HalfDigitBase) { + break; + } + } + + Digit un21 = un32 * HalfDigitBase + un1 - q1 * divisor; + Digit q0 = un21 / vn1; + rhat = un21 - q0 * vn1; + + while (q0 >= HalfDigitBase || q0 * vn0 > rhat * HalfDigitBase + un0) { + q0--; + rhat += vn1; + if (rhat >= HalfDigitBase) { + break; + } + } + + *remainder = (un21 * HalfDigitBase + un0 - q0 * divisor) >> s; + return q1 * HalfDigitBase + q0; +#endif +} + +// Multiplies `source` with `factor` and adds `summand` to the result. +// `result` and `source` may be the same BigInt for inplace modification. +void BigInt::internalMultiplyAdd(BigInt* source, Digit factor, Digit summand, + unsigned n, BigInt* result) { + MOZ_ASSERT(source->digitLength() >= n); + MOZ_ASSERT(result->digitLength() >= n); + + Digit carry = summand; + Digit high = 0; + for (unsigned i = 0; i < n; i++) { + Digit current = source->digit(i); + Digit newCarry = 0; + + // Compute this round's multiplication. + Digit newHigh = 0; + current = digitMul(current, factor, &newHigh); + + // Add last round's carryovers. + current = digitAdd(current, high, &newCarry); + current = digitAdd(current, carry, &newCarry); + + // Store result and prepare for next round. + result->setDigit(i, current); + carry = newCarry; + high = newHigh; + } + + if (result->digitLength() > n) { + result->setDigit(n++, carry + high); + + // Current callers don't pass in such large results, but let's be robust. + while (n < result->digitLength()) { + result->setDigit(n++, 0); + } + } else { + MOZ_ASSERT(!(carry + high)); + } +} + +// Multiplies `this` with `factor` and adds `summand` to the result. +void BigInt::inplaceMultiplyAdd(Digit factor, Digit summand) { + internalMultiplyAdd(this, factor, summand, digitLength(), this); +} + +// Multiplies `multiplicand` with `multiplier` and adds the result to +// `accumulator`, starting at `accumulatorIndex` for the least-significant +// digit. Callers must ensure that `accumulator`'s digitLength and +// corresponding digit storage is long enough to hold the result. +void BigInt::multiplyAccumulate(BigInt* multiplicand, Digit multiplier, + BigInt* accumulator, + unsigned accumulatorIndex) { + MOZ_ASSERT(accumulator->digitLength() > + multiplicand->digitLength() + accumulatorIndex); + if (!multiplier) { + return; + } + + Digit carry = 0; + Digit high = 0; + for (unsigned i = 0; i < multiplicand->digitLength(); + i++, accumulatorIndex++) { + Digit acc = accumulator->digit(accumulatorIndex); + Digit newCarry = 0; + + // Add last round's carryovers. + acc = digitAdd(acc, high, &newCarry); + acc = digitAdd(acc, carry, &newCarry); + + // Compute this round's multiplication. + Digit multiplicandDigit = multiplicand->digit(i); + Digit low = digitMul(multiplier, multiplicandDigit, &high); + acc = digitAdd(acc, low, &newCarry); + + // Store result and prepare for next round. + accumulator->setDigit(accumulatorIndex, acc); + carry = newCarry; + } + + while (carry || high) { + MOZ_ASSERT(accumulatorIndex < accumulator->digitLength()); + Digit acc = accumulator->digit(accumulatorIndex); + Digit newCarry = 0; + acc = digitAdd(acc, high, &newCarry); + high = 0; + acc = digitAdd(acc, carry, &newCarry); + accumulator->setDigit(accumulatorIndex, acc); + carry = newCarry; + accumulatorIndex++; + } +} + +inline int8_t BigInt::absoluteCompare(BigInt* x, BigInt* y) { + MOZ_ASSERT(!x->digitLength() || x->digit(x->digitLength() - 1)); + MOZ_ASSERT(!y->digitLength() || y->digit(y->digitLength() - 1)); + + // Sanity checks to catch negative zeroes escaping to the wild. + MOZ_ASSERT(!x->isNegative() || !x->isZero()); + MOZ_ASSERT(!y->isNegative() || !y->isZero()); + + int diff = x->digitLength() - y->digitLength(); + if (diff) { + return diff < 0 ? -1 : 1; + } + + int i = x->digitLength() - 1; + while (i >= 0 && x->digit(i) == y->digit(i)) { + i--; + } + + if (i < 0) { + return 0; + } + + return x->digit(i) > y->digit(i) ? 1 : -1; +} + +BigInt* BigInt::absoluteAdd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y, + bool resultNegative) { + bool swap = x->digitLength() < y->digitLength(); + // Ensure `left` has at least as many digits as `right`. + HandleBigInt& left = swap ? y : x; + HandleBigInt& right = swap ? x : y; + + if (left->isZero()) { + MOZ_ASSERT(right->isZero()); + return left; + } + + if (right->isZero()) { + return resultNegative == left->isNegative() ? left : neg(cx, left); + } + + RootedBigInt result( + cx, createUninitialized(cx, left->digitLength() + 1, resultNegative)); + if (!result) { + return nullptr; + } + Digit carry = 0; + unsigned i = 0; + for (; i < right->digitLength(); i++) { + Digit newCarry = 0; + Digit sum = digitAdd(left->digit(i), right->digit(i), &newCarry); + sum = digitAdd(sum, carry, &newCarry); + result->setDigit(i, sum); + carry = newCarry; + } + + for (; i < left->digitLength(); i++) { + Digit newCarry = 0; + Digit sum = digitAdd(left->digit(i), carry, &newCarry); + result->setDigit(i, sum); + carry = newCarry; + } + + result->setDigit(i, carry); + + return destructivelyTrimHighZeroDigits(cx, result); +} + +BigInt* BigInt::absoluteSub(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y, + bool resultNegative) { + MOZ_ASSERT(x->digitLength() >= y->digitLength()); + + if (x->isZero()) { + MOZ_ASSERT(y->isZero()); + return x; + } + + if (y->isZero()) { + return resultNegative == x->isNegative() ? x : neg(cx, x); + } + + int8_t comparisonResult = absoluteCompare(x, y); + MOZ_ASSERT(comparisonResult >= 0); + if (comparisonResult == 0) { + return zero(cx); + } + + RootedBigInt result( + cx, createUninitialized(cx, x->digitLength(), resultNegative)); + if (!result) { + return nullptr; + } + Digit borrow = 0; + unsigned i = 0; + for (; i < y->digitLength(); i++) { + Digit newBorrow = 0; + Digit difference = digitSub(x->digit(i), y->digit(i), &newBorrow); + difference = digitSub(difference, borrow, &newBorrow); + result->setDigit(i, difference); + borrow = newBorrow; + } + + for (; i < x->digitLength(); i++) { + Digit newBorrow = 0; + Digit difference = digitSub(x->digit(i), borrow, &newBorrow); + result->setDigit(i, difference); + borrow = newBorrow; + } + + MOZ_ASSERT(!borrow); + return destructivelyTrimHighZeroDigits(cx, result); +} + +// Divides `x` by `divisor`, returning the result in `quotient` and `remainder`. +// Mathematically, the contract is: +// +// quotient = (x - remainder) / divisor, with 0 <= remainder < divisor. +// +// If `quotient` is an empty handle, an appropriately sized BigInt will be +// allocated for it; otherwise the caller must ensure that it is big enough. +// `quotient` can be the same as `x` for an in-place division. `quotient` can +// also be `Nothing()` if the caller is only interested in the remainder. +// +// This function returns false if `quotient` is an empty handle, but allocating +// the quotient failed. Otherwise it returns true, indicating success. +bool BigInt::absoluteDivWithDigitDivisor(ExclusiveContext* cx, HandleBigInt x, + Digit divisor, + const Maybe<MutableHandleBigInt>& quotient, + Digit* remainder, + bool quotientNegative) { + MOZ_ASSERT(divisor); + + MOZ_ASSERT(!x->isZero()); + *remainder = 0; + if (divisor == 1) { + if (quotient) { + BigInt* q; + if (x->isNegative() == quotientNegative) { + q = x; + } else { + q = neg(cx, x); + if (!q) { + return false; + } + } + quotient.value().set(q); + } + return true; + } + + unsigned length = x->digitLength(); + if (quotient) { + if (!quotient.value()) { + BigInt* q = createUninitialized(cx, length, quotientNegative); + if (!q) { + return false; + } + quotient.value().set(q); + } + + for (int i = length - 1; i >= 0; i--) { + Digit q = digitDiv(*remainder, x->digit(i), divisor, remainder); + quotient.value()->setDigit(i, q); + } + } else { + for (int i = length - 1; i >= 0; i--) { + digitDiv(*remainder, x->digit(i), divisor, remainder); + } + } + + return true; +} + +// Adds `summand` onto `this`, starting with `summand`'s 0th digit +// at `this`'s `startIndex`'th digit. Returns the "carry" (0 or 1). +BigInt::Digit BigInt::absoluteInplaceAdd(BigInt* summand, unsigned startIndex) { + Digit carry = 0; + unsigned n = summand->digitLength(); + MOZ_ASSERT(digitLength() > startIndex, + "must start adding at an in-range digit"); + MOZ_ASSERT(digitLength() - startIndex >= n, + "digits being added to must not extend above the digits in " + "this (except for the returned carry digit)"); + for (unsigned i = 0; i < n; i++) { + Digit newCarry = 0; + Digit sum = digitAdd(digit(startIndex + i), summand->digit(i), &newCarry); + sum = digitAdd(sum, carry, &newCarry); + setDigit(startIndex + i, sum); + carry = newCarry; + } + + return carry; +} + +// Subtracts `subtrahend` from this, starting with `subtrahend`'s 0th digit +// at `this`'s `startIndex`-th digit. Returns the "borrow" (0 or 1). +BigInt::Digit BigInt::absoluteInplaceSub(BigInt* subtrahend, + unsigned startIndex) { + Digit borrow = 0; + unsigned n = subtrahend->digitLength(); + MOZ_ASSERT(digitLength() > startIndex, + "must start subtracting from an in-range digit"); + MOZ_ASSERT(digitLength() - startIndex >= n, + "digits being subtracted from must not extend above the " + "digits in this (except for the returned borrow digit)"); + for (unsigned i = 0; i < n; i++) { + Digit newBorrow = 0; + Digit difference = + digitSub(digit(startIndex + i), subtrahend->digit(i), &newBorrow); + difference = digitSub(difference, borrow, &newBorrow); + setDigit(startIndex + i, difference); + borrow = newBorrow; + } + + return borrow; +} + +// Returns whether (factor1 * factor2) > (high << kDigitBits) + low. +inline bool BigInt::productGreaterThan(Digit factor1, Digit factor2, Digit high, + Digit low) { + Digit resultHigh; + Digit resultLow = digitMul(factor1, factor2, &resultHigh); + return resultHigh > high || (resultHigh == high && resultLow > low); +} + +void BigInt::inplaceRightShiftLowZeroBits(unsigned shift) { + MOZ_ASSERT(shift < DigitBits); + MOZ_ASSERT(!(digit(0) & ((static_cast<Digit>(1) << shift) - 1)), + "should only be shifting away zeroes"); + + if (!shift) { + return; + } + + Digit carry = digit(0) >> shift; + unsigned last = digitLength() - 1; + for (unsigned i = 0; i < last; i++) { + Digit d = digit(i + 1); + setDigit(i, (d << (DigitBits - shift)) | carry); + carry = d >> shift; + } + setDigit(last, carry); +} + +// Always copies the input, even when `shift` == 0. +BigInt* BigInt::absoluteLeftShiftAlwaysCopy(ExclusiveContext* cx, HandleBigInt x, + unsigned shift, + LeftShiftMode mode) { + MOZ_ASSERT(shift < DigitBits); + MOZ_ASSERT(!x->isZero()); + + unsigned n = x->digitLength(); + unsigned resultLength = mode == LeftShiftMode::AlwaysAddOneDigit ? n + 1 : n; + RootedBigInt result(cx, + createUninitialized(cx, resultLength, x->isNegative())); + if (!result) { + return nullptr; + } + + if (!shift) { + for (unsigned i = 0; i < n; i++) { + result->setDigit(i, x->digit(i)); + } + if (mode == LeftShiftMode::AlwaysAddOneDigit) { + result->setDigit(n, 0); + } + + return result; + } + + Digit carry = 0; + for (unsigned i = 0; i < n; i++) { + Digit d = x->digit(i); + result->setDigit(i, (d << shift) | carry); + carry = d >> (DigitBits - shift); + } + + if (mode == LeftShiftMode::AlwaysAddOneDigit) { + result->setDigit(n, carry); + } else { + MOZ_ASSERT(mode == LeftShiftMode::SameSizeResult); + MOZ_ASSERT(!carry); + } + + return result; +} + +// Divides `dividend` by `divisor`, returning the result in `quotient` and +// `remainder`. Mathematically, the contract is: +// +// quotient = (dividend - remainder) / divisor, with 0 <= remainder < divisor. +// +// Both `quotient` and `remainder` are optional, for callers that are only +// interested in one of them. See Knuth, Volume 2, section 4.3.1, Algorithm D. +// Also see the overview of the algorithm by Jan Marthedal Rasmussen over at +// https://janmr.com/blog/2014/04/basic-multiple-precision-long-division/. +bool BigInt::absoluteDivWithBigIntDivisor(ExclusiveContext* cx, HandleBigInt dividend, + HandleBigInt divisor, + const Maybe<MutableHandleBigInt>& quotient, + const Maybe<MutableHandleBigInt>& remainder, + bool isNegative) { + MOZ_ASSERT(divisor->digitLength() >= 2); + MOZ_ASSERT(dividend->digitLength() >= divisor->digitLength()); + + // Any early error return is detectable by checking the quotient and/or + // remainder output values. + MOZ_ASSERT(!quotient || !quotient.value()); + MOZ_ASSERT(!remainder || !remainder.value()); + + // The unusual variable names inside this function are consistent with + // Knuth's book, as well as with Go's implementation of this algorithm. + // Maintaining this consistency is probably more useful than trying to + // come up with more descriptive names for them. + const unsigned n = divisor->digitLength(); + const unsigned m = dividend->digitLength() - n; + + // The quotient to be computed. + RootedBigInt q(cx); + if (quotient) { + q = createUninitialized(cx, m + 1, isNegative); + if (!q) { + return false; + } + } + + // In each iteration, `qhatv` holds `divisor` * `current quotient digit`. + // "v" is the book's name for `divisor`, `qhat` the current quotient digit. + RootedBigInt qhatv(cx, createUninitialized(cx, n + 1, isNegative)); + if (!qhatv) { + return false; + } + + // D1. + // Left-shift inputs so that the divisor's MSB is set. This is necessary to + // prevent the digit-wise divisions (see digitDiv call below) from + // overflowing (they take a two digits wide input, and return a one digit + // result). + Digit lastDigit = divisor->digit(n - 1); + unsigned shift = DigitLeadingZeroes(lastDigit); + + RootedBigInt shiftedDivisor(cx); + if (shift > 0) { + shiftedDivisor = absoluteLeftShiftAlwaysCopy(cx, divisor, shift, + LeftShiftMode::SameSizeResult); + if (!shiftedDivisor) { + return false; + } + } else { + shiftedDivisor = divisor; + } + + // Holds the (continuously updated) remaining part of the dividend, which + // eventually becomes the remainder. + RootedBigInt u(cx, + absoluteLeftShiftAlwaysCopy(cx, dividend, shift, + LeftShiftMode::AlwaysAddOneDigit)); + if (!u) { + return false; + } + + // D2. + // Iterate over the dividend's digit (like the "grade school" algorithm). + // `vn1` is the divisor's most significant digit. + Digit vn1 = shiftedDivisor->digit(n - 1); + for (int j = m; j >= 0; j--) { + // D3. + // Estimate the current iteration's quotient digit (see Knuth for details). + // `qhat` is the current quotient digit. + Digit qhat = std::numeric_limits<Digit>::max(); + + // `ujn` is the dividend's most significant remaining digit. + Digit ujn = u->digit(j + n); + if (ujn != vn1) { + // `rhat` is the current iteration's remainder. + Digit rhat = 0; + // Estimate the current quotient digit by dividing the most significant + // digits of dividend and divisor. The result will not be too small, + // but could be a bit too large. + qhat = digitDiv(ujn, u->digit(j + n - 1), vn1, &rhat); + + // Decrement the quotient estimate as needed by looking at the next + // digit, i.e. by testing whether + // qhat * v_{n-2} > (rhat << DigitBits) + u_{j+n-2}. + Digit vn2 = shiftedDivisor->digit(n - 2); + Digit ujn2 = u->digit(j + n - 2); + while (productGreaterThan(qhat, vn2, rhat, ujn2)) { + qhat--; + Digit prevRhat = rhat; + rhat += vn1; + // v[n-1] >= 0, so this tests for overflow. + if (rhat < prevRhat) { + break; + } + } + } + + // D4. + // Multiply the divisor with the current quotient digit, and subtract + // it from the dividend. If there was "borrow", then the quotient digit + // was one too high, so we must correct it and undo one subtraction of + // the (shifted) divisor. + internalMultiplyAdd(shiftedDivisor, qhat, 0, n, qhatv); + Digit c = u->absoluteInplaceSub(qhatv, j); + if (c) { + c = u->absoluteInplaceAdd(shiftedDivisor, j); + u->setDigit(j + n, u->digit(j + n) + c); + qhat--; + } + + if (quotient) { + q->setDigit(j, qhat); + } + } + + if (quotient) { + BigInt* bi = destructivelyTrimHighZeroDigits(cx, q); + if (!bi) { + return false; + } + quotient.value().set(q); + } + + if (remainder) { + u->inplaceRightShiftLowZeroBits(shift); + remainder.value().set(u); + } + + return true; +} + +// Helper for Absolute{And,AndNot,Or,Xor}. +// Performs the given binary `op` on digit pairs of `x` and `y`; when the +// end of the shorter of the two is reached, `kind` configures how +// remaining digits are handled. +// Example: +// y: [ y2 ][ y1 ][ y0 ] +// x: [ x3 ][ x2 ][ x1 ][ x0 ] +// | | | | +// (Fill) (op) (op) (op) +// | | | | +// v v v v +// result: [ 0 ][ x3 ][ r2 ][ r1 ][ r0 ] +template <BigInt::BitwiseOpKind kind, typename BitwiseOp> +inline BigInt* BigInt::absoluteBitwiseOp(ExclusiveContext* cx, HandleBigInt x, + HandleBigInt y, BitwiseOp&& op) { + unsigned xLength = x->digitLength(); + unsigned yLength = y->digitLength(); + unsigned numPairs = std::min(xLength, yLength); + unsigned resultLength; + if (kind == BitwiseOpKind::SymmetricTrim) { + resultLength = numPairs; + } else if (kind == BitwiseOpKind::SymmetricFill) { + resultLength = std::max(xLength, yLength); + } else { + MOZ_ASSERT(kind == BitwiseOpKind::AsymmetricFill); + resultLength = xLength; + } + bool resultNegative = false; + + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + + unsigned i = 0; + for (; i < numPairs; i++) { + result->setDigit(i, op(x->digit(i), y->digit(i))); + } + + if (kind != BitwiseOpKind::SymmetricTrim) { + HandleBigInt& source = + kind == BitwiseOpKind::AsymmetricFill ? x : xLength == i ? y : x; + for (; i < resultLength; i++) { + result->setDigit(i, source->digit(i)); + } + } + + MOZ_ASSERT(i == resultLength); + + return destructivelyTrimHighZeroDigits(cx, result); +} + +BigInt* BigInt::absoluteAnd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp<BitwiseOpKind::SymmetricTrim>(cx, x, y, + std::bit_and<Digit>()); +} + +BigInt* BigInt::absoluteOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp<BitwiseOpKind::SymmetricFill>(cx, x, y, + std::bit_or<Digit>()); +} + +BigInt* BigInt::absoluteAndNot(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + auto digitOperation = [](Digit a, Digit b) { return a & ~b; }; + return absoluteBitwiseOp<BitwiseOpKind::AsymmetricFill>(cx, x, y, + digitOperation); +} + +BigInt* BigInt::absoluteXor(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp<BitwiseOpKind::SymmetricFill>(cx, x, y, + std::bit_xor<Digit>()); +} + +BigInt* BigInt::absoluteAddOne(ExclusiveContext* cx, HandleBigInt x, + bool resultNegative) { + unsigned inputLength = x->digitLength(); + // The addition will overflow into a new digit if all existing digits are + // at maximum. + bool willOverflow = true; + for (unsigned i = 0; i < inputLength; i++) { + if (std::numeric_limits<Digit>::max() != x->digit(i)) { + willOverflow = false; + break; + } + } + + unsigned resultLength = inputLength + willOverflow; + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + + Digit carry = 1; + for (unsigned i = 0; i < inputLength; i++) { + Digit newCarry = 0; + result->setDigit(i, digitAdd(x->digit(i), carry, &newCarry)); + carry = newCarry; + } + if (resultLength > inputLength) { + MOZ_ASSERT(carry == 1); + result->setDigit(inputLength, 1); + } else { + MOZ_ASSERT(!carry); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// Like the above, but you can specify that the allocated result should have +// length `resultLength`, which must be at least as large as `x->digitLength()`. +// The result will be unsigned. +BigInt* BigInt::absoluteSubOne(ExclusiveContext* cx, HandleBigInt x, + unsigned resultLength) { + MOZ_ASSERT(!x->isZero()); + MOZ_ASSERT(resultLength >= x->digitLength()); + bool resultNegative = false; + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + + unsigned length = x->digitLength(); + Digit borrow = 1; + for (unsigned i = 0; i < length; i++) { + Digit newBorrow = 0; + result->setDigit(i, digitSub(x->digit(i), borrow, &newBorrow)); + borrow = newBorrow; + } + MOZ_ASSERT(!borrow); + for (unsigned i = length; i < resultLength; i++) { + result->setDigit(i, 0); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// Lookup table for the maximum number of bits required per character of a +// base-N string representation of a number. To increase accuracy, the array +// value is the actual value multiplied by 32. To generate this table: +// for (var i = 0; i <= 36; i++) { print(Math.ceil(Math.log2(i) * 32) + ","); } +static constexpr uint8_t maxBitsPerCharTable[] = { + 0, 0, 32, 51, 64, 75, 83, 90, 96, // 0..8 + 102, 107, 111, 115, 119, 122, 126, 128, // 9..16 + 131, 134, 136, 139, 141, 143, 145, 147, // 17..24 + 149, 151, 153, 154, 156, 158, 159, 160, // 25..32 + 162, 163, 165, 166, // 33..36 +}; + +static constexpr unsigned bitsPerCharTableShift = 5; +static constexpr size_t bitsPerCharTableMultiplier = 1u + << bitsPerCharTableShift; +static constexpr char radixDigits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +static inline uint64_t CeilDiv(uint64_t numerator, uint64_t denominator) { + MOZ_ASSERT(numerator != 0); + return 1 + (numerator - 1) / denominator; +}; + +// Compute (an overapproximation of) the length of the string representation of +// a BigInt. In base B an X-digit number has maximum value: +// +// B**X - 1 +// +// We're trying to find N for an N-digit number in base |radix| full +// representing a |bitLength|-digit number in base 2, so we have: +// +// radix**N - 1 ≥ 2**bitLength - 1 +// radix**N ≥ 2**bitLength +// N ≥ log2(2**bitLength) / log2(radix) +// N ≥ bitLength / log2(radix) +// +// so the smallest N is: +// +// N = ⌈bitLength / log2(radix)⌉ +// +// We want to avoid floating-point computations and precompute the logarithm, so +// we multiply both sides of the division by |bitsPerCharTableMultiplier|: +// +// N = ⌈(bPCTM * bitLength) / (bPCTM * log2(radix))⌉ +// +// and then because |maxBitsPerChar| representing the denominator may have been +// rounded *up* -- which could produce an overall under-computation -- we reduce +// by one to undo any rounding and conservatively compute: +// +// N ≥ ⌈(bPCTM * bitLength) / (maxBitsPerChar - 1)⌉ +// +size_t BigInt::calculateMaximumCharactersRequired(HandleBigInt x, + unsigned radix) { + MOZ_ASSERT(!x->isZero()); + MOZ_ASSERT(radix >= 2 && radix <= 36); + + size_t length = x->digitLength(); + Digit lastDigit = x->digit(length - 1); + size_t bitLength = length * DigitBits - DigitLeadingZeroes(lastDigit); + + uint8_t maxBitsPerChar = maxBitsPerCharTable[radix]; + uint64_t maximumCharactersRequired = + CeilDiv(static_cast<uint64_t>(bitsPerCharTableMultiplier) * bitLength, + maxBitsPerChar - 1); + maximumCharactersRequired += x->isNegative(); + + return AssertedCast<size_t>(maximumCharactersRequired); +} + +JSLinearString* BigInt::toStringBasePowerOfTwo(ExclusiveContext* cx, HandleBigInt x, + unsigned radix) { + MOZ_ASSERT(mozilla::IsPowerOfTwo(radix)); + MOZ_ASSERT(radix >= 2 && radix <= 32); + MOZ_ASSERT(!x->isZero()); + + const unsigned length = x->digitLength(); + const bool sign = x->isNegative(); + const unsigned bitsPerChar = mozilla::CountTrailingZeroes32(radix); + const unsigned charMask = radix - 1; + // Compute the length of the resulting string: divide the bit length of the + // BigInt by the number of bits representable per character (rounding up). + const Digit msd = x->digit(length - 1); + + const size_t bitLength = length * DigitBits - DigitLeadingZeroes(msd); + const size_t charsRequired = CeilDiv(bitLength, bitsPerChar) + sign; + + if (charsRequired > JSString::MAX_LENGTH) { + ReportOutOfMemory(cx); + return nullptr; + } + + auto resultChars = cx->make_pod_array<char>(charsRequired); + if (!resultChars) { + return nullptr; + } + + Digit digit = 0; + // Keeps track of how many unprocessed bits there are in |digit|. + unsigned availableBits = 0; + size_t pos = charsRequired; + for (unsigned i = 0; i < length - 1; i++) { + Digit newDigit = x->digit(i); + // Take any leftover bits from the last iteration into account. + unsigned current = (digit | (newDigit << availableBits)) & charMask; + MOZ_ASSERT(pos); + resultChars[--pos] = radixDigits[current]; + unsigned consumedBits = bitsPerChar - availableBits; + digit = newDigit >> consumedBits; + availableBits = DigitBits - consumedBits; + while (availableBits >= bitsPerChar) { + MOZ_ASSERT(pos); + resultChars[--pos] = radixDigits[digit & charMask]; + digit >>= bitsPerChar; + availableBits -= bitsPerChar; + } + } + + // Write out the character containing the lowest-order bit of |msd|. + // + // This character may include leftover bits from the Digit below |msd|. For + // example, if |x === 2n**64n| and |radix == 32|: the preceding loop writes + // twelve zeroes for low-order bits 0-59 in |x->digit(0)| (and |x->digit(1)| + // on 32-bit); then the highest 4 bits of of |x->digit(0)| (or |x->digit(1)| + // on 32-bit) and bit 0 of |x->digit(1)| (|x->digit(2)| on 32-bit) will + // comprise the |current == 0b1'0000| computed below for the high-order 'g' + // character. + unsigned current = (digit | (msd << availableBits)) & charMask; + MOZ_ASSERT(pos); + resultChars[--pos] = radixDigits[current]; + + // Write out remaining characters represented by |msd|. (There may be none, + // as in the example above.) + digit = msd >> (bitsPerChar - availableBits); + while (digit != 0) { + MOZ_ASSERT(pos); + resultChars[--pos] = radixDigits[digit & charMask]; + digit >>= bitsPerChar; + } + + if (sign) { + MOZ_ASSERT(pos); + resultChars[--pos] = '-'; + } + + MOZ_ASSERT(pos == 0); + return NewStringCopyN<CanGC>(cx, resultChars.get(), charsRequired); +} + +static constexpr BigInt::Digit MaxPowerInDigit(uint8_t radix) { + BigInt::Digit result = 1; + while (result < BigInt::Digit(-1) / radix) { + result *= radix; + } + return result; +} + +static constexpr uint8_t MaxExponentInDigit(uint8_t radix) { + uint8_t exp = 0; + BigInt::Digit result = 1; + while (result < BigInt::Digit(-1) / radix) { + result *= radix; + exp += 1; + } + return exp; +} + +struct RadixInfo { + BigInt::Digit maxPowerInDigit; + uint8_t maxExponentInDigit; + + constexpr RadixInfo(BigInt::Digit maxPower, uint8_t maxExponent) + : maxPowerInDigit(maxPower), maxExponentInDigit(maxExponent) {} + + explicit constexpr RadixInfo(uint8_t radix) + : RadixInfo(MaxPowerInDigit(radix), MaxExponentInDigit(radix)) {} +}; + +static constexpr const RadixInfo toStringInfo[37] = { + {0, 0}, {0, 0}, RadixInfo(2), RadixInfo(3), RadixInfo(4), + RadixInfo(5), RadixInfo(6), RadixInfo(7), RadixInfo(8), RadixInfo(9), + RadixInfo(10), RadixInfo(11), RadixInfo(12), RadixInfo(13), RadixInfo(14), + RadixInfo(15), RadixInfo(16), RadixInfo(17), RadixInfo(18), RadixInfo(19), + RadixInfo(20), RadixInfo(21), RadixInfo(22), RadixInfo(23), RadixInfo(24), + RadixInfo(25), RadixInfo(26), RadixInfo(27), RadixInfo(28), RadixInfo(29), + RadixInfo(30), RadixInfo(31), RadixInfo(32), RadixInfo(33), RadixInfo(34), + RadixInfo(35), RadixInfo(36), +}; + +JSLinearString* BigInt::toStringGeneric(ExclusiveContext* cx, HandleBigInt x, + unsigned radix) { + MOZ_ASSERT(radix >= 2 && radix <= 36); + MOZ_ASSERT(!x->isZero()); + + size_t maximumCharactersRequired = + calculateMaximumCharactersRequired(x, radix); + if (maximumCharactersRequired > JSString::MAX_LENGTH) { + ReportOutOfMemory(cx); + return nullptr; + } + + UniqueChars resultString(js_pod_malloc<char>(maximumCharactersRequired)); + if (!resultString) { + ReportOutOfMemory(cx); + return nullptr; + } + + size_t writePos = maximumCharactersRequired; + unsigned length = x->digitLength(); + Digit lastDigit; + if (length == 1) { + lastDigit = x->digit(0); + } else { + unsigned chunkChars = toStringInfo[radix].maxExponentInDigit; + Digit chunkDivisor = toStringInfo[radix].maxPowerInDigit; + + unsigned nonZeroDigit = length - 1; + MOZ_ASSERT(x->digit(nonZeroDigit) != 0); + + // `rest` holds the part of the BigInt that we haven't looked at yet. + // Not to be confused with "remainder"! + RootedBigInt rest(cx); + + // In the first round, divide the input, allocating a new BigInt for + // the result == rest; from then on divide the rest in-place. + // + // FIXME: absoluteDivWithDigitDivisor doesn't + // destructivelyTrimHighZeroDigits for in-place divisions, leading to + // worse constant factors. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=1510213. + RootedBigInt dividend(cx, x); + do { + Digit chunk; + if (!absoluteDivWithDigitDivisor(cx, dividend, chunkDivisor, Some(&rest), + &chunk, dividend->isNegative())) { + return nullptr; + } + + dividend = rest; + for (unsigned i = 0; i < chunkChars; i++) { + MOZ_ASSERT(writePos > 0); + resultString[--writePos] = radixDigits[chunk % radix]; + chunk /= radix; + } + MOZ_ASSERT(!chunk); + + if (!rest->digit(nonZeroDigit)) { + nonZeroDigit--; + } + + MOZ_ASSERT(rest->digit(nonZeroDigit) != 0, + "division by a single digit can't remove more than one " + "digit from a number"); + } while (nonZeroDigit > 0); + + lastDigit = rest->digit(0); + } + + do { + MOZ_ASSERT(writePos > 0); + resultString[--writePos] = radixDigits[lastDigit % radix]; + lastDigit /= radix; + } while (lastDigit > 0); + MOZ_ASSERT(writePos < maximumCharactersRequired); + MOZ_ASSERT(maximumCharactersRequired - writePos <= + static_cast<size_t>(maximumCharactersRequired)); + + // Remove leading zeroes. + while (writePos + 1 < maximumCharactersRequired && + resultString[writePos] == '0') { + writePos++; + } + + if (x->isNegative()) { + MOZ_ASSERT(writePos > 0); + resultString[--writePos] = '-'; + } + + MOZ_ASSERT(writePos < maximumCharactersRequired); + // Would be better to somehow adopt resultString directly. + return NewStringCopyN<CanGC>(cx, resultString.get() + writePos, + maximumCharactersRequired - writePos); +} + +BigInt* BigInt::trimHighZeroDigits(ExclusiveContext* cx, HandleBigInt x) { + if (x->isZero()) { + MOZ_ASSERT(!x->isNegative()); + return x; + } + MOZ_ASSERT(x->digitLength()); + + int nonZeroIndex = x->digitLength() - 1; + while (nonZeroIndex >= 0 && x->digit(nonZeroIndex) == 0) { + nonZeroIndex--; + } + + if (nonZeroIndex < 0) { + return zero(cx); + } + + if (nonZeroIndex == static_cast<int>(x->digitLength() - 1)) { + return x; + } + + unsigned newLength = nonZeroIndex + 1; + BigInt* trimmedBigInt = createUninitialized(cx, newLength, x->isNegative()); + if (!trimmedBigInt) { + return nullptr; + } + for (unsigned i = 0; i < newLength; i++) { + trimmedBigInt->setDigit(i, x->digit(i)); + } + + return trimmedBigInt; +} + +BigInt* BigInt::destructivelyTrimHighZeroDigits(ExclusiveContext* cx, HandleBigInt x) { + // TODO: Modify in place instead of allocating. + return trimHighZeroDigits(cx, x); +} + +// The maximum value `radix**charCount - 1` must be represented as a max number +// `2**(N * DigitBits) - 1` for `N` digits, so +// +// 2**(N * DigitBits) - 1 ≥ radix**charcount - 1 +// 2**(N * DigitBits) ≥ radix**charcount +// N * DigitBits ≥ log2(radix**charcount) +// N * DigitBits ≥ charcount * log2(radix) +// N ≥ ⌈charcount * log2(radix) / DigitBits⌉ (conservatively) +// +// or in the code's terms (all numbers promoted to exact mathematical values), +// +// N ≥ ⌈charcount * bitsPerChar / (DigitBits * bitsPerCharTableMultiplier)⌉ +// +// Note that `N` is computed even more conservatively here because `bitsPerChar` +// is rounded up. +bool BigInt::calculateMaximumDigitsRequired(ExclusiveContext* cx, uint8_t radix, + size_t charcount, size_t* result) { + MOZ_ASSERT(2 <= radix && radix <= 36); + + size_t bitsPerChar = maxBitsPerCharTable[radix]; + + MOZ_ASSERT(charcount > 0); + MOZ_ASSERT(charcount <= std::numeric_limits<size_t>::max() / bitsPerChar); + uint64_t n = + CeilDiv(charcount * bitsPerChar, DigitBits * bitsPerCharTableMultiplier); + if (n > MaxDigitLength) { + ReportAllocationOverflow(cx); + return false; + } + + *result = n; + return true; +} + +template <typename CharT> +BigInt* BigInt::parseLiteralDigits(ExclusiveContext* cx, + const Range<const CharT> chars, + unsigned radix, bool isNegative, + bool* haveParseError) { + MOZ_ASSERT(chars.length()); + + RangedPtr<const CharT> start = chars.begin(); + RangedPtr<const CharT> end = chars.end(); + + // Skipping leading zeroes. + while (start[0] == '0') { + start++; + if (start == end) { + return zero(cx); + } + } + + unsigned limit0 = '0' + std::min(radix, 10u); + unsigned limita = 'a' + (radix - 10); + unsigned limitA = 'A' + (radix - 10); + + size_t length; + if (!calculateMaximumDigitsRequired(cx, radix, end - start, &length)) { + return nullptr; + } + RootedBigInt result(cx, createUninitialized(cx, length, isNegative)); + if (!result) { + return nullptr; + } + + result->initializeDigitsToZero(); + + RangedPtr<const CharT> begin = start; + for (; start < end; start++) { + uint32_t digit; + CharT c = *start; + if (c == '_' && start > begin && start < end - 1) { + // skip over block delimiters unless at the very start or end + continue; + } else if (c >= '0' && c < limit0) { + digit = c - '0'; + } else if (c >= 'a' && c < limita) { + digit = c - 'a' + 10; + } else if (c >= 'A' && c < limitA) { + digit = c - 'A' + 10; + } else { + *haveParseError = true; + return nullptr; + } + + result->inplaceMultiplyAdd(static_cast<Digit>(radix), + static_cast<Digit>(digit)); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// BigInt proposal section 7.2 +template <typename CharT> +BigInt* BigInt::parseLiteral(ExclusiveContext* cx, const Range<const CharT> chars, + bool* haveParseError) { + RangedPtr<const CharT> start = chars.begin(); + const RangedPtr<const CharT> end = chars.end(); + bool isNegative = false; + + MOZ_ASSERT(chars.length()); + + if (end - start > 2 && start[0] == '0') { + if (start[1] == 'b' || start[1] == 'B') { + // StringNumericLiteral ::: BinaryIntegerLiteral + return parseLiteralDigits(cx, Range<const CharT>(start + 2, end), 2, + isNegative, haveParseError); + } + if (start[1] == 'x' || start[1] == 'X') { + // StringNumericLiteral ::: HexIntegerLiteral + return parseLiteralDigits(cx, Range<const CharT>(start + 2, end), 16, + isNegative, haveParseError); + } + if (start[1] == 'o' || start[1] == 'O') { + // StringNumericLiteral ::: OctalIntegerLiteral + return parseLiteralDigits(cx, Range<const CharT>(start + 2, end), 8, + isNegative, haveParseError); + } + } + + return parseLiteralDigits(cx, Range<const CharT>(start, end), 10, isNegative, + haveParseError); +} + +// BigInt proposal section 5.1.1 +static bool IsInteger(double d) { + // Step 1 is an assertion checked by the caller. + // Step 2. + if (!mozilla::IsFinite(d)) { + return false; + } + + // Step 3. + double i = JS::ToInteger(d); + + // Step 4. + if (i != d) { + return false; + } + + // Step 5. + return true; +} + +BigInt* BigInt::createFromDouble(ExclusiveContext* cx, double d) { + MOZ_ASSERT(::IsInteger(d), + "Only integer-valued doubles can convert to BigInt"); + + if (d == 0) { + return zero(cx); + } + + int exponent = mozilla::ExponentComponent(d); + MOZ_ASSERT(exponent >= 0); + int length = exponent / DigitBits + 1; + BigInt* result = createUninitialized(cx, length, d < 0); + if (!result) { + return nullptr; + } + + // We construct a BigInt from the double `d` by shifting its mantissa + // according to its exponent and mapping the bit pattern onto digits. + // + // <----------- bitlength = exponent + 1 -----------> + // <----- 52 ------> <------ trailing zeroes ------> + // mantissa: 1yyyyyyyyyyyyyyyyy 0000000000000000000000000000000 + // digits: 0001xxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx + // <--> <------> + // msdTopBits DigitBits + // + using Double = mozilla::FloatingPoint<double>; + uint64_t mantissa = + mozilla::BitwiseCast<uint64_t>(d) & Double::kSignificandBits; + // Add implicit high bit. + mantissa |= 1ull << Double::kSignificandWidth; + + const int mantissaTopBit = Double::kSignificandWidth; // 0-indexed. + + // 0-indexed position of `d`'s most significant bit within the `msd`. + int msdTopBit = exponent % DigitBits; + + // Next digit under construction. + Digit digit; + + // First, build the MSD by shifting the mantissa appropriately. + if (msdTopBit < mantissaTopBit) { + int remainingMantissaBits = mantissaTopBit - msdTopBit; + digit = mantissa >> remainingMantissaBits; + mantissa = mantissa << (64 - remainingMantissaBits); + } else { + MOZ_ASSERT(msdTopBit >= mantissaTopBit); + digit = mantissa << (msdTopBit - mantissaTopBit); + mantissa = 0; + } + result->setDigit(--length, digit); + + // Fill in digits containing mantissa contributions. + while (mantissa) { + MOZ_ASSERT(length > 0, + "double bits were all non-fractional, so there must be " + "digits present to hold them"); + + if (DigitBits == 64) { + result->setDigit(--length, mantissa); + break; + } + + MOZ_ASSERT(DigitBits == 32); + Digit current = mantissa >> 32; + mantissa = mantissa << 32; + result->setDigit(--length, current); + } + + // Fill in low-order zeroes. + for (int i = length - 1; i >= 0; i--) { + result->setDigit(i, 0); + } + + return result; +} + +BigInt* BigInt::createFromUint64(ExclusiveContext* cx, uint64_t n) { + if (n == 0) { + return zero(cx); + } + + const bool isNegative = false; + + if (DigitBits == 32) { + Digit low = n; + Digit high = n >> 32; + size_t length = high ? 2 : 1; + + BigInt* res = createUninitialized(cx, length, isNegative); + if (!res) { + return nullptr; + } + res->setDigit(0, low); + if (high) { + res->setDigit(1, high); + } + return res; + } + + BigInt* res = createUninitialized(cx, 1, isNegative); + if (!res) { + return nullptr; + } + + res->setDigit(0, n); + return res; +} + +BigInt* BigInt::createFromInt64(ExclusiveContext* cx, int64_t n) { + BigInt* res = createFromUint64(cx, Abs(n)); + if (!res) { + return nullptr; + } + + if (n < 0) { + res->lengthSignAndReservedBits_ |= SignBit; + } + MOZ_ASSERT(res->isNegative() == (n < 0)); + + return res; +} + +// BigInt proposal section 5.1.2 +BigInt* js::NumberToBigInt(ExclusiveContext* cx, double d) { + // Step 1 is an assertion checked by the caller. + // Step 2. + if (!::IsInteger(d)) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_NUMBER_TO_BIGINT); + } + return nullptr; + } + + // Step 3. + return BigInt::createFromDouble(cx, d); +} + +BigInt* BigInt::copy(ExclusiveContext* cx, HandleBigInt x) { + if (x->isZero()) { + return zero(cx); + } + + BigInt* result = createUninitialized(cx, x->digitLength(), x->isNegative()); + if (!result) { + return nullptr; + } + for (size_t i = 0; i < x->digitLength(); i++) { + result->setDigit(i, x->digit(i)); + } + return result; +} + +// BigInt proposal section 1.1.7 +BigInt* BigInt::add(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + bool xNegative = x->isNegative(); + + // x + y == x + y + // -x + -y == -(x + y) + if (xNegative == y->isNegative()) { + return absoluteAdd(cx, x, y, xNegative); + } + + // x + -y == x - y == -(y - x) + // -x + y == y - x == -(x - y) + if (absoluteCompare(x, y) >= 0) { + return absoluteSub(cx, x, y, xNegative); + } + + return absoluteSub(cx, y, x, !xNegative); +} + +// BigInt proposal section 1.1.8 +BigInt* BigInt::sub(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + bool xNegative = x->isNegative(); + if (xNegative != y->isNegative()) { + // x - (-y) == x + y + // (-x) - y == -(x + y) + return absoluteAdd(cx, x, y, xNegative); + } + // x - y == -(y - x) + // (-x) - (-y) == y - x == -(x - y) + if (absoluteCompare(x, y) >= 0) { + return absoluteSub(cx, x, y, xNegative); + } + + return absoluteSub(cx, y, x, !xNegative); +} + +// BigInt proposal section 1.1.4 +BigInt* BigInt::mul(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return x; + } + if (y->isZero()) { + return y; + } + + unsigned resultLength = x->digitLength() + y->digitLength(); + bool resultNegative = x->isNegative() != y->isNegative(); + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + result->initializeDigitsToZero(); + + for (size_t i = 0; i < x->digitLength(); i++) { + multiplyAccumulate(y, x->digit(i), result, i); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// BigInt proposal section 1.1.5 +BigInt* BigInt::div(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + // 1. If y is 0n, throw a RangeError exception. + if (y->isZero()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_DIVISION_BY_ZERO); + } + return nullptr; + } + + // 2. Let quotient be the mathematical value of x divided by y. + // 3. Return a BigInt representing quotient rounded towards 0 to the next + // integral value. + if (x->isZero()) { + return x; + } + + if (absoluteCompare(x, y) < 0) { + return zero(cx); + } + + RootedBigInt quotient(cx); + bool resultNegative = x->isNegative() != y->isNegative(); + if (y->digitLength() == 1) { + Digit divisor = y->digit(0); + if (divisor == 1) { + return resultNegative == x->isNegative() ? x : neg(cx, x); + } + + Digit remainder; + if (!absoluteDivWithDigitDivisor(cx, x, divisor, Some("ient), + &remainder, resultNegative)) { + return nullptr; + } + } else { + if (!absoluteDivWithBigIntDivisor(cx, x, y, Some("ient), Nothing(), + resultNegative)) { + return nullptr; + } + } + + return destructivelyTrimHighZeroDigits(cx, quotient); +} + +// BigInt proposal section 1.1.6 +BigInt* BigInt::mod(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + // 1. If y is 0n, throw a RangeError exception. + if (y->isZero()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_DIVISION_BY_ZERO); + } + return nullptr; + } + + // 2. If x is 0n, return x. + if (x->isZero()) { + return x; + } + // 3. Let r be the BigInt defined by the mathematical relation r = x - (y × + // q) where q is a BigInt that is negative only if x/y is negative and + // positive only if x/y is positive, and whose magnitude is as large as + // possible without exceeding the magnitude of the true mathematical + // quotient of x and y. + if (absoluteCompare(x, y) < 0) { + return x; + } + + if (y->digitLength() == 1) { + Digit divisor = y->digit(0); + if (divisor == 1) { + return zero(cx); + } + + Digit remainderDigit; + bool unusedQuotientNegative = false; + if (!absoluteDivWithDigitDivisor(cx, x, divisor, Nothing(), &remainderDigit, + unusedQuotientNegative)) { + MOZ_CRASH("BigInt div by digit failed unexpectedly"); + } + + if (!remainderDigit) { + return zero(cx); + } + + BigInt* remainder = createUninitialized(cx, 1, x->isNegative()); + if (!remainder) { + return nullptr; + } + remainder->setDigit(0, remainderDigit); + return remainder; + } else { + RootedBigInt remainder(cx); + if (!absoluteDivWithBigIntDivisor(cx, x, y, Nothing(), Some(&remainder), + x->isNegative())) { + return nullptr; + } + MOZ_ASSERT(remainder); + return destructivelyTrimHighZeroDigits(cx, remainder); + } +} + +// BigInt proposal section 1.1.3 +BigInt* BigInt::pow(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + // 1. If exponent is < 0, throw a RangeError exception. + if (y->isNegative()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_NEGATIVE_EXPONENT); + } + return nullptr; + } + + // 2. If base is 0n and exponent is 0n, return 1n. + if (y->isZero()) { + return one(cx); + } + + if (x->isZero()) { + return x; + } + + // 3. Return a BigInt representing the mathematical value of base raised + // to the power exponent. + if (x->digitLength() == 1 && x->digit(0) == 1) { + // (-1) ** even_number == 1. + if (x->isNegative() && (y->digit(0) & 1) == 0) { + return neg(cx, x); + } + // (-1) ** odd_number == -1; 1 ** anything == 1. + return x; + } + + // For all bases >= 2, very large exponents would lead to unrepresentable + // results. + static_assert(MaxBitLength < std::numeric_limits<Digit>::max(), + "unexpectedly large MaxBitLength"); + if (y->digitLength() > 1) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + Digit exponent = y->digit(0); + if (exponent == 1) { + return x; + } + if (exponent >= MaxBitLength) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + + static_assert(MaxBitLength <= std::numeric_limits<int>::max(), + "unexpectedly large MaxBitLength"); + int n = static_cast<int>(exponent); + if (x->digitLength() == 1 && x->digit(0) == 2) { + // Fast path for 2^n. + int length = 1 + (n / DigitBits); + // Result is negative for odd powers of -2n. + bool resultNegative = x->isNegative() && (n & 1); + RootedBigInt result(cx, createUninitialized(cx, length, resultNegative)); + if (!result) { + return nullptr; + } + result->initializeDigitsToZero(); + result->setDigit(length - 1, static_cast<Digit>(1) << (n % DigitBits)); + return result; + } + + // This implicitly sets the result's sign correctly. + RootedBigInt result(cx, (n & 1) ? x : nullptr); + RootedBigInt runningSquare(cx, x); + for (n /= 2; n; n /= 2) { + runningSquare = mul(cx, runningSquare, runningSquare); + if (!runningSquare) { + return nullptr; + } + if (n & 1) { + if (!result) { + result = runningSquare; + } else { + result = mul(cx, result, runningSquare); + if (!result) { + return nullptr; + } + } + } + } + return result; +} + +BigInt* BigInt::lshByAbsolute(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero() || y->isZero()) { + return x; + } + + if (y->digitLength() > 1 || y->digit(0) > MaxBitLength) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + Digit shift = y->digit(0); + int digitShift = static_cast<int>(shift / DigitBits); + int bitsShift = static_cast<int>(shift % DigitBits); + int length = x->digitLength(); + bool grow = bitsShift && (x->digit(length - 1) >> (DigitBits - bitsShift)); + int resultLength = length + digitShift + grow; + RootedBigInt result(cx, + createUninitialized(cx, resultLength, x->isNegative())); + if (!result) { + return nullptr; + } + + int i = 0; + for (; i < digitShift; i++) { + result->setDigit(i, 0); + } + + if (bitsShift == 0) { + for (int j = 0; i < resultLength; i++, j++) { + result->setDigit(i, x->digit(j)); + } + } else { + Digit carry = 0; + for (int j = 0; j < length; i++, j++) { + Digit d = x->digit(j); + result->setDigit(i, (d << bitsShift) | carry); + carry = d >> (DigitBits - bitsShift); + } + if (grow) { + result->setDigit(i, carry); + } else { + MOZ_ASSERT(!carry); + } + } + return result; +} + +BigInt* BigInt::rshByMaximum(ExclusiveContext* cx, bool isNegative) { + if (isNegative) { + RootedBigInt negativeOne(cx, createUninitialized(cx, 1, isNegative)); + if (!negativeOne) { + return nullptr; + } + negativeOne->setDigit(0, 1); + return negativeOne; + } + return zero(cx); +} + +BigInt* BigInt::rshByAbsolute(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero() || y->isZero()) { + return x; + } + + if (y->digitLength() > 1 || y->digit(0) >= MaxBitLength) { + return rshByMaximum(cx, x->isNegative()); + } + Digit shift = y->digit(0); + int length = x->digitLength(); + int digitShift = static_cast<int>(shift / DigitBits); + int bitsShift = static_cast<int>(shift % DigitBits); + int resultLength = length - digitShift; + if (resultLength <= 0) { + return rshByMaximum(cx, x->isNegative()); + } + // For negative numbers, round down if any bit was shifted out (so that e.g. + // -5n >> 1n == -3n and not -2n). Check now whether this will happen and + // whether it can cause overflow into a new digit. If we allocate the result + // large enough up front, it avoids having to do a second allocation later. + bool mustRoundDown = false; + if (x->isNegative()) { + const Digit mask = (static_cast<Digit>(1) << bitsShift) - 1; + if ((x->digit(digitShift) & mask)) { + mustRoundDown = true; + } else { + for (int i = 0; i < digitShift; i++) { + if (x->digit(i)) { + mustRoundDown = true; + break; + } + } + } + } + // If bits_shift is non-zero, it frees up bits, preventing overflow. + if (mustRoundDown && bitsShift == 0) { + // Overflow cannot happen if the most significant digit has unset bits. + Digit msd = x->digit(length - 1); + bool roundingCanOverflow = msd == std::numeric_limits<Digit>::max(); + if (roundingCanOverflow) { + resultLength++; + } + } + + MOZ_ASSERT(resultLength <= length); + RootedBigInt result(cx, + createUninitialized(cx, resultLength, x->isNegative())); + if (!result) { + return nullptr; + } + if (!bitsShift) { + // If roundingCanOverflow, manually initialize the overflow digit. + result->setDigit(resultLength - 1, 0); + for (int i = digitShift; i < length; i++) { + result->setDigit(i - digitShift, x->digit(i)); + } + } else { + Digit carry = x->digit(digitShift) >> bitsShift; + int last = length - digitShift - 1; + for (int i = 0; i < last; i++) { + Digit d = x->digit(i + digitShift + 1); + result->setDigit(i, (d << (DigitBits - bitsShift)) | carry); + carry = d >> bitsShift; + } + result->setDigit(last, carry); + } + + if (mustRoundDown) { + MOZ_ASSERT(x->isNegative()); + // Since the result is negative, rounding down means adding one to + // its absolute value. This cannot overflow. TODO: modify the result in + // place. + return absoluteAddOne(cx, result, x->isNegative()); + } + return destructivelyTrimHighZeroDigits(cx, result); +} + +// BigInt proposal section 1.1.9. BigInt::leftShift ( x, y ) +BigInt* BigInt::lsh(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (y->isNegative()) { + return rshByAbsolute(cx, x, y); + } + return lshByAbsolute(cx, x, y); +} + +// BigInt proposal section 1.1.10. BigInt::signedRightShift ( x, y ) +BigInt* BigInt::rsh(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (y->isNegative()) { + return lshByAbsolute(cx, x, y); + } + return rshByAbsolute(cx, x, y); +} + +// BigInt proposal section 1.1.17. BigInt::bitwiseAND ( x, y ) +BigInt* BigInt::bitAnd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return x; + } + + if (y->isZero()) { + return y; + } + + if (!x->isNegative() && !y->isNegative()) { + return absoluteAnd(cx, x, y); + } + + if (x->isNegative() && y->isNegative()) { + int resultLength = std::max(x->digitLength(), y->digitLength()) + 1; + // (-x) & (-y) == ~(x-1) & ~(y-1) == ~((x-1) | (y-1)) + // == -(((x-1) | (y-1)) + 1) + RootedBigInt x1(cx, absoluteSubOne(cx, x, resultLength)); + if (!x1) { + return nullptr; + } + RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength())); + if (!y1) { + return nullptr; + } + RootedBigInt result(cx, absoluteOr(cx, x1, y1)); + if (!result) { + return nullptr; + } + bool resultNegative = true; + return absoluteAddOne(cx, result, resultNegative); + } + + MOZ_ASSERT(x->isNegative() != y->isNegative()); + HandleBigInt& pos = x->isNegative() ? y : x; + HandleBigInt& neg = x->isNegative() ? x : y; + + RootedBigInt neg1(cx, absoluteSubOne(cx, neg, neg->digitLength())); + if (!neg1) { + return nullptr; + } + + // x & (-y) == x & ~(y-1) == x & ~(y-1) + return absoluteAndNot(cx, pos, neg1); +} + +// BigInt proposal section 1.1.18. BigInt::bitwiseXOR ( x, y ) +BigInt* BigInt::bitXor(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return y; + } + + if (y->isZero()) { + return x; + } + + if (!x->isNegative() && !y->isNegative()) { + return absoluteXor(cx, x, y); + } + + if (x->isNegative() && y->isNegative()) { + int resultLength = std::max(x->digitLength(), y->digitLength()); + + // (-x) ^ (-y) == ~(x-1) ^ ~(y-1) == (x-1) ^ (y-1) + RootedBigInt x1(cx, absoluteSubOne(cx, x, resultLength)); + if (!x1) { + return nullptr; + } + RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength())); + if (!y1) { + return nullptr; + } + return absoluteXor(cx, x1, y1); + } + MOZ_ASSERT(x->isNegative() != y->isNegative()); + int resultLength = std::max(x->digitLength(), y->digitLength()) + 1; + + HandleBigInt& pos = x->isNegative() ? y : x; + HandleBigInt& neg = x->isNegative() ? x : y; + + // x ^ (-y) == x ^ ~(y-1) == ~(x ^ (y-1)) == -((x ^ (y-1)) + 1) + RootedBigInt result(cx, absoluteSubOne(cx, neg, resultLength)); + if (!result) { + return nullptr; + } + result = absoluteXor(cx, result, pos); + if (!result) { + return nullptr; + } + bool resultNegative = true; + return absoluteAddOne(cx, result, resultNegative); +} + +// BigInt proposal section 1.1.19. BigInt::bitwiseOR ( x, y ) +BigInt* BigInt::bitOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return y; + } + + if (y->isZero()) { + return x; + } + + unsigned resultLength = std::max(x->digitLength(), y->digitLength()); + bool resultNegative = x->isNegative() || y->isNegative(); + + if (!resultNegative) { + return absoluteOr(cx, x, y); + } + + if (x->isNegative() && y->isNegative()) { + // (-x) | (-y) == ~(x-1) | ~(y-1) == ~((x-1) & (y-1)) + // == -(((x-1) & (y-1)) + 1) + RootedBigInt result(cx, absoluteSubOne(cx, x, resultLength)); + if (!result) { + return nullptr; + } + RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength())); + if (!y1) { + return nullptr; + } + result = absoluteAnd(cx, result, y1); + if (!result) { + return nullptr; + } + return absoluteAddOne(cx, result, resultNegative); + } + + MOZ_ASSERT(x->isNegative() != y->isNegative()); + HandleBigInt& pos = x->isNegative() ? y : x; + HandleBigInt& neg = x->isNegative() ? x : y; + + // x | (-y) == x | ~(y-1) == ~((y-1) &~ x) == -(((y-1) &~ x) + 1) + RootedBigInt result(cx, absoluteSubOne(cx, neg, resultLength)); + if (!result) { + return nullptr; + } + result = absoluteAndNot(cx, result, pos); + if (!result) { + return nullptr; + } + return absoluteAddOne(cx, result, resultNegative); +} + +// BigInt proposal section 1.1.2. BigInt::bitwiseNOT ( x ) +BigInt* BigInt::bitNot(ExclusiveContext* cx, HandleBigInt x) { + if (x->isNegative()) { + // ~(-x) == ~(~(x-1)) == x-1 + return absoluteSubOne(cx, x, x->digitLength()); + } else { + // ~x == -x-1 == -(x+1) + bool resultNegative = true; + return absoluteAddOne(cx, x, resultNegative); + } +} + +int64_t BigInt::toInt64(BigInt* x) { return WrapToSigned(toUint64(x)); } + +uint64_t BigInt::toUint64(BigInt* x) { + if (x->isZero()) { + return 0; + } + + uint64_t digit = x->digit(0); + + if (DigitBits == 32 && x->digitLength() > 1) { + digit |= static_cast<uint64_t>(x->digit(1)) << 32; + } + + // Return the two's complement if x is negative. + if (x->isNegative()) { + return ~(digit - 1); + } + + return digit; +} + +// Compute `2**bits - (x & (2**bits - 1))`. Used when treating BigInt values as +// arbitrary-precision two's complement signed integers. +BigInt* BigInt::truncateAndSubFromPowerOfTwo(ExclusiveContext* cx, HandleBigInt x, + uint64_t bits, + bool resultNegative) { + MOZ_ASSERT(bits != 0); + MOZ_ASSERT(!x->isZero()); + + size_t resultLength = CeilDiv(bits, DigitBits); + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + + // Process all digits except the MSD. + size_t xLength = x->digitLength(); + Digit borrow = 0; + // Take digits from `x` until its length is exhausted. + for (size_t i = 0; i < std::min(resultLength - 1, xLength); i++) { + Digit newBorrow = 0; + Digit difference = digitSub(0, x->digit(i), &newBorrow); + difference = digitSub(difference, borrow, &newBorrow); + result->setDigit(i, difference); + borrow = newBorrow; + } + // Then simulate leading zeroes in `x` as needed. + for (size_t i = xLength; i < resultLength - 1; i++) { + Digit newBorrow = 0; + Digit difference = digitSub(0, borrow, &newBorrow); + result->setDigit(i, difference); + borrow = newBorrow; + } + + // The MSD might contain extra bits that we don't want. + Digit xMSD = resultLength <= xLength ? x->digit(resultLength - 1) : 0; + Digit resultMSD; + if (bits % DigitBits == 0) { + Digit newBorrow = 0; + resultMSD = digitSub(0, xMSD, &newBorrow); + resultMSD = digitSub(resultMSD, borrow, &newBorrow); + } else { + size_t drop = DigitBits - (bits % DigitBits); + xMSD = (xMSD << drop) >> drop; + Digit minuendMSD = Digit(1) << (DigitBits - drop); + Digit newBorrow = 0; + resultMSD = digitSub(minuendMSD, xMSD, &newBorrow); + resultMSD = digitSub(resultMSD, borrow, &newBorrow); + MOZ_ASSERT(newBorrow == 0, "result < 2^bits"); + // If all subtracted bits were zero, we have to get rid of the + // materialized minuendMSD again. + resultMSD &= (minuendMSD - 1); + } + result->setDigit(resultLength - 1, resultMSD); + + return trimHighZeroDigits(cx, result); +} + +BigInt* BigInt::asUintN(ExclusiveContext* cx, HandleBigInt x, uint64_t bits) { + if (x->isZero()) { + return x; + } + + if (bits == 0) { + return zero(cx); + } + + // When truncating a negative number, simulate two's complement. + if (x->isNegative()) { + bool resultNegative = false; + return truncateAndSubFromPowerOfTwo(cx, x, bits, resultNegative); + } + + if (bits <= 64) { + uint64_t u64 = toUint64(x); + uint64_t mask = uint64_t(-1) >> (64 - bits); + return createFromUint64(cx, u64 & mask); + } + + if (bits >= MaxBitLength) { + return x; + } + + Digit msd = x->digit(x->digitLength() - 1); + size_t msdBits = DigitBits - DigitLeadingZeroes(msd); + size_t bitLength = msdBits + (x->digitLength() - 1) * DigitBits; + + if (bits >= bitLength) { + return x; + } + + size_t length = CeilDiv(bits, DigitBits); + bool isNegative = false; + + BigInt* res = createUninitialized(cx, length, isNegative); + if (!res) { + return nullptr; + } + + MOZ_ASSERT(length >= 2, "single-digit cases should be handled above"); + MOZ_ASSERT(length <= x->digitLength()); + for (size_t i = 0; i < length - 1; i++) { + res->setDigit(i, x->digit(i)); + } + + Digit mask = Digit(-1) >> (DigitBits - (bits % DigitBits)); + res->setDigit(length - 1, x->digit(length - 1) & mask); + + return res; +} + +BigInt* BigInt::asIntN(ExclusiveContext* cx, HandleBigInt x, uint64_t bits) { + if (x->isZero()) { + return x; + } + + if (bits == 0) { + return zero(cx); + } + + if (bits == 64) { + return createFromInt64(cx, toInt64(x)); + } + + if (bits > MaxBitLength) { + return x; + } + + Digit msd = x->digit(x->digitLength() - 1); + size_t msdBits = DigitBits - DigitLeadingZeroes(msd); + size_t bitLength = msdBits + (x->digitLength() - 1) * DigitBits; + + if (bits > bitLength) { + return x; + } + + Digit signBit = Digit(1) << ((bits - 1) % DigitBits); + if (bits == bitLength && msd < signBit) { + return x; + } + + // All the cases above were the trivial cases: truncating zero, or to zero + // bits, or to more bits than are in `x` (so we return `x` directly), or we + // already have the 64-bit fast path. If we get here, follow the textbook + // algorithm from the specification. + + // BigInt.asIntN step 3: Let `mod` be `x` modulo `2**bits`. + RootedBigInt mod(cx, asUintN(cx, x, bits)); + if (!mod) { + return nullptr; + } + + // Step 4: If `mod >= 2**(bits - 1)`, return `mod - 2**bits`; otherwise, + // return `mod`. + if (mod->digitLength() == CeilDiv(bits, DigitBits) && + (mod->digit(mod->digitLength() - 1) & signBit) != 0) { + bool resultNegative = true; + return truncateAndSubFromPowerOfTwo(cx, mod, bits, resultNegative); + } + + return mod; +} + +static bool ValidBigIntOperands(ExclusiveContext* cx, HandleValue lhs, + HandleValue rhs) { + MOZ_ASSERT(lhs.isBigInt() || rhs.isBigInt()); + + if (!lhs.isBigInt() || !rhs.isBigInt()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TO_NUMBER); + } + return false; + } + + return true; +} + +bool BigInt::add(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::add(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::sub(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::sub(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::mul(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::mul(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::div(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::div(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::mod(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::mod(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::pow(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::pow(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::neg(ExclusiveContext* cx, HandleValue operand, MutableHandleValue res) { + MOZ_ASSERT(operand.isBigInt()); + + RootedBigInt operandBigInt(cx, operand.toBigInt()); + BigInt* resBigInt = BigInt::neg(cx, operandBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::lsh(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::lsh(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::rsh(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::rsh(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::bitAnd(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::bitAnd(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::bitXor(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::bitXor(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::bitOr(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::bitOr(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::bitNot(ExclusiveContext* cx, HandleValue operand, + MutableHandleValue res) { + MOZ_ASSERT(operand.isBigInt()); + + RootedBigInt operandBigInt(cx, operand.toBigInt()); + BigInt* resBigInt = BigInt::bitNot(cx, operandBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +// BigInt proposal section 7.3 +BigInt* js::ToBigInt(ExclusiveContext* cx, HandleValue val) { + RootedValue v(cx, val); + + if(cx->isJSContext()) { + // Step 1. + if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, &v)) { + return nullptr; + } + + // Step 2. + if (v.isBigInt()) { + return v.toBigInt(); + } + + if (v.isBoolean()) { + return v.toBoolean() ? BigInt::one(cx) : BigInt::zero(cx); + } + + if (v.isString()) { + BigInt* bi = nullptr; + RootedString str(cx, v.toString()); + JS_TRY_VAR_OR_RETURN_NULL(cx, bi, StringToBigInt(cx, str)); + if (!bi) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_INVALID_SYNTAX); + return nullptr; + } + return bi; + } + + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); + } + return nullptr; +} + +double BigInt::numberValue(BigInt* x) { + if (x->isZero()) { + return 0.0; + } + + using Double = mozilla::FloatingPoint<double>; + constexpr uint8_t ExponentShift = Double::kExponentShift; + constexpr uint8_t SignificandWidth = Double::kSignificandWidth; + constexpr unsigned ExponentBias = Double::kExponentBias; + constexpr uint8_t SignShift = Double::kExponentWidth + SignificandWidth; + + size_t length = x->digitLength(); + MOZ_ASSERT(length != 0); + + // Fast path for the likely-common case of up to a uint64_t of magnitude + // that doesn't exceed integral precision in IEEE-754. + if (length <= 64 / DigitBits) { + uint64_t magnitude = x->digit(0); + if (DigitBits == 32 && length > 1) { + magnitude |= uint64_t(x->digit(1)) << 32; + } + const uint64_t MaxIntegralPrecisionDouble = uint64_t(1) + << (SignificandWidth + 1); + if (magnitude <= MaxIntegralPrecisionDouble) { + return x->isNegative() ? -double(magnitude) : +double(magnitude); + } + } + + Digit msd = x->digit(length - 1); + uint8_t msdLeadingZeroes = DigitLeadingZeroes(msd); + + // `2**ExponentBias` is the largest power of two in a finite IEEE-754 + // double. If this bigint has a greater power of two, it'll round to + // infinity. + uint64_t exponent = length * DigitBits - msdLeadingZeroes - 1; + if (exponent > ExponentBias) { + return x->isNegative() ? mozilla::NegativeInfinity<double>() + : mozilla::PositiveInfinity<double>(); + } + + // Otherwise munge the most significant bits of the number into proper + // position in an IEEE-754 double and go to town. + + // Omit the most significant bit: the IEEE-754 format includes this bit + // implicitly for all double-precision integers. + const uint8_t msdIgnoredBits = msdLeadingZeroes + 1; + const uint8_t msdIncludedBits = DigitBits - msdIgnoredBits; + + uint8_t bitsFilled = msdIncludedBits; + + // Shift `msd`'s contributed bits upward to remove high-order zeroes and + // the highest set bit (which is implicit in IEEE-754 integral values so + // must be removed) and to add low-order zeroes. + uint64_t shiftedMantissa = + msdIncludedBits == 0 ? 0 : uint64_t(msd) << (64 - msdIncludedBits); + + // Add in bits from the next one or two digits if `msd` didn't contain all + // bits necessary to define the result. (The extra bit allows us to + // properly round an inexact overall result.) Any lower bits that are + // uselessly set will be shifted away when `shiftedMantissa` is converted to + // a real mantissa. + if (bitsFilled < SignificandWidth + 1) { + MOZ_ASSERT(length >= 2, + "single-Digit numbers with this few bits should have been " + "handled by the fast-path above"); + + Digit second = x->digit(length - 2); + if (DigitBits == 32) { + shiftedMantissa |= uint64_t(second) << msdIgnoredBits; + bitsFilled += DigitBits; + + // Add in bits from another digit, if any, if we still have unfilled + // significand bits. + if (bitsFilled < SignificandWidth + 1 && length >= 3) { + Digit third = x->digit(length - 3); + shiftedMantissa |= uint64_t(third) >> msdIncludedBits; + // The second and third 32-bit digits contributed 64 bits total, filling + // well beyond the mantissa. + bitsFilled = 64; + } + } else { + shiftedMantissa |= second >> msdIncludedBits; + // A full 64-bit digit's worth of bits (some from the most significant + // digit, the rest from the next) fills well beyond the mantissa. + bitsFilled = 64; + } + } + + // Round the overall result, if necessary. (It's possible we don't need to + // round -- the number might not have enough bits to round.) + if (bitsFilled >= SignificandWidth + 1) { + constexpr uint64_t LeastSignificantBit = uint64_t(1) + << (64 - SignificandWidth); + constexpr uint64_t ExtraBit = LeastSignificantBit >> 1; + + // When the first bit outside the significand is set, the overall value + // is rounded: downward (i.e. no change to the bits) if the least + // significant bit in the significand is zero, upward if it instead is + // one. + if ((shiftedMantissa & ExtraBit) && + (shiftedMantissa & LeastSignificantBit)) { + // We're rounding upward: add to the significand bits. If they + // overflow, the exponent must also be increased. If *that* + // overflows, return the appropriate infinity. + uint64_t before = shiftedMantissa; + shiftedMantissa += ExtraBit; + if (shiftedMantissa < before) { + exponent++; + if (exponent > ExponentBias) { + return x->isNegative() ? NegativeInfinity<double>() + : PositiveInfinity<double>(); + } + } + } + } + + uint64_t significandBits = shiftedMantissa >> (64 - SignificandWidth); + uint64_t signBit = uint64_t(x->isNegative() ? 1 : 0) << SignShift; + uint64_t exponentBits = (exponent + ExponentBias) << ExponentShift; + return mozilla::BitwiseCast<double>(signBit | exponentBits | significandBits); +} + +int8_t BigInt::compare(BigInt* x, BigInt* y) { + // Sanity checks to catch negative zeroes escaping to the wild. + MOZ_ASSERT(!x->isNegative() || !x->isZero()); + MOZ_ASSERT(!y->isNegative() || !y->isZero()); + + bool xSign = x->isNegative(); + + if (xSign != y->isNegative()) { + return xSign ? -1 : 1; + } + + if (xSign) { + mozilla::Swap(x, y); + } + + return absoluteCompare(x, y); +} + +bool BigInt::equal(BigInt* lhs, BigInt* rhs) { + if (lhs == rhs) { + return true; + } + if (lhs->digitLength() != rhs->digitLength()) { + return false; + } + if (lhs->isNegative() != rhs->isNegative()) { + return false; + } + for (size_t i = 0; i < lhs->digitLength(); i++) { + if (lhs->digit(i) != rhs->digit(i)) { + return false; + } + } + return true; +} + +int8_t BigInt::compare(BigInt* x, double y) { + MOZ_ASSERT(!mozilla::IsNaN(y)); + + constexpr int LessThan = -1, Equal = 0, GreaterThan = 1; + + // ±Infinity exceeds a finite bigint value. + if (!mozilla::IsFinite(y)) { + return y > 0 ? LessThan : GreaterThan; + } + + // Handle `x === 0n` and `y == 0` special cases. + if (x->isZero()) { + if (y == 0) { + // -0 and +0 are treated identically. + return Equal; + } + + return y > 0 ? LessThan : GreaterThan; + } + + const bool xNegative = x->isNegative(); + if (y == 0) { + return xNegative ? LessThan : GreaterThan; + } + + // Nonzero `x` and `y` with different signs are trivially compared. + const bool yNegative = y < 0; + if (xNegative != yNegative) { + return xNegative ? LessThan : GreaterThan; + } + + // `x` and `y` are same-signed. Determine which has greater magnitude, + // then combine that with the signedness just computed to reach a result. + const int exponent = mozilla::ExponentComponent(y); + if (exponent < 0) { + // `y` is a nonzero fraction of magnitude less than 1. + return xNegative ? LessThan : GreaterThan; + } + + size_t xLength = x->digitLength(); + MOZ_ASSERT(xLength > 0); + + Digit xMSD = x->digit(xLength - 1); + const int shift = DigitLeadingZeroes(xMSD); + int xBitLength = xLength * DigitBits - shift; + + // Differing bit-length makes for a simple comparison. + int yBitLength = exponent + 1; + if (xBitLength < yBitLength) { + return xNegative ? GreaterThan : LessThan; + } + if (xBitLength > yBitLength) { + return xNegative ? LessThan : GreaterThan; + } + + // Compare the high 64 bits of both numbers. (Lower-order bits not present + // in either number are zeroed.) Either that distinguishes `x` and `y`, or + // `x` and `y` differ only if a subsequent nonzero bit in `x` means `x` has + // larger magnitude. + + using Double = mozilla::FloatingPoint<double>; + constexpr uint8_t SignificandWidth = Double::kSignificandWidth; + constexpr uint64_t SignificandBits = Double::kSignificandBits; + + const uint64_t doubleBits = mozilla::BitwiseCast<uint64_t>(y); + const uint64_t significandBits = doubleBits & SignificandBits; + + // Readd the implicit-one bit when constructing `y`'s high 64 bits. + const uint64_t yHigh64Bits = + ((uint64_t(1) << SignificandWidth) | significandBits) + << (64 - SignificandWidth - 1); + + // Cons up `x`'s high 64 bits, backfilling zeroes for binary fractions of 1 + // if `x` doesn't have 64 bits. + uint8_t xBitsFilled = DigitBits - shift; + uint64_t xHigh64Bits = uint64_t(xMSD) << (64 - xBitsFilled); + + // At this point we no longer need to look at the most significant digit. + xLength--; + + // The high 64 bits from `x` will probably not align to a digit boundary. + // `xHasNonZeroLeftoverBits` will be set to true if any remaining + // least-significant bit from the digit holding xHigh64Bits's + // least-significant bit is nonzero. + bool xHasNonZeroLeftoverBits = false; + + if (xBitsFilled < std::min(xBitLength, 64)) { + MOZ_ASSERT(xLength >= 1, + "If there are more bits to fill, there should be " + "more digits to fill them from"); + + Digit second = x->digit(--xLength); + if (DigitBits == 32) { + xBitsFilled += 32; + xHigh64Bits |= uint64_t(second) << (64 - xBitsFilled); + if (xBitsFilled < 64 && xLength >= 1) { + Digit third = x->digit(--xLength); + const uint8_t neededBits = 64 - xBitsFilled; + xHigh64Bits |= uint64_t(third) >> (DigitBits - neededBits); + xHasNonZeroLeftoverBits = (third << neededBits) != 0; + } + } else { + const uint8_t neededBits = 64 - xBitsFilled; + xHigh64Bits |= uint64_t(second) >> (DigitBits - neededBits); + xHasNonZeroLeftoverBits = (second << neededBits) != 0; + } + } + + // If high bits are unequal, the larger one has greater magnitude. + if (yHigh64Bits > xHigh64Bits) { + return xNegative ? GreaterThan : LessThan; + } + if (xHigh64Bits > yHigh64Bits) { + return xNegative ? LessThan : GreaterThan; + } + + // Otherwise the top 64 bits of both are equal. If the values differ, a + // lower-order bit in `x` is nonzero and `x` has greater magnitude than + // `y`; otherwise `x == y`. + if (xHasNonZeroLeftoverBits) { + return xNegative ? LessThan : GreaterThan; + } + while (xLength != 0) { + if (x->digit(--xLength) != 0) { + return xNegative ? LessThan : GreaterThan; + } + } + + return Equal; +} + +bool BigInt::equal(BigInt* lhs, double rhs) { + if (mozilla::IsNaN(rhs)) { + return false; + } + return compare(lhs, rhs) == 0; +} + +// BigInt proposal section 3.2.5 +JS::Result<bool> BigInt::looselyEqual(ExclusiveContext* cx, HandleBigInt lhs, + HandleValue rhs) { + // Step 1. + if (rhs.isBigInt()) { + return equal(lhs, rhs.toBigInt()); + } + + // Steps 2-5 (not applicable). + + // Steps 6-7. + if (rhs.isString()) { + RootedBigInt rhsBigInt(cx); + RootedString rhsString(cx, rhs.toString()); + MOZ_TRY_VAR(rhsBigInt, StringToBigInt(cx, rhsString)); + if (!rhsBigInt) { + return false; + } + return equal(lhs, rhsBigInt); + } + + // Steps 8-9 (not applicable). + + // Steps 10-11. + if (rhs.isObject()) { + RootedValue rhsPrimitive(cx, rhs); + if (!cx->isJSContext() || !ToPrimitive(cx->asJSContext(), &rhsPrimitive)) { + return cx->alreadyReportedError(); + } + return looselyEqual(cx, lhs, rhsPrimitive); + } + + // Step 12. + if (rhs.isNumber()) { + return equal(lhs, rhs.toNumber()); + } + + // Step 13. + return false; +} + +// BigInt proposal section 1.1.12. BigInt::lessThan ( x, y ) +bool BigInt::lessThan(BigInt* x, BigInt* y) { return compare(x, y) < 0; } + +Maybe<bool> BigInt::lessThan(BigInt* lhs, double rhs) { + if (mozilla::IsNaN(rhs)) { + return Maybe<bool>(Nothing()); + } + return Some(compare(lhs, rhs) < 0); +} + +Maybe<bool> BigInt::lessThan(double lhs, BigInt* rhs) { + if (mozilla::IsNaN(lhs)) { + return Maybe<bool>(Nothing()); + } + return Some(-compare(rhs, lhs) < 0); +} + +bool BigInt::lessThan(ExclusiveContext* cx, HandleBigInt lhs, HandleString rhs, + Maybe<bool>& res) { + RootedBigInt rhsBigInt(cx); + JS_TRY_VAR_OR_RETURN_FALSE(cx, rhsBigInt, StringToBigInt(cx, rhs)); + if (!rhsBigInt) { + res = Nothing(); + return true; + } + res = Some(lessThan(lhs, rhsBigInt)); + return true; +} + +bool BigInt::lessThan(ExclusiveContext* cx, HandleString lhs, HandleBigInt rhs, + Maybe<bool>& res) { + RootedBigInt lhsBigInt(cx); + JS_TRY_VAR_OR_RETURN_FALSE(cx, lhsBigInt, StringToBigInt(cx, lhs)); + if (!lhsBigInt) { + res = Nothing(); + return true; + } + res = Some(lessThan(lhsBigInt, rhs)); + return true; +} + +bool BigInt::lessThan(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + Maybe<bool>& res) { + if (lhs.isBigInt()) { + if (rhs.isString()) { + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedString rhsString(cx, rhs.toString()); + return lessThan(cx, lhsBigInt, rhsString, res); + } + + if (rhs.isNumber()) { + res = lessThan(lhs.toBigInt(), rhs.toNumber()); + return true; + } + + MOZ_ASSERT(rhs.isBigInt()); + res = Some(lessThan(lhs.toBigInt(), rhs.toBigInt())); + return true; + } + + MOZ_ASSERT(rhs.isBigInt()); + if (lhs.isString()) { + RootedString lhsString(cx, lhs.toString()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + return lessThan(cx, lhsString, rhsBigInt, res); + } + + MOZ_ASSERT(lhs.isNumber()); + res = lessThan(lhs.toNumber(), rhs.toBigInt()); + return true; +} + +JSLinearString* BigInt::toString(ExclusiveContext* cx, HandleBigInt x, uint8_t radix) { + MOZ_ASSERT(2 <= radix && radix <= 36); + + if (x->isZero()) { + return cx->staticStrings().getInt(0); + } + + if (mozilla::IsPowerOfTwo(radix)) { + return toStringBasePowerOfTwo(cx, x, radix); + } + + return toStringGeneric(cx, x, radix); +} + +template <typename CharT> +static inline BigInt* ParseStringBigIntLiteral(ExclusiveContext* cx, + Range<const CharT> range, + bool* haveParseError) { + auto start = range.begin(); + auto end = range.end(); + + while (start < end && unicode::IsSpace(start[0])) { + start++; + } + + while (start < end && unicode::IsSpace(end[-1])) { + end--; + } + + if (start == end) { + return BigInt::zero(cx); + } + + // StringNumericLiteral ::: StrDecimalLiteral, but without Infinity, decimal + // points, or exponents. Note that the raw '+' or '-' cases fall through + // because the string is too short, and eventually signal a parse error. + if (end - start > 1) { + if (start[0] == '+') { + bool isNegative = false; + start++; + return BigInt::parseLiteralDigits(cx, Range<const CharT>(start, end), 10, + isNegative, haveParseError); + } else if (start[0] == '-') { + bool isNegative = true; + start++; + return BigInt::parseLiteralDigits(cx, Range<const CharT>(start, end), 10, + isNegative, haveParseError); + } + } + + return BigInt::parseLiteral(cx, Range<const CharT>(start, end), + haveParseError); +} + +// Called from BigInt constructor. +JS::Result<BigInt*, JS::OOM&> js::StringToBigInt(ExclusiveContext* cx, + HandleString str) { + JSLinearString* linear = str->ensureLinear(cx); + if (!linear) { + return cx->alreadyReportedOOM(); + } + + BigInt* res = nullptr; + bool parseError = false; + + if(cx->isJSContext()) { + AutoStableStringChars chars(cx->asJSContext()); + if (!chars.init(cx->asJSContext(), str)) { + return cx->alreadyReportedOOM(); + } + + if (chars.isLatin1()) { + res = ParseStringBigIntLiteral(cx->asJSContext(), chars.latin1Range(), &parseError); + } else { + res = ParseStringBigIntLiteral(cx->asJSContext(), chars.twoByteRange(), &parseError); + } + } + + // A nullptr result can indicate either a parse error or out-of-memory. + if (!res && !parseError) { + return cx->alreadyReportedOOM(); + } + + return res; +} + +// Called from parser with already trimmed and validated token. +BigInt* js::ParseBigIntLiteral(ExclusiveContext* cx, + const Range<const char16_t>& chars) { + bool parseError = false; + BigInt* res = BigInt::parseLiteral(cx, chars, &parseError); + if (!res) { + return nullptr; + } + MOZ_RELEASE_ASSERT(!parseError); + return res; +} + +JSAtom* js::BigIntToAtom(ExclusiveContext* cx, HandleBigInt bi) { + JSString* str = BigInt::toString(cx, bi, 10); + if (!str) { + return nullptr; + } + return AtomizeString(cx, str); +} + +JS::ubi::Node::Size JS::ubi::Concrete<BigInt>::size( + mozilla::MallocSizeOf mallocSizeOf) const { + BigInt& bi = get(); + MOZ_ASSERT(bi.isTenured()); + size_t size = js::gc::Arena::thingSize(bi.asTenured().getAllocKind()); + size += bi.sizeOfExcludingThis(mallocSizeOf); + return size; +} + +template <XDRMode mode> +bool js::XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi) { + ExclusiveContext* cx = xdr->cx(); + + uint8_t sign; + uint32_t length; + + if (mode == XDR_ENCODE) { + sign = static_cast<uint8_t>(bi->isNegative()); + uint64_t sz = bi->digitLength() * sizeof(BigInt::Digit); + // As the maximum source code size is currently UINT32_MAX code units + // (see BytecodeCompiler::checkLength), any bigint literal's length in + // word-sized digits will be less than UINT32_MAX as well. That could + // change or FoldConstants could start creating these though, so leave + // this as a release-enabled assert. + MOZ_RELEASE_ASSERT(sz <= UINT32_MAX); + length = static_cast<uint32_t>(sz); + } + + if(!xdr->codeUint8(&sign)) + return false; + if(!xdr->codeUint32(&length)) + return false; + + MOZ_RELEASE_ASSERT(length % sizeof(BigInt::Digit) == 0); + uint32_t digitLength = length / sizeof(BigInt::Digit); + auto buf = cx->make_pod_array<BigInt::Digit>(digitLength); + if (!buf) { + return xdr->fail(JS::TranscodeResult_Throw); + } + + if (mode == XDR_ENCODE) { + std::uninitialized_copy_n(bi->digits().Elements(), digitLength, buf.get()); + } + + if(!xdr->codeBytes(buf.get(), length)) + return false; + + if (mode == XDR_DECODE) { + BigInt* res = BigInt::createUninitialized(cx, digitLength, sign); + if (!res) { + return xdr->fail(JS::TranscodeResult_Throw); + } + std::uninitialized_copy_n(buf.get(), digitLength, bi->digits().Elements()); + bi.set(res); + } + + return true; +} + +template bool js::XDRBigInt(XDRState<XDR_ENCODE>* xdr, MutableHandleBigInt bi); + +template bool js::XDRBigInt(XDRState<XDR_DECODE>* xdr, MutableHandleBigInt bi); + diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h new file mode 100644 index 0000000000..0ccba99634 --- /dev/null +++ b/js/src/vm/BigIntType.h @@ -0,0 +1,379 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_BigIntType_h +#define vm_BigIntType_h + +#include "mozilla/Range.h" +#include "mozilla/Span.h" + +#include "gc/Barrier.h" +#include "gc/Marking.h" +#include "gc/Heap.h" +#include "js/GCHashTable.h" +#include "js/RootingAPI.h" +#include "js/TraceKind.h" +#include "js/TypeDecls.h" +#include "vm/String.h" +#include "vm/Xdr.h" + +// Handle future js::gc::Cell::ReservedBits, we have no reserved bits... +#define js_gc_Cell_ReservedBits 0 +// Handle future js::gc::MinCellSize, 16 bytes, twice our js:gc:CellSize +#define js_gc_MinCellSize (js::gc::CellSize*2) + +namespace JS { + +class BigInt; + +} // namespace JS + +namespace js { + +template <XDRMode mode> +bool XDRBigInt(XDRState<mode>* xdr, MutableHandle<JS::BigInt*> bi); + +} // namespace js + +namespace JS { + +class BigInt final : public js::gc::TenuredCell { + public: + using Digit = uintptr_t; + + private: + // The low js::gc::Cell::ReservedBits are reserved. + static constexpr uintptr_t SignBit = JS_BIT(js_gc_Cell_ReservedBits); + static constexpr uintptr_t LengthShift = js_gc_Cell_ReservedBits + 1; + static constexpr size_t InlineDigitsLength = + (js_gc_MinCellSize - sizeof(uintptr_t)) / sizeof(Digit); + + uintptr_t lengthSignAndReservedBits_; + + // The digit storage starts with the least significant digit (little-endian + // digit order). Byte order within a digit is of course native endian. + union { + Digit* heapDigits_; + Digit inlineDigits_[InlineDigitsLength]; + }; + + public: + static const JS::TraceKind TraceKind = JS::TraceKind::BigInt; + + size_t digitLength() const { + return lengthSignAndReservedBits_ >> LengthShift; + } + + bool hasInlineDigits() const { return digitLength() <= InlineDigitsLength; } + bool hasHeapDigits() const { return !hasInlineDigits(); } + + using Digits = mozilla::Span<Digit>; + Digits digits() { + return Digits(hasInlineDigits() ? inlineDigits_ : heapDigits_, + digitLength()); + } + Digit digit(size_t idx) { return digits()[idx]; } + void setDigit(size_t idx, Digit digit) { digits()[idx] = digit; } + + bool isZero() const { return digitLength() == 0; } + bool isNegative() const { return lengthSignAndReservedBits_ & SignBit; } + + // Offset for direct access from JIT code. + static constexpr size_t offsetOfLengthSignAndReservedBits() { + return offsetof(BigInt, lengthSignAndReservedBits_); + } + + void initializeDigitsToZero(); + + void traceChildren(JSTracer* trc); + void finalize(js::FreeOp* fop); + js::HashNumber hash(); + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + static BigInt* createUninitialized(js::ExclusiveContext* cx, size_t length, + bool isNegative); + static BigInt* createFromDouble(js::ExclusiveContext* cx, double d); + static BigInt* createFromUint64(js::ExclusiveContext* cx, uint64_t n); + static BigInt* createFromInt64(js::ExclusiveContext* cx, int64_t n); + // FIXME: Cache these values. + static BigInt* zero(js::ExclusiveContext* cx); + static BigInt* one(js::ExclusiveContext* cx); + + static BigInt* copy(js::ExclusiveContext* cx, Handle<BigInt*> x); + static BigInt* add(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* sub(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* mul(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* div(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* mod(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* pow(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* neg(js::ExclusiveContext* cx, Handle<BigInt*> x); + static BigInt* lsh(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* rsh(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* bitAnd(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* bitXor(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* bitOr(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* bitNot(js::ExclusiveContext* cx, Handle<BigInt*> x); + + static int64_t toInt64(BigInt* x); + static uint64_t toUint64(BigInt* x); + + static BigInt* asIntN(js::ExclusiveContext* cx, Handle<BigInt*> x, uint64_t bits); + static BigInt* asUintN(js::ExclusiveContext* cx, Handle<BigInt*> x, uint64_t bits); + + // Type-checking versions of arithmetic operations. These methods + // must be called with at least one BigInt operand. Binary + // operations will throw a TypeError if one of the operands is not a + // BigInt value. + static bool add(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool sub(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool mul(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool div(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool mod(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool pow(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool neg(js::ExclusiveContext* cx, Handle<Value> operand, + MutableHandle<Value> res); + static bool lsh(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool rsh(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool bitAnd(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool bitXor(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool bitOr(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool bitNot(js::ExclusiveContext* cx, Handle<Value> operand, + MutableHandle<Value> res); + + static double numberValue(BigInt* x); + + static JSLinearString* toString(js::ExclusiveContext* cx, Handle<BigInt*> x, + uint8_t radix); + template <typename CharT> + static BigInt* parseLiteral(js::ExclusiveContext* cx, + const mozilla::Range<const CharT> chars, + bool* haveParseError); + template <typename CharT> + static BigInt* parseLiteralDigits(js::ExclusiveContext* cx, + const mozilla::Range<const CharT> chars, + unsigned radix, bool isNegative, + bool* haveParseError); + + static int8_t compare(BigInt* lhs, BigInt* rhs); + static bool equal(BigInt* lhs, BigInt* rhs); + static JS::Result<bool> looselyEqual(js::ExclusiveContext* cx, Handle<BigInt*> lhs, + HandleValue rhs); + + static bool lessThan(BigInt* x, BigInt* y); + // These methods return Nothing when the non-BigInt operand is NaN + // or a string that can't be interpreted as a BigInt. + static mozilla::Maybe<bool> lessThan(BigInt* lhs, double rhs); + static mozilla::Maybe<bool> lessThan(double lhs, BigInt* rhs); + static bool lessThan(js::ExclusiveContext* cx, Handle<BigInt*> lhs, HandleString rhs, + mozilla::Maybe<bool>& res); + static bool lessThan(js::ExclusiveContext* cx, HandleString lhs, Handle<BigInt*> rhs, + mozilla::Maybe<bool>& res); + static bool lessThan(js::ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + mozilla::Maybe<bool>& res); + + private: + static constexpr size_t DigitBits = sizeof(Digit) * CHAR_BIT; + static constexpr size_t HalfDigitBits = DigitBits / 2; + static constexpr Digit HalfDigitMask = (1ull << HalfDigitBits) - 1; + + static_assert(DigitBits == 32 || DigitBits == 64, + "Unexpected BigInt Digit size"); + + // The maximum number of digits that the current implementation supports + // would be 0x7fffffff / DigitBits. However, we use a lower limit for now, + // because raising it later is easier than lowering it. Support up to 1 + // million bits. + static constexpr size_t MaxBitLength = 1024 * 1024; + static constexpr size_t MaxDigitLength = MaxBitLength / DigitBits; + + // BigInts can be serialized to strings of radix between 2 and 36. For a + // given bigint, radix 2 will take the most characters (one per bit). + // Ensure that the max bigint size is small enough so that we can fit the + // corresponding character count into a size_t, with space for a possible + // sign prefix. + static_assert(MaxBitLength <= std::numeric_limits<size_t>::max() - 1, + "BigInt max length must be small enough to be serialized as a " + "binary string"); + + static size_t calculateMaximumCharactersRequired(HandleBigInt x, + unsigned radix); + static MOZ_MUST_USE bool calculateMaximumDigitsRequired(js::ExclusiveContext* cx, + uint8_t radix, + size_t charCount, + size_t* result); + + static bool absoluteDivWithDigitDivisor( + js::ExclusiveContext* cx, Handle<BigInt*> x, Digit divisor, + const mozilla::Maybe<MutableHandle<BigInt*>>& quotient, Digit* remainder, + bool quotientNegative); + static void internalMultiplyAdd(BigInt* source, Digit factor, Digit summand, + unsigned, BigInt* result); + static void multiplyAccumulate(BigInt* multiplicand, Digit multiplier, + BigInt* accumulator, + unsigned accumulatorIndex); + static bool absoluteDivWithBigIntDivisor( + js::ExclusiveContext* cx, Handle<BigInt*> dividend, Handle<BigInt*> divisor, + const mozilla::Maybe<MutableHandle<BigInt*>>& quotient, + const mozilla::Maybe<MutableHandle<BigInt*>>& remainder, + bool quotientNegative); + + enum class LeftShiftMode { SameSizeResult, AlwaysAddOneDigit }; + + static BigInt* absoluteLeftShiftAlwaysCopy(js::ExclusiveContext* cx, Handle<BigInt*> x, + unsigned shift, LeftShiftMode); + static bool productGreaterThan(Digit factor1, Digit factor2, Digit high, + Digit low); + static BigInt* lshByAbsolute(js::ExclusiveContext* cx, HandleBigInt x, HandleBigInt y); + static BigInt* rshByAbsolute(js::ExclusiveContext* cx, HandleBigInt x, HandleBigInt y); + static BigInt* rshByMaximum(js::ExclusiveContext* cx, bool isNegative); + static BigInt* truncateAndSubFromPowerOfTwo(js::ExclusiveContext* cx, HandleBigInt x, + uint64_t bits, + bool resultNegative); + + Digit absoluteInplaceAdd(BigInt* summand, unsigned startIndex); + Digit absoluteInplaceSub(BigInt* subtrahend, unsigned startIndex); + void inplaceRightShiftLowZeroBits(unsigned shift); + void inplaceMultiplyAdd(Digit multiplier, Digit part); + + // The result of an SymmetricTrim bitwise op has as many digits as the + // smaller operand. A SymmetricFill bitwise op result has as many digits as + // the larger operand, with high digits (if any) copied from the larger + // operand. AsymmetricFill is like SymmetricFill, except the result has as + // many digits as the first operand; this kind is used for the and-not + // operation. + enum class BitwiseOpKind { SymmetricTrim, SymmetricFill, AsymmetricFill }; + + template <BitwiseOpKind kind, typename BitwiseOp> + static BigInt* absoluteBitwiseOp(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y, BitwiseOp&& op); + + // Return `|x| & |y|`. + static BigInt* absoluteAnd(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y); + + // Return `|x| | |y|`. + static BigInt* absoluteOr(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y); + + // Return `|x| & ~|y|`. + static BigInt* absoluteAndNot(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y); + + // Return `|x| ^ |y|`. + static BigInt* absoluteXor(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y); + + // Return `(|x| + 1) * (resultNegative ? -1 : +1)`. + static BigInt* absoluteAddOne(js::ExclusiveContext* cx, Handle<BigInt*> x, + bool resultNegative); + + // Return `(|x| - 1) * (resultNegative ? -1 : +1)`, with the precondition that + // |x| != 0. + static BigInt* absoluteSubOne(js::ExclusiveContext* cx, Handle<BigInt*> x, + unsigned resultLength); + + // Return `a + b`, incrementing `*carry` if the addition overflows. + static inline Digit digitAdd(Digit a, Digit b, Digit* carry) { + Digit result = a + b; + *carry += static_cast<Digit>(result < a); + return result; + } + + // Return `left - right`, incrementing `*borrow` if the addition overflows. + static inline Digit digitSub(Digit left, Digit right, Digit* borrow) { + Digit result = left - right; + *borrow += static_cast<Digit>(result > left); + return result; + } + + // Compute `a * b`, returning the low half of the result and putting the + // high half in `*high`. + static Digit digitMul(Digit a, Digit b, Digit* high); + + // Divide `(high << DigitBits) + low` by `divisor`, returning the quotient + // and storing the remainder in `*remainder`, with the precondition that + // `high < divisor` so that the result fits in a Digit. + static Digit digitDiv(Digit high, Digit low, Digit divisor, Digit* remainder); + + // Return `(|x| + |y|) * (resultNegative ? -1 : +1)`. + static BigInt* absoluteAdd(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y, bool resultNegative); + + // Return `(|x| - |y|) * (resultNegative ? -1 : +1)`, with the precondition + // that |x| >= |y|. + static BigInt* absoluteSub(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y, bool resultNegative); + + // If `|x| < |y|` return -1; if `|x| == |y|` return 0; otherwise return 1. + static int8_t absoluteCompare(BigInt* lhs, BigInt* rhs); + + static int8_t compare(BigInt* lhs, double rhs); + + static bool equal(BigInt* lhs, double rhs); + + static JSLinearString* toStringBasePowerOfTwo(js::ExclusiveContext* cx, Handle<BigInt*>, + unsigned radix); + static JSLinearString* toStringGeneric(js::ExclusiveContext* cx, Handle<BigInt*>, + unsigned radix); + + static BigInt* trimHighZeroDigits(js::ExclusiveContext* cx, Handle<BigInt*> x); + static BigInt* destructivelyTrimHighZeroDigits(js::ExclusiveContext* cx, + Handle<BigInt*> x); + + friend struct JSStructuredCloneReader; + friend struct JSStructuredCloneWriter; + template <js::XDRMode mode> + friend bool js::XDRBigInt(js::XDRState<mode>* xdr, MutableHandle<BigInt*> bi); + + BigInt() = delete; + BigInt(const BigInt& other) = delete; + void operator=(const BigInt& other) = delete; +}; + +static_assert( + sizeof(BigInt) >= js_gc_MinCellSize, + "sizeof(BigInt) must be greater than the minimum allocation size"); + +static_assert( + sizeof(BigInt) == js_gc_MinCellSize, + "sizeof(BigInt) intended to be the same as the minimum allocation size"); + +} // namespace JS + +namespace js { + +extern JSAtom* BigIntToAtom(js::ExclusiveContext* cx, JS::HandleBigInt bi); + +extern JS::BigInt* NumberToBigInt(js::ExclusiveContext* cx, double d); + +// Parse a BigInt from a string, using the method specified for StringToBigInt. +// Used by the BigInt constructor among other places. +extern JS::Result<JS::BigInt*, JS::OOM&> StringToBigInt( + js::ExclusiveContext* cx, JS::Handle<JSString*> str); + +// Parse a BigInt from an already-validated numeric literal. Used by the +// parser. Can only fail in out-of-memory situations. +extern JS::BigInt* ParseBigIntLiteral( + js::ExclusiveContext* cx, const mozilla::Range<const char16_t>& chars); + +extern JS::BigInt* ToBigInt(js::ExclusiveContext* cx, JS::Handle<JS::Value> v); + +} // namespace js + +#undef js_gc_MinCellSize +#undef js_gc_Cell_ReservedBits + +#endif diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index d5e7a2d058..efbd8a5323 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -275,6 +275,7 @@ macro(objectArguments, objectArguments, "[object Arguments]") \ macro(objectArray, objectArray, "[object Array]") \ macro(objectBoolean, objectBoolean, "[object Boolean]") \ + macro(objectBigInt, objectBigInt, "[object BigInt]") \ macro(objectDate, objectDate, "[object Date]") \ macro(objectError, objectError, "[object Error]") \ macro(objectFunction, objectFunction, "[object Function]") \ @@ -424,5 +425,6 @@ macro(boolean, boolean, "boolean") \ macro(null, null, "null") \ macro(symbol, symbol, "symbol") \ + macro(bigint, bigint, "bigint") \ #endif /* vm_CommonPropertyNames_h */ diff --git a/js/src/vm/EqualityOperations.cpp b/js/src/vm/EqualityOperations.cpp index 6f90450b49..91a9ca06c7 100644 --- a/js/src/vm/EqualityOperations.cpp +++ b/js/src/vm/EqualityOperations.cpp @@ -31,6 +31,10 @@ EqualGivenSameType(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bo *equal = (lval.toDouble() == rval.toDouble()); return true; } + if (lval.isBigInt()) { + *equal = JS::BigInt::equal(lval.toBigInt(), rval.toBigInt()); + return true; + } if (lval.isGCThing()) { // objects or symbols *equal = (lval.toGCThing() == rval.toGCThing()); return true; @@ -134,6 +138,22 @@ js::LooselyEqual(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bool return js::LooselyEqual(cx, lvalue, rval, result); } + if (lval.isBigInt()) { + RootedBigInt lbi(cx, lval.toBigInt()); + bool tmpResult; + JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult, BigInt::looselyEqual(cx, lbi, rval)); + *result = tmpResult; + return true; + } + + if (rval.isBigInt()) { + RootedBigInt rbi(cx, rval.toBigInt()); + bool tmpResult; + JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult, BigInt::looselyEqual(cx, rbi, lval)); + *result = tmpResult; + return true; + } + // Step 12. *result = false; return true; diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 542160ce56..b7d3344b3e 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -15,6 +15,7 @@ #include "jsweakmap.h" #include "builtin/AtomicsObject.h" +#include "builtin/BigInt.h" #include "builtin/Eval.h" #include "builtin/MapObject.h" #include "builtin/ModuleObject.h" diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 79a4b90200..a48c753f1d 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -8,6 +8,8 @@ #include "vm/Interpreter.h" +#include "mozilla/Maybe.h" + #include "jscompartment.h" #include "jsnum.h" #include "jsstr.h" @@ -390,7 +392,7 @@ DefVarOperation(JSContext* cx, HandleObject varobj, HandlePropertyName dn, unsig } static MOZ_ALWAYS_INLINE bool -NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue val, +NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, MutableHandleValue val, MutableHandleValue res) { /* @@ -401,13 +403,16 @@ NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue val int32_t i; if (val.isInt32() && (i = val.toInt32()) != 0 && i != INT32_MIN) { res.setInt32(-i); - } else { - double d; - if (!ToNumber(cx, val, &d)) - return false; - res.setNumber(-d); + return true; } + if (!ToNumeric(cx, val)) + return false; + + if (val.isBigInt()) + return BigInt::neg(cx, val, res); + + res.setNumber(-val.toNumber()); return true; } @@ -657,120 +662,268 @@ ProcessCallSiteObjOperation(JSContext* cx, RootedObject& cso, RootedObject& raw, return true; } -#define RELATIONAL_OP(OP) \ - JS_BEGIN_MACRO \ - /* Optimize for two int-tagged operands (typical loop control). */ \ - if (lhs.isInt32() && rhs.isInt32()) { \ - *res = lhs.toInt32() OP rhs.toInt32(); \ - } else { \ - if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) \ - return false; \ - if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) \ - return false; \ - if (lhs.isString() && rhs.isString()) { \ - JSString* l = lhs.toString(); \ - JSString* r = rhs.toString(); \ - int32_t result; \ - if (!CompareStrings(cx, l, r, &result)) \ - return false; \ - *res = result OP 0; \ - } else { \ - double l, r; \ - if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r)) \ - return false; \ - *res = (l OP r); \ - } \ - } \ - return true; \ - JS_END_MACRO +// BigInt proposal 3.2.4 Abstract Relational Comparison +// Returns Nothing when at least one operand is a NaN, or when +// ToNumeric or StringToBigInt can't interpret a string as a numeric +// value. (These cases correspond to a NaN result in the spec.) +// Otherwise, return a boolean to indicate whether lhs is less than +// rhs. The operands must be primitives; the caller is responsible for +// evaluating them in the correct order. +static MOZ_ALWAYS_INLINE bool +LessThanImpl(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, + mozilla::Maybe<bool>& res) +{ + // Steps 1 and 2 are performed by the caller. + + // Step 3. + if (lhs.isString() && rhs.isString()) { + JSString* l = lhs.toString(); + JSString* r = rhs.toString(); + int32_t result; + if (!CompareStrings(cx, l, r, &result)) { + return false; + } + res = mozilla::Some(result < 0); + return true; + } + + // Step 4a. + if (lhs.isBigInt() && rhs.isString()) { + return BigInt::lessThan(cx, lhs, rhs, res); + } + // Step 4b. + if (lhs.isString() && rhs.isBigInt()) { + return BigInt::lessThan(cx, lhs, rhs, res); + } + + // Steps 4c and 4d. + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + return false; + } + + // Steps 4e-j. + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::lessThan(cx, lhs, rhs, res); + } + + // Step 4e for Number operands. + MOZ_ASSERT(lhs.isNumber() && rhs.isNumber()); + double lhsNum = lhs.toNumber(); + double rhsNum = rhs.toNumber(); + + if (mozilla::IsNaN(lhsNum) || mozilla::IsNaN(rhsNum)) { + res = mozilla::Maybe<bool>(mozilla::Nothing()); + return true; + } + + res = mozilla::Some(lhsNum < rhsNum); + return true; +} static MOZ_ALWAYS_INLINE bool -LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(<); +LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() < rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe<bool> tmpResult; + if (!LessThanImpl(cx, lhs, rhs, tmpResult)) { + return false; + } + *res = tmpResult.valueOr(false); + return true; } static MOZ_ALWAYS_INLINE bool -LessThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(<=); +LessThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() <= rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe<bool> tmpResult; + if (!LessThanImpl(cx, rhs, lhs, tmpResult)) { + return false; + } + *res = !tmpResult.valueOr(true); + return true; } static MOZ_ALWAYS_INLINE bool -GreaterThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(>); +GreaterThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() > rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe<bool> tmpResult; + if (!LessThanImpl(cx, rhs, lhs, tmpResult)) { + return false; + } + *res = tmpResult.valueOr(false); + return true; } static MOZ_ALWAYS_INLINE bool -GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(>=); +GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() >= rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe<bool> tmpResult; + if (!LessThanImpl(cx, lhs, rhs, tmpResult)) { + return false; + } + *res = !tmpResult.valueOr(true); + return true; } static MOZ_ALWAYS_INLINE bool -BitNot(JSContext* cx, HandleValue in, int* out) +BitNot(JSContext* cx, MutableHandleValue in, MutableHandleValue out) { - int i; - if (!ToInt32(cx, in, &i)) + if (!ToInt32OrBigInt(cx, in)) { return false; - *out = ~i; + } + + if (in.isBigInt()) { + return BigInt::bitNot(cx, in, out); + } + + out.setInt32(~in.toInt32()); return true; } static MOZ_ALWAYS_INLINE bool -BitXor(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitXor(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = left ^ right; + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::bitXor(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() ^ rhs.toInt32()); return true; } static MOZ_ALWAYS_INLINE bool -BitOr(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitOr(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = left | right; + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::bitOr(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() | rhs.toInt32()); return true; } static MOZ_ALWAYS_INLINE bool -BitAnd(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitAnd(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = left & right; + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::bitAnd(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() & rhs.toInt32()); return true; } static MOZ_ALWAYS_INLINE bool -BitLsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitLsh(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int32_t left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = uint32_t(left) << (right & 31); + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::lsh(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() << (rhs.toInt32() & 31)); return true; } static MOZ_ALWAYS_INLINE bool -BitRsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitRsh(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int32_t left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = left >> (right & 31); + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::rsh(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() >> (rhs.toInt32() & 31)); return true; } static MOZ_ALWAYS_INLINE bool -UrshOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out) +UrshOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + return false; + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_BIGINT_TO_NUMBER); + return false; + } + uint32_t left; - int32_t right; - if (!ToUint32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + int32_t right; + if (!ToUint32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) { return false; + } left >>= right & 31; out.setNumber(uint32_t(left)); return true; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 3515a9336b..a03fa847f7 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -40,6 +40,7 @@ #include "jit/IonAnalysis.h" #include "vm/AsyncFunction.h" #include "vm/AsyncIteration.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/EqualityOperations.h" // js::StrictlyEqual #include "vm/GeneratorObject.h" @@ -812,6 +813,8 @@ js::TypeOfValue(const Value& v) return TypeOfObject(&v.toObject()); if (v.isBoolean()) return JSTYPE_BOOLEAN; + if (v.isBigInt()) + return JSTYPE_BIGINT; MOZ_ASSERT(v.isSymbol()); return JSTYPE_SYMBOL; } @@ -1325,50 +1328,63 @@ AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Muta return false; } res.setString(str); - } else { - double l, r; - if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r)) - return false; - res.setNumber(l + r); + return true; } + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) + return false; + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::add(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() + rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -SubOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +SubOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(d1 - d2); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::sub(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() - rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -MulOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +MulOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(d1 * d2); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::mul(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() * rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -DivOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +DivOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(NumberDiv(d1, d2)); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::div(cx, lhs, rhs, res); + + res.setNumber(NumberDiv(lhs.toNumber(), rhs.toNumber())); return true; } static MOZ_ALWAYS_INLINE bool -ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +ModOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { int32_t l, r; + if (lhs.isInt32() && rhs.isInt32() && (l = lhs.toInt32()) >= 0 && (r = rhs.toInt32()) > 0) { int32_t mod = l % r; @@ -1376,11 +1392,26 @@ ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue return true; } - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(NumberMod(d1, d2)); + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::mod(cx, lhs, rhs, res); + + res.setNumber(NumberMod(lhs.toNumber(), rhs.toNumber())); + return true; +} + +static MOZ_ALWAYS_INLINE bool +PowOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) +{ + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) + return false; + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::pow(cx, lhs, rhs, res); + + res.setNumber(ecmaPow(lhs.toNumber(), rhs.toNumber())); return true; } @@ -2147,32 +2178,42 @@ CASE(JSOP_BINDVAR) } END_CASE(JSOP_BINDVAR) -#define BITWISE_OP(OP) \ - JS_BEGIN_MACRO \ - int32_t i, j; \ - if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ - goto error; \ - if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ - goto error; \ - i = i OP j; \ - REGS.sp--; \ - REGS.sp[-1].setInt32(i); \ - JS_END_MACRO - CASE(JSOP_BITOR) - BITWISE_OP(|); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitOr(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_BITOR) CASE(JSOP_BITXOR) - BITWISE_OP(^); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitXor(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_BITXOR) CASE(JSOP_BITAND) - BITWISE_OP(&); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitAnd(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_BITAND) -#undef BITWISE_OP - CASE(JSOP_EQ) if (!LooseEqualityOp<true>(cx, REGS)) goto error; @@ -2228,8 +2269,9 @@ CASE(JSOP_LT) bool cond; MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); - if (!LessThanOperation(cx, lval, rval, &cond)) + if (!LessThanOperation(cx, lval, rval, &cond)) { goto error; + } TRY_BRANCH_AFTER_COND(cond, 2); REGS.sp[-2].setBoolean(cond); REGS.sp--; @@ -2241,8 +2283,9 @@ CASE(JSOP_LE) bool cond; MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); - if (!LessThanOrEqualOperation(cx, lval, rval, &cond)) + if (!LessThanOrEqualOperation(cx, lval, rval, &cond)) { goto error; + } TRY_BRANCH_AFTER_COND(cond, 2); REGS.sp[-2].setBoolean(cond); REGS.sp--; @@ -2254,8 +2297,9 @@ CASE(JSOP_GT) bool cond; MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); - if (!GreaterThanOperation(cx, lval, rval, &cond)) + if (!GreaterThanOperation(cx, lval, rval, &cond)) { goto error; + } TRY_BRANCH_AFTER_COND(cond, 2); REGS.sp[-2].setBoolean(cond); REGS.sp--; @@ -2267,43 +2311,47 @@ CASE(JSOP_GE) bool cond; MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); - if (!GreaterThanOrEqualOperation(cx, lval, rval, &cond)) + if (!GreaterThanOrEqualOperation(cx, lval, rval, &cond)) { goto error; + } TRY_BRANCH_AFTER_COND(cond, 2); REGS.sp[-2].setBoolean(cond); REGS.sp--; } END_CASE(JSOP_GE) -#define SIGNED_SHIFT_OP(OP) \ - JS_BEGIN_MACRO \ - int32_t i, j; \ - if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ - goto error; \ - if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ - goto error; \ - i = i OP (j & 31); \ - REGS.sp--; \ - REGS.sp[-1].setInt32(i); \ - JS_END_MACRO - CASE(JSOP_LSH) - SIGNED_SHIFT_OP(<<); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitLsh(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_LSH) CASE(JSOP_RSH) - SIGNED_SHIFT_OP(>>); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitRsh(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_RSH) -#undef SIGNED_SHIFT_OP - CASE(JSOP_URSH) { - HandleValue lval = REGS.stackHandleAt(-2); - HandleValue rval = REGS.stackHandleAt(-1); + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!UrshOperation(cx, lval, rval, res)) + if (!UrshOperation(cx, lhs, rhs, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_URSH) @@ -2313,8 +2361,9 @@ CASE(JSOP_ADD) MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!AddOperation(cx, lval, rval, res)) + if (!AddOperation(cx, lval, rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_ADD) @@ -2324,8 +2373,9 @@ CASE(JSOP_SUB) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!SubOperation(cx, lval, rval, res)) + if (!SubOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_SUB) @@ -2335,8 +2385,9 @@ CASE(JSOP_MUL) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!MulOperation(cx, lval, rval, res)) + if (!MulOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_MUL) @@ -2346,8 +2397,9 @@ CASE(JSOP_DIV) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!DivOperation(cx, lval, rval, res)) + if (!DivOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_DIV) @@ -2357,8 +2409,9 @@ CASE(JSOP_MOD) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!ModOperation(cx, lval, rval, res)) + if (!ModOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_MOD) @@ -2368,8 +2421,9 @@ CASE(JSOP_POW) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!math_pow_handle(cx, lval, rval, res)) + if (!PowOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_POW) @@ -2384,11 +2438,11 @@ END_CASE(JSOP_NOT) CASE(JSOP_BITNOT) { - int32_t i; - HandleValue value = REGS.stackHandleAt(-1); - if (!BitNot(cx, value, &i)) + MutableHandleValue value = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-1); + if (!BitNot(cx, value, res)) { goto error; - REGS.sp[-1].setInt32(i); + } } END_CASE(JSOP_BITNOT) @@ -2396,7 +2450,7 @@ CASE(JSOP_NEG) { ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-1); - if (!NegOperation(cx, script, REGS.pc, val, res)) + if (!NegOperation(cx, script, REGS.pc, &val, res)) goto error; } END_CASE(JSOP_NEG) @@ -4122,6 +4176,13 @@ CASE(JSOP_IS_CONSTRUCTING) PUSH_MAGIC(JS_IS_CONSTRUCTING); END_CASE(JSOP_IS_CONSTRUCTING) +CASE(JSOP_BIGINT) +{ + PUSH_COPY(script->getConst(GET_UINT32_INDEX(REGS.pc))); + MOZ_ASSERT(REGS.sp[-1].isBigInt()); +} +END_CASE(JSOP_BIGINT) + DEFAULT() { char numBuf[12]; @@ -4222,7 +4283,8 @@ js::GetProperty(JSContext* cx, HandleValue v, HandlePropertyName name, MutableHa // Optimize common cases like (2).toString() or "foo".valueOf() to not // create a wrapper object. - if (v.isPrimitive() && !v.isNullOrUndefined()) { + if (v.isPrimitive() && !v.isNullOrUndefined() && !v.isBigInt()) + { NativeObject* proto; if (v.isNumber()) { proto = GlobalObject::getOrCreateNumberPrototype(cx, cx->global()); @@ -4577,6 +4639,12 @@ js::ModValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Mut } bool +js::PowValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) +{ + return PowOperation(cx, lhs, rhs, res); +} + +bool js::UrshValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { return UrshOperation(cx, lhs, rhs, res); diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 1927e8cc7f..3c7b80a14c 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -457,6 +457,9 @@ bool ModValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res); bool +PowValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res); + +bool UrshValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res); bool diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 9b6d3dda72..2a28cf23c0 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -17,6 +17,7 @@ #include "jit/BaselineJIT.h" #include "jit/Ion.h" #include "vm/ArrayObject.h" +#include "vm/BigIntType.h" #include "vm/Runtime.h" #include "vm/Shape.h" #include "vm/String.h" @@ -546,6 +547,13 @@ StatsCellCallback(JSRuntime* rt, void* data, void* thing, JS::TraceKind traceKin zStats->symbolsGCHeap += thingSize; break; + case JS::TraceKind::BigInt: { + JS::BigInt* bi = static_cast<BigInt*>(thing); + zStats->bigIntsGCHeap += thingSize; + zStats->bigIntsMallocHeap += bi->sizeOfExcludingThis(rtStats->mallocSizeOf_); + break; + } + case JS::TraceKind::BaseShape: { JS::ShapeInfo info; // This zeroes all the sizes. info.shapesGCHeapBase += thingSize; diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index f6856f2290..ff707aac08 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2357,14 +2357,20 @@ * Operands: * Stack: arg => rval */ \ - macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) - + macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Pushes a BigInt constant onto the stack. + * Category: Literals + * Type: Constants + * Operands: uint32_t constIndex + * Stack: => val + */ \ + macro(JSOP_BIGINT, 235, "bigint", NULL, 5, 0, 1, JOF_BIGINT) /* * In certain circumstances it may be useful to "pad out" the opcode space to * a power of two. Use this macro to do so. */ #define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \ - macro(235) \ macro(236) \ macro(237) \ macro(238) \ diff --git a/js/src/vm/StringBuffer.cpp b/js/src/vm/StringBuffer.cpp index e4f0e4f4d6..ce0fc4e719 100644 --- a/js/src/vm/StringBuffer.cpp +++ b/js/src/vm/StringBuffer.cpp @@ -170,6 +170,13 @@ js::ValueToStringBufferSlow(JSContext* cx, const Value& arg, StringBuffer& sb) JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SYMBOL_TO_STRING); return false; } + if (v.isBigInt()) { + RootedBigInt i(cx, v.toBigInt()); + JSLinearString* str = BigInt::toString(cx, i, 10); + if (!str) + return false; + return sb.append(str); + } MOZ_ASSERT(v.isUndefined()); return sb.append(cx->names().undefined); } diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index 26e57976fc..ac73df30e6 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -31,6 +31,7 @@ #include "mozilla/CheckedInt.h" #include "mozilla/EndianUtils.h" #include "mozilla/FloatingPoint.h" +#include "mozilla/RangedPtr.h" #include <algorithm> @@ -39,6 +40,8 @@ #include "jsdate.h" #include "jswrapper.h" +#include "vm/BigIntType.h" + #include "builtin/MapObject.h" #include "js/Date.h" #include "js/GCHashTable.h" @@ -57,6 +60,7 @@ using mozilla::IsNaN; using mozilla::LittleEndian; using mozilla::NativeEndian; using mozilla::NumbersAreIdentical; +using mozilla::RangedPtr; using JS::CanonicalizeNaN; // When you make updates here, make sure you consider whether you need to bump the @@ -104,6 +108,9 @@ enum StructuredDataType : uint32_t { SCTAG_SHARED_ARRAY_BUFFER_OBJECT, + SCTAG_BIGINT, + SCTAG_BIGINT_OBJECT, + SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100, SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8, SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8, @@ -349,6 +356,8 @@ struct JSStructuredCloneReader { JSString* readStringImpl(uint32_t nchars); JSString* readString(uint32_t data); + BigInt* readBigInt(uint32_t data); + bool checkDouble(double d); bool readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp, bool v1Read = false); @@ -439,6 +448,8 @@ struct JSStructuredCloneWriter { bool traverseSet(HandleObject obj); bool traverseSavedFrame(HandleObject obj); + bool writeBigInt(uint32_t tag, BigInt* bi); + bool reportDataCloneError(uint32_t errorId); bool parseTransferable(); @@ -1055,6 +1066,23 @@ JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str) : out.writeChars(linear->twoByteChars(nogc), length); } +bool +JSStructuredCloneWriter::writeBigInt(uint32_t tag, BigInt* bi) +{ + bool signBit = bi->isNegative(); + size_t length = bi->digitLength(); + // The length must fit in 31 bits to leave room for a sign bit. + if (length > size_t(INT32_MAX)) { + return false; + } + uint32_t lengthAndSign = length | (static_cast<uint32_t>(signBit) << 31); + + if (!out.writePair(tag, lengthAndSign)) { + return false; + } + return out.writeArray(bi->digits().data(), length); +} + inline void JSStructuredCloneWriter::checkStack() { @@ -1388,6 +1416,8 @@ JSStructuredCloneWriter::startWrite(HandleValue v) return out.writePair(SCTAG_NULL, 0); } else if (v.isUndefined()) { return out.writePair(SCTAG_UNDEFINED, 0); + } else if (v.isBigInt()) { + return writeBigInt(SCTAG_BIGINT, v.toBigInt()); } else if (v.isObject()) { RootedObject obj(context(), &v.toObject()); @@ -1441,6 +1471,12 @@ JSStructuredCloneWriter::startWrite(HandleValue v) return writeString(SCTAG_STRING_OBJECT, unboxed.toString()); } else if (cls == ESClass::Map) { return traverseMap(obj); + } else if (cls == ESClass::BigInt) { + RootedValue unboxed(context()); + if (!Unbox(context(), obj, &unboxed)) { + return false; + } + return writeBigInt(SCTAG_BIGINT_OBJECT, unboxed.toBigInt()); } else if (cls == ESClass::Set) { return traverseSet(obj); } else if (SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) { @@ -1745,6 +1781,22 @@ JSStructuredCloneReader::readString(uint32_t data) return latin1 ? readStringImpl<Latin1Char>(nchars) : readStringImpl<char16_t>(nchars); } +BigInt* JSStructuredCloneReader::readBigInt(uint32_t data) { + size_t length = data & JS_BITMASK(31); + bool isNegative = data & (1 << 31); + if (length == 0) { + return BigInt::zero(context()); + } + BigInt* result = BigInt::createUninitialized(context(), length, isNegative); + if (!result) { + return nullptr; + } + if (!in.readArray(result->digits().data(), length)) { + return nullptr; + } + return result; +} + static uint32_t TagToV1ArrayType(uint32_t tag) { @@ -2021,6 +2073,19 @@ JSStructuredCloneReader::startRead(MutableHandleValue vp) break; } + case SCTAG_BIGINT: + case SCTAG_BIGINT_OBJECT: { + RootedBigInt bi(context(), readBigInt(data)); + if (!bi) { + return false; + } + vp.setBigInt(bi); + if (tag == SCTAG_BIGINT_OBJECT && !PrimitiveToObject(context(), vp)) { + return false; + } + break; + } + case SCTAG_DATE_OBJECT: { double d; if (!in.readDouble(&d) || !checkDouble(d)) diff --git a/js/src/vm/TypeInference-inl.h b/js/src/vm/TypeInference-inl.h index 5e1bac6bc9..8c9b179223 100644 --- a/js/src/vm/TypeInference-inl.h +++ b/js/src/vm/TypeInference-inl.h @@ -213,6 +213,8 @@ PrimitiveTypeFlag(JSValueType type) return TYPE_FLAG_STRING; case JSVAL_TYPE_SYMBOL: return TYPE_FLAG_SYMBOL; + case JSVAL_TYPE_BIGINT: + return TYPE_FLAG_BIGINT; case JSVAL_TYPE_MAGIC: return TYPE_FLAG_LAZYARGS; default: @@ -238,6 +240,8 @@ TypeFlagPrimitive(TypeFlags flags) return JSVAL_TYPE_STRING; case TYPE_FLAG_SYMBOL: return JSVAL_TYPE_SYMBOL; + case TYPE_FLAG_BIGINT: + return JSVAL_TYPE_BIGINT; case TYPE_FLAG_LAZYARGS: return JSVAL_TYPE_MAGIC; default: diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index ca34b184e8..a36926eb94 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -111,6 +111,8 @@ TypeSet::NonObjectTypeString(TypeSet::Type type) return "string"; case JSVAL_TYPE_SYMBOL: return "symbol"; + case JSVAL_TYPE_BIGINT: + return "BigInt"; case JSVAL_TYPE_MAGIC: return "lazyargs"; default: @@ -366,6 +368,8 @@ TypeSet::mightBeMIRType(jit::MIRType type) const return baseFlags() & TYPE_FLAG_STRING; case jit::MIRType::Symbol: return baseFlags() & TYPE_FLAG_SYMBOL; + case jit::MIRType::BigInt: + return baseFlags() & TYPE_FLAG_BIGINT; case jit::MIRType::MagicOptimizedArguments: return baseFlags() & TYPE_FLAG_LAZYARGS; case jit::MIRType::MagicHole: @@ -755,6 +759,8 @@ TypeSet::print(FILE* fp) fprintf(fp, " string"); if (flags & TYPE_FLAG_SYMBOL) fprintf(fp, " symbol"); + if (flags & TYPE_FLAG_BIGINT) + fprintf(fp, " BigInt"); if (flags & TYPE_FLAG_LAZYARGS) fprintf(fp, " lazyargs"); @@ -1631,6 +1637,8 @@ GetMIRTypeFromTypeFlags(TypeFlags flags) return jit::MIRType::String; case TYPE_FLAG_SYMBOL: return jit::MIRType::Symbol; + case TYPE_FLAG_BIGINT: + return jit::MIRType::BigInt; case TYPE_FLAG_LAZYARGS: return jit::MIRType::MagicOptimizedArguments; case TYPE_FLAG_ANYOBJECT: diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 764f99ca07..58c606912c 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -59,17 +59,18 @@ enum : uint32_t { TYPE_FLAG_DOUBLE = 0x10, TYPE_FLAG_STRING = 0x20, TYPE_FLAG_SYMBOL = 0x40, - TYPE_FLAG_LAZYARGS = 0x80, - TYPE_FLAG_ANYOBJECT = 0x100, + TYPE_FLAG_BIGINT = 0x80, + TYPE_FLAG_LAZYARGS = 0x100, + TYPE_FLAG_ANYOBJECT = 0x200, /* Mask containing all primitives */ TYPE_FLAG_PRIMITIVE = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_BOOLEAN | TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING | - TYPE_FLAG_SYMBOL, + TYPE_FLAG_SYMBOL |TYPE_FLAG_BIGINT, /* Mask/shift for the number of objects in objectSet */ - TYPE_FLAG_OBJECT_COUNT_MASK = 0x3e00, - TYPE_FLAG_OBJECT_COUNT_SHIFT = 9, + TYPE_FLAG_OBJECT_COUNT_MASK = 0x3c00, + TYPE_FLAG_OBJECT_COUNT_SHIFT = 10, TYPE_FLAG_OBJECT_COUNT_LIMIT = 7, TYPE_FLAG_DOMOBJECT_COUNT_LIMIT = TYPE_FLAG_OBJECT_COUNT_MASK >> TYPE_FLAG_OBJECT_COUNT_SHIFT, @@ -78,7 +79,7 @@ enum : uint32_t { TYPE_FLAG_UNKNOWN = 0x00004000, /* Mask of normal type flags on a type set. */ - TYPE_FLAG_BASE_MASK = 0x000041ff, + TYPE_FLAG_BASE_MASK = TYPE_FLAG_PRIMITIVE | TYPE_FLAG_LAZYARGS | TYPE_FLAG_ANYOBJECT | TYPE_FLAG_UNKNOWN, /* Additional flags for HeapTypeSet sets. */ @@ -109,6 +110,10 @@ enum : uint32_t { }; typedef uint32_t TypeFlags; +static_assert(TYPE_FLAG_PRIMITIVE < TYPE_FLAG_ANYOBJECT && + TYPE_FLAG_LAZYARGS < TYPE_FLAG_ANYOBJECT, + "TYPE_FLAG_ANYOBJECT should be greater than primitive type flags"); + /* Flags and other state stored in ObjectGroup::Flags */ enum : uint32_t { /* Whether this group is associated with some allocation site. */ @@ -366,6 +371,7 @@ class TypeSet static inline Type DoubleType() { return Type(JSVAL_TYPE_DOUBLE); } static inline Type StringType() { return Type(JSVAL_TYPE_STRING); } static inline Type SymbolType() { return Type(JSVAL_TYPE_SYMBOL); } + static inline Type BigIntType() { return Type(JSVAL_TYPE_BIGINT); } static inline Type MagicArgType() { return Type(JSVAL_TYPE_MAGIC); } static inline Type AnyObjectType() { return Type(JSVAL_TYPE_OBJECT); } static inline Type UnknownType() { return Type(JSVAL_TYPE_UNKNOWN); } diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 9d82fca6ec..ac93ec9b14 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -1898,10 +1898,8 @@ DataViewObject::class_constructor(JSContext* cx, unsigned argc, Value* vp) template <typename NativeType> /* static */ uint8_t* -DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, double offset) +DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset) { - MOZ_ASSERT(offset >= 0); - const size_t TypeSize = sizeof(NativeType); if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, @@ -1959,6 +1957,8 @@ template <> struct DataToRepType<int16_t> { typedef uint16_t result; }; template <> struct DataToRepType<uint16_t> { typedef uint16_t result; }; template <> struct DataToRepType<int32_t> { typedef uint32_t result; }; template <> struct DataToRepType<uint32_t> { typedef uint32_t result; }; +template <> struct DataToRepType<int64_t> { typedef uint64_t result; }; +template <> struct DataToRepType<uint64_t> { typedef uint64_t result; }; template <> struct DataToRepType<float> { typedef uint32_t result; }; template <> struct DataToRepType<double> { typedef uint64_t result; }; @@ -1987,31 +1987,6 @@ struct DataViewIO } }; -static bool -ToIndex(JSContext* cx, HandleValue v, double* index) -{ - if (v.isUndefined()) { - *index = 0.0; - return true; - } - - double integerIndex; - if (!ToInteger(cx, v, &integerIndex)) - return false; - - // Inlined version of ToLength. - // 1. Already an integer - // 2. Step eliminates < 0, +0 == -0 with SameValueZero - // 3/4. Limit to <= 2^53-1, so everything above should fail. - if (integerIndex < 0 || integerIndex > 9007199254740991) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); - return false; - } - - *index = integerIndex; - return true; -} - template<typename NativeType> /* static */ bool DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj, @@ -2021,7 +1996,7 @@ DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj, // Step 3. unnecessary assert // Step 4. - double getIndex; + uint64_t getIndex; if (!ToIndex(cx, args.get(0), &getIndex)) return false; @@ -2059,6 +2034,28 @@ WebIDLCast(JSContext* cx, HandleValue value, NativeType* out) } template <> +inline bool WebIDLCast<int64_t>(JSContext* cx, HandleValue value, int64_t* out) +{ + RootedBigInt bi(cx, ToBigInt(cx, value)); + if (!bi) { + return false; + } + *out = BigInt::toInt64(bi); + return true; +} + +template <> +inline bool WebIDLCast<uint64_t>(JSContext* cx, HandleValue value, uint64_t* out) +{ + RootedBigInt bi(cx, ToBigInt(cx, value)); + if (!bi) { + return false; + } + *out = BigInt::toUint64(bi); + return true; +} + +template <> inline bool WebIDLCast<float>(JSContext* cx, HandleValue value, float* out) { @@ -2076,6 +2073,8 @@ WebIDLCast<double>(JSContext* cx, HandleValue value, double* out) return ToNumber(cx, value, out); } +// https://tc39.github.io/ecma262/#sec-setviewvalue +// SetViewValue ( view, requestIndex, isLittleEndian, type, value ) template<typename NativeType> /* static */ bool DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj, @@ -2085,11 +2084,11 @@ DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj, // Step 3. unnecessary assert // Step 4. - double getIndex; + uint64_t getIndex; if (!ToIndex(cx, args.get(0), &getIndex)) return false; - // Step 5. Should just call ToNumber (unobservable) + // Step 5. Extended by the BigInt proposal to call either ToBigInt or ToNumber NativeType value; if (!WebIDLCast(cx, args.get(1), &value)) return false; @@ -2245,6 +2244,60 @@ DataViewObject::fun_getUint32(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod<is, getUint32Impl>(cx, args); } +// BigInt proposal 7.26 +// DataView.prototype.getBigInt64 ( byteOffset [ , littleEndian ] ) +bool DataViewObject::getBigInt64Impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); + + int64_t val; + if (!read(cx, thisView, args, &val, "getBigInt64")) { + return false; + } + + BigInt* bi = BigInt::createFromInt64(cx, val); + if (!bi) { + return false; + } + args.rval().setBigInt(bi); + return true; +} + +bool DataViewObject::fun_getBigInt64(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, getBigInt64Impl>(cx, args); +} + +// BigInt proposal 7.27 +// DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] ) +bool DataViewObject::getBigUint64Impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); + + int64_t val; + if (!read(cx, thisView, args, &val, "getBigUint64")) { + return false; + } + + BigInt* bi = BigInt::createFromUint64(cx, val); + if (!bi) { + return false; + } + args.rval().setBigInt(bi); + return true; +} + +bool DataViewObject::fun_getBigUint64(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, getBigUint64Impl>(cx, args); +} + bool DataViewObject::getFloat32Impl(JSContext* cx, const CallArgs& args) { @@ -2409,6 +2462,48 @@ DataViewObject::fun_setUint32(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod<is, setUint32Impl>(cx, args); } +// BigInt proposal 7.28 +// DataView.prototype.setBigInt64 ( byteOffset, value [ , littleEndian ] ) +bool DataViewObject::setBigInt64Impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); + + if (!write<int64_t>(cx, thisView, args, "setBigInt64")) { + return false; + } + args.rval().setUndefined(); + return true; +} + +bool DataViewObject::fun_setBigInt64(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, setBigInt64Impl>(cx, args); +} + +// BigInt proposal 7.29 +// DataView.prototype.setBigUint64 ( byteOffset, value [ , littleEndian ] ) +bool DataViewObject::setBigUint64Impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); + + if (!write<uint64_t>(cx, thisView, args, "setBigUint64")) { + return false; + } + args.rval().setUndefined(); + return true; +} + +bool DataViewObject::fun_setBigUint64(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, setBigUint64Impl>(cx, args); +} + bool DataViewObject::setFloat32Impl(JSContext* cx, const CallArgs& args) { @@ -2820,6 +2915,13 @@ const JSFunctionSpec DataViewObject::jsfuncs[] = { JS_FS_END }; +const JSFunctionSpec DataViewObject::bigIntMethods[] = { + JS_FN("getBigInt64", DataViewObject::fun_getBigInt64, 1, 0), + JS_FN("getBigUint64", DataViewObject::fun_getBigUint64, 1, 0), + JS_FN("setBigInt64", DataViewObject::fun_setBigInt64, 2, 0), + JS_FN("setBigUint64", DataViewObject::fun_setBigUint64, 2, 0), + JS_FS_END}; + template<Value ValueGetter(DataViewObject* view)> bool DataViewObject::getterImpl(JSContext* cx, const CallArgs& args) @@ -2888,6 +2990,9 @@ DataViewObject::initClass(JSContext* cx) if (!JS_DefineFunctions(cx, proto, DataViewObject::jsfuncs)) return false; + if (!JS_DefineFunctions(cx, proto, DataViewObject::bigIntMethods)) + return false; + if (!DefineToStringTag(cx, proto, cx->names().DataView)) return false; diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 2616402b3d..196d347075 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -442,7 +442,7 @@ class DataViewObject : public NativeObject template <typename NativeType> static uint8_t* - getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, double offset); + getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset); template<Value ValueGetter(DataViewObject* view)> static bool @@ -521,6 +521,12 @@ class DataViewObject : public NativeObject static bool getUint32Impl(JSContext* cx, const CallArgs& args); static bool fun_getUint32(JSContext* cx, unsigned argc, Value* vp); + static bool getBigInt64Impl(JSContext* cx, const CallArgs& args); + static bool fun_getBigInt64(JSContext* cx, unsigned argc, Value* vp); + + static bool getBigUint64Impl(JSContext* cx, const CallArgs& args); + static bool fun_getBigUint64(JSContext* cx, unsigned argc, Value* vp); + static bool getFloat32Impl(JSContext* cx, const CallArgs& args); static bool fun_getFloat32(JSContext* cx, unsigned argc, Value* vp); @@ -545,6 +551,12 @@ class DataViewObject : public NativeObject static bool setUint32Impl(JSContext* cx, const CallArgs& args); static bool fun_setUint32(JSContext* cx, unsigned argc, Value* vp); + static bool setBigInt64Impl(JSContext* cx, const CallArgs& args); + static bool fun_setBigInt64(JSContext* cx, unsigned argc, Value* vp); + + static bool setBigUint64Impl(JSContext* cx, const CallArgs& args); + static bool fun_setBigUint64(JSContext* cx, unsigned argc, Value* vp); + static bool setFloat32Impl(JSContext* cx, const CallArgs& args); static bool fun_setFloat32(JSContext* cx, unsigned argc, Value* vp); @@ -564,6 +576,10 @@ class DataViewObject : public NativeObject private: static const JSFunctionSpec jsfuncs[]; + + static const JSFunctionSpec bigIntMethods[]; + static bool finishInit(JSContext* cx, JS::HandleObject ctor, + JS::HandleObject proto); }; static inline int32_t diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp index ab966cbb4d..3a3b1ee2bf 100644 --- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -23,6 +23,7 @@ #include "js/TypeDecls.h" #include "js/Utility.h" #include "js/Vector.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/EnvironmentObject.h" #include "vm/GlobalObject.h" @@ -202,7 +203,11 @@ Node::exposeToJS() const v.setString(as<JSString>()); } else if (is<JS::Symbol>()) { v.setSymbol(as<JS::Symbol>()); - } else { + } + else if (is<BigInt>()) { + v.setBigInt(as<BigInt>()); + } + else { v.setUndefined(); } @@ -314,6 +319,7 @@ template JS::Zone* TracerConcrete<js::ObjectGroup>::zone() const; template JS::Zone* TracerConcrete<js::RegExpShared>::zone() const; template JS::Zone* TracerConcrete<js::Scope>::zone() const; template JS::Zone* TracerConcrete<JS::Symbol>::zone() const; +template JS::Zone* TracerConcrete<BigInt>::zone() const; template JS::Zone* TracerConcrete<JSString>::zone() const; template<typename Referent> @@ -337,6 +343,7 @@ template UniquePtr<EdgeRange> TracerConcrete<js::ObjectGroup>::edges(JSContext* template UniquePtr<EdgeRange> TracerConcrete<js::RegExpShared>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<js::Scope>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<JS::Symbol>::edges(JSContext* cx, bool wantNames) const; +template UniquePtr<EdgeRange> TracerConcrete<BigInt>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges(JSContext* cx, bool wantNames) const; template<typename Referent> @@ -392,6 +399,7 @@ Concrete<JSObject>::jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& o } const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol"; +const char16_t Concrete<BigInt>::concreteTypeName[] = u"JS::BigInt"; const char16_t Concrete<JSScript>::concreteTypeName[] = u"JSScript"; const char16_t Concrete<js::LazyScript>::concreteTypeName[] = u"js::LazyScript"; const char16_t Concrete<js::jit::JitCode>::concreteTypeName[] = u"js::jit::JitCode"; diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index a91b0e804d..3eee8c4c18 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -8708,7 +8708,7 @@ js::CompileAsmJS(ExclusiveContext* cx, AsmJSParser& parser, ParseNode* stmtList, // generating bytecode for asm.js functions, allowing this asm.js module // function to be the finished result. MOZ_ASSERT(funbox->function()->isInterpreted()); - funbox->object = moduleFun; + funbox->clobberFunction(moduleFun); // Success! Write to the console with a "warning" message. *validated = true; diff --git a/js/src/wasm/WasmTextUtils.cpp b/js/src/wasm/WasmTextUtils.cpp index aab3deb4b2..78367f3984 100644 --- a/js/src/wasm/WasmTextUtils.cpp +++ b/js/src/wasm/WasmTextUtils.cpp @@ -54,7 +54,7 @@ template<class T> bool js::wasm::RenderNaN(StringBuffer& sb, Raw<T> num) { - typedef typename mozilla::SelectTrait<T> Traits; + typedef typename mozilla::FloatingPoint<T> Traits; MOZ_ASSERT(IsNaN(num.fp())); diff --git a/js/xpconnect/src/XPCVariant.cpp b/js/xpconnect/src/XPCVariant.cpp index f8363993c7..c663e7ebf2 100644 --- a/js/xpconnect/src/XPCVariant.cpp +++ b/js/xpconnect/src/XPCVariant.cpp @@ -179,7 +179,7 @@ XPCArrayHomogenizer::GetTypeForArray(JSContext* cx, HandleObject array, type = tDbl; } else if (val.isBoolean()) { type = tBool; - } else if (val.isUndefined() || val.isSymbol()) { + } else if (val.isUndefined() || val.isSymbol() || val.isBigInt()) { state = tVar; break; } else if (val.isNull()) { @@ -187,7 +187,7 @@ XPCArrayHomogenizer::GetTypeForArray(JSContext* cx, HandleObject array, } else if (val.isString()) { type = tStr; } else { - MOZ_ASSERT(val.isObject(), "invalid type of jsval!"); + MOZ_RELEASE_ASSERT(val.isObject(), "invalid type of jsval!"); jsobj = &val.toObject(); bool isArray; @@ -273,8 +273,8 @@ bool XPCVariant::InitializeData(JSContext* cx) mData.SetFromBool(val.toBoolean()); return true; } - // We can't represent symbol on C++ side, so pretend it is void. - if (val.isUndefined() || val.isSymbol()) { + // We can't represent symbol or BigInt on C++ side, so pretend it is void. + if (val.isUndefined() || val.isSymbol() || val.isBigInt()) { mData.SetToVoid(); return true; } @@ -302,7 +302,7 @@ bool XPCVariant::InitializeData(JSContext* cx) } // leaving only JSObject... - MOZ_ASSERT(val.isObject(), "invalid type of jsval!"); + MOZ_RELEASE_ASSERT(val.isObject(), "invalid type of jsval!"); RootedObject jsobj(cx, &val.toObject()); diff --git a/mfbt/Attributes.h b/mfbt/Attributes.h index 9bce9a128a..f5572e1861 100644 --- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -301,6 +301,130 @@ #else # define MOZ_FALLTHROUGH /* FALLTHROUGH */ #endif +/* + * MOZ_ASAN_BLACKLIST is a macro to tell AddressSanitizer (a compile-time + * instrumentation shipped with Clang and GCC) to not instrument the annotated + * function. Furthermore, it will prevent the compiler from inlining the + * function because inlining currently breaks the blacklisting mechanism of + * AddressSanitizer. + */ +#if defined(__has_feature) +# if __has_feature(address_sanitizer) +# define MOZ_HAVE_ASAN_BLACKLIST +# endif +#elif defined(__GNUC__) +# if defined(__SANITIZE_ADDRESS__) +# define MOZ_HAVE_ASAN_BLACKLIST +# endif +#endif + +#if defined(MOZ_HAVE_ASAN_BLACKLIST) +# define MOZ_ASAN_BLACKLIST \ + MOZ_NEVER_INLINE __attribute__((no_sanitize_address)) +#else +# define MOZ_ASAN_BLACKLIST /* nothing */ +#endif + +/* + * MOZ_TSAN_BLACKLIST is a macro to tell ThreadSanitizer (a compile-time + * instrumentation shipped with Clang) to not instrument the annotated function. + * Furthermore, it will prevent the compiler from inlining the function because + * inlining currently breaks the blacklisting mechanism of ThreadSanitizer. + */ +#if defined(__has_feature) +# if __has_feature(thread_sanitizer) +# define MOZ_TSAN_BLACKLIST \ + MOZ_NEVER_INLINE __attribute__((no_sanitize_thread)) +# else +# define MOZ_TSAN_BLACKLIST /* nothing */ +# endif +#else +# define MOZ_TSAN_BLACKLIST /* nothing */ +#endif + +#if defined(__has_attribute) +# if __has_attribute(no_sanitize) +# define MOZ_HAVE_NO_SANITIZE_ATTR +# endif +#endif + +#ifdef __clang__ +# ifdef MOZ_HAVE_NO_SANITIZE_ATTR +# define MOZ_HAVE_UNSIGNED_OVERFLOW_SANITIZE_ATTR +# define MOZ_HAVE_SIGNED_OVERFLOW_SANITIZE_ATTR +# endif +#endif + +/* + * MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW disables *un*signed integer overflow + * checking on the function it annotates, in builds configured to perform it. + * (Currently this is only Clang using -fsanitize=unsigned-integer-overflow, or + * via --enable-unsigned-overflow-sanitizer in Mozilla's build system.) It has + * no effect in other builds. + * + * Place this attribute at the very beginning of a function declaration. + * + * Unsigned integer overflow isn't *necessarily* a bug. It's well-defined in + * C/C++, and code may reasonably depend upon it. For example, + * + * MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW inline bool + * IsDecimal(char aChar) + * { + * // For chars less than '0', unsigned integer underflow occurs, to a value + * // much greater than 10, so the overall test is false. + * // For chars greater than '0', no overflow occurs, and only '0' to '9' + * // pass the overall test. + * return static_cast<unsigned int>(aChar) - '0' < 10; + * } + * + * But even well-defined unsigned overflow often causes bugs when it occurs, so + * it should be restricted to functions annotated with this attribute. + * + * The compiler instrumentation to detect unsigned integer overflow has costs + * both at compile time and at runtime. Functions that are repeatedly inlined + * at compile time will also implicitly inline the necessary instrumentation, + * increasing compile time. Similarly, frequently-executed functions that + * require large amounts of instrumentation will also notice significant runtime + * slowdown to execute that instrumentation. Use this attribute to eliminate + * those costs -- but only after carefully verifying that no overflow can occur. + */ +#ifdef MOZ_HAVE_UNSIGNED_OVERFLOW_SANITIZE_ATTR +# define MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +#else +# define MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW /* nothing */ +#endif + +/* + * MOZ_NO_SANITIZE_SIGNED_OVERFLOW disables *signed* integer overflow checking + * on the function it annotates, in builds configured to perform it. (Currently + * this is only Clang using -fsanitize=signed-integer-overflow, or via + * --enable-signed-overflow-sanitizer in Mozilla's build system. GCC support + * will probably be added in the future.) It has no effect in other builds. + * + * Place this attribute at the very beginning of a function declaration. + * + * Signed integer overflow is undefined behavior in C/C++: *anything* can happen + * when it occurs. *Maybe* wraparound behavior will occur, but maybe also the + * compiler will assume no overflow happens and will adversely optimize the rest + * of your code. Code that contains signed integer overflow needs to be fixed. + * + * The compiler instrumentation to detect signed integer overflow has costs both + * at compile time and at runtime. Functions that are repeatedly inlined at + * compile time will also implicitly inline the necessary instrumentation, + * increasing compile time. Similarly, frequently-executed functions that + * require large amounts of instrumentation will also notice significant runtime + * slowdown to execute that instrumentation. Use this attribute to eliminate + * those costs -- but only after carefully verifying that no overflow can occur. + */ +#ifdef MOZ_HAVE_SIGNED_OVERFLOW_SANITIZE_ATTR +# define MOZ_NO_SANITIZE_SIGNED_OVERFLOW \ + __attribute__((no_sanitize("signed-integer-overflow"))) +#else +# define MOZ_NO_SANITIZE_SIGNED_OVERFLOW /* nothing */ +#endif + +#undef MOZ_HAVE_NO_SANITIZE_ATTR /* * The following macros are attributes that support the static analysis plugin diff --git a/mfbt/FloatingPoint.h b/mfbt/FloatingPoint.h index 7d73d7e848..a2846ce298 100644 --- a/mfbt/FloatingPoint.h +++ b/mfbt/FloatingPoint.h @@ -33,33 +33,38 @@ namespace mozilla { * compiler bustage, particularly PGO-specific bustage. */ -struct FloatTypeTraits +namespace detail { + +/* + * These implementations assume float/double are 32/64-bit single/double + * format number types compatible with the IEEE-754 standard. C++ doesn't + * require this, but we required it in implementations of these algorithms that + * preceded this header, so we shouldn't break anything to continue doing so. + */ +template<typename T> +struct FloatingPointTrait; + +template<> +struct FloatingPointTrait<float> { +protected: typedef uint32_t Bits; - static const unsigned kExponentBias = 127; - static const unsigned kExponentShift = 23; - - static const Bits kSignBit = 0x80000000UL; - static const Bits kExponentBits = 0x7F800000UL; - static const Bits kSignificandBits = 0x007FFFFFUL; + static constexpr unsigned kExponentWidth = 8; + static constexpr unsigned kSignificandWidth = 23; }; -struct DoubleTypeTraits +template<> +struct FloatingPointTrait<double> { +protected: typedef uint64_t Bits; - static const unsigned kExponentBias = 1023; - static const unsigned kExponentShift = 52; - - static const Bits kSignBit = 0x8000000000000000ULL; - static const Bits kExponentBits = 0x7ff0000000000000ULL; - static const Bits kSignificandBits = 0x000fffffffffffffULL; + static constexpr unsigned kExponentWidth = 11; + static constexpr unsigned kSignificandWidth = 52; }; -template<typename T> struct SelectTrait; -template<> struct SelectTrait<float> : public FloatTypeTraits {}; -template<> struct SelectTrait<double> : public DoubleTypeTraits {}; +} // namespace detail /* * This struct contains details regarding the encoding of floating-point @@ -88,30 +93,65 @@ template<> struct SelectTrait<double> : public DoubleTypeTraits {}; * http://en.wikipedia.org/wiki/Floating_point#IEEE_754:_floating_point_in_modern_computers */ template<typename T> -struct FloatingPoint : public SelectTrait<T> +struct FloatingPoint final : private detail::FloatingPointTrait<T> { - typedef SelectTrait<T> Base; - typedef typename Base::Bits Bits; +private: + using Base = detail::FloatingPointTrait<T>; + +public: + /** + * An unsigned integral type suitable for accessing the bitwise representation + * of T. + */ + using Bits = typename Base::Bits; + + static_assert(sizeof(T) == sizeof(Bits), "Bits must be same size as T"); - static_assert((Base::kSignBit & Base::kExponentBits) == 0, + /** The bit-width of the exponent component of T. */ + using Base::kExponentWidth; + + /** The bit-width of the significand component of T. */ + using Base::kSignificandWidth; + + static_assert(1 + kExponentWidth + kSignificandWidth == + CHAR_BIT * sizeof(T), + "sign bit plus bit widths should sum to overall bit width"); + + /** + * The exponent field in an IEEE-754 floating point number consists of bits + * encoding an unsigned number. The *actual* represented exponent (for all + * values finite and not denormal) is that value, minus a bias |kExponentBias| + * so that a useful range of numbers is represented. + */ + static constexpr unsigned kExponentBias = (1U << (kExponentWidth - 1)) - 1; + + /** + * The amount by which the bits of the exponent-field in an IEEE-754 floating + * point number are shifted from the LSB of the floating point type. + */ + static constexpr unsigned kExponentShift = kSignificandWidth; + + /** The sign bit in the floating point representation. */ + static constexpr Bits kSignBit = + static_cast<Bits>(1) << (CHAR_BIT * sizeof(Bits) - 1); + + /** The exponent bits in the floating point representation. */ + static constexpr Bits kExponentBits = + ((static_cast<Bits>(1) << kExponentWidth) - 1) << kSignificandWidth; + + /** The significand bits in the floating point representation. */ + static constexpr Bits kSignificandBits = + (static_cast<Bits>(1) << kSignificandWidth) - 1; + + static_assert((kSignBit & kExponentBits) == 0, "sign bit shouldn't overlap exponent bits"); - static_assert((Base::kSignBit & Base::kSignificandBits) == 0, + static_assert((kSignBit & kSignificandBits) == 0, "sign bit shouldn't overlap significand bits"); - static_assert((Base::kExponentBits & Base::kSignificandBits) == 0, + static_assert((kExponentBits & kSignificandBits) == 0, "exponent bits shouldn't overlap significand bits"); - static_assert((Base::kSignBit | Base::kExponentBits | Base::kSignificandBits) == - ~Bits(0), + static_assert((kSignBit | kExponentBits | kSignificandBits) == ~Bits(0), "all bits accounted for"); - - /* - * These implementations assume float/double are 32/64-bit single/double - * format number types compatible with the IEEE-754 standard. C++ don't - * require this to be the case. But we required this in implementations of - * these algorithms that preceded this header, so we shouldn't break anything - * if we keep doing so. - */ - static_assert(sizeof(T) == sizeof(Bits), "Bits must be same size as T"); }; /** Determines whether a float/double is NaN. */ diff --git a/mfbt/HashFunctions.h b/mfbt/HashFunctions.h index cc9a1d68c1..d287081174 100644 --- a/mfbt/HashFunctions.h +++ b/mfbt/HashFunctions.h @@ -51,6 +51,7 @@ #include "mozilla/Char16.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Types.h" +#include "mozilla/WrappingOperations.h" #include <stdint.h> @@ -95,7 +96,9 @@ AddU32ToHash(uint32_t aHash, uint32_t aValue) * Otherwise, if |aHash| is 0 (as it often is for the beginning of a * message), the expression * - * (kGoldenRatioU32 * RotateBitsLeft(aHash, 5)) |xor| aValue + * mozilla::WrappingMultiply(kGoldenRatioU32, RotateBitsLeft(aHash, 5)) + * |xor| + * aValue * * evaluates to |aValue|. * @@ -113,7 +116,8 @@ AddU32ToHash(uint32_t aHash, uint32_t aValue) * multiplicative effect. Our golden ratio constant has order 2^29, which is * more than enough for our purposes.) */ - return kGoldenRatioU32 * (RotateBitsLeft32(aHash, 5) ^ aValue); + return mozilla::WrappingMultiply(kGoldenRatioU32, + (RotateBitsLeft32(aHash, 5) ^ aValue)); } /** diff --git a/mfbt/WrappingOperations.h b/mfbt/WrappingOperations.h new file mode 100644 index 0000000000..c93626f653 --- /dev/null +++ b/mfbt/WrappingOperations.h @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Math operations that implement wraparound semantics on overflow or underflow + * without performing C++ undefined behavior or tripping up compiler-based + * integer-overflow sanitizers. + */ + +#ifndef mozilla_WrappingOperations_h +#define mozilla_WrappingOperations_h + +#include "mozilla/Attributes.h" +#include "mozilla/TypeTraits.h" + +#include <limits.h> + +namespace mozilla { + +namespace detail { + +template<typename UnsignedType> +struct WrapToSignedHelper +{ + static_assert(mozilla::IsUnsigned<UnsignedType>::value, + "WrapToSigned must be passed an unsigned type"); + + using SignedType = typename mozilla::MakeSigned<UnsignedType>::Type; + + static constexpr SignedType MaxValue = + (UnsignedType(1) << (CHAR_BIT * sizeof(SignedType) - 1)) - 1; + static constexpr SignedType MinValue = -MaxValue - 1; + + static constexpr UnsignedType MinValueUnsigned = + static_cast<UnsignedType>(MinValue); + static constexpr UnsignedType MaxValueUnsigned = + static_cast<UnsignedType>(MaxValue); + + // Overflow-correctness was proven in bug 1432646 and is explained in the + // comment below. This function is very hot, both at compile time and + // runtime, so disable all overflow checking in it. + MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW MOZ_NO_SANITIZE_SIGNED_OVERFLOW + static constexpr SignedType compute(UnsignedType aValue) + { + // This algorithm was originally provided here: + // https://stackoverflow.com/questions/13150449/efficient-unsigned-to-signed-cast-avoiding-implementation-defined-behavior + // + // If the value is in the non-negative signed range, just cast. + // + // If the value will be negative, compute its delta from the first number + // past the max signed integer, then add that to the minimum signed value. + // + // At the low end: if |u| is the maximum signed value plus one, then it has + // the same mathematical value as |MinValue| cast to unsigned form. The + // delta is zero, so the signed form of |u| is |MinValue| -- exactly the + // result of adding zero delta to |MinValue|. + // + // At the high end: if |u| is the maximum *unsigned* value, then it has all + // bits set. |MinValue| cast to unsigned form is purely the high bit set. + // So the delta is all bits but high set -- exactly |MaxValue|. And as + // |MinValue = -MaxValue - 1|, we have |MaxValue + (-MaxValue - 1)| to + // equal -1. + // + // Thus the delta below is in signed range, the corresponding cast is safe, + // and this computation produces values spanning [MinValue, 0): exactly the + // desired range of all negative signed integers. + return (aValue <= MaxValueUnsigned) + ? static_cast<SignedType>(aValue) + : static_cast<SignedType>(aValue - MinValueUnsigned) + MinValue; + } +}; + +} // namespace detail + +/** + * Convert an unsigned value to signed, if necessary wrapping around. + * + * This is the behavior normal C++ casting will perform in most implementations + * these days -- but this function makes explicit that such conversion is + * happening. + */ +template<typename UnsignedType> +inline constexpr typename detail::WrapToSignedHelper<UnsignedType>::SignedType +WrapToSigned(UnsignedType aValue) +{ + return detail::WrapToSignedHelper<UnsignedType>::compute(aValue); +} + +namespace detail { + +template<typename T> +struct WrappingMultiplyHelper +{ +private: + using UnsignedT = typename MakeUnsigned<T>::Type; + + MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW + static UnsignedT + multiply(UnsignedT aX, UnsignedT aY) + { + // |mozilla::WrappingMultiply| isn't constexpr because MSVC warns about well- + // defined unsigned integer overflows that may happen here. + // https://msdn.microsoft.com/en-us/library/4kze989h.aspx And constexpr + // seems to cause the warning to be emitted at |WrappingMultiply| call *sites* + // instead of here, so these #pragmas are ineffective. + // + // https://stackoverflow.com/questions/37658794/integer-constant-overflow-warning-in-constexpr + // + // If/when MSVC fix this bug, we should make these functions constexpr. + + // Begin with |1U| to ensure the overall operation chain is never promoted + // to signed integer operations that might have *signed* integer overflow. + return static_cast<UnsignedT>(1U * aX * aY); + } + + static T + toResult(UnsignedT aX, UnsignedT aY) + { + // We could always return WrapToSigned and rely on unsigned conversion + // undoing the wrapping when |T| is unsigned, but this seems clearer. + return IsSigned<T>::value + ? WrapToSigned(multiply(aX, aY)) + : multiply(aX, aY); + } + +public: + MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW + static T compute(T aX, T aY) + { + return toResult(static_cast<UnsignedT>(aX), static_cast<UnsignedT>(aY)); + } +}; + +} // namespace detail + +/** + * Multiply two integers of the same type, and return the result converted to + * that type using wraparound semantics. This function: + * + * 1) makes explicit the desire for and dependence upon wraparound semantics, + * 2) provides wraparound semantics *safely* with no signed integer overflow + * that would have undefined behavior, and + * 3) won't trip up {,un}signed-integer overflow sanitizers (see + * build/autoconf/sanitize.m4) at runtime. + * + * For N-bit unsigned integer types, this is equivalent to multiplying the two + * numbers, then taking the result mod 2**N: + * + * WrappingMultiply(uint32_t(42), uint32_t(17)) is 714 (714 mod 2**32); + * WrappingMultiply(uint8_t(16), uint8_t(24)) is 128 (384 mod 2**8); + * WrappingMultiply(uint16_t(3), uint16_t(32768)) is 32768 (98304 mod 2*16). + * + * Use this function for any unsigned multiplication that can wrap (instead of + * normal C++ multiplication) to play nice with the sanitizers. But it's + * especially important to use it for uint16_t multiplication: in most compilers + * for uint16_t*uint16_t some operand values will trigger signed integer + * overflow with undefined behavior! http://kqueue.org/blog/2013/09/17/cltq/ + * has the grody details. Other than that one weird case, WrappingMultiply on + * unsigned types is the same as C++ multiplication. + * + * For N-bit signed integer types, this is equivalent to multiplying the two + * numbers wrapped to unsigned, taking the product mod 2**N, then wrapping that + * number to the signed range: + * + * WrappingMultiply(int16_t(-456), int16_t(123)) is 9448 ((-56088 mod 2**16) + 2**16); + * WrappingMultiply(int32_t(-7), int32_t(-9)) is 63 (63 mod 2**32); + * WrappingMultiply(int8_t(16), int8_t(24)) is -128 ((384 mod 2**8) - 2**8); + * WrappingMultiply(int8_t(16), int8_t(255)) is -16 ((4080 mod 2**8) - 2**8). + * + * There is no ready equivalent to this operation in C++, as applying C++ + * multiplication to signed integer types in ways that trigger overflow has + * undefined behavior. However, it's how multiplication *tends* to behave with + * most compilers in most situations, even though it's emphatically not required + * to do so. + */ +template<typename T> +inline T +WrappingMultiply(T aX, T aY) +{ + return detail::WrappingMultiplyHelper<T>::compute(aX, aY); +} + +} /* namespace mozilla */ + +#endif /* mozilla_WrappingOperations_h */ diff --git a/mfbt/moz.build b/mfbt/moz.build index 20c7234dd3..54b13f21b9 100644 --- a/mfbt/moz.build +++ b/mfbt/moz.build @@ -101,6 +101,7 @@ EXPORTS.mozilla = [ 'Variant.h', 'Vector.h', 'WeakPtr.h', + 'WrappingOperations.h', 'XorShift128PlusRNG.h', ] |