From f041afc61d4e39177d8003187b070023bfa3e31a Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 01:59:24 -0500 Subject: [PATCH 01/22] Issue #1240 - Part 1 - Define a new BigInt primitive type. Based on https://bugzilla.mozilla.org/show_bug.cgi?id=1366287 Part 1.0. However leaving out the --enable-bigint changes. --- js/public/Conversions.h | 2 +- js/public/GCPolicyAPI.h | 2 + js/public/MemoryMetrics.h | 4 ++ js/public/TraceKind.h | 4 +- js/public/TracingAPI.h | 2 + js/public/TypeDecls.h | 5 ++ js/public/UbiNode.h | 16 +++++++ js/public/Value.h | 41 ++++++++++++++++- js/src/NamespaceImports.h | 6 +++ js/src/builtin/MapObject.cpp | 14 +++++- js/src/gc/GCInternals.h | 2 + js/src/gc/Heap.h | 2 + js/src/gc/Marking.cpp | 10 ++++ js/src/gc/Marking.h | 4 +- js/src/gc/Tracer.cpp | 5 ++ js/src/gdb/mozilla/jsval.py | 11 +++++ js/src/gdb/tests/test-jsval.cpp | 4 ++ js/src/gdb/tests/test-jsval.py | 2 + js/src/js.msg | 9 ++++ js/src/jsarray.cpp | 6 +++ js/src/jsatom.cpp | 8 ++++ js/src/jsbool.cpp | 3 ++ js/src/jscompartment.cpp | 15 ++++++ js/src/jscompartment.h | 1 + js/src/jscompartmentinlines.h | 8 ++++ js/src/jsgc.cpp | 7 ++- js/src/jsgc.h | 3 ++ js/src/jsnum.cpp | 16 +++++-- js/src/jsobj.cpp | 11 +++-- js/src/json.cpp | 5 ++ js/src/jspubtd.h | 1 + js/src/jsstr.cpp | 4 ++ js/src/moz.build | 1 + js/src/vm/BigIntType.cpp | 82 +++++++++++++++++++++++++++++++++ js/src/vm/BigIntType.h | 58 +++++++++++++++++++++++ js/src/vm/CommonPropertyNames.h | 1 + js/src/vm/Interpreter.cpp | 6 ++- js/src/vm/MemoryMetrics.cpp | 8 ++++ js/src/vm/StringBuffer.cpp | 6 +++ js/src/vm/TypeInference-inl.h | 4 ++ js/src/vm/TypeInference.cpp | 4 ++ js/src/vm/TypeInference.h | 18 +++++--- js/src/vm/UbiNode.cpp | 10 +++- 43 files changed, 411 insertions(+), 20 deletions(-) create mode 100644 js/src/vm/BigIntType.cpp create mode 100644 js/src/vm/BigIntType.h diff --git a/js/public/Conversions.h b/js/public/Conversions.h index 4978583eca..1850a42f0e 100644 --- a/js/public/Conversions.h +++ b/js/public/Conversions.h @@ -120,7 +120,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 : public GCPointerPolicy {}; +template <> struct GCPolicy : public GCPointerPolicy {}; template <> struct GCPolicy : public GCPointerPolicy {}; template <> struct GCPolicy : public GCPointerPolicy {}; template <> struct GCPolicy : public GCPointerPolicy {}; 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 class Handle; template class MutableHandle; @@ -53,6 +54,7 @@ typedef Handle HandleObject; typedef Handle HandleScript; typedef Handle HandleString; typedef Handle HandleSymbol; +typedef Handle HandleBigInt; typedef Handle HandleValue; typedef Handle> HandleValueVector; @@ -62,6 +64,7 @@ typedef MutableHandle MutableHandleObject; typedef MutableHandle MutableHandleScript; typedef MutableHandle MutableHandleString; typedef MutableHandle MutableHandleSymbol; +typedef MutableHandle MutableHandleBigInt; typedef MutableHandle MutableHandleValue; typedef MutableHandle> MutableHandleValueVector; @@ -70,6 +73,7 @@ typedef Rooted RootedFunction; typedef Rooted RootedScript; typedef Rooted RootedString; typedef Rooted RootedSymbol; +typedef Rooted RootedBigInt; typedef Rooted RootedId; typedef Rooted RootedValue; @@ -81,6 +85,7 @@ typedef PersistentRooted PersistentRootedObject; typedef PersistentRooted PersistentRootedScript; typedef PersistentRooted PersistentRootedString; typedef PersistentRooted PersistentRootedSymbol; +typedef PersistentRooted PersistentRootedBigInt; typedef PersistentRooted 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 @@ -1056,6 +1056,22 @@ class JS_PUBLIC_API(Concrete) : TracerConcrete { static const char16_t concreteTypeName[]; }; +template<> +class JS_PUBLIC_API(Concrete) : TracerConcrete { + 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) : TracerConcreteWithCompartment { protected: diff --git a/js/public/Value.h b/js/public/Value.h index c645f07733..dff673cec8 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) @@ -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(data.asBits & JSVAL_SHIFTED_TAG_BIGINT); +#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; @@ -1097,6 +1125,14 @@ SymbolValue(JS::Symbol* sym) return v; } +static inline Value +BigIntValue(JS::BigInt* bi) +{ + Value v; + v.setBigInt(bi); + return v; +} + static inline Value BooleanValue(bool boo) { @@ -1365,6 +1401,7 @@ class WrappedPtrOperations 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 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 : 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)); } 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/MapObject.cpp b/js/src/builtin/MapObject.cpp index 893e0448a4..8bd87d8f16 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 v.toBigInt()->hash(); if (v.isObject()) return hcs.scramble(v.asRawBits()); @@ -100,6 +102,16 @@ 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 + // integer. This test should use a comparison function that doesn't + // require a JSContext once one is defined in the BigInt class. + if (!b && (value.isBigInt() && other.value.isBigInt())) { + JS::RootingContext* rcx = GetJSContextFromMainThread(); + RootedValue valueRoot(rcx, value); + RootedValue otherRoot(rcx, other.value); + SameValue(nullptr, valueRoot, otherRoot, &b); + } + #ifdef DEBUG bool same; JS::RootingContext* rcx = GetJSContextFromMainThread(); 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 9f030135e7..9a8a26d579 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -113,6 +113,7 @@ enum class AllocKind { FAT_INLINE_ATOM, ATOM, SYMBOL, + BIGINT, JITCODE, SCOPE, REGEXP_SHARED, @@ -153,6 +154,7 @@ enum class AllocKind { 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(fun)); } +void +JS::BigInt::traceChildren(JSTracer* trc) +{ + return; +} + struct TraverseObjectFunctor { template @@ -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::value && !mozilla::IsBaseOf::value && !mozilla::IsBaseOf::value && - !mozilla::IsBaseOf::value, T> + !mozilla::IsBaseOf::value && + !mozilla::IsBaseOf::value, T> { static_assert(!mozilla::IsSame::value && !mozilla::IsSame::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..3bd197aa77 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::create(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/js.msg b/js/src/js.msg index 242d81a5c8..7e6ddd81bc 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -619,3 +619,12 @@ 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_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..89e9f71aae 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,12 @@ ToAtomSlow(ExclusiveContext* cx, typename MaybeRooted::HandleTyp } return nullptr; } + if (v.isBigInt()) { + JSAtom* atom = BigIntToAtom(cx, v.toBigInt()); + 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..ee24f76987 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()->toBoolean(); 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 @@ -339,6 +339,21 @@ JSCompartment::wrap(JSContext* cx, MutableHandleString strp) return true; } +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) { 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 bi); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandleObject obj); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle desc); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle> 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/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/jsnum.cpp b/js/src/jsnum.cpp index 549596e579..f103f4dc79 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1452,9 +1452,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) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index b0cf6bc69b..75d45e76d5 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3104,9 +3104,14 @@ 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 nullptr because BigIntObject has not been defined yet. + return nullptr; } /* diff --git a/js/src/json.cpp b/js/src/json.cpp index f3cf22dac9..084deb2b94 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -626,6 +626,11 @@ Str(JSContext* cx, const Value& v, StringifyContext* scx) return NumberValueToStringBuffer(cx, v, scx->sb); } + 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/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/jsstr.cpp b/js/src/jsstr.cpp index fdee274c32..bd1ca38972 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -3734,6 +3734,10 @@ js::ToStringSlow(ExclusiveContext* cx, typename MaybeRooted::Han JSMSG_SYMBOL_TO_STRING); } return nullptr; + } else if (v.isBigInt()) { + if (!allowGC) + return nullptr; + str = BigInt::toString(cx, v.toBigInt()); } else { MOZ_ASSERT(v.isUndefined()); str = cx->names().undefined; diff --git a/js/src/moz.build b/js/src/moz.build index cecb7ae32d..321fdb88a9 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -392,6 +392,7 @@ SOURCES += [ 'jsdtoa.cpp', 'jsmath.cpp', 'jsutil.cpp', + 'vm/BigIntType.cpp', 'vm/Initialization.cpp', ] diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp new file mode 100644 index 0000000000..394f8f8784 --- /dev/null +++ b/js/src/vm/BigIntType.cpp @@ -0,0 +1,82 @@ +/* -*- 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 "vm/BigIntType.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/HashFunctions.h" + +#include "jsapi.h" +#include "jscntxt.h" + +#include "gc/Allocator.h" +#include "gc/Tracer.h" +#include "vm/SelfHosting.h" + +using namespace js; + +BigInt* +BigInt::create(js::ExclusiveContext* cx) +{ + BigInt* x = Allocate(cx); + if (!x) + return nullptr; + return x; +} + +BigInt* +BigInt::copy(js::ExclusiveContext* cx, HandleBigInt x) +{ + BigInt* bi = create(cx); + if (!bi) + return nullptr; + return bi; +} + +JSLinearString* +BigInt::toString(ExclusiveContext* cx, BigInt* x) +{ + return nullptr; +} + +void +BigInt::finalize(js::FreeOp* fop) +{ + return; +} + +JSAtom* +js::BigIntToAtom(ExclusiveContext* cx, BigInt* bi) +{ + JSString* str = BigInt::toString(cx, bi); + if (!str) + return nullptr; + return AtomizeString(cx, str); +} + +bool +BigInt::toBoolean() +{ + return false; +} + +js::HashNumber +BigInt::hash() +{ + return 0; +} + +size_t +BigInt::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + return 0; +} + +JS::ubi::Node::Size +JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const +{ + MOZ_ASSERT(get().isTenured()); + return js::gc::Arena::thingSize(get().asTenured().getAllocKind()); +} diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h new file mode 100644 index 0000000000..3c5768e818 --- /dev/null +++ b/js/src/vm/BigIntType.h @@ -0,0 +1,58 @@ +/* -*- 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 vm_BigIntType_h +#define vm_BigIntType_h + +#include "gc/Barrier.h" +#include "gc/Marking.h" +#include "gc/Heap.h" +#include "js/GCHashTable.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "vm/String.h" + +namespace JS { + +class BigInt final : public js::gc::TenuredCell +{ + private: + // The minimum allocation size is currently 16 bytes (see + // SortedArenaList in gc/ArenaList.h). + uint8_t unused_[16 + (16 % js::gc::CellSize)]; + + public: + // Allocate and initialize a BigInt value + static BigInt* create(js::ExclusiveContext* cx); + + static const JS::TraceKind TraceKind = JS::TraceKind::BigInt; + + void traceChildren(JSTracer* trc); + + void finalize(js::FreeOp* fop); + + js::HashNumber hash(); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + static JSLinearString* toString(js::ExclusiveContext* cx, BigInt* x); + bool toBoolean(); + + static BigInt* copy(js::ExclusiveContext* cx, Handle x); +}; + +static_assert(sizeof(BigInt) >= js::gc::CellSize, + "sizeof(BigInt) must be greater than the minimum allocation size"); + +} // namespace JS + +namespace js { + +extern JSAtom* +BigIntToAtom(ExclusiveContext* cx, JS::BigInt* bi); + +} // namespace js + +#endif diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index d5e7a2d058..84582b0ac5 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -424,5 +424,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/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 3515a9336b..36e5415f49 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; } @@ -4222,7 +4225,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()); 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(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/StringBuffer.cpp b/js/src/vm/StringBuffer.cpp index e4f0e4f4d6..38f1e74e71 100644 --- a/js/src/vm/StringBuffer.cpp +++ b/js/src/vm/StringBuffer.cpp @@ -170,6 +170,12 @@ js::ValueToStringBufferSlow(JSContext* cx, const Value& arg, StringBuffer& sb) JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SYMBOL_TO_STRING); return false; } + if (v.isBigInt()) { + JSString* str = BigInt::toString(cx, v.toBigInt()); + if (!str) + return false; + return sb.append(str); + } MOZ_ASSERT(v.isUndefined()); return sb.append(cx->names().undefined); } 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..83da598815 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: @@ -755,6 +757,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"); 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/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()); } else if (is()) { v.setSymbol(as()); - } else { + } + else if (is()) { + v.setBigInt(as()); + } + else { v.setUndefined(); } @@ -314,6 +319,7 @@ template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; template @@ -337,6 +343,7 @@ template UniquePtr TracerConcrete::edges(JSContext* template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template @@ -392,6 +399,7 @@ Concrete::jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& o } const char16_t Concrete::concreteTypeName[] = u"JS::Symbol"; +const char16_t Concrete::concreteTypeName[] = u"JS::BigInt"; const char16_t Concrete::concreteTypeName[] = u"JSScript"; const char16_t Concrete::concreteTypeName[] = u"js::LazyScript"; const char16_t Concrete::concreteTypeName[] = u"js::jit::JitCode"; From 13ac0d64830ef62373945b7f6e956c29d8f66bc1 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 02:40:25 -0500 Subject: [PATCH 02/22] Issue #1240 - Part 2 - Define the BigIntObject class for BigInt wrapper objects. Based on https://bugzilla.mozilla.org/show_bug.cgi?id=1366287 Part 3. In our Part 3 we will fast forward to the V8 implementation skipping GMP. --- js/public/Class.h | 1 + js/src/builtin/BigInt.cpp | 184 ++++++++++++++++++++++++++++++++ js/src/builtin/BigInt.h | 51 +++++++++ js/src/builtin/Object.cpp | 5 + js/src/jsfriendapi.cpp | 4 + js/src/jsobj.cpp | 4 +- js/src/json.cpp | 5 + js/src/jsprototypes.h | 1 + js/src/jsstr.cpp | 2 +- js/src/moz.build | 1 + js/src/vm/BigIntType.cpp | 74 ++++++++++++- js/src/vm/BigIntType.h | 11 +- js/src/vm/CommonPropertyNames.h | 1 + js/src/vm/GlobalObject.cpp | 1 + js/src/vm/StringBuffer.cpp | 2 +- 15 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 js/src/builtin/BigInt.cpp create mode 100644 js/src/builtin/BigInt.h 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/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp new file mode 100644 index 0000000000..2790a20ccc --- /dev/null +++ b/js/src/builtin/BigInt.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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()); +} + +static JSObject* +CreateBigIntPrototype(JSContext* cx, JSProtoKey key) +{ + return GlobalObject::createBlankPrototype(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(); + bn.setFixedSlot(PRIMITIVE_VALUE_SLOT, BigIntValue(bigInt)); + return &bn; +} + +BigInt* +BigIntObject::unbox() const +{ + return getFixedSlot(PRIMITIVE_VALUE_SLOT).toBigInt(); +} + +bool +js::intrinsic_ToBigInt(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + + BigInt* result = ToBigInt(cx, args[0]); + if (!result) + return false; + + args.rval().setBigInt(result); + return true; +} + +// 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().unbox()); + + args.rval().setBigInt(bi); + return true; +} + +bool +BigIntObject::valueOf(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(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().unbox()); + + // Steps 2-3. + uint8_t radix = 10; + + // Steps 4-5. + if (args.hasDefined(0)) { + double d; + if (!ToInteger(cx, args[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(cx, args); +} + +const ClassSpec BigIntObject::classSpec_ = { + GenericCreateConstructor, + CreateBigIntPrototype, + nullptr, + 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_FS_END +}; diff --git a/js/src/builtin/BigInt.h b/js/src/builtin/BigInt.h new file mode 100644 index 0000000000..75bf99867e --- /dev/null +++ b/js/src/builtin/BigInt.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 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); + + JS::BigInt* unbox() const; + + private: + static const JSPropertySpec properties[]; + static const JSFunctionSpec methods[]; +}; + +extern JSObject* +InitBigIntClass(JSContext* cx, Handle global); + +extern bool +intrinsic_ToBigInt(JSContext* cx, unsigned argc, JS::Value* vp); + +} // namespace js + +#endif 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 from showing up as Function. 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 +#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()) *cls = ESClass::Error; + else if (obj->is()) + *cls = ESClass::BigInt; else *cls = ESClass::Other; diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 75d45e76d5..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" @@ -3110,8 +3111,7 @@ js::PrimitiveToObject(JSContext* cx, const Value& v) } MOZ_ASSERT(v.isBigInt()); RootedBigInt bigInt(cx, v.toBigInt()); - // Return nullptr because BigIntObject has not been defined yet. - return nullptr; + return BigIntObject::create(cx, bigInt); } /* diff --git a/js/src/json.cpp b/js/src/json.cpp index 084deb2b94..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().unbox()); } } @@ -626,6 +630,7 @@ 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; 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/jsstr.cpp b/js/src/jsstr.cpp index bd1ca38972..3b5461b441 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -3737,7 +3737,7 @@ js::ToStringSlow(ExclusiveContext* cx, typename MaybeRooted::Han } else if (v.isBigInt()) { if (!allowGC) return nullptr; - str = BigInt::toString(cx, v.toBigInt()); + str = BigInt::toString(cx, v.toBigInt(), 10); } else { MOZ_ASSERT(v.isUndefined()); str = cx->names().undefined; diff --git a/js/src/moz.build b/js/src/moz.build index 321fdb88a9..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', diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index 394f8f8784..50f92bce49 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -11,6 +11,7 @@ #include "jsapi.h" #include "jscntxt.h" +#include "builtin/BigInt.h" #include "gc/Allocator.h" #include "gc/Tracer.h" #include "vm/SelfHosting.h" @@ -18,7 +19,7 @@ using namespace js; BigInt* -BigInt::create(js::ExclusiveContext* cx) +BigInt::create(ExclusiveContext* cx) { BigInt* x = Allocate(cx); if (!x) @@ -27,7 +28,51 @@ BigInt::create(js::ExclusiveContext* cx) } BigInt* -BigInt::copy(js::ExclusiveContext* cx, HandleBigInt x) +BigInt::create(ExclusiveContext* cx, double d) +{ + return nullptr; +} + +// 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 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::create(cx, d); +} + +BigInt* +BigInt::copy(ExclusiveContext* cx, HandleBigInt x) { BigInt* bi = create(cx); if (!bi) @@ -35,8 +80,29 @@ BigInt::copy(js::ExclusiveContext* cx, HandleBigInt x) return bi; } +// 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. + // Boolean and string conversions are not yet supported. + if (v.isBigInt()) + return v.toBigInt(); + + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); + } + return nullptr; +} + JSLinearString* -BigInt::toString(ExclusiveContext* cx, BigInt* x) +BigInt::toString(ExclusiveContext* cx, BigInt* x, uint8_t radix) { return nullptr; } @@ -50,7 +116,7 @@ BigInt::finalize(js::FreeOp* fop) JSAtom* js::BigIntToAtom(ExclusiveContext* cx, BigInt* bi) { - JSString* str = BigInt::toString(cx, bi); + JSString* str = BigInt::toString(cx, bi, 10); if (!str) return nullptr; return AtomizeString(cx, str); diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h index 3c5768e818..8d934271a3 100644 --- a/js/src/vm/BigIntType.h +++ b/js/src/vm/BigIntType.h @@ -27,6 +27,8 @@ class BigInt final : public js::gc::TenuredCell // Allocate and initialize a BigInt value static BigInt* create(js::ExclusiveContext* cx); + static BigInt* create(js::ExclusiveContext* cx, double d); + static const JS::TraceKind TraceKind = JS::TraceKind::BigInt; void traceChildren(JSTracer* trc); @@ -37,10 +39,12 @@ class BigInt final : public js::gc::TenuredCell size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; - static JSLinearString* toString(js::ExclusiveContext* cx, BigInt* x); bool toBoolean(); static BigInt* copy(js::ExclusiveContext* cx, Handle x); + + static JSLinearString* toString(js::ExclusiveContext* cx, BigInt* x, uint8_t radix); + }; static_assert(sizeof(BigInt) >= js::gc::CellSize, @@ -53,6 +57,11 @@ namespace js { extern JSAtom* BigIntToAtom(ExclusiveContext* cx, JS::BigInt* bi); +extern JS::BigInt* +NumberToBigInt(ExclusiveContext* cx, double d); + +extern JS::BigInt* +ToBigInt(ExclusiveContext* cx, JS::Handle v); } // namespace js #endif diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 84582b0ac5..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]") \ 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/StringBuffer.cpp b/js/src/vm/StringBuffer.cpp index 38f1e74e71..58a3c3e164 100644 --- a/js/src/vm/StringBuffer.cpp +++ b/js/src/vm/StringBuffer.cpp @@ -171,7 +171,7 @@ js::ValueToStringBufferSlow(JSContext* cx, const Value& arg, StringBuffer& sb) return false; } if (v.isBigInt()) { - JSString* str = BigInt::toString(cx, v.toBigInt()); + JSLinearString* str = BigInt::toString(cx, v.toBigInt(), 10); if (!str) return false; return sb.append(str); From ddd49121a6072528203ad80bebfddc08e2cc28b6 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 03:02:29 -0500 Subject: [PATCH 03/22] Issue #1240 - Part 3a - Adjust mozilla::FloatingPoint's definition. So only the barest details are specified for floating-point encodings, with every other number, bit mask, &c. mathematically derived. Also add a bunch of documentation comments. https://bugzilla.mozilla.org/show_bug.cgi?id=1508725 Prerequisite for our V8 fast forward. --- js/public/Value.h | 4 +- js/src/wasm/WasmTextUtils.cpp | 2 +- mfbt/FloatingPoint.h | 108 +++++++++++++++++++++++----------- 3 files changed, 77 insertions(+), 37 deletions(-) diff --git a/js/public/Value.h b/js/public/Value.h index dff673cec8..498f39ea74 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -507,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::kSignBit) <= JSVAL_SHIFTED_TAG_MAX_DOUBLE; #endif } @@ -1090,7 +1090,7 @@ IsCanonicalized(double d) uint64_t bits; mozilla::BitwiseCast(d, &bits); - return (bits & ~mozilla::DoubleTypeTraits::kSignBit) == detail::CanonicalizedNaNBits; + return (bits & ~mozilla::FloatingPoint::kSignBit) == detail::CanonicalizedNaNBits; } static inline Value 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 bool js::wasm::RenderNaN(StringBuffer& sb, Raw num) { - typedef typename mozilla::SelectTrait Traits; + typedef typename mozilla::FloatingPoint Traits; MOZ_ASSERT(IsNaN(num.fp())); 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 +struct FloatingPointTrait; + +template<> +struct FloatingPointTrait { +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 { +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 struct SelectTrait; -template<> struct SelectTrait : public FloatTypeTraits {}; -template<> struct SelectTrait : public DoubleTypeTraits {}; +} // namespace detail /* * This struct contains details regarding the encoding of floating-point @@ -88,30 +93,65 @@ template<> struct SelectTrait : public DoubleTypeTraits {}; * http://en.wikipedia.org/wiki/Floating_point#IEEE_754:_floating_point_in_modern_computers */ template -struct FloatingPoint : public SelectTrait +struct FloatingPoint final : private detail::FloatingPointTrait { - typedef SelectTrait Base; - typedef typename Base::Bits Bits; +private: + using Base = detail::FloatingPointTrait; - static_assert((Base::kSignBit & Base::kExponentBits) == 0, +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"); + + /** 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(1) << (CHAR_BIT * sizeof(Bits) - 1); + + /** The exponent bits in the floating point representation. */ + static constexpr Bits kExponentBits = + ((static_cast(1) << kExponentWidth) - 1) << kSignificandWidth; + + /** The significand bits in the floating point representation. */ + static constexpr Bits kSignificandBits = + (static_cast(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. */ From d42d5ce138b6b21a3b815c83f20677a44bd5684b Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 03:27:12 -0500 Subject: [PATCH 04/22] Issue #1240 - Part 3b - Implement WrappingOperations.h for wraparound math operations & conversions. https://bugzilla.mozilla.org/show_bug.cgi?id=1441657 Implement mozilla::WrappingMultiply. Prerequisite for our V8 fast forward. --- js/public/Conversions.h | 1 + js/public/Utility.h | 3 +- js/src/jsmath.cpp | 13 ++- mfbt/Attributes.h | 124 +++++++++++++++++++++++++ mfbt/HashFunctions.h | 8 +- mfbt/WrappingOperations.h | 188 ++++++++++++++++++++++++++++++++++++++ mfbt/moz.build | 1 + 7 files changed, 328 insertions(+), 10 deletions(-) create mode 100644 mfbt/WrappingOperations.h diff --git a/js/public/Conversions.h b/js/public/Conversions.h index 1850a42f0e..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 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 #include @@ -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/src/jsmath.cpp b/js/src/jsmath.cpp index 47cdbde8a3..afbb4d9a37 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 // for std::max #include @@ -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; } 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(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/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 @@ -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 + +namespace mozilla { + +namespace detail { + +template +struct WrapToSignedHelper +{ + static_assert(mozilla::IsUnsigned::value, + "WrapToSigned must be passed an unsigned type"); + + using SignedType = typename mozilla::MakeSigned::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(MinValue); + static constexpr UnsignedType MaxValueUnsigned = + static_cast(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(aValue) + : static_cast(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 +inline constexpr typename detail::WrapToSignedHelper::SignedType +WrapToSigned(UnsignedType aValue) +{ + return detail::WrapToSignedHelper::compute(aValue); +} + +namespace detail { + +template +struct WrappingMultiplyHelper +{ +private: + using UnsignedT = typename MakeUnsigned::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(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::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(aX), static_cast(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 +inline T +WrappingMultiply(T aX, T aY) +{ + return detail::WrappingMultiplyHelper::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', ] From 07b6c03dc77b2b5bc0f5c80ddfeecc4f155acaec Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 04:07:05 -0500 Subject: [PATCH 05/22] Issue #1240 - Part 3c - Fast-forward to the V8 version of BigIntType. Disabling some sections temporarily since the dependencies are not there yet. Based on the following: https://bugzilla.mozilla.org/show_bug.cgi?id=1502797 https://bugzilla.mozilla.org/show_bug.cgi?id=1471134 https://bugzilla.mozilla.org/show_bug.cgi?id=1441098 Part 3 & 4 Add structured clone support for BigInt and Enable BigInt wrapping from DOM bindings. https://bugzilla.mozilla.org/show_bug.cgi?id=1522738 --- dom/bindings/BindingUtils.h | 15 +- js/src/builtin/BigInt.cpp | 1 - js/src/builtin/BigInt.h | 1 - js/src/gdb/tests/test-jsval.cpp | 2 +- js/src/js.msg | 1 + js/src/jsatom.cpp | 3 +- js/src/jsbool.cpp | 2 +- js/src/jsstr.cpp | 3 +- js/src/vm/BigIntType.cpp | 3241 ++++++++++++++++++++++++++++++- js/src/vm/BigIntType.h | 396 +++- js/src/vm/StringBuffer.cpp | 3 +- js/src/vm/StructuredClone.cpp | 65 + 12 files changed, 3586 insertions(+), 147 deletions(-) diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index d55be9eb89..310cee15d1 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -1037,11 +1037,18 @@ MaybeWrapValue(JSContext* cx, JS::MutableHandle rval) return MaybeWrapStringValue(cx, rval); } - if (!rval.isObject()) { - return true; + if (rval.isObject()) { + return MaybeWrapObjectValue(cx, rval); } - - 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); + } + MOZ_ASSERT(rval.isSymbol()); + return true; } namespace binding_detail { diff --git a/js/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp index 2790a20ccc..72dcb3be56 100644 --- a/js/src/builtin/BigInt.cpp +++ b/js/src/builtin/BigInt.cpp @@ -1,5 +1,4 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/. */ diff --git a/js/src/builtin/BigInt.h b/js/src/builtin/BigInt.h index 75bf99867e..daa9fafac7 100644 --- a/js/src/builtin/BigInt.h +++ b/js/src/builtin/BigInt.h @@ -1,5 +1,4 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/. */ diff --git a/js/src/gdb/tests/test-jsval.cpp b/js/src/gdb/tests/test-jsval.cpp index 3bd197aa77..2007464cbe 100644 --- a/js/src/gdb/tests/test-jsval.cpp +++ b/js/src/gdb/tests/test-jsval.cpp @@ -19,7 +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::create(cx))); + RootedValue bi(cx, BigIntValue(BigInt::zero(cx))); RootedValue global(cx); global.setObject(*CurrentGlobalOrNull(cx)); diff --git a/js/src/js.msg b/js/src/js.msg index 7e6ddd81bc..1cb571d189 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -623,6 +623,7 @@ MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyn // 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") diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 89e9f71aae..2a72ac38a3 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -487,7 +487,8 @@ ToAtomSlow(ExclusiveContext* cx, typename MaybeRooted::HandleTyp return nullptr; } if (v.isBigInt()) { - JSAtom* atom = BigIntToAtom(cx, v.toBigInt()); + RootedBigInt i(cx, v.toBigInt()); + JSAtom* atom = BigIntToAtom(cx, i); if (!allowGC && !atom) cx->recoverFromOutOfMemory(); return atom; diff --git a/js/src/jsbool.cpp b/js/src/jsbool.cpp index ee24f76987..0a70fe49f2 100644 --- a/js/src/jsbool.cpp +++ b/js/src/jsbool.cpp @@ -172,7 +172,7 @@ js::ToBooleanSlow(HandleValue v) if (v.isString()) return v.toString()->length() != 0; if (v.isBigInt()) - return v.toBigInt()->toBoolean(); + return !v.toBigInt()->isZero(); MOZ_ASSERT(v.isObject()); return !EmulatesUndefined(&v.toObject()); diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 3b5461b441..593cf4d708 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -3737,7 +3737,8 @@ js::ToStringSlow(ExclusiveContext* cx, typename MaybeRooted::Han } else if (v.isBigInt()) { if (!allowGC) return nullptr; - str = BigInt::toString(cx, v.toBigInt(), 10); + 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/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index 50f92bce49..c2910c6c19 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -1,148 +1,3201 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- +/* -*- 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 + * 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 +#include +#include #include "jsapi.h" +#include "jsnum.h" #include "jscntxt.h" #include "builtin/BigInt.h" #include "gc/Allocator.h" -#include "gc/Tracer.h" +#include "js/Initialization.h" +#include "js/Utility.h" #include "vm/SelfHosting.h" +#include "vm/String.h" + using namespace js; -BigInt* -BigInt::create(ExclusiveContext* cx) -{ - BigInt* x = Allocate(cx); - if (!x) - return nullptr; - return x; +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::create(ExclusiveContext* cx, double d) -{ +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 heapDigits; + if (length > InlineDigitsLength) { + heapDigits = cx->make_pod_array(length); + if (!heapDigits) { + return nullptr; + } + } else { + heapDigits = nullptr; + } + + BigInt* x = Allocate(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(a) * static_cast(b); + *high = result >> DigitBits; + + return static_cast(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((-static_cast(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& 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(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& quotient, + const Maybe& 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::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 +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(cx, x, y, + std::bit_and()); +} + +BigInt* BigInt::absoluteOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp(cx, x, y, + std::bit_or()); +} + +BigInt* BigInt::absoluteAndNot(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + auto digitOperation = [](Digit a, Digit b) { return a & ~b; }; + return absoluteBitwiseOp(cx, x, y, + digitOperation); +} + +BigInt* BigInt::absoluteXor(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp(cx, x, y, + std::bit_xor()); +} + +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::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(bitsPerCharTableMultiplier) * bitLength, + maxBitsPerChar - 1); + maximumCharactersRequired += x->isNegative(); + + return AssertedCast(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(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(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(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(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(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(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::max() / bitsPerChar); + uint64_t n = + CeilDiv(charcount * bitsPerChar, DigitBits * bitsPerCharTableMultiplier); + if (n > MaxDigitLength) { + ReportAllocationOverflow(cx); + return false; + } + + *result = n; + return true; +} + +template +BigInt* BigInt::parseLiteralDigits(ExclusiveContext* cx, + const Range chars, + unsigned radix, bool isNegative, + bool* haveParseError) { + MOZ_ASSERT(chars.length()); + + RangedPtr start = chars.begin(); + RangedPtr 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(); + + for (; start < end; start++) { + uint32_t digit; + CharT c = *start; + 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(radix), + static_cast(digit)); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// BigInt proposal section 7.2 +template +BigInt* BigInt::parseLiteral(ExclusiveContext* cx, const Range chars, + bool* haveParseError) { + RangedPtr start = chars.begin(); + const RangedPtr 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(start + 2, end), 2, + isNegative, haveParseError); + } + if (start[1] == 'x' || start[1] == 'X') { + // StringNumericLiteral ::: HexIntegerLiteral + return parseLiteralDigits(cx, Range(start + 2, end), 16, + isNegative, haveParseError); + } + if (start[1] == 'o' || start[1] == 'O') { + // StringNumericLiteral ::: OctalIntegerLiteral + return parseLiteralDigits(cx, Range(start + 2, end), 8, + isNegative, haveParseError); + } + } + + return parseLiteralDigits(cx, Range(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; +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 3. + double i = JS::ToInteger(d); - // Step 4. - if (i != d) - return false; + // Step 4. + if (i != d) { + return false; + } - // Step 5. - return true; + // 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; + uint64_t mantissa = + mozilla::BitwiseCast(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; +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::create(cx, d); + // Step 3. + return BigInt::createFromDouble(cx, d); } -BigInt* -BigInt::copy(ExclusiveContext* cx, HandleBigInt x) -{ - BigInt* bi = create(cx); - if (!bi) - return nullptr; - return bi; +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 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::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::max(), + "unexpectedly large MaxBitLength"); + int n = static_cast(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(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(shift / DigitBits); + int bitsShift = static_cast(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(shift / DigitBits); + int bitsShift = static_cast(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(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::max(); + if (roundingCanOverflow) { + resultLength++; + } + } + + MOZ_ASSERT(resultLength <= length); + RootedBigInt result(cx, + createUninitialized(cx, resultLength, x->isNegative())); + if (!result) { + return nullptr; + } + if (!bitsShift) { + 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(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); +BigInt* js::ToBigInt(ExclusiveContext* cx, HandleValue val) { + RootedValue v(cx, val); - if(cx->isJSContext()) { - // Step 1. - if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, &v)) + 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; + } - // Step 2. - // Boolean and string conversions are not yet supported. - if (v.isBigInt()) - return v.toBigInt(); - - JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); - } - return nullptr; + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); + } + return nullptr; } -JSLinearString* -BigInt::toString(ExclusiveContext* cx, BigInt* x, uint8_t radix) -{ - return nullptr; +double BigInt::numberValue(BigInt* x) { + if (x->isZero()) { + return 0.0; + } + + using Double = mozilla::FloatingPoint; + 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() + : mozilla::PositiveInfinity(); + } + + // 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() + : PositiveInfinity(); + } + } + } + } + + 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(signBit | exponentBits | significandBits); } -void -BigInt::finalize(js::FreeOp* fop) -{ - return; +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); } -JSAtom* -js::BigIntToAtom(ExclusiveContext* cx, BigInt* bi) -{ - JSString* str = BigInt::toString(cx, bi, 10); - if (!str) - return nullptr; - return AtomizeString(cx, str); -} - -bool -BigInt::toBoolean() -{ +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; } -js::HashNumber -BigInt::hash() -{ - return 0; +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; + constexpr uint8_t SignificandWidth = Double::kSignificandWidth; + constexpr uint64_t SignificandBits = Double::kSignificandBits; + + const uint64_t doubleBits = mozilla::BitwiseCast(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; } -size_t -BigInt::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const -{ - return 0; +bool BigInt::equal(BigInt* lhs, double rhs) { + if (mozilla::IsNaN(rhs)) { + return false; + } + return compare(lhs, rhs) == 0; } -JS::ubi::Node::Size -JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const -{ - MOZ_ASSERT(get().isTenured()); - return js::gc::Arena::thingSize(get().asTenured().getAllocKind()); +// BigInt proposal section 3.2.5 +JS::Result 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 BigInt::lessThan(BigInt* lhs, double rhs) { + if (mozilla::IsNaN(rhs)) { + return Maybe(Nothing()); + } + return Some(compare(lhs, rhs) < 0); +} + +Maybe BigInt::lessThan(double lhs, BigInt* rhs) { + if (mozilla::IsNaN(lhs)) { + return Maybe(Nothing()); + } + return Some(-compare(rhs, lhs) < 0); +} + +bool BigInt::lessThan(ExclusiveContext* cx, HandleBigInt lhs, HandleString rhs, + Maybe& 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& 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& 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 +static inline BigInt* ParseStringBigIntLiteral(ExclusiveContext* cx, + Range 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(start, end), 10, + isNegative, haveParseError); + } else if (start[0] == '-') { + bool isNegative = true; + start++; + return BigInt::parseLiteralDigits(cx, Range(start, end), 10, + isNegative, haveParseError); + } + } + + return BigInt::parseLiteral(cx, Range(start, end), + haveParseError); +} + +// Called from BigInt constructor. +JS::Result 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& 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::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; +} + +#if 0 // Future XDR support +template +XDRResult js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi) { + JSContext* cx = xdr->cx(); + + uint8_t sign; + uint32_t length; + + if (mode == XDR_ENCODE) { + cx->check(bi); + sign = static_cast(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(sz); + } + + MOZ_TRY(xdr->codeUint8(&sign)); + MOZ_TRY(xdr->codeUint32(&length)); + + MOZ_RELEASE_ASSERT(length % sizeof(BigInt::Digit) == 0); + uint32_t digitLength = length / sizeof(BigInt::Digit); + auto buf = cx->make_pod_array(digitLength); + if (!buf) { + return xdr->fail(JS::TranscodeResult_Throw); + } + + if (mode == XDR_ENCODE) { + std::uninitialized_copy_n(bi->digits().Elements(), digitLength, buf.get()); + } + + MOZ_TRY(xdr->codeBytes(buf.get(), length)); + + 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 Ok(); +} + +template XDRResult js::XDRBigInt(XDRState* xdr, + MutableHandleBigInt bi); + +template XDRResult js::XDRBigInt(XDRState* xdr, + MutableHandleBigInt bi); +#endif diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h index 8d934271a3..f2a02e9b82 100644 --- a/js/src/vm/BigIntType.h +++ b/js/src/vm/BigIntType.h @@ -1,4 +1,4 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- +/* -*- 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/. */ @@ -6,62 +6,374 @@ #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 final : public js::gc::TenuredCell -{ - private: - // The minimum allocation size is currently 16 bytes (see - // SortedArenaList in gc/ArenaList.h). - uint8_t unused_[16 + (16 % js::gc::CellSize)]; +#if 0 // Future XDR support +class BigInt; - public: - // Allocate and initialize a BigInt value - static BigInt* create(js::ExclusiveContext* cx); - - static BigInt* create(js::ExclusiveContext* cx, double d); - - static const JS::TraceKind TraceKind = JS::TraceKind::BigInt; - - void traceChildren(JSTracer* trc); - - void finalize(js::FreeOp* fop); - - js::HashNumber hash(); - - size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; - - bool toBoolean(); - - static BigInt* copy(js::ExclusiveContext* cx, Handle x); - - static JSLinearString* toString(js::ExclusiveContext* cx, BigInt* x, uint8_t radix); - -}; - -static_assert(sizeof(BigInt) >= js::gc::CellSize, - "sizeof(BigInt) must be greater than the minimum allocation size"); - -} // namespace JS +} // namespace JS namespace js { -extern JSAtom* -BigIntToAtom(ExclusiveContext* cx, JS::BigInt* bi); +template +XDRResult XDRBigInt(XDRState* xdr, MutableHandle bi); -extern JS::BigInt* -NumberToBigInt(ExclusiveContext* cx, double d); +} // namespace js -extern JS::BigInt* -ToBigInt(ExclusiveContext* cx, JS::Handle v); -} // namespace js +namespace JS { +#endif + +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; + 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; } + + 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 x); + static BigInt* add(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* sub(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* mul(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* div(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* mod(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* pow(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* neg(js::ExclusiveContext* cx, Handle x); + static BigInt* lsh(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* rsh(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* bitAnd(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* bitXor(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* bitOr(js::ExclusiveContext* cx, Handle x, Handle y); + static BigInt* bitNot(js::ExclusiveContext* cx, Handle x); + + static int64_t toInt64(BigInt* x); + static uint64_t toUint64(BigInt* x); + + static BigInt* asIntN(js::ExclusiveContext* cx, Handle x, uint64_t bits); + static BigInt* asUintN(js::ExclusiveContext* cx, Handle 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 lhs, Handle rhs, + MutableHandle res); + static bool sub(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool mul(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool div(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool mod(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool pow(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool neg(js::ExclusiveContext* cx, Handle operand, + MutableHandle res); + static bool lsh(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool rsh(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool bitAnd(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool bitXor(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool bitOr(js::ExclusiveContext* cx, Handle lhs, Handle rhs, + MutableHandle res); + static bool bitNot(js::ExclusiveContext* cx, Handle operand, + MutableHandle res); + + static double numberValue(BigInt* x); + + static JSLinearString* toString(js::ExclusiveContext* cx, Handle x, + uint8_t radix); + template + static BigInt* parseLiteral(js::ExclusiveContext* cx, + const mozilla::Range chars, + bool* haveParseError); + template + static BigInt* parseLiteralDigits(js::ExclusiveContext* cx, + const mozilla::Range 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 looselyEqual(js::ExclusiveContext* cx, Handle 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 lessThan(BigInt* lhs, double rhs); + static mozilla::Maybe lessThan(double lhs, BigInt* rhs); + static bool lessThan(js::ExclusiveContext* cx, Handle lhs, HandleString rhs, + mozilla::Maybe& res); + static bool lessThan(js::ExclusiveContext* cx, HandleString lhs, Handle rhs, + mozilla::Maybe& res); + static bool lessThan(js::ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + mozilla::Maybe& 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::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 x, Digit divisor, + const mozilla::Maybe>& 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 dividend, Handle divisor, + const mozilla::Maybe>& quotient, + const mozilla::Maybe>& remainder, + bool quotientNegative); + + enum class LeftShiftMode { SameSizeResult, AlwaysAddOneDigit }; + + static BigInt* absoluteLeftShiftAlwaysCopy(js::ExclusiveContext* cx, Handle 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 + static BigInt* absoluteBitwiseOp(js::ExclusiveContext* cx, Handle x, + Handle y, BitwiseOp&& op); + + // Return `|x| & |y|`. + static BigInt* absoluteAnd(js::ExclusiveContext* cx, Handle x, + Handle y); + + // Return `|x| | |y|`. + static BigInt* absoluteOr(js::ExclusiveContext* cx, Handle x, + Handle y); + + // Return `|x| & ~|y|`. + static BigInt* absoluteAndNot(js::ExclusiveContext* cx, Handle x, + Handle y); + + // Return `|x| ^ |y|`. + static BigInt* absoluteXor(js::ExclusiveContext* cx, Handle x, + Handle y); + + // Return `(|x| + 1) * (resultNegative ? -1 : +1)`. + static BigInt* absoluteAddOne(js::ExclusiveContext* cx, Handle x, + bool resultNegative); + + // Return `(|x| - 1) * (resultNegative ? -1 : +1)`, with the precondition that + // |x| != 0. + static BigInt* absoluteSubOne(js::ExclusiveContext* cx, Handle 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(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(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 x, + Handle y, bool resultNegative); + + // Return `(|x| - |y|) * (resultNegative ? -1 : +1)`, with the precondition + // that |x| >= |y|. + static BigInt* absoluteSub(js::ExclusiveContext* cx, Handle x, + Handle 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, + unsigned radix); + static JSLinearString* toStringGeneric(js::ExclusiveContext* cx, Handle, + unsigned radix); + + static BigInt* trimHighZeroDigits(js::ExclusiveContext* cx, Handle x); + static BigInt* destructivelyTrimHighZeroDigits(js::ExclusiveContext* cx, + Handle x); + + friend struct JSStructuredCloneReader; + friend struct JSStructuredCloneWriter; +#if 0 // Future XDR support + template + friend js::XDRResult js::XDRBigInt(js::XDRState* xdr, + MutableHandle bi); +#endif + + 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 StringToBigInt( + js::ExclusiveContext* cx, JS::Handle 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& chars); + +extern JS::BigInt* ToBigInt(js::ExclusiveContext* cx, JS::Handle v); + +} // namespace js + +#undef js_gc_MinCellSize +#undef js_gc_Cell_ReservedBits #endif diff --git a/js/src/vm/StringBuffer.cpp b/js/src/vm/StringBuffer.cpp index 58a3c3e164..ce0fc4e719 100644 --- a/js/src/vm/StringBuffer.cpp +++ b/js/src/vm/StringBuffer.cpp @@ -171,7 +171,8 @@ js::ValueToStringBufferSlow(JSContext* cx, const Value& arg, StringBuffer& sb) return false; } if (v.isBigInt()) { - JSLinearString* str = BigInt::toString(cx, v.toBigInt(), 10); + RootedBigInt i(cx, v.toBigInt()); + JSLinearString* str = BigInt::toString(cx, i, 10); if (!str) return false; return sb.append(str); 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 @@ -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(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(nchars) : readStringImpl(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)) From dc23241afb590107033c3e204460bc7f7eb55493 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 18 Jul 2023 20:23:02 -0500 Subject: [PATCH 06/22] Issue #1240 - Part 4 - Implement parser support for BigInt literals. https://bugzilla.mozilla.org/show_bug.cgi?id=1505849 Partially based on https://bugzilla.mozilla.org/show_bug.cgi?id=1456568 Un-result-ified the BigInt XDR code, so we can enable it. https://bugzilla.mozilla.org/show_bug.cgi?id=1419094 Uninitialised memory read with BigInt right-shift https://bugzilla.mozilla.org/show_bug.cgi?id=1679003 --- dom/bindings/BindingUtils.h | 1 - js/public/Value.h | 2 +- js/src/builtin/ReflectParse.cpp | 11 +++- js/src/frontend/BytecodeEmitter.cpp | 32 +++++++++-- js/src/frontend/BytecodeEmitter.h | 17 +++--- js/src/frontend/FoldConstants.cpp | 11 ++++ js/src/frontend/FullParseHandler.h | 12 +++++ js/src/frontend/NameFunctions.cpp | 3 ++ js/src/frontend/ParseNode.cpp | 68 +++++++++++++++++------ js/src/frontend/ParseNode.h | 80 ++++++++++++++++++++++++---- js/src/frontend/Parser.cpp | 64 ++++++++++++++++++---- js/src/frontend/Parser.h | 16 ++++-- js/src/frontend/SharedContext.h | 5 +- js/src/frontend/SyntaxParseHandler.h | 1 + js/src/frontend/TokenKind.h | 1 + js/src/frontend/TokenStream.cpp | 29 ++++++++++ js/src/jit/BaselineCompiler.cpp | 7 +++ js/src/jit/BaselineCompiler.h | 1 + js/src/jit/IonBuilder.cpp | 1 + js/src/jsopcode.cpp | 3 ++ js/src/jsopcode.h | 1 + js/src/jsscript.cpp | 53 +++++++++++++++++- js/src/vm/BigIntType.cpp | 27 +++++----- js/src/vm/BigIntType.h | 9 +--- js/src/vm/Interpreter.cpp | 7 +++ js/src/vm/Opcodes.h | 12 +++-- js/src/wasm/AsmJS.cpp | 2 +- 27 files changed, 395 insertions(+), 81 deletions(-) diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 310cee15d1..467b134864 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -1047,7 +1047,6 @@ MaybeWrapValue(JSContext* cx, JS::MutableHandle rval) if (rval.isBigInt()) { return JS_WrapValue(cx, rval); } - MOZ_ASSERT(rval.isSymbol()); return true; } diff --git a/js/public/Value.h b/js/public/Value.h index 498f39ea74..e21b104a80 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -667,7 +667,7 @@ class MOZ_NON_PARAM alignas(8) Value #if defined(JS_NUNBOX32) return data.s.payload.bi; #elif defined(JS_PUNBOX64) - return reinterpret_cast(data.asBits & JSVAL_SHIFTED_TAG_BIGINT); + return reinterpret_cast(data.asBits & JSVAL_PAYLOAD_MASK); #endif } 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().objbox()->object); + RootedObject re1(cx, pn->as().objbox()->object()); LOCAL_ASSERT(re1 && re1->is()); RootedObject re2(cx, CloneRegExpObject(cx, re1)); @@ -3619,6 +3621,13 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst) val.setNumber(pn->as().value()); break; + case PNK_BIGINT: + { + BigInt* x = pn->as().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()); + *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().value()); return true; + case PNK_BIGINT: + vp.setBigInt(as().box()->value()); + return true; case PNK_TEMPLATE_STRING: case PNK_STRING: vp.setString(as().atom()); @@ -4834,6 +4842,15 @@ BytecodeEmitter::emitCopyDataProperties(CopyOption option) return true; } +bool +BytecodeEmitter::emitBigIntOp(BigInt* bigint) +{ + if (!constList.append(BigIntValue(bigint))) { + return false; + } + return emitIndex32(JSOP_BIGINT, constList.length() - 1); +} + bool BytecodeEmitter::emitIterator() { @@ -9579,6 +9596,12 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: return false; break; + case PNK_BIGINT: + if (!emitBigIntOp(pn->as().box()->value())) { + return false; + } + break; + case PNK_REGEXP: if (!emitRegExp(objectList.add(pn->as().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 list; + Rooted 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& 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().box()->value()->isZero()) ? Falsy : Truthy; + case PNK_STRING: case PNK_TEMPLATE_STRING: { bool isNonZeroLengthString = (pn->as().atom()->length() > 0); @@ -591,6 +596,8 @@ FoldTypeOfExpr(ExclusiveContext* cx, UnaryNode* node, Parser& 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& parser, bo MOZ_ASSERT(pn->is()); return true; + case PNK_BIGINT: + MOZ_ASSERT(pn->is()); + 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_(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 + BigIntLiteralType newBigInt(BigInt* bi, const TokenPos& pos, Boxer& boxer) { + BigIntBox* box = boxer.newBigIntBox(bi); + if (!box) { + return null(); + } + return new_(box, pos); + } + BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return new_(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()); break; + case PNK_BIGINT: + MOZ_ASSERT(cur->is()); + 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()); return PushResult::Recyclable; + case PNK_BIGINT: + MOZ_ASSERT(pn->is()); + 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().dump(indent); return; + case PN_BIGINT: + as().dump(indent); + return; case PN_REGEXP: as().dump(indent); return; @@ -759,6 +766,12 @@ NumericLiteral::dump(int indent) } } +void +BigIntLiteral::dump(int indent) +{ + fprintf(stderr, "(%s)", parseNodeNames[size_t(getKind())]); +} + void RegExpLiteral::dump(int indent) { @@ -962,23 +975,45 @@ LexicalScopeNode::dump(int indent) } #endif -ObjectBox::ObjectBox(JSObject* object, ObjectBox* traceLink) - : object(object), - traceLink(traceLink), - emitLink(nullptr) +TraceListNode::TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink) + : gcThing(gcThing), + traceLink(traceLink) { - MOZ_ASSERT(!object->is()); - MOZ_ASSERT(object->isTenured()); + MOZ_ASSERT(gcThing->isTenured()); } -ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink) - : object(function), - traceLink(traceLink), +BigIntBox* +TraceListNode::asBigIntBox() +{ + MOZ_ASSERT(isBigIntBox()); + return static_cast(this); +} + +ObjectBox* +TraceListNode::asObjectBox() +{ + MOZ_ASSERT(isObjectBox()); + return static_cast(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()); + MOZ_ASSERT(!object()->is()); +} + +ObjectBox::ObjectBox(JSFunction* function, TraceListNode* traceLink) + : TraceListNode(function, traceLink), + emitLink(nullptr) +{ + MOZ_ASSERT(object()->is()); 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(); } + bool isObjectBox() const { return gcThing->is(); } + + BigIntBox* asBigIntBox(); + ObjectBox* asObjectBox(); - ObjectBox(JSObject* object, ObjectBox* traceLink); - bool isFunctionBox() { return object->is(); } - 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(); } +}; + +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(); } + + bool isFunctionBox() const { return object()->is(); } + 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::setAwaitHandling(AwaitHandling awaitHandling) parser->setAwaitHandling(awaitHandling); } -template -ObjectBox* -Parser::newObjectBox(JSObject* obj) +template +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::newObjectBox(JSObject* obj) * function. */ - ObjectBox* objbox = alloc.new_(obj, traceListHead); - if (!objbox) { + BoxT* box = alloc.template new_(arg, traceListHead); + if (!box) { ReportOutOfMemory(context); return nullptr; } - traceListHead = objbox; + traceListHead = box; - return objbox; + return box; +} + +ObjectBox* +ParserBase::newObjectBox(JSObject* obj) +{ + return newTraceListNode(obj); +} + +BigIntBox* +ParserBase::newBigIntBox(BigInt* val) +{ + return newTraceListNode(val); } template @@ -10579,6 +10591,37 @@ Parser::newRegExp() return handler.newRegExp(reobj, pos(), *this); } +template <> +BigIntLiteral* +Parser::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 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::newBigInt() +{ + // The tokenizer has already checked the syntax of the bigint. + + return handler.newBigInt(); +} + template void Parser::checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos, @@ -11474,6 +11517,9 @@ Parser::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 + 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& 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* function() const { return &object()->as(); } + 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..b20e315707 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,16 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } } + if (isBigInt) { + size_t length = userbuf.addressOfNextRawChar() - numStart - 1; + tokenbuf.clear(); + if(!tokenbuf.reserve(length)) + goto error; + tokenbuf.infallibleAppend(numStart, length); + 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. @@ -1777,6 +1792,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 +1815,16 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } } + if (isBigInt) { + size_t length = userbuf.addressOfNextRawChar() - numStart - 1; + tokenbuf.clear(); + if(!tokenbuf.reserve(length)) + goto error; + 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/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 @@ -1620,6 +1620,13 @@ BaselineCompiler::emit_JSOP_DOUBLE() return true; } +bool +BaselineCompiler::emit_JSOP_BIGINT() +{ + frame.push(script->getConst(GET_UINT32_INDEX(pc))); + return true; +} + bool BaselineCompiler::emit_JSOP_STRING() { 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/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 0a9734a335..80c2c0aa9d 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1765,6 +1765,7 @@ IonBuilder::inspectOpcode(JSOp op) return jsop_compare(op); case JSOP_DOUBLE: + case JSOP_BIGINT: pushConstant(info().getConst(pc)); return true; 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/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* xdr, MutableHandleValue vp) SCRIPT_NULL, SCRIPT_OBJECT, SCRIPT_VOID, - SCRIPT_HOLE + SCRIPT_HOLE, + SCRIPT_BIGINT }; ConstTag tag; @@ -104,6 +105,8 @@ js::XDRScriptConst(XDRState* 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* 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(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(dst, src, src->objects()->vector); diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index c2910c6c19..d32489a8f6 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -1987,6 +1987,8 @@ BigInt* BigInt::rshByAbsolute(ExclusiveContext* cx, HandleBigInt x, HandleBigInt 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)); } @@ -3144,16 +3146,14 @@ JS::ubi::Node::Size JS::ubi::Concrete::size( return size; } -#if 0 // Future XDR support template -XDRResult js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi) { - JSContext* cx = xdr->cx(); +bool js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi) { + ExclusiveContext* cx = xdr->cx(); uint8_t sign; uint32_t length; if (mode == XDR_ENCODE) { - cx->check(bi); sign = static_cast(bi->isNegative()); uint64_t sz = bi->digitLength() * sizeof(BigInt::Digit); // As the maximum source code size is currently UINT32_MAX code units @@ -3165,8 +3165,10 @@ XDRResult js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi) { length = static_cast(sz); } - MOZ_TRY(xdr->codeUint8(&sign)); - MOZ_TRY(xdr->codeUint32(&length)); + 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); @@ -3179,7 +3181,8 @@ XDRResult js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi) { std::uninitialized_copy_n(bi->digits().Elements(), digitLength, buf.get()); } - MOZ_TRY(xdr->codeBytes(buf.get(), length)); + if(!xdr->codeBytes(buf.get(), length)) + return false; if (mode == XDR_DECODE) { BigInt* res = BigInt::createUninitialized(cx, digitLength, sign); @@ -3190,12 +3193,10 @@ XDRResult js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi) { bi.set(res); } - return Ok(); + return true; } -template XDRResult js::XDRBigInt(XDRState* xdr, - MutableHandleBigInt bi); +template bool js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi); + +template bool js::XDRBigInt(XDRState* xdr, MutableHandleBigInt bi); -template XDRResult js::XDRBigInt(XDRState* xdr, - MutableHandleBigInt bi); -#endif diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h index f2a02e9b82..cfc3489934 100644 --- a/js/src/vm/BigIntType.h +++ b/js/src/vm/BigIntType.h @@ -26,7 +26,6 @@ namespace JS { -#if 0 // Future XDR support class BigInt; } // namespace JS @@ -34,12 +33,11 @@ class BigInt; namespace js { template -XDRResult XDRBigInt(XDRState* xdr, MutableHandle bi); +bool XDRBigInt(XDRState* xdr, MutableHandle bi); } // namespace js namespace JS { -#endif class BigInt final : public js::gc::TenuredCell { public: @@ -332,11 +330,8 @@ class BigInt final : public js::gc::TenuredCell { friend struct JSStructuredCloneReader; friend struct JSStructuredCloneWriter; -#if 0 // Future XDR support template - friend js::XDRResult js::XDRBigInt(js::XDRState* xdr, - MutableHandle bi); -#endif + friend bool js::XDRBigInt(js::XDRState* xdr, MutableHandle bi); BigInt() = delete; BigInt(const BigInt& other) = delete; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 36e5415f49..b00f50971c 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -4125,6 +4125,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]; 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/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 534fc5f699..3615df8ae8 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -8709,7 +8709,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; From 25ee8102471b26a04774bb882761cc0d07f42536 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 02:17:04 -0500 Subject: [PATCH 07/22] Issue #1240 - Part 5a - BigInt to Number conversion. https://bugzilla.mozilla.org/show_bug.cgi?id=1466893 --- js/src/jsnum.cpp | 40 +++++++++++++++++++++++++++++++++++----- js/src/jsnum.h | 14 ++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index f103f4dc79..62f694c216 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -524,15 +524,21 @@ 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())); } - 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); @@ -1492,6 +1498,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. diff --git a/js/src/jsnum.h b/js/src/jsnum.h index f169a9c38d..295d237c22 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -362,6 +362,20 @@ 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); +} + void FIX_FPU(); } /* namespace js */ From 44b5d8dec393b3df597f53c7e8b4e2a14c9b31c3 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 02:19:32 -0500 Subject: [PATCH 08/22] Issue #1240 - Part 5b - BigInt support for basic arithmetic operations. https://bugzilla.mozilla.org/show_bug.cgi?id=1471134 Parts 3-5. --- js/src/jit/Recover.cpp | 4 +- js/src/jit/SharedIC.cpp | 9 ++-- js/src/jsmath.cpp | 28 +++++------ js/src/jsmath.h | 4 -- js/src/jsnum.cpp | 1 + js/src/vm/Interpreter-inl.h | 15 +++--- js/src/vm/Interpreter.cpp | 98 +++++++++++++++++++++++++------------ js/src/vm/Interpreter.h | 3 ++ 8 files changed, 98 insertions(+), 64 deletions(-) diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index 833ad871d0..b9bc5a0f35 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -778,7 +778,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 +805,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..40e4563510 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -942,7 +942,7 @@ 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: { @@ -1481,10 +1481,13 @@ DoUnaryArithFallback(JSContext* cx, void* payload, ICUnaryArith_Fallback* stub_, 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/jsmath.cpp b/js/src/jsmath.cpp index afbb4d9a37..a4d18c4021 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -684,28 +684,22 @@ js::ecmaPow(double x, double y) return pow(x, y); } -bool -js::math_pow_handle(JSContext* cx, HandleValue base, HandleValue power, MutableHandleValue result) -{ - double x; - if (!ToNumber(cx, base, &x)) - return false; - - double y; - if (!ToNumber(cx, power, &y)) - return false; - - double z = ecmaPow(x, y); - result.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()); + double x; + if (!ToNumber(cx, args.get(0), &x)) + return false; + + double y; + if (!ToNumber(cx, args.get(1), &y)) + return false; + + double z = ecmaPow(x, y); + args.rval().setNumber(z); + return true; } uint64_t 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 @@ -123,10 +123,6 @@ math_min(JSContext* cx, unsigned argc, js::Value* vp); 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); diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 62f694c216..c64a1149f0 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -529,6 +529,7 @@ Number(JSContext* cx, unsigned argc, Value* vp) return false; if (args[0].isBigInt()) args[0].setNumber(BigInt::numberValue(args[0].toBigInt())); + MOZ_ASSERT(args[0].isNumber()); } if (!isConstructing) { diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 79a4b90200..579cdc13ab 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -390,7 +390,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 +401,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; } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index b00f50971c..e8e7b6ef3b 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1328,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; } - return true; -} - -static MOZ_ALWAYS_INLINE bool -SubOperation(JSContext* cx, HandleValue lhs, HandleValue 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::add(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) +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 -DivOperation(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(NumberDiv(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 -ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +DivOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) +{ + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) + return false; + + 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, 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; @@ -1379,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; } @@ -2327,7 +2355,7 @@ CASE(JSOP_SUB) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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--; } @@ -2338,7 +2366,7 @@ CASE(JSOP_MUL) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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--; } @@ -2349,7 +2377,7 @@ CASE(JSOP_DIV) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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--; } @@ -2360,7 +2388,7 @@ CASE(JSOP_MOD) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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--; } @@ -2371,7 +2399,7 @@ CASE(JSOP_POW) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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--; } @@ -2399,7 +2427,7 @@ CASE(JSOP_NEG) { ReservedRooted 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) @@ -4587,6 +4615,12 @@ js::ModValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Mut return ModOperation(cx, lhs, rhs, res); } +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) { 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 @@ -456,6 +456,9 @@ DivValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Mutable 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); From d44e3c99411ceafa8d3944bfc1d79ddd5348fee8 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 03:00:43 -0500 Subject: [PATCH 09/22] Issue #1240 - Part 5c -Implement ToInt32OrBigInt operation. https://bugzilla.mozilla.org/show_bug.cgi?id=1490387 --- js/src/jsnum.cpp | 21 +++++++++++++++++++++ js/src/jsnum.h | 12 ++++++++++++ 2 files changed, 33 insertions(+) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index c64a1149f0..b778c8d739 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1614,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) { diff --git a/js/src/jsnum.h b/js/src/jsnum.h index 295d237c22..9866a91eeb 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -376,6 +376,18 @@ ToNumeric(ExclusiveContext* cx, JS::MutableHandleValue vp) 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 */ From 20b52c9e2ef9152e72f1a346cabb5ff150b0e2c8 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 03:02:35 -0500 Subject: [PATCH 10/22] Issue #1240 - Part 5d - Use the radix when parsing BigInts. Fixes Hex, Octal and Binary BigInt literal parsing. Previously it was only parsing in decimal. --- js/src/frontend/TokenStream.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index b20e315707..b2fe0afcf4 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1692,7 +1692,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; @@ -1818,8 +1818,20 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) if (isBigInt) { size_t length = userbuf.addressOfNextRawChar() - numStart - 1; tokenbuf.clear(); - if(!tokenbuf.reserve(length)) + 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; From 03734326e8a1c137f537c90fbab37e9c55c2adb0 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 14:32:00 -0500 Subject: [PATCH 11/22] Issue #1240 - Part 5e - BigInt bitwise operators. https://bugzilla.mozilla.org/show_bug.cgi?id=1490387 --- js/src/jit/CodeGenerator.cpp | 4 +- js/src/jit/MIR.cpp | 12 +++- js/src/jit/MIR.h | 4 +- js/src/jit/Recover.cpp | 51 +++++++------- js/src/jit/SharedIC.cpp | 34 ++++----- js/src/jit/shared/LIR-shared.h | 4 +- js/src/vm/Interpreter-inl.h | 95 +++++++++++++++++-------- js/src/vm/Interpreter.cpp | 123 +++++++++++++++++++-------------- 8 files changed, 197 insertions(+), 130 deletions(-) diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 1f7d292f29..7daf6e731b 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -10306,7 +10306,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(BitNot, "BitNot"); void @@ -10316,7 +10316,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(BitAnd, "BitAnd"); static const VMFunction BitOrInfo = FunctionInfo(BitOr, "BitOr"); static const VMFunction BitXorInfo = FunctionInfo(BitXor, "BitXor"); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 6b89af32de..2d4e02efea 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -2849,6 +2849,7 @@ MBinaryBitwiseInstruction::infer(BaselineInspector*, jsbytecode*) getOperand(1)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Symbol)) { specialization_ = MIRType::None; + setResultType(MIRType::Value); } else { specializeAs(MIRType::Int32); } @@ -2858,9 +2859,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(); @@ -2871,9 +2873,13 @@ 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)) + { specialization_ = MIRType::None; - else + setResultType(MIRType::Value); + } else { specialization_ = MIRType::Int32; + setResultType(MIRType::Int32); + } } void @@ -3828,7 +3834,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; } diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 6c0616c71d..f29cf6c353 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -5701,7 +5701,7 @@ class MBitNot : MUnaryInstruction(input) { specialization_ = MIRType::None; - setResultType(MIRType::Int32); + setResultType(MIRType::Value); setMovable(); } @@ -5861,7 +5861,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/Recover.cpp b/js/src/jit/Recover.cpp index b9bc5a0f35..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; diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index 40e4563510..375edb1400 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -946,43 +946,39 @@ DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub 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,10 +1471,10 @@ 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: { 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 { public: LIR_HEADER(BitNotV) @@ -3271,7 +3271,7 @@ class LBitOpI64 : public LInstructionHelper }; // Call a VM function to perform a bitwise operation. -class LBitOpV : public LCallInstructionHelper<1, 2 * BOX_PIECES, 0> +class LBitOpV : public LCallInstructionHelper { JSOp jsop_; diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 579cdc13ab..ecdb619868 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -708,72 +708,113 @@ GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandle } 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 e8e7b6ef3b..a03fa847f7 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -2178,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(cx, REGS)) goto error; @@ -2259,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--; @@ -2272,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--; @@ -2285,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--; @@ -2298,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) @@ -2344,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) @@ -2355,8 +2373,9 @@ CASE(JSOP_SUB) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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) @@ -2366,8 +2385,9 @@ CASE(JSOP_MUL) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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) @@ -2377,8 +2397,9 @@ CASE(JSOP_DIV) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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) @@ -2388,8 +2409,9 @@ CASE(JSOP_MOD) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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) @@ -2399,8 +2421,9 @@ CASE(JSOP_POW) ReservedRooted lval(&rootValue0, REGS.sp[-2]); ReservedRooted rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!PowOperation(cx, &lval, &rval, res)) + if (!PowOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_POW) @@ -2415,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) From e3ec7384711e972a8aab3a140685e2ac2cd95dff Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 15:27:59 -0500 Subject: [PATCH 12/22] Issue #1240 - Part 5f - Add DataView methods for BigInt access. https://bugzilla.mozilla.org/show_bug.cgi?id=1528582 --- js/src/vm/TypedArrayObject.cpp | 134 ++++++++++++++++++++++++++++++++- js/src/vm/TypedArrayObject.h | 16 ++++ 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 9d82fca6ec..0225c0578c 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -1959,6 +1959,8 @@ template <> struct DataToRepType { typedef uint16_t result; }; template <> struct DataToRepType { typedef uint16_t result; }; template <> struct DataToRepType { typedef uint32_t result; }; template <> struct DataToRepType { typedef uint32_t result; }; +template <> struct DataToRepType { typedef uint64_t result; }; +template <> struct DataToRepType { typedef uint64_t result; }; template <> struct DataToRepType { typedef uint32_t result; }; template <> struct DataToRepType { typedef uint64_t result; }; @@ -2058,6 +2060,28 @@ WebIDLCast(JSContext* cx, HandleValue value, NativeType* out) return true; } +template <> +inline bool WebIDLCast(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(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(JSContext* cx, HandleValue value, float* out) @@ -2076,6 +2100,8 @@ WebIDLCast(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 /* static */ bool DataViewObject::write(JSContext* cx, Handle obj, @@ -2089,7 +2115,7 @@ DataViewObject::write(JSContext* cx, Handle obj, 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 +2271,60 @@ DataViewObject::fun_getUint32(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod(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 thisView(cx, &args.thisv().toObject().as()); + + 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(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 thisView(cx, &args.thisv().toObject().as()); + + 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(cx, args); +} + bool DataViewObject::getFloat32Impl(JSContext* cx, const CallArgs& args) { @@ -2409,6 +2489,48 @@ DataViewObject::fun_setUint32(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod(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 thisView(cx, &args.thisv().toObject().as()); + + if (!write(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(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 thisView(cx, &args.thisv().toObject().as()); + + if (!write(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(cx, args); +} + bool DataViewObject::setFloat32Impl(JSContext* cx, const CallArgs& args) { @@ -2820,6 +2942,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 bool DataViewObject::getterImpl(JSContext* cx, const CallArgs& args) @@ -2888,6 +3017,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..ccdee22bee 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -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 From e8f0ea219fa245178e023ba4dfb44f393e37fac1 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 16:00:07 -0500 Subject: [PATCH 13/22] Issue #1240 - Part 6a - Implement BigInt.prototype.toLocaleString. https://bugzilla.mozilla.org/show_bug.cgi?id=1366287 Part 5. --- js/src/builtin/BigInt.cpp | 27 +++++++++++++++++++++++++++ js/src/builtin/BigInt.h | 2 ++ 2 files changed, 29 insertions(+) diff --git a/js/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp index 72dcb3be56..4aaa84bf1d 100644 --- a/js/src/builtin/BigInt.cpp +++ b/js/src/builtin/BigInt.cpp @@ -153,6 +153,32 @@ BigIntObject::toString(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod(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().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(cx, args); +} + const ClassSpec BigIntObject::classSpec_ = { GenericCreateConstructor, CreateBigIntPrototype, @@ -179,5 +205,6 @@ const JSPropertySpec BigIntObject::properties[] = { 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 }; diff --git a/js/src/builtin/BigInt.h b/js/src/builtin/BigInt.h index daa9fafac7..13c1985b30 100644 --- a/js/src/builtin/BigInt.h +++ b/js/src/builtin/BigInt.h @@ -31,6 +31,8 @@ class BigIntObject : public NativeObject 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); JS::BigInt* unbox() const; From e004b990541b82aec5cdd6dfb5e5ce73c70e7e41 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 16:36:58 -0500 Subject: [PATCH 14/22] Issue #1240 - Part 6b - Use ToIndex when constructing TypedArray with length argument. https://bugzilla.mozilla.org/show_bug.cgi?id=1317383 Part 2. --- js/src/jsnum.cpp | 29 +++++++++++++++++++++++++++++ js/src/jsnum.h | 9 +++++++++ js/src/vm/ArrayBufferObject.cpp | 21 +++++++++------------ js/src/vm/TypedArrayObject.cpp | 33 +++------------------------------ js/src/vm/TypedArrayObject.h | 2 +- 5 files changed, 51 insertions(+), 43 deletions(-) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index b778c8d739..573b55cc85 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1768,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 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 9866a91eeb..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) { 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/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 0225c0578c..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 /* static */ uint8_t* -DataViewObject::getDataPointer(JSContext* cx, Handle obj, double offset) +DataViewObject::getDataPointer(JSContext* cx, Handle 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, @@ -1989,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 /* static */ bool DataViewObject::read(JSContext* cx, Handle obj, @@ -2023,7 +1996,7 @@ DataViewObject::read(JSContext* cx, Handle obj, // Step 3. unnecessary assert // Step 4. - double getIndex; + uint64_t getIndex; if (!ToIndex(cx, args.get(0), &getIndex)) return false; @@ -2111,7 +2084,7 @@ DataViewObject::write(JSContext* cx, Handle obj, // Step 3. unnecessary assert // Step 4. - double getIndex; + uint64_t getIndex; if (!ToIndex(cx, args.get(0), &getIndex)) return false; diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index ccdee22bee..196d347075 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -442,7 +442,7 @@ class DataViewObject : public NativeObject template static uint8_t* - getDataPointer(JSContext* cx, Handle obj, double offset); + getDataPointer(JSContext* cx, Handle obj, uint64_t offset); template static bool From bbbab3898f41c99f31b6bd0420d1dabe6bae03a9 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 16:39:34 -0500 Subject: [PATCH 15/22] Issue #1240 - Part 6c - Implement asIntN and asUintN methods for BigInt values. https://bugzilla.mozilla.org/show_bug.cgi?id=1501104 --- js/src/builtin/BigInt.cpp | 64 ++++++++++++++++++++++++++++++++++++++- js/src/builtin/BigInt.h | 3 ++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/js/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp index 4aaa84bf1d..f6c244ce39 100644 --- a/js/src/builtin/BigInt.cpp +++ b/js/src/builtin/BigInt.cpp @@ -179,10 +179,66 @@ BigIntObject::toLocaleString(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod(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[0], &bits)) { + return false; + } + + // Step 2. + RootedBigInt bi(cx, ToBigInt(cx, args[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[0], &bits)) { + return false; + } + + // Step 2. + RootedBigInt bi(cx, ToBigInt(cx, args[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, CreateBigIntPrototype, - nullptr, + BigIntObject::staticMethods, nullptr, BigIntObject::methods, BigIntObject::properties @@ -208,3 +264,9 @@ const JSFunctionSpec BigIntObject::methods[] = { 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 index 13c1985b30..f1bf471ecb 100644 --- a/js/src/builtin/BigInt.h +++ b/js/src/builtin/BigInt.h @@ -33,12 +33,15 @@ class BigIntObject : public NativeObject 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* From 1355043d402bc8d9ee257729922e399008f41f0e Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 17:10:33 -0500 Subject: [PATCH 16/22] Issue #1240 - Part 5g - Implement BigInt comparison operators. Implement BigInt support for equality operators. https://bugzilla.mozilla.org/show_bug.cgi?id=1486173 Part 3. Implement BigInt support for relational comparison operators. https://bugzilla.mozilla.org/show_bug.cgi?id=1492669 --- js/src/vm/EqualityOperations.cpp | 20 ++++ js/src/vm/Interpreter-inl.h | 179 +++++++++++++++++++++++++------ 2 files changed, 164 insertions(+), 35 deletions(-) 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/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index ecdb619868..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" @@ -660,51 +662,158 @@ 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 -LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(<); +LessThanImpl(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, + mozilla::Maybe& 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(mozilla::Nothing()); + return true; + } + + res = mozilla::Some(lhsNum < rhsNum); + return true; } static MOZ_ALWAYS_INLINE bool -LessThanOrEqualOperation(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 tmpResult; + if (!LessThanImpl(cx, lhs, rhs, tmpResult)) { + return false; + } + *res = tmpResult.valueOr(false); + return true; } static MOZ_ALWAYS_INLINE bool -GreaterThanOperation(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 tmpResult; + if (!LessThanImpl(cx, rhs, lhs, tmpResult)) { + return false; + } + *res = !tmpResult.valueOr(true); + return true; } static MOZ_ALWAYS_INLINE bool -GreaterThanOrEqualOperation(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 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) +{ + 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 tmpResult; + if (!LessThanImpl(cx, lhs, rhs, tmpResult)) { + return false; + } + *res = !tmpResult.valueOr(true); + return true; } static MOZ_ALWAYS_INLINE bool From 4a3f1b7570f1380e0060f38420661725943ce553 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 20 Jul 2023 17:25:46 -0500 Subject: [PATCH 17/22] Issue #1240 - Part 7 - Handle BigInt values in XPCVariant code. https://bugzilla.mozilla.org/show_bug.cgi?id=1603055 --- js/xpconnect/src/XPCVariant.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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()); From 7a30b0fa2dc687e80beee09babd67392f45c720b Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 21 Jul 2023 04:10:47 -0500 Subject: [PATCH 18/22] Issue #1240 - Part 8 - Fix incorrect asserts with debug enabled. Fix BigInt errors in remainder operations https://bugzilla.mozilla.org/show_bug.cgi?id=1524136 Also fix 2 debug asserts in fallthroughs due to missing BigInt cases. --- js/public/Value.h | 2 ++ js/src/jit/BaselineIC.cpp | 3 +++ js/src/vm/BigIntType.cpp | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/js/public/Value.h b/js/public/Value.h index e21b104a80..30f4670049 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -1508,6 +1508,8 @@ DispatchTyped(F f, const JS::Value& val, Args&&... args) return f(&val.toObject(), mozilla::Forward(args)...); if (val.isSymbol()) return f(val.toSymbol(), mozilla::Forward(args)...); + if (val.isBigInt()) + return f(val.toBigInt(), mozilla::Forward(args)...); if (MOZ_UNLIKELY(val.isPrivateGCThing())) return DispatchTyped(f, val.toGCCellPtr(), mozilla::Forward(args)...); MOZ_ASSERT(!val.isGCThing()); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 61d77adc2e..0a87121c6e 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -8093,6 +8093,9 @@ ICTypeOf_Typed::Compiler::generateStubCode(MacroAssembler& masm) masm.branchTestSymbol(Assembler::NotEqual, R0, &failure); break; + case JSTYPE_BIGINT: + return false; + default: MOZ_CRASH("Unexpected type"); } diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index d32489a8f6..6beed9af11 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -1778,7 +1778,7 @@ BigInt* BigInt::mod(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { return nullptr; } MOZ_ASSERT(remainder); - return remainder; + return destructivelyTrimHighZeroDigits(cx, remainder); } } @@ -2195,7 +2195,7 @@ uint64_t BigInt::toUint64(BigInt* x) { uint64_t digit = x->digit(0); - if (DigitBits == 32 && x->digitLength() >= 1) { + if (DigitBits == 32 && x->digitLength() > 1) { digit |= static_cast(x->digit(1)) << 32; } From 4aa193b16b8642c553d3ebf8ff552b08b707ee2b Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 21 Jul 2023 14:20:11 -0500 Subject: [PATCH 19/22] Issue #1240 - Part 9 - Fix incorrectly parsing decimal BigInt 0n. The decimal parser strips leading 0s, so prevent tokenbuf being empty. --- js/src/frontend/TokenStream.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index b2fe0afcf4..b11c7df584 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1646,9 +1646,12 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) if (isBigInt) { size_t length = userbuf.addressOfNextRawChar() - numStart - 1; tokenbuf.clear(); - if(!tokenbuf.reserve(length)) + if(!tokenbuf.reserve(length > 0 ? length : 1)) goto error; - tokenbuf.infallibleAppend(numStart, length); + if(length > 0) + tokenbuf.infallibleAppend(numStart, length); + else + tokenbuf.infallibleAppend("0", 1); tp->type = TOK_BIGINT; goto out; } From 2d5ee3c4571d554eb074448dce13e0e9bb789462 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 21 Jul 2023 20:25:52 -0500 Subject: [PATCH 20/22] Issue #1240 - Part 10 - Implement minimal Ion support for BigInt. https://bugzilla.mozilla.org/show_bug.cgi?id=1507484 Implement IC support for BigInt. https://bugzilla.mozilla.org/show_bug.cgi?id=1522431 --- js/src/jit/BaselineBailouts.cpp | 1 + js/src/jit/BaselineCacheIR.cpp | 3 + js/src/jit/BaselineIC.cpp | 3 +- js/src/jit/CacheIR.cpp | 3 + js/src/jit/CodeGenerator.cpp | 61 ++++++++++++++++--- js/src/jit/CodeGenerator.h | 4 +- js/src/jit/IonAnalysis.cpp | 1 + js/src/jit/IonBuilder.cpp | 13 +++- js/src/jit/IonTypes.h | 10 +++ js/src/jit/LIR.h | 1 + js/src/jit/Lowering.cpp | 10 ++- js/src/jit/MCallOptimize.cpp | 21 +++++-- js/src/jit/MIR.cpp | 38 +++++++++++- js/src/jit/MIR.h | 50 +++++++++++---- js/src/jit/MacroAssembler-inl.h | 1 + js/src/jit/MacroAssembler.cpp | 6 +- js/src/jit/MacroAssembler.h | 10 +++ js/src/jit/Snapshots.cpp | 2 + js/src/jit/TypePolicy.cpp | 10 ++- js/src/jit/VMFunctions.cpp | 18 +++++- js/src/jit/VMFunctions.h | 1 + js/src/jit/arm/MacroAssembler-arm-inl.h | 23 +++++++ js/src/jit/arm/MacroAssembler-arm.cpp | 43 +++++++++++++ js/src/jit/arm/MacroAssembler-arm.h | 7 +++ js/src/jit/mips32/MacroAssembler-mips32-inl.h | 13 ++++ js/src/jit/mips64/CodeGenerator-mips64.cpp | 6 ++ js/src/jit/mips64/MacroAssembler-mips64-inl.h | 15 +++++ js/src/jit/mips64/MacroAssembler-mips64.cpp | 15 +++++ js/src/jit/mips64/MacroAssembler-mips64.h | 3 + js/src/jit/none/MacroAssembler-none.h | 2 + js/src/jit/shared/CodeGenerator-shared.cpp | 1 + js/src/jit/shared/Lowering-shared-inl.h | 3 +- js/src/jit/shared/Lowering-shared.cpp | 3 + js/src/jit/x64/CodeGenerator-x64.cpp | 6 ++ js/src/jit/x64/MacroAssembler-x64.h | 30 +++++++++ .../MacroAssembler-x86-shared-inl.h | 24 ++++++++ js/src/jit/x86/MacroAssembler-x86.h | 21 +++++++ js/src/vm/BigIntType.h | 5 ++ js/src/vm/TypeInference.cpp | 4 ++ 39 files changed, 448 insertions(+), 43 deletions(-) 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/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 0a87121c6e..2827f4b1d5 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -8094,7 +8094,8 @@ ICTypeOf_Typed::Compiler::generateStubCode(MacroAssembler& masm) break; case JSTYPE_BIGINT: - return false; + 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 7daf6e731b..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); @@ -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 80c2c0aa9d..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); @@ -4761,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); @@ -7210,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) @@ -8349,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 2d4e02efea..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,7 +2864,8 @@ 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); @@ -2872,7 +2891,8 @@ 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; setResultType(MIRType::Value); @@ -2886,7 +2906,8 @@ 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); @@ -3880,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; @@ -4459,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)) @@ -5612,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 f29cf6c353..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: 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 + inline void branchTestBigIntImpl(Condition cond, const T& t, Label* label) + DEFINED_ON(arm, arm64, x86_shared); + template inline void branchTestNullImpl(Condition cond, const T& t, Label* label) DEFINED_ON(arm, arm64, x86_shared); template diff --git a/js/src/jit/Snapshots.cpp b/js/src/jit/Snapshots.cpp index 586dbb1d8f..96fb823067 100644 --- a/js/src/jit/Snapshots.cpp +++ b/js/src/jit/Snapshots.cpp @@ -409,6 +409,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 +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 void unboxBoolean(T, Register) { MOZ_CRASH(); } template void unboxString(T, Register) { MOZ_CRASH(); } template void unboxSymbol(T, Register) { MOZ_CRASH(); } + template void unboxBigInt(T, Register) { MOZ_CRASH(); } template void unboxObject(T, Register) { MOZ_CRASH(); } template 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 void loadUnboxedValue(T, MIRType, AnyRegister) { MOZ_CRASH(); } template 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/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 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 +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 inline void loadInt32OrDouble(const T& src, FloatRegister dest); diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h index cfc3489934..0ccba99634 100644 --- a/js/src/vm/BigIntType.h +++ b/js/src/vm/BigIntType.h @@ -80,6 +80,11 @@ class BigInt final : public js::gc::TenuredCell { 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); diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 83da598815..a36926eb94 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -368,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: @@ -1635,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: From 15115c97fd170cb11d2328e01d91e5acacec78f2 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sun, 23 Jul 2023 15:36:19 -0500 Subject: [PATCH 21/22] Issue #1240 - Part 11 - Fix several issue reported on review. Skip over block delimiters when parsing BigInt literals. Update BigInt hashing to account for the possibility of moving GC. https://bugzilla.mozilla.org/show_bug.cgi?id=1531018 Make HashableValue comparison of BigInts infallible. https://bugzilla.mozilla.org/show_bug.cgi?id=1530406 Fix BigInt constructor API CallArgs usage. https://bugzilla.mozilla.org/show_bug.cgi?id=1526279 --- js/src/builtin/BigInt.cpp | 24 +++++------------------- js/src/builtin/BigInt.h | 3 --- js/src/builtin/MapObject.cpp | 15 ++++++--------- js/src/vm/BigIntType.cpp | 6 +++++- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/js/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp index f6c244ce39..6c78970d74 100644 --- a/js/src/builtin/BigInt.cpp +++ b/js/src/builtin/BigInt.cpp @@ -76,20 +76,6 @@ BigIntObject::unbox() const return getFixedSlot(PRIMITIVE_VALUE_SLOT).toBigInt(); } -bool -js::intrinsic_ToBigInt(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - - BigInt* result = ToBigInt(cx, args[0]); - if (!result) - return false; - - args.rval().setBigInt(result); - return true; -} - // BigInt proposal section 5.3.4 bool BigIntObject::valueOf_impl(JSContext* cx, const CallArgs& args) @@ -129,7 +115,7 @@ BigIntObject::toString_impl(JSContext* cx, const CallArgs& args) // Steps 4-5. if (args.hasDefined(0)) { double d; - if (!ToInteger(cx, args[0], &d)) + if (!ToInteger(cx, args.get(0), &d)) return false; if (d < 2 || d > 36) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_RADIX); @@ -187,12 +173,12 @@ BigIntObject::asUintN(JSContext* cx, unsigned argc, Value* vp) // Step 1. uint64_t bits; - if (!ToIndex(cx, args[0], &bits)) { + if (!ToIndex(cx, args.get(0), &bits)) { return false; } // Step 2. - RootedBigInt bi(cx, ToBigInt(cx, args[1])); + RootedBigInt bi(cx, ToBigInt(cx, args.get(1))); if (!bi) { return false; } @@ -215,12 +201,12 @@ BigIntObject::asIntN(JSContext* cx, unsigned argc, Value* vp) // Step 1. uint64_t bits; - if (!ToIndex(cx, args[0], &bits)) { + if (!ToIndex(cx, args.get(0), &bits)) { return false; } // Step 2. - RootedBigInt bi(cx, ToBigInt(cx, args[1])); + RootedBigInt bi(cx, ToBigInt(cx, args.get(1))); if (!bi) { return false; } diff --git a/js/src/builtin/BigInt.h b/js/src/builtin/BigInt.h index f1bf471ecb..6971549fc3 100644 --- a/js/src/builtin/BigInt.h +++ b/js/src/builtin/BigInt.h @@ -47,9 +47,6 @@ class BigIntObject : public NativeObject extern JSObject* InitBigIntClass(JSContext* cx, Handle global); -extern bool -intrinsic_ToBigInt(JSContext* cx, unsigned argc, JS::Value* vp); - } // namespace js #endif diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index 8bd87d8f16..fe748a6bde 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -82,7 +82,7 @@ HashValue(const Value& v, const mozilla::HashCodeScrambler& hcs) if (v.isSymbol()) return v.toSymbol()->hash(); if (v.isBigInt()) - return v.toBigInt()->hash(); + return MaybeForwarded(v.toBigInt())->hash(); if (v.isObject()) return hcs.scramble(v.asRawBits()); @@ -103,13 +103,9 @@ HashableValue::operator==(const HashableValue& other) const bool b = (value.asRawBits() == other.value.asRawBits()); // BigInt values are considered equal if they represent the same - // integer. This test should use a comparison function that doesn't - // require a JSContext once one is defined in the BigInt class. + // mathematical value. if (!b && (value.isBigInt() && other.value.isBigInt())) { - JS::RootingContext* rcx = GetJSContextFromMainThread(); - RootedValue valueRoot(rcx, value); - RootedValue otherRoot(rcx, other.value); - SameValue(nullptr, valueRoot, otherRoot, &b); + b = BigInt::equal(value.toBigInt(), other.value.toBigInt()); } #ifdef DEBUG @@ -390,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/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index 6beed9af11..7b8375526f 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -1391,10 +1391,14 @@ BigInt* BigInt::parseLiteralDigits(ExclusiveContext* cx, result->initializeDigitsToZero(); + RangedPtr begin = start; for (; start < end; start++) { uint32_t digit; CharT c = *start; - if (c >= '0' && c < limit0) { + 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; From 0fc38724d1d5f150d7a62b7d4652162a75e55f4c Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 17 Jul 2023 02:01:35 -0500 Subject: [PATCH 22/22] No Issue - Fix intermitted crash on MacOS 14 Sonoma Beta 3. Not sure why this is happening but mIOSurface is nullptr. If we just skip binding, it will be created on the next call to BeginUpdate. --- widget/cocoa/RectTextureImage.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/cocoa/RectTextureImage.mm b/widget/cocoa/RectTextureImage.mm index c67af97d0a..270ac5d8b5 100644 --- a/widget/cocoa/RectTextureImage.mm +++ b/widget/cocoa/RectTextureImage.mm @@ -144,7 +144,7 @@ RectTextureImage::DeleteTexture() void RectTextureImage::BindIOSurfaceToTexture(gl::GLContext* aGL) { - if (!mTexture) { + if (!mTexture && mIOSurface) { MOZ_ASSERT(aGL); aGL->fGenTextures(1, &mTexture); aGL->fActiveTexture(LOCAL_GL_TEXTURE0);