From eb7a856c766a508cde0a9ad90a233b272c7a9e54 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 01:59:24 -0500 Subject: 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 371e4119f9..3e14ed906f 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -111,6 +111,7 @@ enum class AllocKind : uint8_t { FAT_INLINE_ATOM, ATOM, SYMBOL, + BIGINT, JITCODE, SCOPE, REGEXP_SHARED, @@ -151,6 +152,7 @@ enum class AllocKind : uint8_t { D(FAT_INLINE_ATOM, String, js::FatInlineAtom, js::FatInlineAtom) \ D(ATOM, String, js::NormalAtom, js::NormalAtom) \ D(SYMBOL, Symbol, JS::Symbol, JS::Symbol) \ + D(BIGINT, BigInt, JS::BigInt, JS::BigInt) \ D(JITCODE, JitCode, js::jit::JitCode, js::jit::JitCode) \ D(SCOPE, Scope, js::Scope, js::Scope) \ D(REGEXP_SHARED, RegExpShared, js::RegExpShared, js::RegExpShared) diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 42c872a1da..13ec5b0c05 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -21,6 +21,7 @@ #include "js/SliceBudget.h" #include "vm/ArgumentsObject.h" #include "vm/ArrayObject.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/EnvironmentObject.h" #include "vm/Scope.h" @@ -875,6 +876,7 @@ js::GCMarker::markAndTraceChildren(T* thing) namespace js { template <> void GCMarker::traverse(BaseShape* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(JS::Symbol* thing) { markAndTraceChildren(thing); } +template <> void GCMarker::traverse(JS::BigInt* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(RegExpShared* thing) { markAndTraceChildren(thing); } } // namespace js @@ -1458,6 +1460,12 @@ js::GCMarker::lazilyMarkChildren(ObjectGroup* group) traverseEdge(group, static_cast(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 d7c1b8e84a..041a1d4b8c 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"; -- cgit v1.2.3 From b2ae4d388529c1a63959cc169f3ec1f7fdce9558 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 02:40:25 -0500 Subject: 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); -- cgit v1.2.3 From 3aeea5f9d0b99a1c2785b2fca33dab9dd4f553b3 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 03:02:29 -0500 Subject: 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; + +public: + /** + * An unsigned integral type suitable for accessing the bitwise representation + * of T. + */ + using Bits = typename Base::Bits; + + static_assert(sizeof(T) == sizeof(Bits), "Bits must be same size as T"); - static_assert((Base::kSignBit & Base::kExponentBits) == 0, + /** The bit-width of the exponent component of T. */ + using Base::kExponentWidth; + + /** The bit-width of the significand component of T. */ + using Base::kSignificandWidth; + + static_assert(1 + kExponentWidth + kSignificandWidth == + CHAR_BIT * sizeof(T), + "sign bit plus bit widths should sum to overall bit width"); + + /** + * The exponent field in an IEEE-754 floating point number consists of bits + * encoding an unsigned number. The *actual* represented exponent (for all + * values finite and not denormal) is that value, minus a bias |kExponentBias| + * so that a useful range of numbers is represented. + */ + static constexpr unsigned kExponentBias = (1U << (kExponentWidth - 1)) - 1; + + /** + * The amount by which the bits of the exponent-field in an IEEE-754 floating + * point number are shifted from the LSB of the floating point type. + */ + static constexpr unsigned kExponentShift = kSignificandWidth; + + /** The sign bit in the floating point representation. */ + static constexpr Bits kSignBit = + static_cast(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. */ -- cgit v1.2.3 From 42cf583a575c2a2693c03982ded04bbea2468922 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 03:27:12 -0500 Subject: 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 75ab8fcb29..b98dba57cd 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', ] -- cgit v1.2.3 From cb1aa7f456b4d44f4a1f5cdc63b490c8fb743a1b Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 13 Jul 2023 04:07:05 -0500 Subject: 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 | 3235 +++++++++++++++++++++++++++++++++++++-- js/src/vm/BigIntType.h | 370 ++++- js/src/vm/StringBuffer.cpp | 3 +- js/src/vm/StructuredClone.cpp | 65 + 12 files changed, 3570 insertions(+), 131 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); + } + // This could be optimized by checking the zone first, similar to + // the way strings are handled. At present, this is used primarily + // for structured cloning, so avoiding the overhead of JS_WrapValue + // calls is less important than for other types. + if (rval.isBigInt()) { + return JS_WrapValue(cx, rval); } - - return MaybeWrapObjectValue(cx, rval); + 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; +using mozilla::Abs; +using mozilla::AssertedCast; +using mozilla::BitwiseCast; +using mozilla::IsFinite; +using mozilla::Maybe; +using mozilla::NegativeInfinity; +using mozilla::Nothing; +using mozilla::PositiveInfinity; +using mozilla::Range; +using mozilla::RangedPtr; +using mozilla::Some; +using mozilla::WrapToSigned; + +static inline unsigned DigitLeadingZeroes(BigInt::Digit x) { + return sizeof(x) == 4 ? mozilla::CountLeadingZeroes32(x) + : mozilla::CountLeadingZeroes64(x); +} + +BigInt* BigInt::createUninitialized(ExclusiveContext* cx, size_t length, + bool isNegative) { + if (length > MaxDigitLength) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + + UniquePtr 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)); + } } -BigInt* -BigInt::create(ExclusiveContext* cx, double d) -{ +// 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 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; +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; + } - // Step 3. - double i = JS::ToInteger(d); + if (y->isZero()) { + return resultNegative == x->isNegative() ? x : neg(cx, x); + } - // Step 4. - if (i != d) - return false; + 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; + } - // Step 5. + 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; } -// 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); +// 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; } - return nullptr; + } + } + + // 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--; } - // Step 3. - return BigInt::create(cx, d); + 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; } -BigInt* -BigInt::copy(ExclusiveContext* cx, HandleBigInt x) -{ - BigInt* bi = create(cx); - if (!bi) - return nullptr; - return bi; +// 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 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; +BigInt* BigInt::absoluteAnd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp(cx, x, y, + std::bit_and()); +} - // Step 2. - // Boolean and string conversions are not yet supported. - if (v.isBigInt()) - return v.toBigInt(); +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; + } + } - JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); - } + 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); } -JSLinearString* -BigInt::toString(ExclusiveContext* cx, BigInt* x, uint8_t radix) -{ +// 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); } -void -BigInt::finalize(js::FreeOp* fop) -{ - return; +// 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); } -JSAtom* -js::BigIntToAtom(ExclusiveContext* cx, BigInt* bi) -{ - JSString* str = BigInt::toString(cx, bi, 10); - if (!str) +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; - return AtomizeString(cx, str); + } + + 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); } -bool -BigInt::toBoolean() -{ - return false; +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; } -js::HashNumber -BigInt::hash() -{ - return 0; +BigInt* BigInt::destructivelyTrimHighZeroDigits(ExclusiveContext* cx, HandleBigInt x) { + // TODO: Modify in place instead of allocating. + return trimHighZeroDigits(cx, x); } -size_t -BigInt::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const -{ - return 0; +// 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; } -JS::ubi::Node::Size -JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const -{ - MOZ_ASSERT(get().isTenured()); - return js::gc::Arena::thingSize(get().asTenured().getAllocKind()); +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; + } + + // Step 3. + double i = JS::ToInteger(d); + + // Step 4. + if (i != d) { + return false; + } + + // Step 5. + return true; +} + +BigInt* BigInt::createFromDouble(ExclusiveContext* cx, double d) { + MOZ_ASSERT(::IsInteger(d), + "Only integer-valued doubles can convert to BigInt"); + + if (d == 0) { + return zero(cx); + } + + int exponent = mozilla::ExponentComponent(d); + MOZ_ASSERT(exponent >= 0); + int length = exponent / DigitBits + 1; + BigInt* result = createUninitialized(cx, length, d < 0); + if (!result) { + return nullptr; + } + + // We construct a BigInt from the double `d` by shifting its mantissa + // according to its exponent and mapping the bit pattern onto digits. + // + // <----------- bitlength = exponent + 1 -----------> + // <----- 52 ------> <------ trailing zeroes ------> + // mantissa: 1yyyyyyyyyyyyyyyyy 0000000000000000000000000000000 + // digits: 0001xxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx + // <--> <------> + // msdTopBits DigitBits + // + using Double = mozilla::FloatingPoint; + 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; + } + + // Step 3. + return BigInt::createFromDouble(cx, d); +} + +BigInt* BigInt::copy(ExclusiveContext* cx, HandleBigInt x) { + if (x->isZero()) { + return zero(cx); + } + + BigInt* result = createUninitialized(cx, x->digitLength(), x->isNegative()); + if (!result) { + return nullptr; + } + for (size_t i = 0; i < x->digitLength(); i++) { + result->setDigit(i, x->digit(i)); + } + return result; +} + +// BigInt proposal section 1.1.7 +BigInt* BigInt::add(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + bool xNegative = x->isNegative(); + + // x + y == x + y + // -x + -y == -(x + y) + if (xNegative == y->isNegative()) { + return absoluteAdd(cx, x, y, xNegative); + } + + // x + -y == x - y == -(y - x) + // -x + y == y - x == -(x - y) + if (absoluteCompare(x, y) >= 0) { + return absoluteSub(cx, x, y, xNegative); + } + + return absoluteSub(cx, y, x, !xNegative); +} + +// BigInt proposal section 1.1.8 +BigInt* BigInt::sub(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + bool xNegative = x->isNegative(); + if (xNegative != y->isNegative()) { + // x - (-y) == x + y + // (-x) - y == -(x + y) + return absoluteAdd(cx, x, y, xNegative); + } + // x - y == -(y - x) + // (-x) - (-y) == y - x == -(x - y) + if (absoluteCompare(x, y) >= 0) { + return absoluteSub(cx, x, y, xNegative); + } + + return absoluteSub(cx, y, x, !xNegative); +} + +// BigInt proposal section 1.1.4 +BigInt* BigInt::mul(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return x; + } + if (y->isZero()) { + return y; + } + + unsigned resultLength = x->digitLength() + y->digitLength(); + bool resultNegative = x->isNegative() != y->isNegative(); + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + result->initializeDigitsToZero(); + + for (size_t i = 0; i < x->digitLength(); i++) { + multiplyAccumulate(y, x->digit(i), result, i); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// BigInt proposal section 1.1.5 +BigInt* BigInt::div(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + // 1. If y is 0n, throw a RangeError exception. + if (y->isZero()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_DIVISION_BY_ZERO); + } + return nullptr; + } + + // 2. Let quotient be the mathematical value of x divided by y. + // 3. Return a BigInt representing quotient rounded towards 0 to the next + // integral value. + if (x->isZero()) { + return x; + } + + if (absoluteCompare(x, y) < 0) { + return zero(cx); + } + + RootedBigInt quotient(cx); + bool resultNegative = x->isNegative() != y->isNegative(); + if (y->digitLength() == 1) { + Digit divisor = y->digit(0); + if (divisor == 1) { + return resultNegative == x->isNegative() ? x : neg(cx, x); + } + + Digit remainder; + if (!absoluteDivWithDigitDivisor(cx, x, divisor, Some("ient), + &remainder, resultNegative)) { + return nullptr; + } + } else { + if (!absoluteDivWithBigIntDivisor(cx, x, y, Some("ient), Nothing(), + resultNegative)) { + return nullptr; + } + } + + return destructivelyTrimHighZeroDigits(cx, quotient); +} + +// BigInt proposal section 1.1.6 +BigInt* BigInt::mod(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + // 1. If y is 0n, throw a RangeError exception. + if (y->isZero()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_DIVISION_BY_ZERO); + } + return nullptr; + } + + // 2. If x is 0n, return x. + if (x->isZero()) { + return x; + } + // 3. Let r be the BigInt defined by the mathematical relation r = x - (y × + // q) where q is a BigInt that is negative only if x/y is negative and + // positive only if x/y is positive, and whose magnitude is as large as + // possible without exceeding the magnitude of the true mathematical + // quotient of x and y. + if (absoluteCompare(x, y) < 0) { + return x; + } + + if (y->digitLength() == 1) { + Digit divisor = y->digit(0); + if (divisor == 1) { + return zero(cx); + } + + Digit remainderDigit; + bool unusedQuotientNegative = false; + if (!absoluteDivWithDigitDivisor(cx, x, divisor, Nothing(), &remainderDigit, + unusedQuotientNegative)) { + MOZ_CRASH("BigInt div by digit failed unexpectedly"); + } + + if (!remainderDigit) { + return zero(cx); + } + + BigInt* remainder = createUninitialized(cx, 1, x->isNegative()); + if (!remainder) { + return nullptr; + } + remainder->setDigit(0, remainderDigit); + return remainder; + } else { + RootedBigInt remainder(cx); + if (!absoluteDivWithBigIntDivisor(cx, x, y, Nothing(), Some(&remainder), + x->isNegative())) { + return nullptr; + } + MOZ_ASSERT(remainder); + return 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); + + if(cx->isJSContext()) { + // Step 1. + if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, &v)) { + return nullptr; + } + + // Step 2. + if (v.isBigInt()) { + return v.toBigInt(); + } + + if (v.isBoolean()) { + return v.toBoolean() ? BigInt::one(cx) : BigInt::zero(cx); + } + + if (v.isString()) { + BigInt* bi = nullptr; + RootedString str(cx, v.toString()); + JS_TRY_VAR_OR_RETURN_NULL(cx, bi, StringToBigInt(cx, str)); + if (!bi) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_INVALID_SYNTAX); + return nullptr; + } + return bi; + } + + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); + } + return nullptr; +} + +double BigInt::numberValue(BigInt* x) { + if (x->isZero()) { + return 0.0; + } + + using Double = mozilla::FloatingPoint; + 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); +} + +int8_t BigInt::compare(BigInt* x, BigInt* y) { + // Sanity checks to catch negative zeroes escaping to the wild. + MOZ_ASSERT(!x->isNegative() || !x->isZero()); + MOZ_ASSERT(!y->isNegative() || !y->isZero()); + + bool xSign = x->isNegative(); + + if (xSign != y->isNegative()) { + return xSign ? -1 : 1; + } + + if (xSign) { + mozilla::Swap(x, y); + } + + return absoluteCompare(x, y); +} + +bool BigInt::equal(BigInt* lhs, BigInt* rhs) { + if (lhs == rhs) { + return true; + } + if (lhs->digitLength() != rhs->digitLength()) { + return false; + } + if (lhs->isNegative() != rhs->isNegative()) { + return false; + } + for (size_t i = 0; i < lhs->digitLength(); i++) { + if (lhs->digit(i) != rhs->digit(i)) { + return false; + } + } + return true; +} + +int8_t BigInt::compare(BigInt* x, double y) { + MOZ_ASSERT(!mozilla::IsNaN(y)); + + constexpr int LessThan = -1, Equal = 0, GreaterThan = 1; + + // ±Infinity exceeds a finite bigint value. + if (!mozilla::IsFinite(y)) { + return y > 0 ? LessThan : GreaterThan; + } + + // Handle `x === 0n` and `y == 0` special cases. + if (x->isZero()) { + if (y == 0) { + // -0 and +0 are treated identically. + return Equal; + } + + return y > 0 ? LessThan : GreaterThan; + } + + const bool xNegative = x->isNegative(); + if (y == 0) { + return xNegative ? LessThan : GreaterThan; + } + + // Nonzero `x` and `y` with different signs are trivially compared. + const bool yNegative = y < 0; + if (xNegative != yNegative) { + return xNegative ? LessThan : GreaterThan; + } + + // `x` and `y` are same-signed. Determine which has greater magnitude, + // then combine that with the signedness just computed to reach a result. + const int exponent = mozilla::ExponentComponent(y); + if (exponent < 0) { + // `y` is a nonzero fraction of magnitude less than 1. + return xNegative ? LessThan : GreaterThan; + } + + size_t xLength = x->digitLength(); + MOZ_ASSERT(xLength > 0); + + Digit xMSD = x->digit(xLength - 1); + const int shift = DigitLeadingZeroes(xMSD); + int xBitLength = xLength * DigitBits - shift; + + // Differing bit-length makes for a simple comparison. + int yBitLength = exponent + 1; + if (xBitLength < yBitLength) { + return xNegative ? GreaterThan : LessThan; + } + if (xBitLength > yBitLength) { + return xNegative ? LessThan : GreaterThan; + } + + // Compare the high 64 bits of both numbers. (Lower-order bits not present + // in either number are zeroed.) Either that distinguishes `x` and `y`, or + // `x` and `y` differ only if a subsequent nonzero bit in `x` means `x` has + // larger magnitude. + + using Double = mozilla::FloatingPoint; + 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; +} + +bool BigInt::equal(BigInt* lhs, double rhs) { + if (mozilla::IsNaN(rhs)) { + return false; + } + return compare(lhs, rhs) == 0; +} + +// BigInt proposal section 3.2.5 +JS::Result 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 { + +#if 0 // Future XDR support +class BigInt; + +} // namespace JS + +namespace js { + +template +XDRResult XDRBigInt(XDRState* xdr, MutableHandle bi); + +} // 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); -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)]; + 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); - public: - // Allocate and initialize a BigInt value - static BigInt* create(js::ExclusiveContext* cx); + 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 BigInt* create(js::ExclusiveContext* cx, double d); + 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); - static const JS::TraceKind TraceKind = JS::TraceKind::BigInt; + private: + static constexpr size_t DigitBits = sizeof(Digit) * CHAR_BIT; + static constexpr size_t HalfDigitBits = DigitBits / 2; + static constexpr Digit HalfDigitMask = (1ull << HalfDigitBits) - 1; - void traceChildren(JSTracer* trc); + static_assert(DigitBits == 32 || DigitBits == 64, + "Unexpected BigInt Digit size"); - void finalize(js::FreeOp* fop); + // 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; - js::HashNumber hash(); + // 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"); - size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + 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); - bool toBoolean(); + 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); - static BigInt* copy(js::ExclusiveContext* cx, Handle x); + enum class LeftShiftMode { SameSizeResult, AlwaysAddOneDigit }; - static JSLinearString* toString(js::ExclusiveContext* cx, BigInt* x, uint8_t radix); + 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::CellSize, - "sizeof(BigInt) must be greater than the minimum allocation size"); +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 namespace js { -extern JSAtom* -BigIntToAtom(ExclusiveContext* cx, JS::BigInt* bi); +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); -extern JS::BigInt* -NumberToBigInt(ExclusiveContext* cx, double d); +} // namespace js -extern JS::BigInt* -ToBigInt(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)) -- cgit v1.2.3 From 8c25550bafe1145a8b697b210e7d1879a7f7d1ac Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 17 Jul 2023 02:01:35 -0500 Subject: 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); -- cgit v1.2.3 From 069fa50258931976dfdfeb7f9320af1ac0b6d176 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 18 Jul 2023 20:23:02 -0500 Subject: 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 | 66 ++++++++++++++++++++++------- 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, 394 insertions(+), 80 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), +TraceListNode::TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink) + : gcThing(gcThing), + traceLink(traceLink) +{ + MOZ_ASSERT(gcThing->isTenured()); +} + +BigIntBox* +TraceListNode::asBigIntBox() +{ + MOZ_ASSERT(isBigIntBox()); + return static_cast(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->isTenured()); + MOZ_ASSERT(!object()->is()); } -ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink) - : object(function), - traceLink(traceLink), +ObjectBox::ObjectBox(JSFunction* function, TraceListNode* traceLink) + : TraceListNode(function, traceLink), emitLink(nullptr) { - MOZ_ASSERT(object->is()); + 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 d617941503..82a532f4ed 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 box; +} + +ObjectBox* +ParserBase::newObjectBox(JSObject* obj) +{ + return newTraceListNode(obj); +} - return objbox; +BigIntBox* +ParserBase::newBigIntBox(BigInt* val) +{ + return newTraceListNode(val); } template @@ -10589,6 +10601,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, @@ -11484,6 +11527,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 041a1d4b8c..acde6d8ae8 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 4e00a25206..5812da797e 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -8708,7 +8708,7 @@ js::CompileAsmJS(ExclusiveContext* cx, AsmJSParser& parser, ParseNode* stmtList, // generating bytecode for asm.js functions, allowing this asm.js module // function to be the finished result. MOZ_ASSERT(funbox->function()->isInterpreted()); - funbox->object = moduleFun; + funbox->clobberFunction(moduleFun); // Success! Write to the console with a "warning" message. *validated = true; -- cgit v1.2.3 From 93a10f9e2f4e3480a9b89eb820c92b6c5b68dc24 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 02:17:04 -0500 Subject: 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 */ -- cgit v1.2.3 From 37e02a4bd459f4c6def492230a9b50cf4c3fdf4a Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 02:19:32 -0500 Subject: 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 | 18 ++++------ js/src/jsmath.h | 4 --- js/src/jsnum.cpp | 1 + js/src/vm/Interpreter-inl.h | 15 ++++---- js/src/vm/Interpreter.cpp | 88 +++++++++++++++++++++++++++++++-------------- js/src/vm/Interpreter.h | 3 ++ 8 files changed, 88 insertions(+), 54 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 b98dba57cd..8f17ff15ca 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -685,29 +685,23 @@ js::ecmaPow(double x, double y) } bool -js::math_pow_handle(JSContext* cx, HandleValue base, HandleValue power, MutableHandleValue result) +js::math_pow(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + double x; - if (!ToNumber(cx, base, &x)) + if (!ToNumber(cx, args.get(0), &x)) return false; double y; - if (!ToNumber(cx, power, &y)) + if (!ToNumber(cx, args.get(1), &y)) return false; double z = ecmaPow(x, y); - result.setNumber(z); + args.rval().setNumber(z); return true; } -bool -js::math_pow(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - return math_pow_handle(cx, args.get(0), args.get(1), args.rval()); -} - uint64_t js::GenerateRandomSeed() { diff --git a/js/src/jsmath.h b/js/src/jsmath.h index e716edce14..558d3d356a 100644 --- a/js/src/jsmath.h +++ b/js/src/jsmath.h @@ -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; } + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) + return false; + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::add(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() + rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -SubOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +SubOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(d1 - d2); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::sub(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() - rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -MulOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +MulOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(d1 * d2); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::mul(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() * rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -DivOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +DivOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(NumberDiv(d1, d2)); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::div(cx, lhs, rhs, res); + + res.setNumber(NumberDiv(lhs.toNumber(), rhs.toNumber())); return true; } static MOZ_ALWAYS_INLINE bool -ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +ModOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { int32_t l, r; + if (lhs.isInt32() && rhs.isInt32() && (l = lhs.toInt32()) >= 0 && (r = rhs.toInt32()) > 0) { int32_t mod = l % r; @@ -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); -- cgit v1.2.3 From a816a6b6de15a851554790ff7b84a4669959327e Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 03:00:43 -0500 Subject: 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 */ -- cgit v1.2.3 From 0da765e15618f1bb5d167062ab5ec6f0491589a6 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 03:02:35 -0500 Subject: 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; -- cgit v1.2.3 From 32acbcb96b64d895826685fa5ea999b0b4bf0893 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 14:32:00 -0500 Subject: 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) -- cgit v1.2.3 From f734e4f724fc83c8c296ca4c1f0eee8728fdb343 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 15:27:59 -0500 Subject: 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 -- cgit v1.2.3 From 700bead8ba82020d32eb0050bd809f8743192088 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 16:00:07 -0500 Subject: 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; -- cgit v1.2.3 From 2b198142f021e33ca5a6bef52abc04b95042fb33 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 16:36:58 -0500 Subject: 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 -- cgit v1.2.3 From f193a4a7dce742005883f67ec4260637d41661aa Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 16:39:34 -0500 Subject: 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* -- cgit v1.2.3 From 24e4a09270cd7194c09e6370cf308a4350ae5e37 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Jul 2023 17:10:33 -0500 Subject: 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 | 177 +++++++++++++++++++++++++++++++-------- 2 files changed, 163 insertions(+), 34 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 +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 -LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(<); +LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() < rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe tmpResult; + if (!LessThanImpl(cx, lhs, rhs, tmpResult)) { + return false; + } + *res = tmpResult.valueOr(false); + return true; } static MOZ_ALWAYS_INLINE bool -LessThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(<=); +LessThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() <= rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe tmpResult; + if (!LessThanImpl(cx, rhs, lhs, tmpResult)) { + return false; + } + *res = !tmpResult.valueOr(true); + return true; } static MOZ_ALWAYS_INLINE bool -GreaterThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(>); +GreaterThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() > rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe tmpResult; + if (!LessThanImpl(cx, rhs, lhs, tmpResult)) { + return false; + } + *res = tmpResult.valueOr(false); + return true; } static MOZ_ALWAYS_INLINE bool -GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(>=); +GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() >= rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe tmpResult; + if (!LessThanImpl(cx, lhs, rhs, tmpResult)) { + return false; + } + *res = !tmpResult.valueOr(true); + return true; } static MOZ_ALWAYS_INLINE bool -- cgit v1.2.3 From f4c134225c4433969392611eeb99e75be47e4660 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 20 Jul 2023 17:25:46 -0500 Subject: 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()); -- cgit v1.2.3 From 0587c8511449705ea2827312982df834d151b72a Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 21 Jul 2023 04:10:47 -0500 Subject: 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; } -- cgit v1.2.3 From 6257513c7e0e1e90b47b35e0c2ea63eeeb802ed8 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 21 Jul 2023 14:20:11 -0500 Subject: 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; } -- cgit v1.2.3 From 94609cf97bae8b30f51ddabd94cfc2d301d59b83 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 21 Jul 2023 20:25:52 -0500 Subject: 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 +++++++++++ .../jit/x86-shared/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 6d9a461412..3111941315 100644 --- a/js/src/jit/Snapshots.cpp +++ b/js/src/jit/Snapshots.cpp @@ -404,6 +404,8 @@ ValTypeToString(JSValueType type) { return "string"; case JSVAL_TYPE_SYMBOL: return "symbol"; + case JSVAL_TYPE_BIGINT: + return "BigInt"; case JSVAL_TYPE_BOOLEAN: return "boolean"; case JSVAL_TYPE_OBJECT: diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 396fdc57f8..1222cdd2b2 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -700,7 +700,8 @@ ToDoublePolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) case MIRType::Object: case MIRType::String: case MIRType::Symbol: - // Objects might be effectful. Symbols give TypeError. + case MIRType::BigInt: + // Objects might be effectful. Symbols and BigInts give TypeError. break; default: break; @@ -748,7 +749,8 @@ ToInt32Policy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) case MIRType::Object: case MIRType::String: case MIRType::Symbol: - // Objects might be effectful. Symbols give TypeError. + case MIRType::BigInt: + // Objects might be effectful. Symbols and BigInts give TypeError. break; default: break; @@ -765,7 +767,8 @@ ToStringPolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) MOZ_ASSERT(ins->isToString()); MIRType type = ins->getOperand(0)->type(); - if (type == MIRType::Object || type == MIRType::Symbol) { + if (type == MIRType::Object || type == MIRType::Symbol || + type == MIRType::BigInt) { ins->replaceOperand(0, BoxAt(alloc, ins, ins->getOperand(0))); return true; } @@ -955,6 +958,7 @@ StoreUnboxedScalarPolicy::adjustValueInput(TempAllocator& alloc, MInstruction* i case MIRType::Object: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: value = BoxAt(alloc, ins, value); break; default: diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 6e5676f153..8802d3582b 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1236,15 +1236,27 @@ AssertValidSymbolPtr(JSContext* cx, JS::Symbol* sym) MOZ_ASSERT(sym->getAllocKind() == gc::AllocKind::SYMBOL); } +void +AssertValidBigIntPtr(JSContext* cx, JS::BigInt* bi) { + // FIXME: check runtime? + MOZ_ASSERT(cx->zone() == bi->zone()); + MOZ_ASSERT(bi->isAligned()); + MOZ_ASSERT(bi->isTenured()); + MOZ_ASSERT(bi->getAllocKind() == gc::AllocKind::BIGINT); +} + void AssertValidValue(JSContext* cx, Value* v) { - if (v->isObject()) + if (v->isObject()) { AssertValidObjectPtr(cx, &v->toObject()); - else if (v->isString()) + } else if (v->isString()) { AssertValidStringPtr(cx, v->toString()); - else if (v->isSymbol()) + } else if (v->isSymbol()) { AssertValidSymbolPtr(cx, v->toSymbol()); + } else if (v->isBigInt()) { + AssertValidBigIntPtr(cx, v->toBigInt()); + } } bool diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 32830038d1..f4280f5800 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -751,6 +751,7 @@ void AssertValidObjectPtr(JSContext* cx, JSObject* obj); void AssertValidObjectOrNullPtr(JSContext* cx, JSObject* obj); void AssertValidStringPtr(JSContext* cx, JSString* str); void AssertValidSymbolPtr(JSContext* cx, JS::Symbol* sym); +void AssertValidBigIntPtr(JSContext* cx, JS::BigInt* bi); void AssertValidValue(JSContext* cx, Value* v); void MarkValueFromIon(JSRuntime* rt, Value* vp); diff --git a/js/src/jit/arm/MacroAssembler-arm-inl.h b/js/src/jit/arm/MacroAssembler-arm-inl.h index 2cc26b2242..3fc07e0de5 100644 --- a/js/src/jit/arm/MacroAssembler-arm-inl.h +++ b/js/src/jit/arm/MacroAssembler-arm-inl.h @@ -1880,6 +1880,29 @@ MacroAssembler::branchTestSymbolImpl(Condition cond, const T& t, Label* label) ma_b(label, c); } +void MacroAssembler::branchTestBigInt(Condition cond, Register tag, Label* label) { + branchTestBigIntImpl(cond, tag, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const BaseIndex& address, Label* label) { + branchTestBigIntImpl(cond, address, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) { + branchTestBigIntImpl(cond, value, label); +} + +template +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: -- cgit v1.2.3 From f359533b82ec4131d5af7955685e9178887ba9cd Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sun, 23 Jul 2023 15:36:19 -0500 Subject: 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; -- cgit v1.2.3 From a3096d09f90f1238a1546e66b14474c8248df7c2 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Tue, 25 Jul 2023 01:02:59 +0200 Subject: Update UXP/Goanna version now BigInt has landed. --- config/milestone.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/milestone.txt b/config/milestone.txt index 30351e76a8..b463b5d4d2 100644 --- a/config/milestone.txt +++ b/config/milestone.txt @@ -8,4 +8,4 @@ # Referenced by milestone.py. #-------------------------------------------------------- -6.2.0 \ No newline at end of file +6.3.0 \ No newline at end of file -- cgit v1.2.3 From edacbbd96538a60495dd50010b5549defc1ea35c Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 24 Jul 2023 23:30:27 -0500 Subject: Issue #2026 - Part 1 - Implement BigInt64 and BigUint64Array. https://bugzilla.mozilla.org/show_bug.cgi?id=1456569 --- js/public/Class.h | 2 +- js/src/builtin/Object.cpp | 4 +- js/src/builtin/TypedArray.js | 26 +- js/src/builtin/TypedObject.h | 22 +- js/src/builtin/TypedObjectConstants.h | 10 +- js/src/jit/BaselineIC.cpp | 3 + js/src/jit/IonTypes.h | 31 +- js/src/jit/MIR.cpp | 9 +- js/src/jit/MIR.h | 3 + js/src/jit/MacroAssembler-inl.h | 4 +- js/src/jit/MacroAssembler.cpp | 17 +- js/src/jit/RangeAnalysis.cpp | 2 + js/src/jit/x64/CodeGenerator-x64.cpp | 2 + js/src/jit/x64/Lowering-x64.cpp | 2 + js/src/jit/x64/MacroAssembler-x64.cpp | 6 + js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp | 2 + js/src/jit/x86/Lowering-x86.cpp | 2 + js/src/jit/x86/MacroAssembler-x86.cpp | 6 + js/src/jsarray.cpp | 7 + js/src/jsfriendapi.h | 76 +++- js/src/jsobj.cpp | 8 +- js/src/jsprototypes.h | 2 + js/src/vm/ArrayBufferObject.h | 16 + js/src/vm/BigIntType.cpp | 16 + js/src/vm/BigIntType.h | 2 + js/src/vm/CommonPropertyNames.h | 2 + js/src/vm/GlobalObject.h | 30 ++ js/src/vm/NativeObject-inl.h | 11 +- js/src/vm/NativeObject.cpp | 53 +-- js/src/vm/NativeObject.h | 5 +- js/src/vm/ObjectGroup.cpp | 2 + js/src/vm/SelfHosting.cpp | 47 +++ js/src/vm/StructuredClone.cpp | 13 +- js/src/vm/TypedArrayCommon.h | 103 ++++- js/src/vm/TypedArrayObject.cpp | 454 +++++++++++++++------ js/src/vm/TypedArrayObject.h | 20 +- 36 files changed, 788 insertions(+), 232 deletions(-) diff --git a/js/public/Class.h b/js/public/Class.h index 8aeacdc541..40885e6082 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -913,7 +913,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 50) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 52) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 388af28f26..f4353480ba 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -842,7 +842,9 @@ EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPr if (obj->is()) { HandleNativeObject nobj = obj.as(); if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) { - value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)); + if(!nobj->getDenseOrTypedArrayElement(cx, JSID_TO_INT(id), &value)) { + return false; + } } else { shape = nobj->lookup(cx, id); if (!shape || !(shape->attributes() & JSPROP_ENUMERATE)) diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 22023aa7ca..57f6d738ca 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -879,7 +879,7 @@ function SetFromNonTypedArray(target, array, targetOffset, targetLength, targetB // Steps 12-15, 21, 23-24. while (targetOffset < limitOffset) { // Steps 24a-c. - var kNumber = ToNumber(src[k]); + var kNumber = ToNumeric(src[k]); // Step 24d. This explicit check will be unnecessary when we implement // throw-on-getting/setting-element-in-detached-buffer semantics. @@ -1098,6 +1098,30 @@ function TypedArrayCompare(x, y) { return Number_isNaN(y) ? -1 : 0; } +// https://tc39.github.io/proposal-bigint/#sec-%typedarray%.prototype.sort +// TypedArray SortCompare specialization for BigInt values. +function TypedArrayCompareBigInt(x, y) { + // Step 1. + // eslint-disable-next-line valid-typeof + assert(typeof x === "bigint" && typeof y === "bigint", + "x and y are not BigInts."); + + // Step 2 (Implemented in TypedArraySort). + + // Step 6. + if (x < y) + return -1; + + // Step 7. + if (x > y) + return 1; + + // Steps 3-5, 8-9 (Not applicable when sorting BigInt values). + + // Step 10. + return 0; +} + // TypedArray SortCompare specialization for integer values. function TypedArrayCompareInt(x, y) { // Step 1. diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index 83700001d4..9318a0f795 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -245,6 +245,10 @@ class ScalarTypeDescr : public SimpleTypeDescr "TypedObjectConstants.h must be consistent with Scalar::Type"); static_assert(Scalar::Uint32 == JS_SCALARTYPEREPR_UINT32, "TypedObjectConstants.h must be consistent with Scalar::Type"); + static_assert(Scalar::BigInt64 == JS_SCALARTYPEREPR_BIGINT64, + "TypedObjectConstants.h must be consistent with Scalar::Type"); + static_assert(Scalar::BigUint64 == JS_SCALARTYPEREPR_BIGUINT64, + "TypedObjectConstants.h must be consistent with Scalar::Type"); static_assert(Scalar::Float32 == JS_SCALARTYPEREPR_FLOAT32, "TypedObjectConstants.h must be consistent with Scalar::Type"); static_assert(Scalar::Float64 == JS_SCALARTYPEREPR_FLOAT64, @@ -270,14 +274,16 @@ class ScalarTypeDescr : public SimpleTypeDescr // unique C representation. In particular, omits Uint8Clamped since it // is just a Uint8. #define JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(macro_) \ - macro_(Scalar::Int8, int8_t, int8) \ - macro_(Scalar::Uint8, uint8_t, uint8) \ - macro_(Scalar::Int16, int16_t, int16) \ - macro_(Scalar::Uint16, uint16_t, uint16) \ - macro_(Scalar::Int32, int32_t, int32) \ - macro_(Scalar::Uint32, uint32_t, uint32) \ - macro_(Scalar::Float32, float, float32) \ - macro_(Scalar::Float64, double, float64) + macro_(Scalar::Int8, int8_t, int8) \ + macro_(Scalar::Uint8, uint8_t, uint8) \ + macro_(Scalar::Int16, int16_t, int16) \ + macro_(Scalar::Uint16, uint16_t, uint16) \ + macro_(Scalar::Int32, int32_t, int32) \ + macro_(Scalar::Uint32, uint32_t, uint32) \ + macro_(Scalar::Float32, float, float32) \ + macro_(Scalar::Float64, double, float64) \ + macro_(Scalar::BigInt64, int64_t, bigint64) \ + macro_(Scalar::BigUint64, uint64_t, biguint64) // Must be in same order as the enum ScalarTypeDescr::Type: #define JS_FOR_EACH_SCALAR_TYPE_REPR(macro_) \ diff --git a/js/src/builtin/TypedObjectConstants.h b/js/src/builtin/TypedObjectConstants.h index a28c9159a5..aa930d29bb 100644 --- a/js/src/builtin/TypedObjectConstants.h +++ b/js/src/builtin/TypedObjectConstants.h @@ -88,10 +88,12 @@ #define JS_SCALARTYPEREPR_FLOAT32 6 #define JS_SCALARTYPEREPR_FLOAT64 7 #define JS_SCALARTYPEREPR_UINT8_CLAMPED 8 -#define JS_SCALARTYPEREPR_FLOAT32X4 11 -#define JS_SCALARTYPEREPR_INT8X16 12 -#define JS_SCALARTYPEREPR_INT16X8 13 -#define JS_SCALARTYPEREPR_INT32X4 14 +#define JS_SCALARTYPEREPR_BIGINT64 9 +#define JS_SCALARTYPEREPR_BIGUINT64 10 +#define JS_SCALARTYPEREPR_FLOAT32X4 13 +#define JS_SCALARTYPEREPR_INT8X16 14 +#define JS_SCALARTYPEREPR_INT16X8 15 +#define JS_SCALARTYPEREPR_INT32X4 16 // These constants are for use exclusively in JS code. In C++ code, // prefer ReferenceTypeRepresentation::TYPE_ANY etc, which allows diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 2827f4b1d5..600b56d096 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -3204,6 +3204,9 @@ StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type, Addres } else { masm.jump(failure); } + } else if (type == Scalar::BigInt64 || type == Scalar::BigUint64) { + // FIXME: https://bugzil.la/1536703 + masm.jump(failure); } else { Label notInt32; masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index c9714343dc..50b09cc30e 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -724,34 +724,9 @@ ScalarTypeToMIRType(Scalar::Type type) return MIRType::Int16x8; case Scalar::Int32x4: return MIRType::Int32x4; - case Scalar::MaxTypedArrayViewType: - break; - } - MOZ_CRASH("unexpected SIMD kind"); -} - -static inline unsigned -ScalarTypeToLength(Scalar::Type type) -{ - switch (type) { - case Scalar::Int8: - case Scalar::Uint8: - case Scalar::Int16: - case Scalar::Uint16: - case Scalar::Int32: - case Scalar::Uint32: - case Scalar::Int64: - case Scalar::Float32: - case Scalar::Float64: - case Scalar::Uint8Clamped: - return 1; - case Scalar::Float32x4: - case Scalar::Int32x4: - return 4; - case Scalar::Int16x8: - return 8; - case Scalar::Int8x16: - return 16; + case Scalar::BigInt64: + case Scalar::BigUint64: + MOZ_CRASH("NYI"); case Scalar::MaxTypedArrayViewType: break; } diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 63ff8f7201..2264bed4f2 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -6005,7 +6005,14 @@ jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints, return false; *arrayType = types->getTypedArrayType(constraints); - return *arrayType != Scalar::MaxTypedArrayViewType; + + // FIXME: https://bugzil.la/1536699 + if (*arrayType == Scalar::MaxTypedArrayViewType || + Scalar::isBigIntType(*arrayType)) { + return false; + } + + return true; } bool diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 6a2adc9622..72c5214845 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -14443,6 +14443,9 @@ MIRTypeForTypedArrayRead(Scalar::Type arrayType, bool observedDouble) return MIRType::Float32; case Scalar::Float64: return MIRType::Double; + case Scalar::BigInt64: + case Scalar::BigUint64: + return MIRType::BigInt; default: break; } diff --git a/js/src/jit/MacroAssembler-inl.h b/js/src/jit/MacroAssembler-inl.h index 336b9e2df8..98316eb643 100644 --- a/js/src/jit/MacroAssembler-inl.h +++ b/js/src/jit/MacroAssembler-inl.h @@ -644,7 +644,7 @@ void MacroAssembler::canonicalizeFloatIfDeterministic(FloatRegister reg) { #ifdef JS_MORE_DETERMINISTIC - // See the comment in TypedArrayObjectTemplate::getIndexValue. + // See the comment in TypedArrayObjectTemplate::getElement. canonicalizeFloat(reg); #endif // JS_MORE_DETERMINISTIC } @@ -662,7 +662,7 @@ void MacroAssembler::canonicalizeDoubleIfDeterministic(FloatRegister reg) { #ifdef JS_MORE_DETERMINISTIC - // See the comment in TypedArrayObjectTemplate::getIndexValue. + // See the comment in TypedArrayObjectTemplate::getElement. canonicalizeDouble(reg); #endif // JS_MORE_DETERMINISTIC } diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index fdbcc9f23c..07f7b6fdcd 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -345,6 +345,11 @@ MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T& src, AnyRegi branchTest32(Assembler::Signed, dest.gpr(), dest.gpr(), fail); } break; + case Scalar::BigInt64: + case Scalar::BigUint64: + // FIXME: https://bugzil.la/1536702 + jump(fail); + break; case Scalar::Float32: loadFloat32(src, dest.fpu()); canonicalizeFloat(dest.fpu()); @@ -447,17 +452,25 @@ MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T& src, const V tagValue(JSVAL_TYPE_INT32, temp, dest); } break; - case Scalar::Float32: + case Scalar::Float32: { loadFromTypedArray(arrayType, src, AnyRegister(ScratchFloat32Reg), dest.scratchReg(), nullptr); convertFloat32ToDouble(ScratchFloat32Reg, ScratchDoubleReg); boxDouble(ScratchDoubleReg, dest); break; - case Scalar::Float64: + } + case Scalar::Float64: { loadFromTypedArray(arrayType, src, AnyRegister(ScratchDoubleReg), dest.scratchReg(), nullptr); boxDouble(ScratchDoubleReg, dest); break; + } + // FIXME: https://bugzil.la/1536702 + case Scalar::BigInt64: + case Scalar::BigUint64: { + jump(fail); + break; + } default: MOZ_CRASH("Invalid typed array type"); } diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index b5f617ce32..52c737e677 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1780,6 +1780,8 @@ GetTypedArrayRange(TempAllocator& alloc, Scalar::Type type) case Scalar::Int32: return Range::NewInt32Range(alloc, INT32_MIN, INT32_MAX); + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Int64: case Scalar::Float32: case Scalar::Float64: diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp index 9dea005abb..3f4052f51d 100644 --- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -445,6 +445,8 @@ CodeGeneratorX64::wasmStore(const wasm::MemoryAccessDesc& access, const LAllocat case Scalar::Int16x8: case Scalar::Int32x4: case Scalar::Uint8Clamped: + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); } diff --git a/js/src/jit/x64/Lowering-x64.cpp b/js/src/jit/x64/Lowering-x64.cpp index db92d8dac5..4aebe05af2 100644 --- a/js/src/jit/x64/Lowering-x64.cpp +++ b/js/src/jit/x64/Lowering-x64.cpp @@ -239,6 +239,8 @@ LIRGeneratorX64::visitWasmStore(MWasmStore* ins) case Scalar::Int32x4: valueAlloc = useRegisterAtStart(value); break; + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); diff --git a/js/src/jit/x64/MacroAssembler-x64.cpp b/js/src/jit/x64/MacroAssembler-x64.cpp index 759977bac1..1fab669d2b 100644 --- a/js/src/jit/x64/MacroAssembler-x64.cpp +++ b/js/src/jit/x64/MacroAssembler-x64.cpp @@ -713,6 +713,8 @@ MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, Operand srcAddr, break; case Scalar::Int64: MOZ_CRASH("int64 loads must use load64"); + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); @@ -759,6 +761,8 @@ MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAdd case Scalar::Int16x8: case Scalar::Int32x4: MOZ_CRASH("non-int64 loads should use load()"); + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); @@ -822,6 +826,8 @@ MacroAssembler::wasmStore(const wasm::MemoryAccessDesc& access, AnyRegister valu MOZ_ASSERT(access.numSimdElems() == 8, "unexpected partial store"); storeUnalignedSimd128Int(value.fpu(), dstAddr); break; + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp index 3111ae4c92..5ec00da849 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -402,6 +402,8 @@ CodeGeneratorX86Shared::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTyp case Scalar::Int8x16: case Scalar::Int16x8: case Scalar::Int32x4: + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); case Scalar::Float32: diff --git a/js/src/jit/x86/Lowering-x86.cpp b/js/src/jit/x86/Lowering-x86.cpp index ad3dd8afcb..27859c0772 100644 --- a/js/src/jit/x86/Lowering-x86.cpp +++ b/js/src/jit/x86/Lowering-x86.cpp @@ -326,6 +326,8 @@ LIRGeneratorX86::visitWasmStore(MWasmStore* ins) add(lir, ins); return; } + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); diff --git a/js/src/jit/x86/MacroAssembler-x86.cpp b/js/src/jit/x86/MacroAssembler-x86.cpp index aef460e2f4..9962b9c594 100644 --- a/js/src/jit/x86/MacroAssembler-x86.cpp +++ b/js/src/jit/x86/MacroAssembler-x86.cpp @@ -616,6 +616,8 @@ MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, Operand srcAddr, vmovdquWithPatch(srcAddr, out.fpu()); break; case Scalar::Int64: + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected type"); @@ -727,6 +729,8 @@ MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAdd case Scalar::Int16x8: case Scalar::Int32x4: MOZ_CRASH("non-int64 loads should use load()"); + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); @@ -790,6 +794,8 @@ MacroAssembler::wasmStore(const wasm::MemoryAccessDesc& access, AnyRegister valu case Scalar::Int64: MOZ_CRASH("Should be handled in storeI64."); case Scalar::MaxTypedArrayViewType: + case Scalar::BigInt64: + case Scalar::BigUint64: MOZ_CRASH("unexpected type"); } append(wasm::MemoryPatch(size())); diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index cc0fcbebd9..4f5c717be5 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -376,6 +376,13 @@ js::GetElements(JSContext* cx, HandleObject aobj, uint32_t length, Value* vp) } } + if (aobj->is()) { + Handle typedArray = aobj.as(); + if (typedArray->length() == length) { + return TypedArrayObject::getElements(cx, typedArray, vp); + } + } + if (js::GetElementsOp op = aobj->getOpsGetElements()) { ElementAdder adder(cx, vp, length, ElementAdder::GetElement); return op(cx, aobj, 0, length, &adder); diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 6a13d4343c..6a873e15da 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1467,12 +1467,15 @@ GetSCOffset(JSStructuredCloneWriter* writer); namespace Scalar { -/** - * Scalar types that can appear in typed arrays and typed objects. The enum - * values must to be kept in sync with the JS_SCALARTYPEREPR_ constants, as - * well as the TypedArrayObject::classes and TypedArrayObject::protoClasses - * definitions. - */ +// Scalar types that can appear in typed arrays and typed objects. +// The enum values must be kept in sync with: +// * the JS_SCALARTYPEREPR constants +// * the TYPEDARRAY_KIND constants +// * the SCTAG_TYPED_ARRAY constants +// * JS_FOR_EACH_TYPEDARRAY +// * JS_FOR_PROTOTYPES_ +// * JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE +// * JIT compilation enum Type { Int8 = 0, Uint8, @@ -1489,6 +1492,9 @@ enum Type { */ Uint8Clamped, + BigInt64, + BigUint64, + /** * Types that don't have their own TypedArray equivalent, for now. */ @@ -1518,6 +1524,8 @@ byteSize(Type atype) return 4; case Int64: case Float64: + case BigInt64: + case BigUint64: return 8; case Int8x16: case Int16x8: @@ -1539,6 +1547,7 @@ isSignedIntType(Type atype) { case Int8x16: case Int16x8: case Int32x4: + case BigInt64: return true; case Uint8: case Uint8Clamped: @@ -1547,12 +1556,39 @@ isSignedIntType(Type atype) { case Float32: case Float64: case Float32x4: + case BigUint64: return false; default: MOZ_CRASH("invalid scalar type"); } } +static inline bool isBigIntType(Type atype) { + switch (atype) { + case BigInt64: + case BigUint64: + return true; + case Int8: + case Int16: + case Int32: + case Int64: + case Uint8: + case Uint8Clamped: + case Uint16: + case Uint32: + case Float32: + case Float64: + case Int8x16: + case Int16x8: + case Int32x4: + case Float32x4: + return false; + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid scalar type"); +} + static inline bool isSimdType(Type atype) { switch (atype) { @@ -1566,6 +1602,8 @@ isSimdType(Type atype) { case Int64: case Float32: case Float64: + case BigInt64: + case BigUint64: return false; case Int8x16: case Int16x8: @@ -1598,6 +1636,8 @@ scalarByteSize(Type atype) { case Int64: case Float32: case Float64: + case BigInt64: + case BigUint64: case MaxTypedArrayViewType: break; } @@ -1628,6 +1668,10 @@ JS_NewInt32Array(JSContext* cx, uint32_t nelements); extern JS_FRIEND_API(JSObject*) JS_NewUint32Array(JSContext* cx, uint32_t nelements); extern JS_FRIEND_API(JSObject*) +JS_NewBigInt64Array(JSContext* cx, int32_t nelements); +extern JS_FRIEND_API(JSObject*) +JS_NewBigUint64Array(JSContext* cx, int32_t nelements); +extern JS_FRIEND_API(JSObject*) JS_NewFloat32Array(JSContext* cx, uint32_t nelements); extern JS_FRIEND_API(JSObject*) JS_NewFloat64Array(JSContext* cx, uint32_t nelements); @@ -1655,6 +1699,10 @@ JS_NewInt32ArrayFromArray(JSContext* cx, JS::HandleObject array); extern JS_FRIEND_API(JSObject*) JS_NewUint32ArrayFromArray(JSContext* cx, JS::HandleObject array); extern JS_FRIEND_API(JSObject*) +JS_NewBigInt64ArrayWithBuffer(JSContext* cx, JS::HandleObject array); +extern JS_FRIEND_API(JSObject*) +JS_NewBigUint64ArrayWithBuffer(JSContext* cx, JS::HandleObject array); +extern JS_FRIEND_API(JSObject*) JS_NewFloat32ArrayFromArray(JSContext* cx, JS::HandleObject array); extern JS_FRIEND_API(JSObject*) JS_NewFloat64ArrayFromArray(JSContext* cx, JS::HandleObject array); @@ -1688,6 +1736,12 @@ extern JS_FRIEND_API(JSObject*) JS_NewUint32ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, uint32_t byteOffset, int32_t length); extern JS_FRIEND_API(JSObject*) +JS_NewBigInt64ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, + uint32_t byteOffset, int32_t length); +extern JS_FRIEND_API(JSObject*) +JS_NewBigUint64ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, + uint32_t byteOffset, int32_t length); +extern JS_FRIEND_API(JSObject*) JS_NewFloat32ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, uint32_t byteOffset, int32_t length); extern JS_FRIEND_API(JSObject*) @@ -1747,6 +1801,10 @@ JS_IsInt32Array(JSObject* obj); extern JS_FRIEND_API(bool) JS_IsUint32Array(JSObject* obj); extern JS_FRIEND_API(bool) +JS_IsBigInt64Array(JSObject* obj); +extern JS_FRIEND_API(bool) +JS_IsBigUint64Array(JSObject* obj); +extern JS_FRIEND_API(bool) JS_IsFloat32Array(JSObject* obj); extern JS_FRIEND_API(bool) JS_IsFloat64Array(JSObject* obj); @@ -1784,6 +1842,10 @@ UnwrapInt32Array(JSObject* obj); extern JS_FRIEND_API(JSObject*) UnwrapUint32Array(JSObject* obj); extern JS_FRIEND_API(JSObject*) +UnwrapBigInt64Array(JSObject* obj); +extern JS_FRIEND_API(JSObject*) +UnwrapBigUint64Array(JSObject* obj); +extern JS_FRIEND_API(JSObject*) UnwrapFloat32Array(JSObject* obj); extern JS_FRIEND_API(JSObject*) UnwrapFloat64Array(JSObject* obj); @@ -1807,6 +1869,8 @@ extern JS_FRIEND_DATA(const Class* const) Int16ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Uint16ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Int32ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Uint32ArrayClassPtr; +extern JS_FRIEND_DATA(const Class* const) BigInt64ArrayClassPtr; +extern JS_FRIEND_DATA(const Class* const) BigUint64ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Float32ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Float64ArrayClassPtr; diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index d0f9430bc8..730805e038 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2356,15 +2356,15 @@ js::LookupOwnPropertyPure(ExclusiveContext* cx, JSObject* obj, jsid id, Property } static inline bool -NativeGetPureInline(NativeObject* pobj, jsid id, PropertyResult prop, Value* vp) +NativeGetPureInline(NativeObject* pobj, jsid id, PropertyResult prop, Value* vp, + ExclusiveContext* cx) { if (prop.isDenseOrTypedArrayElement()) { // For simplicity we ignore the TypedArray with string index case. if (!JSID_IS_INT(id)) return false; - *vp = pobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)); - return true; + return pobj->getDenseOrTypedArrayElement(cx, JSID_TO_INT(id), vp); } // Fail if we have a custom getter. @@ -2395,7 +2395,7 @@ js::GetPropertyPure(ExclusiveContext* cx, JSObject* obj, jsid id, Value* vp) return true; } - return pobj->isNative() && NativeGetPureInline(&pobj->as(), id, prop, vp); + return pobj->isNative() && NativeGetPureInline(&pobj->as(), id, prop, vp, cx); } static inline bool diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 75168d37ef..34f835bc1d 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -91,6 +91,8 @@ real(Float32Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Float32)) \ real(Float64Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Float64)) \ real(Uint8ClampedArray, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint8Clamped)) \ + real(BigInt64Array, InitViaClassSpec, TYPED_ARRAY_CLASP(BigInt64)) \ + real(BigUint64Array, InitViaClassSpec, TYPED_ARRAY_CLASP(BigUint64)) \ real(BigInt, InitViaClassSpec, OCLASP(BigInt)) \ real(Proxy, InitProxyClass, js::ProxyClassPtr) \ real(WeakMap, InitWeakMapClass, OCLASP(WeakMap)) \ diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index 87dce34ba1..4ff7962cfb 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -463,9 +463,11 @@ struct uint8_clamped { explicit uint8_clamped(uint8_t x) { *this = x; } explicit uint8_clamped(uint16_t x) { *this = x; } explicit uint8_clamped(uint32_t x) { *this = x; } + explicit uint8_clamped(uint64_t x) { *this = x; } explicit uint8_clamped(int8_t x) { *this = x; } explicit uint8_clamped(int16_t x) { *this = x; } explicit uint8_clamped(int32_t x) { *this = x; } + explicit uint8_clamped(int64_t x) { *this = x; } explicit uint8_clamped(double x) { *this = x; } uint8_clamped& operator=(const uint8_clamped& x) = default; @@ -485,6 +487,11 @@ struct uint8_clamped { return *this; } + uint8_clamped& operator=(uint64_t x) { + val = (x > 255) ? 255 : uint8_t(x); + return *this; + } + uint8_clamped& operator=(int8_t x) { val = (x >= 0) ? uint8_t(x) : 0; return *this; @@ -508,6 +515,15 @@ struct uint8_clamped { return *this; } + uint8_clamped& operator=(int64_t x) { + val = (x >= 0) + ? ((x < 255) + ? uint8_t(x) + : 255) + : 0; + return *this; + } + uint8_clamped& operator=(const double x) { val = uint8_t(ClampDoubleToUint8(x)); return *this; diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index 7b8375526f..44bb1923f7 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -2626,6 +2626,22 @@ BigInt* js::ToBigInt(ExclusiveContext* cx, HandleValue val) { return nullptr; } +JS::Result js::ToBigInt64(JSContext* cx, HandleValue v) { + BigInt* bi = ToBigInt(cx, v); + if (!bi) { + return cx->alreadyReportedError(); + } + return BigInt::toInt64(bi); +} + +JS::Result js::ToBigUint64(JSContext* cx, HandleValue v) { + BigInt* bi = ToBigInt(cx, v); + if (!bi) { + return cx->alreadyReportedError(); + } + return BigInt::toUint64(bi); +} + double BigInt::numberValue(BigInt* x) { if (x->isZero()) { return 0.0; diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h index 0ccba99634..7cdd757994 100644 --- a/js/src/vm/BigIntType.h +++ b/js/src/vm/BigIntType.h @@ -358,6 +358,8 @@ namespace js { extern JSAtom* BigIntToAtom(js::ExclusiveContext* cx, JS::HandleBigInt bi); extern JS::BigInt* NumberToBigInt(js::ExclusiveContext* cx, double d); +extern JS::Result ToBigInt64(JSContext* cx, JS::Handle v); +extern JS::Result ToBigUint64(JSContext* cx, JS::Handle v); // Parse a BigInt from a string, using the method specified for StringToBigInt. // Used by the BigInt constructor among other places. diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index efbd8a5323..f05a4db9be 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -36,6 +36,8 @@ macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \ macro(async, async, "async") \ macro(await, await, "await") \ + macro(bigint64, bigint64, "bigint64") \ + macro(biguint64, biguint64, "biguint64") \ macro(Bool8x16, Bool8x16, "Bool8x16") \ macro(Bool16x8, Bool16x8, "Bool16x8") \ macro(Bool32x4, Bool32x4, "Bool32x4") \ diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 1e10fe5da3..c4d9cf7287 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -82,6 +82,8 @@ class GlobalObject : public NativeObject FROM_BUFFER_INT16, FROM_BUFFER_UINT32, FROM_BUFFER_INT32, + FROM_BUFFER_UINT64, + FROM_BUFFER_INT64, FROM_BUFFER_FLOAT32, FROM_BUFFER_FLOAT64, FROM_BUFFER_UINT8CLAMPED, @@ -957,6 +959,20 @@ GlobalObject::setCreateArrayFromBuffer(Handle fun) setCreateArrayFromBufferHelper(FROM_BUFFER_INT32, fun); } +template<> +inline void +GlobalObject::setCreateArrayFromBuffer(Handle fun) +{ + setCreateArrayFromBufferHelper(FROM_BUFFER_UINT64, fun); +} + +template<> +inline void +GlobalObject::setCreateArrayFromBuffer(Handle fun) +{ + setCreateArrayFromBufferHelper(FROM_BUFFER_INT64, fun); +} + template<> inline void GlobalObject::setCreateArrayFromBuffer(Handle fun) @@ -1020,6 +1036,20 @@ GlobalObject::createArrayFromBuffer() const return createArrayFromBufferHelper(FROM_BUFFER_INT32); } +template<> +inline Value +GlobalObject::createArrayFromBuffer() const +{ + return createArrayFromBufferHelper(FROM_BUFFER_UINT64); +} + +template<> +inline Value +GlobalObject::createArrayFromBuffer() const +{ + return createArrayFromBufferHelper(FROM_BUFFER_INT64); +} + template<> inline Value GlobalObject::createArrayFromBuffer() const diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h index 69976bc462..1fa5cbd10e 100644 --- a/js/src/vm/NativeObject-inl.h +++ b/js/src/vm/NativeObject-inl.h @@ -239,12 +239,15 @@ NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t return DenseElementResult::Success; } -inline Value -NativeObject::getDenseOrTypedArrayElement(uint32_t idx) +template +inline bool +NativeObject::getDenseOrTypedArrayElement(ExclusiveContext* cx, uint32_t idx, + typename MaybeRooted::MutableHandleType val) { if (is()) - return as().getElement(idx); - return getDenseElement(idx); + return as().getElement(cx, idx, val); + val.set(getDenseElement(idx)); + return true; } /* static */ inline NativeObject* diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index a6bb9826ee..d21f88b15e 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -20,6 +20,7 @@ #include "vm/ArrayObject-inl.h" #include "vm/EnvironmentObject-inl.h" #include "vm/Shape-inl.h" +#include "vm/TypedArrayObject.h" using namespace js; @@ -1283,8 +1284,7 @@ GetExistingPropertyValue(ExclusiveContext* cx, HandleNativeObject obj, HandleId Handle prop, MutableHandleValue vp) { if (prop.isDenseOrTypedArrayElement()) { - vp.set(obj->getDenseOrTypedArrayElement(JSID_TO_INT(id))); - return true; + return obj->getDenseOrTypedArrayElement(cx, JSID_TO_INT(id), vp); } if (!cx->shouldBeJSContext()) return false; @@ -1814,7 +1814,9 @@ js::NativeGetOwnPropertyDescriptor(JSContext* cx, HandleNativeObject obj, Handle desc.attributesRef() &= ~JSPROP_SHARED; if (prop.isDenseOrTypedArrayElement()) { - desc.value().set(obj->getDenseOrTypedArrayElement(JSID_TO_INT(id))); + if (!obj->getDenseOrTypedArrayElement(cx, JSID_TO_INT(id), desc.value())) { + return false; + } } else { RootedShape shape(cx, prop.shape()); if (!NativeGetExistingProperty(cx, obj, obj, shape, desc.value())) @@ -2110,8 +2112,7 @@ NativeGetPropertyInline(JSContext* cx, // Steps 5-8. Special case for dense elements because // GetExistingProperty doesn't support those. if (prop.isDenseOrTypedArrayElement()) { - vp.set(pobj->getDenseOrTypedArrayElement(JSID_TO_INT(id))); - return true; + return pobj->template getDenseOrTypedArrayElement(cx, JSID_TO_INT(id), vp); } typename MaybeRooted::RootType shape(cx, prop.shape()); @@ -2364,38 +2365,6 @@ SetNonexistentProperty(JSContext* cx, HandleId id, HandleValue v, HandleValue re return SetPropertyByDefining(cx, id, v, receiver, result); } -/* - * Set an existing own property obj[index] that's a dense element or typed - * array element. - */ -static bool -SetDenseOrTypedArrayElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v, - ObjectOpResult& result) -{ - if (obj->is()) { - double d; - if (!ToNumber(cx, v, &d)) - return false; - - // Silently do nothing for out-of-bounds sets, for consistency with - // current behavior. (ES6 currently says to throw for this in - // strict mode code, so we may eventually need to change.) - uint32_t len = obj->as().length(); - if (index < len) - TypedArrayObject::setElement(obj->as(), index, d); - return result.succeed(); - } - - if (WouldDefinePastNonwritableLength(obj, index)) - return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH); - - if (!obj->maybeCopyElementsForWrite(cx)) - return false; - - obj->setDenseElementWithType(cx, index, v); - return result.succeed(); -} - /* * Finish the assignment `receiver[id] = v` when an existing property (shape) * has been found on a native object (pobj). This implements ES6 draft rev 32 @@ -2416,8 +2385,14 @@ SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleVa return result.fail(JSMSG_READ_ONLY); // Pure optimization for the common case: - if (receiver.isObject() && pobj == &receiver.toObject()) - return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), v, result); + if (receiver.isObject() && pobj == &receiver.toObject()) { + uint32_t index = JSID_TO_INT(id); + + if (pobj->is()) { + Rooted tobj(cx, &pobj->as()); + return SetTypedArrayElement(cx, tobj, index, v, result); + } + } // Steps 5.b-f. return SetPropertyByDefining(cx, id, v, receiver, result); diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index c5865caa03..86977d109f 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -18,6 +18,7 @@ #include "gc/Barrier.h" #include "gc/Heap.h" #include "gc/Marking.h" +#include "js/RootingAPI.h" #include "js/Value.h" #include "vm/Shape.h" #include "vm/ShapedObject.h" @@ -1088,7 +1089,9 @@ class NativeObject : public ShapedObject static inline void removeDenseElementForSparseIndex(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index); - inline Value getDenseOrTypedArrayElement(uint32_t idx); + template inline bool + getDenseOrTypedArrayElement(ExclusiveContext* cx, uint32_t idx, + typename MaybeRooted::MutableHandleType val); void copyDenseElements(uint32_t dstStart, const Value* src, uint32_t count) { MOZ_ASSERT(dstStart + count <= getDenseCapacity()); diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index 741531f015..408e346608 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -686,6 +686,8 @@ GetClassForProtoKey(JSProtoKey key) case JSProto_Float32Array: case JSProto_Float64Array: case JSProto_Uint8ClampedArray: + case JSProto_BigInt64Array: + case JSProto_BigUint64Array: return &TypedArrayObject::classes[key - JSProto_Int8Array]; case JSProto_ArrayBuffer: diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index de497d02e1..e73e55d9a0 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -44,6 +44,7 @@ #include "jit/InlinableNatives.h" #include "js/CharacterEncoding.h" #include "js/Date.h" +#include "vm/BigIntType.h" #include "vm/Compression.h" #include "vm/GeneratorObject.h" #include "vm/Interpreter.h" @@ -1181,6 +1182,18 @@ intrinsic_IsFloat32TypedArray(JSContext* cx, unsigned argc, Value* vp) return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Float32); } +static bool +intrinsic_IsBigInt64TypedArray(JSContext* cx, unsigned argc, Value* vp) +{ + return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::BigInt64); +} + +static bool +intrinsic_IsBigUint64TypedArray(JSContext* cx, unsigned argc, Value* vp) +{ + return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::BigUint64); +} + static bool intrinsic_TypedArrayBuffer(JSContext* cx, unsigned argc, Value* vp) { @@ -1521,6 +1534,14 @@ struct DisjointElements CopyValues(dest, src.cast(), count); return; + case Scalar::BigInt64: + CopyValues(dest, src.cast(), count); + return; + + case Scalar::BigUint64: + CopyValues(dest, src.cast(), count); + return; + case Scalar::Uint8Clamped: CopyValues(dest, src.cast(), count); return; @@ -1579,6 +1600,16 @@ CopyToDisjointArray(TypedArrayObject* target, uint32_t targetOffset, SharedMem(), src, srcType, count); + break; + } + + case Scalar::BigUint64: { + DisjointElements::copy(dest.cast(), src, srcType, count); + break; + } + case Scalar::Uint8Clamped: { DisjointElements::copy(dest.cast(), src, srcType, count); break; @@ -2164,6 +2195,18 @@ intrinsic_PromiseResolve(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool intrinsic_ToBigInt(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + BigInt* res = ToBigInt(cx, args[0]); + if (!res) { + return false; + } + args.rval().setBigInt(res); + return true; +} + // The self-hosting global isn't initialized with the normal set of builtins. // Instead, individual C++-implemented functions that're required by // self-hosted code are defined as global functions. Accessing these @@ -2399,6 +2442,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("IsUint32TypedArray", intrinsic_IsUint32TypedArray, 1,0), JS_FN("IsInt32TypedArray", intrinsic_IsInt32TypedArray, 1,0), JS_FN("IsFloat32TypedArray", intrinsic_IsFloat32TypedArray, 1,0), + JS_FN("IsBigInt64TypedArray", intrinsic_IsBigInt64TypedArray, 1,0), + JS_FN("IsBigUint64TypedArray", intrinsic_IsBigUint64TypedArray, 1,0), JS_INLINABLE_FN("IsTypedArray", intrinsic_IsInstanceOfBuiltin, 1,0, IntrinsicIsTypedArray), @@ -2591,6 +2636,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("CallPromiseMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0), + JS_FN("ToBigInt", intrinsic_ToBigInt, 1, 0), + JS_FS_END }; diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index ac73df30e6..e99cfe8f71 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -121,7 +121,8 @@ enum StructuredDataType : uint32_t { SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32, SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64, SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped, - SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::MaxTypedArrayViewType - 1, + // BigInt64 and BigUint64 are not supported in the v1 format. + SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED, /* * Define a separate range of numbers for Transferable-only tags, since @@ -1808,7 +1809,7 @@ bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp, bool v1Read) { - if (arrayType > Scalar::Uint8Clamped) { + if (arrayType > (v1Read ? Scalar::Uint8Clamped : Scalar::BigUint64)) { JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, "unhandled typed array element type"); return false; @@ -1872,6 +1873,12 @@ JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, Mut case Scalar::Uint8Clamped: obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems); break; + case Scalar::BigInt64: + obj = JS_NewBigInt64ArrayWithBuffer(context(), buffer, byteOffset, nelems); + break; + case Scalar::BigUint64: + obj = JS_NewBigUint64ArrayWithBuffer(context(), buffer, byteOffset, nelems); + break; default: MOZ_CRASH("Can't happen: arrayType range checked above"); } @@ -2007,6 +2014,8 @@ JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, case Scalar::Float32: return in.readArray((uint32_t*) buffer.dataPointer(), nelems); case Scalar::Float64: + case Scalar::BigInt64: + case Scalar::BigUint64: return in.readArray((uint64_t*) buffer.dataPointer(), nelems); default: MOZ_CRASH("Can't happen: arrayType range checked by caller"); diff --git a/js/src/vm/TypedArrayCommon.h b/js/src/vm/TypedArrayCommon.h index eb7b94f109..8e66587a1e 100644 --- a/js/src/vm/TypedArrayCommon.h +++ b/js/src/vm/TypedArrayCommon.h @@ -112,6 +112,20 @@ ConvertNumber(float src) return JS::ToUint32(src); } +template <> +inline int64_t +ConvertNumber(float src) +{ + return JS::ToInt64(src); +} + +template <> +inline uint64_t +ConvertNumber(float src) +{ + return JS::ToUint64(src); +} + template<> inline int8_t ConvertNumber(double src) { @@ -160,6 +174,20 @@ ConvertNumber(double src) return JS::ToUint32(src); } +template <> +inline int64_t +ConvertNumber(double src) +{ + return JS::ToInt64(src); +} + +template <> +inline uint64_t +ConvertNumber(double src) +{ + return JS::ToUint64(src); +} + template inline To ConvertNumber(From src) @@ -178,6 +206,8 @@ template<> struct TypeIDOfType { static const Scalar::Type id = Scalar: template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Uint16; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Int32; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Uint32; }; +template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::BigInt64; }; +template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::BigUint64; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Float32; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Float64; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Uint8Clamped; }; @@ -355,6 +385,18 @@ class ElementSpecific Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } + case Scalar::BigInt64: { + SharedMem src = data.cast(); + for (uint32_t i = 0; i < count; ++i) + Ops::store(dest++, ConvertNumber(Ops::load(src++))); + break; + } + case Scalar::BigUint64: { + SharedMem src = data.cast(); + for (uint32_t i = 0; i < count; ++i) + Ops::store(dest++, ConvertNumber(Ops::load(src++))); + break; + } case Scalar::Float32: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) @@ -399,12 +441,12 @@ class ElementSpecific SharedMem dest = target->template as().viewDataEither().template cast() + offset; - MOZ_ASSERT(!canConvertInfallibly(MagicValue(JS_ELEMENTS_HOLE)), + MOZ_ASSERT(!canConvertInfallibly(MagicValue(JS_ELEMENTS_HOLE), target->type()), "the following loop must abort on holes"); const Value* srcValues = source->as().getDenseElements(); for (; i < bound; i++) { - if (!canConvertInfallibly(srcValues[i])) + if (!canConvertInfallibly(srcValues[i], target->type())) break; Ops::store(dest + i, infallibleValueToNative(srcValues[i])); } @@ -459,7 +501,7 @@ class ElementSpecific const Value* srcValues = source->getDenseElements(); for (; i < len; i++) { - if (!canConvertInfallibly(srcValues[i])) + if (!canConvertInfallibly(srcValues[i], target->type())) break; Ops::store(dest + i, infallibleValueToNative(srcValues[i])); } @@ -568,6 +610,18 @@ class ElementSpecific Ops::store(dest++, ConvertNumber(*src++)); break; } + case Scalar::BigInt64: { + int64_t* src = static_cast(data); + for (uint32_t i = 0; i < len; ++i) + Ops::store(dest++, ConvertNumber(*src++)); + break; + } + case Scalar::BigUint64: { + uint64_t* src = static_cast(data); + for (uint32_t i = 0; i < len; ++i) + Ops::store(dest++, ConvertNumber(*src++)); + break; + } case Scalar::Float32: { float* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) @@ -589,8 +643,11 @@ class ElementSpecific } static bool - canConvertInfallibly(const Value& v) + canConvertInfallibly(const Value& v, Scalar::Type type) { + if (type == Scalar::BigInt64 || type == Scalar::BigUint64) { + return false; + } return v.isNumber() || v.isBoolean() || v.isNull() || v.isUndefined(); } @@ -615,11 +672,21 @@ class ElementSpecific { MOZ_ASSERT(!v.isMagic()); - if (MOZ_LIKELY(canConvertInfallibly(v))) { + if (MOZ_LIKELY(canConvertInfallibly(v, TypeIDOfType::id))) { *result = infallibleValueToNative(v); return true; } + if (std::is_same::value) { + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v)); + return true; + } + + if (std::is_same::value) { + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v)); + return true; + } + double d; MOZ_ASSERT(v.isString() || v.isObject() || v.isSymbol()); if (!(v.isString() ? StringToNumber(cx, v.toString(), &d) : ToNumber(cx, v, &d))) @@ -668,6 +735,8 @@ class TypedArrayMethods typedef typename SomeTypedArray::template OfType::Type Uint16ArrayType; typedef typename SomeTypedArray::template OfType::Type Int32ArrayType; typedef typename SomeTypedArray::template OfType::Type Uint32ArrayType; + typedef typename SomeTypedArray::template OfType::Type BigInt64ArrayType; + typedef typename SomeTypedArray::template OfType::Type BigUint64ArrayType; typedef typename SomeTypedArray::template OfType::Type Float32ArrayType; typedef typename SomeTypedArray::template OfType::Type Float64ArrayType; typedef typename SomeTypedArray::template OfType::Type Uint8ClampedArrayType; @@ -759,6 +828,14 @@ class TypedArrayMethods if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); + case Scalar::BigInt64: + if (isShared) + return ElementSpecific::setFromTypedArray(cx, target, source, offset); + return ElementSpecific::setFromTypedArray(cx, target, source, offset); + case Scalar::BigUint64: + if (isShared) + return ElementSpecific::setFromTypedArray(cx, target, source, offset); + return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Float32: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); @@ -816,6 +893,14 @@ class TypedArrayMethods if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); + case Scalar::BigInt64: + if (isShared) + return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); + return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); + case Scalar::BigUint64: + if (isShared) + return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); + return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Float32: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); @@ -870,6 +955,14 @@ class TypedArrayMethods if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::BigInt64: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::BigUint64: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Float32: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index ac93ec9b14..28e4090eb8 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -65,6 +65,33 @@ using JS::ToUint32; * the subclasses. */ +bool TypedArrayObject::convertForSideEffect(JSContext* cx, HandleValue v) const +{ + switch (type()) { + case Scalar::BigInt64: + case Scalar::BigUint64: { + return ToBigInt(cx, v) != nullptr; + } + case Scalar::Int8: + case Scalar::Uint8: + case Scalar::Int16: + case Scalar::Uint16: + case Scalar::Int32: + case Scalar::Uint32: + case Scalar::Float32: + case Scalar::Float64: + case Scalar::Uint8Clamped: { + double ignore; + return ToNumber(cx, v, &ignore); + } + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + MOZ_CRASH("Unsupported TypedArray type"); + } + MOZ_ASSERT_UNREACHABLE("Invalid scalar type"); + return false; +} + /* static */ int TypedArrayObject::lengthOffset() { @@ -414,30 +441,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject return v.isObject() && v.toObject().hasClass(instanceClass()); } - static void - setIndexValue(TypedArrayObject& tarray, uint32_t index, double d) - { - // If the array is an integer array, we only handle up to - // 32-bit ints from this point on. if we want to handle - // 64-bit ints, we'll need some changes. - - // Assign based on characteristics of the destination type - if (ArrayTypeIsFloatingPoint()) { - setIndex(tarray, index, NativeType(d)); - } else if (ArrayTypeIsUnsigned()) { - MOZ_ASSERT(sizeof(NativeType) <= 4); - uint32_t n = ToUint32(d); - setIndex(tarray, index, NativeType(n)); - } else if (ArrayTypeID() == Scalar::Uint8Clamped) { - // The uint8_clamped type has a special rounding converter - // for doubles. - setIndex(tarray, index, NativeType(d)); - } else { - MOZ_ASSERT(sizeof(NativeType) <= 4); - int32_t n = ToInt32(d); - setIndex(tarray, index, NativeType(n)); - } - } + static bool convertValue(JSContext* cx, HandleValue v, NativeType* result); static TypedArrayObject* makeProtoInstance(JSContext* cx, HandleObject proto, AllocKind allocKind) @@ -1001,9 +1005,128 @@ class TypedArrayObjectTemplate : public TypedArrayObject jit::AtomicOperations::storeSafeWhenRacy(tarray.viewDataEither().cast() + index, val); } - static Value getIndexValue(JSObject* tarray, uint32_t index); + static bool getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, MutableHandleValue val); + static bool getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp); + + static bool setElement(JSContext* cx, Handle obj, uint64_t index, HandleValue v, + ObjectOpResult& result); + static bool defineElement(JSContext* cx, HandleObject obj, uint64_t index, HandleValue v, + ObjectOpResult& result); }; +template +bool TypedArrayObjectTemplate::convertValue(JSContext* cx, HandleValue v, NativeType* result) +{ + double d; + if (!ToNumber(cx, v, &d)) { + return false; + } + +#ifdef JS_MORE_DETERMINISTIC + // See the comment in ElementSpecific::doubleToNative. + d = JS::CanonicalizeNaN(d); +#endif + + // Assign based on characteristics of the destination type + if (ArrayTypeIsFloatingPoint()) { + *result = NativeType(d); + } else if (ArrayTypeIsUnsigned()) { + MOZ_ASSERT(sizeof(NativeType) <= 4); + uint32_t n = ToUint32(d); + *result = NativeType(n); + } else if (ArrayTypeID() == Scalar::Uint8Clamped) { + // The uint8_clamped type has a special rounding converter + // for doubles. + *result = NativeType(d); + } else { + MOZ_ASSERT(sizeof(NativeType) <= 4); + int32_t n = ToInt32(d); + *result = NativeType(n); + } + return true; +} + +template <> +bool TypedArrayObjectTemplate::convertValue(JSContext* cx, HandleValue v, int64_t* result) +{ + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v)); + return true; +} + +template <> +bool TypedArrayObjectTemplate::convertValue(JSContext* cx, HandleValue v, uint64_t* result) +{ + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v)); + return true; +} + +// https://tc39.github.io/proposal-bigint/#sec-integerindexedelementset +// 7.8 IntegerIndexedElementSet ( O, index, value ) +template +/* static */ bool TypedArrayObjectTemplate::setElement( + JSContext* cx, Handle obj, uint64_t index, HandleValue v, + ObjectOpResult& result) +{ + // Steps 1-2 are enforced by the caller. + + // Steps 3-6. + NativeType nativeValue; + if (!convertValue(cx, v, &nativeValue)) { + return false; + } + + // Step 8. + if (obj->hasDetachedBuffer()) { + return result.failSoft(JSMSG_TYPED_ARRAY_DETACHED); + } + + // Steps 9-10 are enforced by the caller. + + // Step 11. + uint32_t length = obj->length(); + + // Step 12. + if (index >= length) { + return result.failSoft(JSMSG_BAD_INDEX); + } + + // Steps 7, 13-16. + TypedArrayObjectTemplate::setIndex(*obj, index, nativeValue); + + // Step 17. + return result.succeed(); +} + +// Version of IntegerIndexedElementSet with no length check, used in +// [[DefineOwnProperty]] +template +/* static */ bool TypedArrayObjectTemplate::defineElement( + JSContext* cx, HandleObject obj, uint64_t index, HandleValue v, + ObjectOpResult& result) +{ + // Steps 1-2 are enforced by the caller. + + // Steps 3-6. + NativeType nativeValue; + if (!convertValue(cx, v, &nativeValue)) { + return false; + } + + // Step 8. + if (obj->as().hasDetachedBuffer()) { + return result.fail(JSMSG_TYPED_ARRAY_DETACHED); + } + + // Steps 9-12 are enforced by the caller. + + // Steps 7, 13-16. + TypedArrayObjectTemplate::setIndex(obj->as(), + index, nativeValue); + + // Step 17. + return result.succeed(); +} + #define CREATE_TYPE_FOR_TYPED_ARRAY(T, N) \ typedef TypedArrayObjectTemplate N##Array; JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPE_FOR_TYPED_ARRAY) @@ -1240,6 +1363,13 @@ TypedArrayObjectTemplate::fromTypedArray(JSContext* cx, HandleObject other, b } } + // BigInt proposal 7.24, step 19.c. + if (Scalar::isBigIntType(ArrayTypeID()) != + Scalar::isBigIntType(srcArray->type())) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); + return nullptr; + } + // Steps 3-4 (remaining part), 18-21. Rooted obj(cx, makeInstance(cx, buffer, 0, elementLength, proto)); if (!obj) @@ -1584,40 +1714,43 @@ ArrayBufferObject::createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Valu return CallNonGenericMethod >(cx, args); } -// this default implementation is only valid for integer types -// less than 32-bits in size. +// This default implementation is only valid for integer types less +// than 32-bits in size. template -Value -TypedArrayObjectTemplate::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { static_assert(sizeof(NativeType) < 4, "this method must only handle NativeType values that are " "always exact int32_t values"); - return Int32Value(getIndex(tarray, index)); + *vp = Int32Value(getIndex(tarray, index)); + return true; } namespace { -// and we need to specialize for 32-bit integers and floats +// We need to specialize for floats and other integer types. template<> -Value -TypedArrayObjectTemplate::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { - return Int32Value(getIndex(tarray, index)); + *vp = Int32Value(getIndex(tarray, index)); + return true; } template<> -Value -TypedArrayObjectTemplate::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { uint32_t val = getIndex(tarray, index); - return NumberValue(val); + *vp = NumberValue(val); + return true; } template<> -Value -TypedArrayObjectTemplate::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { float val = getIndex(tarray, index); double dval = val; @@ -1632,12 +1765,13 @@ TypedArrayObjectTemplate::getIndexValue(JSObject* tarray, uint32_t index) * This could be removed for platforms/compilers known to convert a 32-bit * non-canonical nan to a 64-bit canonical nan. */ - return DoubleValue(CanonicalizeNaN(dval)); + *vp = DoubleValue(CanonicalizeNaN(dval)); + return true; } template<> -Value -TypedArrayObjectTemplate::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { double val = getIndex(tarray, index); @@ -1648,9 +1782,60 @@ TypedArrayObjectTemplate::getIndexValue(JSObject* tarray, uint32_t index * confuse the engine into interpreting a double-typed jsval as an * object-typed jsval. */ - return DoubleValue(CanonicalizeNaN(val)); + *vp = DoubleValue(CanonicalizeNaN(val)); + return true; +} + +template <> +bool +TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) +{ + return false; } +template <> +bool +TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) +{ + return false; +} +} /* anonymous namespace */ + +namespace { + +template +bool TypedArrayObjectTemplate::getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, + MutableHandleValue val) +{ + MOZ_ALWAYS_TRUE(getElementPure(tarray, index, val.address())); + return true; +} + +template <> +bool TypedArrayObjectTemplate::getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, + MutableHandleValue val) +{ + int64_t n = getIndex(tarray, index); + BigInt* res = BigInt::createFromInt64(cx, n); + if (!res) { + return false; + } + val.setBigInt(res); + return true; +} + +template <> +bool TypedArrayObjectTemplate::getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, + MutableHandleValue val) +{ + uint64_t n = getIndex(tarray, index); + BigInt* res = BigInt::createFromUint64(cx, n); + if (!res) { + return false; + } + val.setBigInt(res); + return true; +} } /* anonymous namespace */ static NewObjectKind @@ -2544,28 +2729,17 @@ DataViewObject::fun_setFloat64(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod(cx, args); } -Value -TypedArrayObject::getElement(uint32_t index) +namespace js { + +template <> +bool TypedArrayObject::getElement(ExclusiveContext* cx, uint32_t index, MutableHandleValue val) { switch (type()) { - case Scalar::Int8: - return Int8Array::getIndexValue(this, index); - case Scalar::Uint8: - return Uint8Array::getIndexValue(this, index); - case Scalar::Int16: - return Int16Array::getIndexValue(this, index); - case Scalar::Uint16: - return Uint16Array::getIndexValue(this, index); - case Scalar::Int32: - return Int32Array::getIndexValue(this, index); - case Scalar::Uint32: - return Uint32Array::getIndexValue(this, index); - case Scalar::Float32: - return Float32Array::getIndexValue(this, index); - case Scalar::Float64: - return Float64Array::getIndexValue(this, index); - case Scalar::Uint8Clamped: - return Uint8ClampedArray::getIndexValue(this, index); +#define GET_ELEMENT(T, N) \ + case Scalar::N: \ + return N##Array::getElement(cx, this, index, val); + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT) +#undef GET_ELEMENT case Scalar::Int64: case Scalar::Float32x4: case Scalar::Int8x16: @@ -2578,44 +2752,23 @@ TypedArrayObject::getElement(uint32_t index) MOZ_CRASH("Unknown TypedArray type"); } -void -TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) -{ - MOZ_ASSERT(index < obj.length()); +template <> +bool TypedArrayObject::getElement( + ExclusiveContext* cx, uint32_t index, + typename MaybeRooted::MutableHandleType vp) { + return getElementPure(index, vp.address()); +} -#ifdef JS_MORE_DETERMINISTIC - // See the comment in ElementSpecific::doubleToNative. - d = JS::CanonicalizeNaN(d); -#endif +} // namespace js - switch (obj.type()) { - case Scalar::Int8: - Int8Array::setIndexValue(obj, index, d); - return; - case Scalar::Uint8: - Uint8Array::setIndexValue(obj, index, d); - return; - case Scalar::Uint8Clamped: - Uint8ClampedArray::setIndexValue(obj, index, d); - return; - case Scalar::Int16: - Int16Array::setIndexValue(obj, index, d); - return; - case Scalar::Uint16: - Uint16Array::setIndexValue(obj, index, d); - return; - case Scalar::Int32: - Int32Array::setIndexValue(obj, index, d); - return; - case Scalar::Uint32: - Uint32Array::setIndexValue(obj, index, d); - return; - case Scalar::Float32: - Float32Array::setIndexValue(obj, index, d); - return; - case Scalar::Float64: - Float64Array::setIndexValue(obj, index, d); - return; +bool TypedArrayObject::getElementPure(uint32_t index, Value* vp) +{ + switch (type()) { +#define GET_ELEMENT_PURE(T, N) \ + case Scalar::N: \ + return N##Array::getElementPure(this, index, vp); + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT_PURE) +#undef GET_ELEMENT case Scalar::Int64: case Scalar::Float32x4: case Scalar::Int8x16: @@ -2628,6 +2781,38 @@ TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) MOZ_CRASH("Unknown TypedArray type"); } +/* static */ +bool TypedArrayObject::getElements(JSContext* cx, Handle tarray, Value* vp) +{ + uint32_t length = tarray->length(); + MOZ_ASSERT_IF(length > 0, !tarray->hasDetachedBuffer()); + + switch (tarray->type()) { +#define GET_ELEMENTS(T, N) \ + case Scalar::N: \ + for (uint32_t i = 0; i < length; ++i, ++vp) { \ + if (!N##Array::getElement(cx, tarray, i, \ + MutableHandleValue::fromMarkedLocation(vp))) { \ + return false; \ + } \ + } \ + return true; + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENTS) +#undef GET_ELEMENTS + default: + MOZ_CRASH("Unknown TypedArray type"); + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: + break; + } + + MOZ_CRASH("Unknown TypedArray type"); +} + /*** *** JS impl ***/ @@ -2680,6 +2865,8 @@ IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Int32, int32_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint32, uint32_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Float32, float) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Float64, double) +IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(BigInt64, int64_t) +IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(BigUint64, uint64_t) #define IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Name, ExternalType, InternalType) \ JS_FRIEND_API(JSObject*) JS_GetObjectAs ## Name ## Array(JSObject* obj, \ @@ -2711,6 +2898,8 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Int32, int32_t, int32_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint32, uint32_t, uint32_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float32, float, float) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double) +IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(BigInt64, int64_t, int64_t) +IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(BigUint64, uint32_t, uint64_t) static const ClassOps TypedArrayClassOps = { nullptr, /* addProperty */ @@ -2748,7 +2937,9 @@ static const JSPropertySpec static_prototype_properties[Scalar::MaxTypedArrayVie IMPL_TYPED_ARRAY_PROPERTIES(Uint32), IMPL_TYPED_ARRAY_PROPERTIES(Float32), IMPL_TYPED_ARRAY_PROPERTIES(Float64), - IMPL_TYPED_ARRAY_PROPERTIES(Uint8Clamped) + IMPL_TYPED_ARRAY_PROPERTIES(Uint8Clamped), + IMPL_TYPED_ARRAY_PROPERTIES(BigInt64), + IMPL_TYPED_ARRAY_PROPERTIES(BigUint64) }; #define IMPL_TYPED_ARRAY_CLASS_SPEC(_type) \ @@ -2772,7 +2963,9 @@ static const ClassSpec TypedArrayObjectClassSpecs[Scalar::MaxTypedArrayViewType] IMPL_TYPED_ARRAY_CLASS_SPEC(Uint32), IMPL_TYPED_ARRAY_CLASS_SPEC(Float32), IMPL_TYPED_ARRAY_CLASS_SPEC(Float64), - IMPL_TYPED_ARRAY_CLASS_SPEC(Uint8Clamped) + IMPL_TYPED_ARRAY_CLASS_SPEC(Uint8Clamped), + IMPL_TYPED_ARRAY_CLASS_SPEC(BigInt64), + IMPL_TYPED_ARRAY_CLASS_SPEC(BigUint64) }; #define IMPL_TYPED_ARRAY_CLASS(_type) \ @@ -2798,7 +2991,9 @@ const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = { IMPL_TYPED_ARRAY_CLASS(Uint32), IMPL_TYPED_ARRAY_CLASS(Float32), IMPL_TYPED_ARRAY_CLASS(Float64), - IMPL_TYPED_ARRAY_CLASS(Uint8Clamped) + IMPL_TYPED_ARRAY_CLASS(Uint8Clamped), + IMPL_TYPED_ARRAY_CLASS(BigInt64), + IMPL_TYPED_ARRAY_CLASS(BigUint64) }; #define IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(_type) \ @@ -2822,7 +3017,9 @@ static const ClassSpec TypedArrayObjectProtoClassSpecs[Scalar::MaxTypedArrayView IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint32), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Float32), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Float64), - IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint8Clamped) + IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint8Clamped), + IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(BigInt64), + IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(BigUint64) }; // The various typed array prototypes are supposed to 1) be normal objects, @@ -2856,7 +3053,9 @@ const Class TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = { IMPL_TYPED_ARRAY_PROTO_CLASS(Uint32), IMPL_TYPED_ARRAY_PROTO_CLASS(Float32), IMPL_TYPED_ARRAY_PROTO_CLASS(Float64), - IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Clamped) + IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Clamped), + IMPL_TYPED_ARRAY_PROTO_CLASS(BigInt64), + IMPL_TYPED_ARRAY_PROTO_CLASS(BigUint64) }; /* static */ bool @@ -3112,6 +3311,25 @@ js::StringIsTypedArrayIndex(const char16_t* s, size_t length, uint64_t* indexp); template bool js::StringIsTypedArrayIndex(const Latin1Char* s, size_t length, uint64_t* indexp); +bool js::SetTypedArrayElement(JSContext* cx, Handle obj, + uint64_t index, HandleValue v, ObjectOpResult& result) +{ + TypedArrayObject* tobj = &obj->as(); + + switch (tobj->type()) { +#define SET_TYPED_ARRAY_ELEMENT(T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate::setElement(cx, obj, index, v, result); + JS_FOR_EACH_TYPED_ARRAY(SET_TYPED_ARRAY_ELEMENT) +#undef SET_TYPED_ARRAY_ELEMENT + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + break; + } + + MOZ_CRASH("Unsupported TypedArray type"); +} + /* ES6 draft rev 34 (2015 Feb 20) 9.4.5.3 [[DefineOwnProperty]] step 3.c. */ bool js::DefineTypedArrayElement(JSContext* cx, HandleObject obj, uint64_t index, @@ -3147,22 +3365,18 @@ js::DefineTypedArrayElement(JSContext* cx, HandleObject obj, uint64_t index, // Step x. if (desc.hasValue()) { - // The following step numbers refer to 9.4.5.9 - // IntegerIndexedElementSet. - - // Steps 1-2 are enforced by the caller. - - // Step 3. - double numValue; - if (!ToNumber(cx, desc.value(), &numValue)) - return false; - - // Steps 4-5, 8-9. - if (obj->as().hasDetachedBuffer()) - return result.fail(JSMSG_TYPED_ARRAY_DETACHED); - - // Steps 10-16. - TypedArrayObject::setElement(obj->as(), index, numValue); + TypedArrayObject* tobj = &obj->as(); + switch (tobj->type()) { +#define DEFINE_TYPED_ARRAY_ELEMENT(T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate::defineElement(cx, obj, index, \ + desc.value(), result); + JS_FOR_EACH_TYPED_ARRAY(DEFINE_TYPED_ARRAY_ELEMENT) +#undef DEFINE_TYPED_ARRAY_ELEMENT + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + break; + } } // Step xii. diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 196d347075..06aaea0617 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -24,7 +24,9 @@ macro(uint32_t, Uint32) \ macro(float, Float32) \ macro(double, Float64) \ - macro(uint8_clamped, Uint8Clamped) + macro(uint8_clamped, Uint8Clamped) \ + macro(int64_t, BigInt64) \ + macro(uint64_t, BigUint64) typedef struct JSProperty JSProperty; @@ -183,8 +185,12 @@ class TypedArrayObject : public NativeObject void assertZeroLengthArrayData() const {}; #endif - Value getElement(uint32_t index); - static void setElement(TypedArrayObject& obj, uint32_t index, double d); + template + bool getElement(ExclusiveContext* cx, uint32_t index, + typename MaybeRooted::MutableHandleType val); + bool getElementPure(uint32_t index, Value* vp); + + static bool getElements(JSContext* cx, Handle tarray, Value* vp); void notifyBufferDetached(JSContext* cx, void* newData); @@ -306,6 +312,8 @@ class TypedArrayObject : public NativeObject static bool is(HandleValue v); static bool set(JSContext* cx, unsigned argc, Value* vp); + + bool convertForSideEffect(JSContext* cx, HandleValue v) const; }; MOZ_MUST_USE bool TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp); @@ -373,6 +381,10 @@ IsTypedArrayIndex(jsid id, uint64_t* indexp) return StringIsTypedArrayIndex(s, length, indexp); } +bool SetTypedArrayElement(JSContext* cx, Handle obj, + uint64_t index, HandleValue v, + ObjectOpResult& result); + /* * Implements [[DefineOwnProperty]] for TypedArrays when the property * key is a TypedArray index. @@ -396,6 +408,8 @@ TypedArrayShift(Scalar::Type viewType) case Scalar::Uint32: case Scalar::Float32: return 2; + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Int64: case Scalar::Float64: return 3; -- cgit v1.2.3 From 1edc4e41d3d4fd1f3bd9886cba0c0e38b24c194b Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 25 Jul 2023 01:41:42 -0500 Subject: Issue #2026 - Part 2a - Support BigInt in NumberFormat and toLocaleString. https://bugzilla.mozilla.org/show_bug.cgi?id=1543677 --- js/public/Value.h | 5 +++ js/src/builtin/BigInt.cpp | 28 +----------- js/src/builtin/BigInt.h | 2 - js/src/builtin/BigInt.js | 34 +++++++++++++++ js/src/builtin/intl/NumberFormat.cpp | 84 ++++++++++++++++++++++++------------ js/src/builtin/intl/NumberFormat.h | 2 +- js/src/builtin/intl/NumberFormat.js | 6 +-- js/src/builtin/intl/PluralRules.cpp | 4 +- js/src/jsnum.cpp | 3 +- js/src/jsnum.h | 5 +-- js/src/moz.build | 1 + js/src/vm/SelfHosting.cpp | 17 +++++++- 12 files changed, 121 insertions(+), 70 deletions(-) create mode 100644 js/src/builtin/BigInt.js diff --git a/js/public/Value.h b/js/public/Value.h index 30f4670049..a6ceaad669 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -553,6 +553,10 @@ class MOZ_NON_PARAM alignas(8) Value return isObject() || isNull(); } + bool isNumeric() const { + return isNumber() || isBigInt(); + } + bool isGCThing() const { #if defined(JS_NUNBOX32) /* gcc sometimes generates signed < without explicit casts. */ @@ -1410,6 +1414,7 @@ class WrappedPtrOperations bool isNullOrUndefined() const { return value().isNullOrUndefined(); } bool isObjectOrNull() const { return value().isObjectOrNull(); } + bool isNumeric() const { return value().isNumeric(); } bool toBoolean() const { return value().toBoolean(); } double toNumber() const { return value().toNumber(); } diff --git a/js/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp index 6c78970d74..8a630534a8 100644 --- a/js/src/builtin/BigInt.cpp +++ b/js/src/builtin/BigInt.cpp @@ -139,32 +139,6 @@ 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); -} - // BigInt proposal section 5.2.1. BigInt.asUintN ( bits, bigint ) bool BigIntObject::asUintN(JSContext* cx, unsigned argc, Value* vp) @@ -247,7 +221,7 @@ 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_SELF_HOSTED_FN("toLocaleString", "BigInt_toLocaleString", 0, 0), JS_FS_END }; diff --git a/js/src/builtin/BigInt.h b/js/src/builtin/BigInt.h index 6971549fc3..be447b2285 100644 --- a/js/src/builtin/BigInt.h +++ b/js/src/builtin/BigInt.h @@ -31,8 +31,6 @@ 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); static bool asUintN(JSContext* cx, unsigned argc, JS::Value* vp); static bool asIntN(JSContext* cx, unsigned argc, JS::Value* vp); diff --git a/js/src/builtin/BigInt.js b/js/src/builtin/BigInt.js new file mode 100644 index 0000000000..3ed3da5933 --- /dev/null +++ b/js/src/builtin/BigInt.js @@ -0,0 +1,34 @@ +/* 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/. */ + +/** + * Format this BigInt object into a string, using the locale and formatting + * options provided. + * + * Spec PR: https://github.com/tc39/ecma402/pull/236 + */ +function BigInt_toLocaleString() { + // Step 1. Note that valueOf enforces "thisBigIntValue" restrictions. + var x = callFunction(std_BigInt_valueOf, this); + + var locales = arguments.length > 0 ? arguments[0] : undefined; + var options = arguments.length > 1 ? arguments[1] : undefined; + + // Step 2. + var numberFormat; + if (locales === undefined && options === undefined) { + // This cache only optimizes when no explicit locales and options + // arguments were supplied. + if (!IsRuntimeDefaultLocale(numberFormatCache.runtimeDefaultLocale)) { + numberFormatCache.numberFormat = intl_NumberFormat(locales, options); + numberFormatCache.runtimeDefaultLocale = RuntimeDefaultLocale(); + } + numberFormat = numberFormatCache.numberFormat; + } else { + numberFormat = intl_NumberFormat(locales, options); + } + + // Step 3. + return intl_FormatNumber(numberFormat, x, /* formatToParts = */ false); +} diff --git a/js/src/builtin/intl/NumberFormat.cpp b/js/src/builtin/intl/NumberFormat.cpp index 9ee3b02109..298b0a5b97 100644 --- a/js/src/builtin/intl/NumberFormat.cpp +++ b/js/src/builtin/intl/NumberFormat.cpp @@ -33,6 +33,7 @@ using namespace js; using mozilla::AssertedCast; using mozilla::IsFinite; +using mozilla::IsNegative; using mozilla::IsNaN; using mozilla::IsNegativeZero; using js::intl::CallICU; @@ -401,24 +402,43 @@ NewUNumberFormat(JSContext* cx, Handle numberFormat) } static JSString* -PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double* x, +PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, HandleValue x, UFieldPositionIterator* fpositer) { - // PartitionNumberPattern doesn't consider -0.0 to be negative. - if (IsNegativeZero(*x)) - *x = 0.0; - - return CallICU(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) { - return unum_formatDoubleForFields(nf, *x, chars, size, fpositer, status); - }); + if (x.isNumber()) { + double num = x.toNumber(); + + // PartitionNumberPattern doesn't consider -0.0 to be negative. + if (IsNegativeZero(num)) + num = 0.0; + + return CallICU(cx, [nf, num, fpositer](UChar* chars, int32_t size, UErrorCode* status) { + return unum_formatDoubleForFields(nf, num, chars, size, fpositer, status); + }); + } else if(x.isBigInt()) { + RootedBigInt bi(cx, x.toBigInt()); + JSLinearString* str = BigInt::toString(cx, bi, 10); + if (!str) { + return nullptr; + } + MOZ_ASSERT(str->hasLatin1Chars()); + + JS::AutoCheckCannotGC noGC(cx); + const char* latinchars = reinterpret_cast(str->latin1Chars(noGC)); + size_t length = str->length(); + return CallICU(cx, [nf, latinchars, length](UChar* chars, int32_t size, UErrorCode* status) { + return unum_formatDecimal(nf, latinchars, length, chars, size, nullptr, status); + }); + } + return nullptr; } bool -js::intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result) +js::FormatNumeric(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result) { // Passing null for |fpositer| will just not compute partition information, // letting us common up all ICU number-formatting code. - JSString* str = PartitionNumberPattern(cx, nf, &x, nullptr); + JSString* str = PartitionNumberPattern(cx, nf, x, nullptr); if (!str) return false; @@ -429,7 +449,7 @@ js::intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleV using FieldType = ImmutablePropertyNamePtr JSAtomState::*; static FieldType -GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d) +GetFieldTypeForNumberField(UNumberFormatFields fieldName, HandleValue x) { // See intl/icu/source/i18n/unicode/unum.h for a detailed field list. This // list is deliberately exhaustive: cases might have to be added/removed if @@ -438,10 +458,15 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d) // version-testing #ifdefs, should cross-version divergence occur. switch (fieldName) { case UNUM_INTEGER_FIELD: - if (IsNaN(d)) - return &JSAtomState::nan; - if (!IsFinite(d)) - return &JSAtomState::infinity; + if (x.isNumber()) { + double d = x.toNumber(); + if (IsNaN(d)) { + return &JSAtomState::nan; + } + if (!IsFinite(d)) { + return &JSAtomState::infinity; + } + } return &JSAtomState::integer; case UNUM_GROUPING_SEPARATOR_FIELD: @@ -454,13 +479,17 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d) return &JSAtomState::fraction; case UNUM_SIGN_FIELD: { - MOZ_ASSERT(!IsNegativeZero(d), - "-0 should have been excluded by PartitionNumberPattern"); - - // Manual trawling through the ICU call graph appears to indicate that - // the basic formatting we request will never include a positive sign. - // But this analysis may be mistaken, so don't absolutely trust it. - return d < 0 ? &JSAtomState::minusSign : &JSAtomState::plusSign; + // Manual trawling through the ICU call graph appears to indicate that + // the basic formatting we request will never include a positive sign. + // But this analysis may be mistaken, so don't absolutely trust it. + MOZ_ASSERT(!x.isNumber() || !IsNaN(x.toNumber()), + "ICU appearing not to produce positive-sign among fields, " + "plus our coercing all NaNs to one with sign bit unset " + "(i.e. \"positive\"), means we shouldn't reach here with a " + "NaN value"); + bool isNegative = + x.isNumber() ? IsNegative(x.toNumber()) : x.toBigInt()->isNegative(); + return isNegative ? &JSAtomState::minusSign : &JSAtomState::plusSign; } case UNUM_PERCENT_FIELD: @@ -495,7 +524,7 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d) } static bool -intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result) +FormatNumericToParts(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result) { UErrorCode status = U_ZERO_ERROR; @@ -508,7 +537,7 @@ intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x, MutableHand MOZ_ASSERT(fpositer); ScopedICUObject toClose(fpositer); - RootedString overallResult(cx, PartitionNumberPattern(cx, nf, &x, fpositer)); + RootedString overallResult(cx, PartitionNumberPattern(cx, nf, x, fpositer)); if (!overallResult) return false; @@ -824,7 +853,7 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(args[0].isObject()); - MOZ_ASSERT(args[1].isNumber()); + MOZ_ASSERT(args[1].isNumeric()); MOZ_ASSERT(args[2].isBoolean()); Rooted numberFormat(cx, &args[0].toObject().as()); @@ -842,8 +871,7 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) // Use the UNumberFormat to actually format the number. if (args[2].toBoolean()) { - return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval()); + return FormatNumericToParts(cx, nf, args.get(1), args.rval()); } - return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval()); + return FormatNumeric(cx, nf, args.get(1), args.rval()); } - diff --git a/js/src/builtin/intl/NumberFormat.h b/js/src/builtin/intl/NumberFormat.h index befa0c3e0d..bc2f659527 100644 --- a/js/src/builtin/intl/NumberFormat.h +++ b/js/src/builtin/intl/NumberFormat.h @@ -71,7 +71,7 @@ intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp); extern MOZ_MUST_USE bool intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp); extern MOZ_MUST_USE bool -intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result); +FormatNumeric(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result); } // namespace js diff --git a/js/src/builtin/intl/NumberFormat.js b/js/src/builtin/intl/NumberFormat.js index 238a59405b..261bff1dc6 100644 --- a/js/src/builtin/intl/NumberFormat.js +++ b/js/src/builtin/intl/NumberFormat.js @@ -454,14 +454,14 @@ function numberFormatFormatToBind(value) { // ES5.1 10.5, step 4.d.ii. // Step 1.a.ii-iii. - var x = ToNumber(value); + var x = ToNumeric(value); return intl_FormatNumber(this, x, /* formatToParts = */ false); } /** * Returns a function bound to this NumberFormat that returns a String value - * representing the result of calling ToNumber(value) according to the + * representing the result of calling ToNumeric(value) according to the * effective locale and the formatting options of this NumberFormat. * * Spec: ECMAScript Internationalization API Specification, 11.4.3. @@ -497,7 +497,7 @@ function Intl_NumberFormat_formatToParts(value) { getNumberFormatInternals(nf); // Step 4. - var x = ToNumber(value); + var x = ToNumeric(value); // Step 5. return intl_FormatNumber(nf, x, /* formatToParts = */ true); diff --git a/js/src/builtin/intl/PluralRules.cpp b/js/src/builtin/intl/PluralRules.cpp index e1e8e37044..ce2f3c3893 100644 --- a/js/src/builtin/intl/PluralRules.cpp +++ b/js/src/builtin/intl/PluralRules.cpp @@ -292,8 +292,6 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) if (!type) return false; - double x = args[1].toNumber(); - // We need a NumberFormat in order to format the number // using the number formatting options (minimum/maximum*Digits) // before we push the result to PluralRules @@ -302,7 +300,7 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) // API: http://bugs.icu-project.org/trac/ticket/12763 // RootedValue fmtNumValue(cx); - if (!intl_FormatNumber(cx, nf, x, &fmtNumValue)) + if (!FormatNumeric(cx, nf, args[1], &fmtNumValue)) return false; RootedString fmtNumValueString(cx, fmtNumValue.toString()); AutoStableStringChars stableChars(cx); diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 573b55cc85..4e8a5288e5 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1503,8 +1503,7 @@ js::ToInt8Slow(JSContext *cx, const HandleValue v, int8_t *out) bool js::ToNumericSlow(ExclusiveContext* cx, MutableHandleValue vp) { - MOZ_ASSERT(!vp.isNumber()); - MOZ_ASSERT(!vp.isBigInt()); + MOZ_ASSERT(!vp.isNumeric()); // Step 1. if (!vp.isPrimitive()) { diff --git a/js/src/jsnum.h b/js/src/jsnum.h index bd53fdc1a0..ee07d0a35d 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -378,10 +378,9 @@ ToNumericSlow(ExclusiveContext* cx, JS::MutableHandleValue vp); MOZ_ALWAYS_INLINE MOZ_MUST_USE bool ToNumeric(ExclusiveContext* cx, JS::MutableHandleValue vp) { - if (vp.isNumber()) - return true; - if (vp.isBigInt()) + if (vp.isNumeric()) { return true; + } return ToNumericSlow(cx, vp); } diff --git a/js/src/moz.build b/js/src/moz.build index b75afc2628..8e14de6e85 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -705,6 +705,7 @@ selfhosted.inputs = [ 'builtin/Utilities.js', 'builtin/Array.js', 'builtin/AsyncIteration.js', + 'builtin/BigInt.js', 'builtin/Classes.js', 'builtin/Date.js', 'builtin/Error.js', diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index e73e55d9a0..2a60a1885c 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -22,6 +22,7 @@ #include "jswrapper.h" #include "selfhosted.out.h" +#include "builtin/BigInt.h" #include "builtin/intl/Collator.h" #include "builtin/intl/DateTimeFormat.h" #include "builtin/intl/IntlObject.h" @@ -2207,6 +2208,17 @@ static bool intrinsic_ToBigInt(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool intrinsic_ToNumeric(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + if (!ToNumeric(cx, args[0])) { + return false; + } + args.rval().set(args[0]); + return true; +} + // The self-hosting global isn't initialized with the normal set of builtins. // Instead, individual C++-implemented functions that're required by // self-hosted code are defined as global functions. Accessing these @@ -2230,6 +2242,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("std_Array_reverse", array_reverse, 0,0), JS_FNINFO("std_Array_splice", array_splice, &array_splice_info, 2,0), + JS_FN("std_BigInt_valueOf", BigIntObject::valueOf, 0,0), + JS_FN("std_Date_now", date_now, 0,0), JS_FN("std_Date_valueOf", date_valueOf, 0,0), @@ -2637,7 +2651,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0), JS_FN("ToBigInt", intrinsic_ToBigInt, 1, 0), - + JS_FN("ToNumeric", intrinsic_ToNumeric, 1, 0), + JS_FS_END }; -- cgit v1.2.3 From 7a5b68c98ebde399166bef6fecb932760dc1006d Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 25 Jul 2023 01:59:56 -0500 Subject: Issue #2026 - Part 2b - Format BigInts representable as int64_t without first converting them to strings. https://bugzilla.mozilla.org/show_bug.cgi?id=1543677 --- js/src/builtin/intl/NumberFormat.cpp | 30 +++++++++++++++++---------- js/src/vm/BigIntType.cpp | 40 +++++++++++++++++++++++++++++++++++- js/src/vm/BigIntType.h | 5 +++++ 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/js/src/builtin/intl/NumberFormat.cpp b/js/src/builtin/intl/NumberFormat.cpp index 298b0a5b97..8820166f56 100644 --- a/js/src/builtin/intl/NumberFormat.cpp +++ b/js/src/builtin/intl/NumberFormat.cpp @@ -417,18 +417,26 @@ PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, HandleValue x, }); } else if(x.isBigInt()) { RootedBigInt bi(cx, x.toBigInt()); - JSLinearString* str = BigInt::toString(cx, bi, 10); - if (!str) { - return nullptr; - } - MOZ_ASSERT(str->hasLatin1Chars()); + int64_t num; - JS::AutoCheckCannotGC noGC(cx); - const char* latinchars = reinterpret_cast(str->latin1Chars(noGC)); - size_t length = str->length(); - return CallICU(cx, [nf, latinchars, length](UChar* chars, int32_t size, UErrorCode* status) { - return unum_formatDecimal(nf, latinchars, length, chars, size, nullptr, status); - }); + if (BigInt::isInt64(bi, &num)) { + return CallICU(cx, [nf, num](UChar* chars, int32_t size, UErrorCode* status) { + return unum_formatInt64(nf, num, chars, size, nullptr, status); + }); + } else { + JSLinearString* str = BigInt::toString(cx, bi, 10); + if (!str) { + return nullptr; + } + MOZ_ASSERT(str->hasLatin1Chars()); + + JS::AutoCheckCannotGC noGC(cx); + const char* latinchars = reinterpret_cast(str->latin1Chars(noGC)); + size_t length = str->length(); + return CallICU(cx, [nf, latinchars, length](UChar* chars, int32_t size, UErrorCode* status) { + return unum_formatDecimal(nf, latinchars, length, chars, size, nullptr, status); + }); + } } return nullptr; } diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index 44bb1923f7..8382c641fa 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -87,6 +87,7 @@ #include "mozilla/WrappingOperations.h" #include +#include #include #include @@ -2211,6 +2212,43 @@ uint64_t BigInt::toUint64(BigInt* x) { return digit; } +bool BigInt::isInt64(BigInt* x, int64_t* result) { + MOZ_MAKE_MEM_UNDEFINED(result, sizeof(*result)); + + size_t length = x->digitLength(); + if (length > (DigitBits == 32 ? 2 : 1)) { + return false; + } + + if (length == 0) { + *result = 0; + return true; + } + + uint64_t magnitude = x->digit(0); + if (DigitBits == 32 && length > 1) { + magnitude |= static_cast(x->digit(1)) << 32; + } + + if (x->isNegative()) { + constexpr uint64_t Int64MinMagnitude = uint64_t(1) << 63; + if (magnitude <= Int64MinMagnitude) { + *result = magnitude == Int64MinMagnitude + ? std::numeric_limits::min() + : -AssertedCast(magnitude); + return true; + } + } else { + if (magnitude <= + static_cast(std::numeric_limits::max())) { + *result = AssertedCast(magnitude); + return true; + } + } + + return false; +} + // 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, @@ -2661,7 +2699,7 @@ double BigInt::numberValue(BigInt* x) { if (length <= 64 / DigitBits) { uint64_t magnitude = x->digit(0); if (DigitBits == 32 && length > 1) { - magnitude |= uint64_t(x->digit(1)) << 32; + magnitude |= static_cast(x->digit(1)) << 32; } const uint64_t MaxIntegralPrecisionDouble = uint64_t(1) << (SignificandWidth + 1); diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h index 7cdd757994..ea0317fd9c 100644 --- a/js/src/vm/BigIntType.h +++ b/js/src/vm/BigIntType.h @@ -119,6 +119,11 @@ class BigInt final : public js::gc::TenuredCell { static int64_t toInt64(BigInt* x); static uint64_t toUint64(BigInt* x); + // Return true if the BigInt is without loss of precision representable as an + // int64 and store the int64 value in the output. Otherwise return false and + // leave the value of the output parameter unspecified. + static bool isInt64(BigInt* x, int64_t* result); + static BigInt* asIntN(js::ExclusiveContext* cx, Handle x, uint64_t bits); static BigInt* asUintN(js::ExclusiveContext* cx, Handle x, uint64_t bits); -- cgit v1.2.3 From 56a82b8ed44c20354dc82453aa76f9f89f68d2e0 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 25 Jul 2023 02:27:03 -0500 Subject: Issue #2026 - Part 3a - Add support for BigInt in devtools. (Server side) https://bugzilla.mozilla.org/show_bug.cgi?id=1527867 --- devtools/server/actors/object.js | 6 ++++ devtools/server/tests/unit/test_objectgrips-08.js | 42 +++++++++++++---------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/devtools/server/actors/object.js b/devtools/server/actors/object.js index ab04b97048..7688e35c88 100644 --- a/devtools/server/actors/object.js +++ b/devtools/server/actors/object.js @@ -2159,6 +2159,12 @@ function createValueGrip(value, pool, makeObjectGrip) { } return value; + case "bigint": + return { + type: "BigInt", + text: value.toString(), + }; + case "undefined": return { type: "undefined" }; diff --git a/devtools/server/tests/unit/test_objectgrips-08.js b/devtools/server/tests/unit/test_objectgrips-08.js index ecaa7146db..2bd48a34c6 100644 --- a/devtools/server/tests/unit/test_objectgrips-08.js +++ b/devtools/server/tests/unit/test_objectgrips-08.js @@ -41,25 +41,15 @@ function test_object_grip() let objClient = gThreadClient.pauseGrip(args[0]); objClient.getPrototypeAndProperties(function (aResponse) { - do_check_eq(aResponse.ownProperties.a.configurable, true); - do_check_eq(aResponse.ownProperties.a.enumerable, true); - do_check_eq(aResponse.ownProperties.a.writable, true); - do_check_eq(aResponse.ownProperties.a.value.type, "Infinity"); + const {a, b, c, d, e, f, g} = aResponse.ownProperties; - do_check_eq(aResponse.ownProperties.b.configurable, true); - do_check_eq(aResponse.ownProperties.b.enumerable, true); - do_check_eq(aResponse.ownProperties.b.writable, true); - do_check_eq(aResponse.ownProperties.b.value.type, "-Infinity"); - - do_check_eq(aResponse.ownProperties.c.configurable, true); - do_check_eq(aResponse.ownProperties.c.enumerable, true); - do_check_eq(aResponse.ownProperties.c.writable, true); - do_check_eq(aResponse.ownProperties.c.value.type, "NaN"); - - do_check_eq(aResponse.ownProperties.d.configurable, true); - do_check_eq(aResponse.ownProperties.d.enumerable, true); - do_check_eq(aResponse.ownProperties.d.writable, true); - do_check_eq(aResponse.ownProperties.d.value.type, "-0"); + testPropertyType(a, "Infinity"); + testPropertyType(b, "-Infinity"); + testPropertyType(c, "NaN"); + testPropertyType(d, "-0"); + testPropertyType(e, "BigInt"); + testPropertyType(f, "BigInt"); + testPropertyType(g, "BigInt"); gThreadClient.resume(function () { gClient.close().then(gCallback); @@ -67,6 +57,20 @@ function test_object_grip() }); }); - gDebuggee.eval("stopMe({ a: Infinity, b: -Infinity, c: NaN, d: -0 })"); + gDebuggee.eval(`stopMe({ + a: Infinity, + b: -Infinity, + c: NaN, + d: -0, + e: 1n, + f: -2n, + g: 0n, + })`); } +function testPropertyType(prop, expectedType) { + do_check_eq(prop.configurable, true); + do_check_eq(prop.enumerable, true); + do_check_eq(prop.writable, true); + do_check_eq(prop.value.type, expectedType); +} -- cgit v1.2.3 From fa32034f305b18480aefded068962b2862e7ccd3 Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 28 Jul 2023 23:35:59 +0200 Subject: Issue #2026 - Part 3b - Add BigInt Devtools support. (legacy frontend) --- devtools/client/shared/widgets/VariablesView.jsm | 19 ++++++++++--------- devtools/client/webconsole/console-output.js | 10 ++++++++++ devtools/server/actors/object.js | 6 ++++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/devtools/client/shared/widgets/VariablesView.jsm b/devtools/client/shared/widgets/VariablesView.jsm index f7be87f44f..2ba8d74916 100644 --- a/devtools/client/shared/widgets/VariablesView.jsm +++ b/devtools/client/shared/widgets/VariablesView.jsm @@ -3236,14 +3236,6 @@ VariablesView.prototype.isOverridden = function (aItem) { * The variable's descriptor. */ VariablesView.isPrimitive = function (aDescriptor) { - // For accessor property descriptors, the getter and setter need to be - // contained in 'get' and 'set' properties. - let getter = aDescriptor.get; - let setter = aDescriptor.set; - if (getter || setter) { - return false; - } - // As described in the remote debugger protocol, the value grip // must be contained in a 'value' property. let grip = aDescriptor.value; @@ -3261,7 +3253,8 @@ VariablesView.isPrimitive = function (aDescriptor) { type == "NaN" || type == "-0" || type == "symbol" || - type == "longString") { + type == "longString" || + type == "BigInt") { return true; } @@ -3354,6 +3347,10 @@ VariablesView.getGrip = function (aValue) { return { type: "-0" }; } return aValue; + case "bigint": + return { type: "BigInt", + text: aValue.toString(), + }; case "undefined": // document.all is also "undefined" if (aValue === undefined) { @@ -3496,6 +3493,10 @@ VariablesView.stringifiers.byType = { return keyString + " \u2192 " + valueString; }, + BigInt: function (aGrip, aOptions) { + return aGrip.text + "n"; + }, + }; // VariablesView.stringifiers.byType VariablesView.stringifiers.byObjectClass = { diff --git a/devtools/client/webconsole/console-output.js b/devtools/client/webconsole/console-output.js index 649ec65ae6..8fbaec94fe 100644 --- a/devtools/client/webconsole/console-output.js +++ b/devtools/client/webconsole/console-output.js @@ -1293,6 +1293,7 @@ Messages.Extended.prototype = extend(Messages.Simple.prototype, { { let map = { "number": "cm-number", + "bigint": "cm-number", "longstring": "console-string", "string": "console-string", "regexp": "cm-string-2", @@ -3364,6 +3365,15 @@ Widgets.ObjectRenderers.add({ render: WrappedPrimitiveRenderer, }); +/** + * The widget used for displaying BigInt previews. + */ +Widgets.ObjectRenderers.add({ + byClass: "BigInt", + + render: WrappedPrimitiveRenderer, +}); + /** * The widget used for displaying String previews. */ diff --git a/devtools/server/actors/object.js b/devtools/server/actors/object.js index ab04b97048..7688e35c88 100644 --- a/devtools/server/actors/object.js +++ b/devtools/server/actors/object.js @@ -2159,6 +2159,12 @@ function createValueGrip(value, pool, makeObjectGrip) { } return value; + case "bigint": + return { + type: "BigInt", + text: value.toString(), + }; + case "undefined": return { type: "undefined" }; -- cgit v1.2.3 From 5a7348a8d1f1189a018774be97df3c042473ce53 Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 29 Jul 2023 01:39:08 +0200 Subject: Issue #2026 - Part 3c - Add BigInt Devtools support. (new frontend) --- devtools/client/debugger/new/bundle.js | 69 +++++++++++++++++++++-- devtools/client/shared/components/reps/big-int.js | 48 ++++++++++++++++ devtools/client/shared/components/reps/moz.build | 1 + devtools/client/shared/components/reps/rep.js | 12 ++-- 4 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 devtools/client/shared/components/reps/big-int.js diff --git a/devtools/client/debugger/new/bundle.js b/devtools/client/debugger/new/bundle.js index 2291d48f34..f94bc3aee4 100644 --- a/devtools/client/debugger/new/bundle.js +++ b/devtools/client/debugger/new/bundle.js @@ -2509,6 +2509,10 @@ var Debugger = var Attribute = _require9.Attribute; + var _require23 = __webpack_require__(460); + + var BigInt = _require23.BigInt; + var _require10 = __webpack_require__(47); var DateTime = _require10.DateTime; @@ -2565,7 +2569,7 @@ var Debugger = // XXX there should be a way for extensions to register a new // or modify an existing rep. - var reps = [RegExp, StyleSheet, Event, DateTime, TextNode, Attribute, Func, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, SymbolRep]; + var reps = [RegExp, StyleSheet, Event, DateTime, TextNode, Attribute, Func, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, BigInt, SymbolRep]; /** * Generic rep that is using for rendering native JS types or an object. @@ -2605,10 +2609,12 @@ var Debugger = var defaultRep = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Obj; var type = typeof object; - if (type == "object" && object instanceof String) { - type = "string"; - } else if (type == "object" && object.type === "symbol") { - type = "symbol"; + if (type == "object") { + if (object instanceof String) { + type = "string"; + } else if (["symbol", "BigInt"].includes(object.type)) { + type = object.type; + } } if (isGrip(object)) { @@ -58325,6 +58331,59 @@ var Debugger = setBundle }; +/***/ }, +/* 460 */ +/***/ function(module, exports, __webpack_require__) { + + var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ + /* vim: set ft=javascript ts=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/. */ + + "use strict"; + + // Make this available to both AMD and CJS environments + + !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) { + // Dependencies + var React = __webpack_require__(2); + + // Shortcuts + var span = React.DOM.span; + + /** + * Renders a number + */ + + var BigInt = React.createClass({ + displayName: "BigInt", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + render: function () { + let {object} = this.props; + let {text} = object; + + return span({ className: "objectBox objectBox-number" }, `${text}n`); + } + }); + + function supportsObject(object, type) { + return type == "BigInt"; + } + + // Exports from this module + + exports.BigInt = { + rep: BigInt, + supportsObject: supportsObject + }; + }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + + /***/ } /******/ ]); //# sourceMappingURL=bundle.js.map diff --git a/devtools/client/shared/components/reps/big-int.js b/devtools/client/shared/components/reps/big-int.js new file mode 100644 index 0000000000..2dd251fba3 --- /dev/null +++ b/devtools/client/shared/components/reps/big-int.js @@ -0,0 +1,48 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a BigInt + */ + const BigInt = React.createClass({ + displayName: "BigInt", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + render: function () { + let {object} = this.props; + let {text} = object; + + return ( + span({className: "objectBox objectBox-number"}, + `${text}n` + ) + ); + } + }); + + function supportsObject(object, type) { + return (type == "BigInt"); + } + + // Exports from this module + + exports.BigInt = { + rep: BigInt, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/moz.build b/devtools/client/shared/components/reps/moz.build index 722bd7fdda..9100c0e452 100644 --- a/devtools/client/shared/components/reps/moz.build +++ b/devtools/client/shared/components/reps/moz.build @@ -6,6 +6,7 @@ DevToolsModules( 'array.js', 'attribute.js', + 'big-int.js', 'caption.js', 'comment-node.js', 'date-time.js', diff --git a/devtools/client/shared/components/reps/rep.js b/devtools/client/shared/components/reps/rep.js index 80d25b69e4..93a2bf3d7c 100644 --- a/devtools/client/shared/components/reps/rep.js +++ b/devtools/client/shared/components/reps/rep.js @@ -26,6 +26,7 @@ define(function (require, exports, module) { // DOM types (grips) const { Attribute } = require("./attribute"); + const { BigInt } = require("./big-int"); const { DateTime } = require("./date-time"); const { Document } = require("./document"); const { Event } = require("./event"); @@ -70,6 +71,7 @@ define(function (require, exports, module) { Null, StringRep, Number, + BigInt, SymbolRep, InfinityRep, NaNRep, @@ -111,10 +113,12 @@ define(function (require, exports, module) { */ function getRep(object, defaultRep = Obj) { let type = typeof object; - if (type == "object" && object instanceof String) { - type = "string"; - } else if (object && type == "object" && object.type) { - type = object.type; + if (type == "object") { + if (object instanceof String) { + type = "string"; + } else if (["symbol", "BigInt"].includes(object.type)) { + type = object.type; + } } if (isGrip(object)) { -- cgit v1.2.3 From 3dc659b86d3f1082387440f579468f1d86b048e8 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 28 Jul 2023 19:54:06 -0500 Subject: Issue #2026 - Part 4 - Fill in missing dense elements case and fix a comment. https://bugzilla.mozilla.org/show_bug.cgi?id=1456569 Skipped this during the initial implementation, not sure when or if this code path is used, but I figure it should be there just in case. Also fix debug builgs by removing an no longer valid MOZ_ASSERT. --- js/src/jit/shared/Assembler-shared.h | 1 - js/src/vm/NativeObject.cpp | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 6d623293cd..f18cbb9e1d 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -725,7 +725,6 @@ class MemoryAccessDesc trapOffset_(trapOffset) { MOZ_ASSERT(Scalar::isSimdType(type) == (numSimdElems > 0)); - MOZ_ASSERT(numSimdElems <= jit::ScalarTypeToLength(type)); MOZ_ASSERT(mozilla::IsPowerOfTwo(align)); MOZ_ASSERT_IF(isSimd(), hasTrap()); MOZ_ASSERT_IF(isAtomic(), hasTrap()); diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index d21f88b15e..cde86fb829 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -2392,6 +2392,15 @@ SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleVa Rooted tobj(cx, &pobj->as()); return SetTypedArrayElement(cx, tobj, index, v, result); } + + if (WouldDefinePastNonwritableLength(pobj, index)) + return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH); + + if (!pobj->maybeCopyElementsForWrite(cx)) + return false; + + pobj->setDenseElementWithType(cx, index, v); + return result.succeed(); } // Steps 5.b-f. -- cgit v1.2.3 From 1851f19271ef2d46736f61433e567b583c474d08 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 28 Jul 2023 19:55:53 -0500 Subject: Issue #1240 - Follow-up: Fix incorrect values in Number() constructor. https://bugzilla.mozilla.org/show_bug.cgi?id=1466893 Our code base was using the return value to create the Number object. However with the BigInt changes, it is no longer stored in rval, use args[0]. --- js/src/jsnum.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 4e8a5288e5..fd23e6ccd5 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -545,7 +545,9 @@ Number(JSContext* cx, unsigned argc, Value* vp) RootedObject proto(cx); if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return false; - JSObject* obj = NumberObject::create(cx, args.rval().toNumber(), proto); + + double d = args.length() > 0 ? args[0].toNumber() : 0; + JSObject* obj = NumberObject::create(cx, d, proto); if (!obj) return false; args.rval().setObject(*obj); -- cgit v1.2.3 From 7f8ea9ae2f92b71eefcd1dc286baaf529cc5686a Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 28 Jul 2023 19:57:55 -0500 Subject: Issue #1240 - Follow-up: Add missing JSVAL_TYPE_BIGINT cases in JitFrames.cpp. As pointed out by roytam1 during testing with Discord. --- js/src/jit/JitFrames.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index c343800e0d..7ee9b24eab 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -1776,6 +1776,12 @@ FromSymbolPayload(uintptr_t payload) return SymbolValue(reinterpret_cast(payload)); } +static Value +FromBigIntPayload(uintptr_t payload) +{ + return BigIntValue(reinterpret_cast(payload)); +} + static Value FromTypedPayload(JSValueType type, uintptr_t payload) { @@ -1788,6 +1794,8 @@ FromTypedPayload(JSValueType type, uintptr_t payload) return FromStringPayload(payload); case JSVAL_TYPE_SYMBOL: return FromSymbolPayload(payload); + case JSVAL_TYPE_BIGINT: + return FromBigIntPayload(payload); case JSVAL_TYPE_OBJECT: return FromObjectPayload(payload); default: @@ -1887,6 +1895,8 @@ SnapshotIterator::allocationValue(const RValueAllocation& alloc, ReadMethod rm) return FromStringPayload(fromStack(alloc.stackOffset2())); case JSVAL_TYPE_SYMBOL: return FromSymbolPayload(fromStack(alloc.stackOffset2())); + case JSVAL_TYPE_BIGINT: + return FromBigIntPayload(fromStack(alloc.stackOffset2())); case JSVAL_TYPE_OBJECT: return FromObjectPayload(fromStack(alloc.stackOffset2())); default: -- cgit v1.2.3 From e748187360f8c987b487dd90c5dea1b489498d43 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 2 Aug 2023 02:41:40 -0500 Subject: Issue #2282 - - Properly implement Performance Timeline Level 2 w3c spec. https://bugzilla.mozilla.org/show_bug.cgi?id=1539006 Do not throw from PerformanceObserver.observe when none of the entryTypes are known. https://bugzilla.mozilla.org/show_bug.cgi?id=1403027 Implement PerformanceObserver::takeRecords(). https://bugzilla.mozilla.org/show_bug.cgi?id=1436692 "server" is not a valid PerformanceEntry type. https://bugzilla.mozilla.org/show_bug.cgi?id=1463065 Fix a null ptr crash in PerformanceObserver::Observe. https://bugzilla.mozilla.org/show_bug.cgi?id=1631346 --- dom/locales/en-US/chrome/dom/dom.properties | 2 + dom/performance/Performance.cpp | 18 +- dom/performance/PerformanceObserver.cpp | 230 ++++++++++++++++++--- dom/performance/PerformanceObserver.h | 18 ++ dom/performance/tests/test_performance_observer.js | 14 +- dom/webidl/PerformanceObserver.webidl | 10 +- dom/workers/WorkerPrivate.cpp | 38 +++- dom/workers/WorkerPrivate.h | 3 + 8 files changed, 284 insertions(+), 49 deletions(-) diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 27b4eebf12..f0a6363af0 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -321,3 +321,5 @@ PushStateFloodingPrevented=Call to pushState or replaceState ignored due to exce # LOCALIZATION NOTE: Do not translate "Reload" ReloadFloodingPrevented=Call to Reload ignored due to excessive calls within a short timeframe. DOMQuadBoundsAttrWarning=DOMQuad.bounds is deprecated in favor of DOMQuad.getBounds() +UnsupportedEntryTypesIgnored=Ignoring unsupported entryTypes: %S. +AllEntryTypesIgnored=No valid entryTypes; aborting registration. diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp index 217faa5afe..e8a46409ef 100755 --- a/dom/performance/Performance.cpp +++ b/dom/performance/Performance.cpp @@ -712,9 +712,21 @@ Performance::QueueEntry(PerformanceEntry* aEntry) if (mObservers.IsEmpty()) { return; } - NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, - PerformanceObserver, - QueueEntry, (aEntry)); + nsTObserverArray interestedObservers; + nsTObserverArray::ForwardIterator observerIt( + mObservers); + while (observerIt.HasMore()) { + PerformanceObserver* observer = observerIt.GetNext(); + if (observer->ObservesTypeOfEntry(aEntry)) { + interestedObservers.AppendElement(observer); + } + } + + if (interestedObservers.IsEmpty()) { + return; + } + + NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers, PerformanceObserver, QueueEntry, (aEntry)); if (!mPendingNotificationObserversTask) { RunNotificationObserversTask(); diff --git a/dom/performance/PerformanceObserver.cpp b/dom/performance/PerformanceObserver.cpp index 26f93e8fc1..5482c1f2a6 100644 --- a/dom/performance/PerformanceObserver.cpp +++ b/dom/performance/PerformanceObserver.cpp @@ -9,6 +9,7 @@ #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceEntryBinding.h" #include "mozilla/dom/PerformanceObserverBinding.h" +#include "nsIScriptError.h" #include "nsPIDOMWindow.h" #include "nsQueryObject.h" #include "nsString.h" @@ -27,12 +28,14 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PerformanceObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PerformanceObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(PerformanceObserver) @@ -43,10 +46,14 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceObserver) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END +const char UnsupportedEntryTypesIgnoredMsgId[] = "UnsupportedEntryTypesIgnored"; +const char AllEntryTypesIgnoredMsgId[] = "AllEntryTypesIgnored"; + PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner, PerformanceObserverCallback& aCb) : mOwner(aOwner) , mCallback(&aCb) + , mObserverType(ObserverTypeUndefined) , mConnected(false) { MOZ_ASSERT(mOwner); @@ -56,6 +63,7 @@ PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner, PerformanceObserver::PerformanceObserver(WorkerPrivate* aWorkerPrivate, PerformanceObserverCallback& aCb) : mCallback(&aCb) + , mObserverType(ObserverTypeUndefined) , mConnected(false) { MOZ_ASSERT(aWorkerPrivate); @@ -115,7 +123,8 @@ PerformanceObserver::Notify() mQueuedEntries.Clear(); ErrorResult rv; - mCallback->Call(this, *list, *this, rv); + RefPtr callback(mCallback); + callback->Call(this, *list, *this, rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); } @@ -126,63 +135,227 @@ PerformanceObserver::QueueEntry(PerformanceEntry* aEntry) { MOZ_ASSERT(aEntry); - nsAutoString entryType; - aEntry->GetEntryType(entryType); - if (!mEntryTypes.Contains(entryType)) { + if (!ObservesTypeOfEntry(aEntry)) { return; } mQueuedEntries.AppendElement(aEntry); } +/* + * Keep this list in alphabetical order. + * https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute + */ static const char16_t *const sValidTypeNames[4] = { u"mark", u"measure", - u"resource", - u"server" + u"navigation", + u"resource" }; void -PerformanceObserver::Observe(const PerformanceObserverInit& aOptions, - ErrorResult& aRv) -{ - if (aOptions.mEntryTypes.IsEmpty()) { - aRv.Throw(NS_ERROR_DOM_TYPE_ERR); +PerformanceObserver::ReportUnsupportedTypesErrorToConsole( + bool aIsMainThread, const char* msgId, const nsString& aInvalidTypes) { + if (!aIsMainThread) { + nsTArray params; + params.AppendElement(aInvalidTypes); + WorkerPrivate::ReportErrorToConsole(msgId, params); + } else { + nsCOMPtr ownerWindow = do_QueryInterface(mOwner); + nsIDocument* document = ownerWindow->GetExtantDoc(); + const char16_t* params[] = {aInvalidTypes.get()}; + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), document, + nsContentUtils::eDOM_PROPERTIES, msgId, params, 1); + } + return; +} + +void PerformanceObserver::Observe(const PerformanceObserverInit& aOptions, + ErrorResult& aRv) { + const Optional>& maybeEntryTypes = aOptions.mEntryTypes; + const Optional& maybeType = aOptions.mType; + const Optional& maybeBuffered = aOptions.mBuffered; + + if (!mPerformance) { + aRv.Throw(NS_ERROR_FAILURE); return; } - nsTArray validEntryTypes; + if (!maybeEntryTypes.WasPassed() && !maybeType.WasPassed()) { + /* Per spec (3.3.1.2), this should be a syntax error. */ + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } - for (const char16_t* name : sValidTypeNames) { - nsDependentString validTypeName(name); - if (aOptions.mEntryTypes.Contains(validTypeName) && - !validEntryTypes.Contains(validTypeName)) { - validEntryTypes.AppendElement(validTypeName); + if (maybeEntryTypes.WasPassed() && + (maybeType.WasPassed() || maybeBuffered.WasPassed())) { + /* Per spec (3.3.1.3), this, too, should be a syntax error. */ + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + /* 3.3.1.4.1 */ + if (mObserverType == ObserverTypeUndefined) { + if (maybeEntryTypes.WasPassed()) { + mObserverType = ObserverTypeMultiple; + } else { + mObserverType = ObserverTypeSingle; } } - if (validEntryTypes.IsEmpty()) { - aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + /* 3.3.1.4.2 */ + if (mObserverType == ObserverTypeSingle && maybeEntryTypes.WasPassed()) { + aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR); + return; + } + /* 3.3.1.4.3 */ + if (mObserverType == ObserverTypeMultiple && maybeType.WasPassed()) { + aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR); return; } - mEntryTypes.SwapElements(validEntryTypes); + /* 3.3.1.5 */ + if (mObserverType == ObserverTypeMultiple) { + const Sequence& entryTypes = maybeEntryTypes.Value(); - mPerformance->AddObserver(this); + if (entryTypes.IsEmpty()) { + return; + } + + /* 3.3.1.5.2 */ + nsTArray validEntryTypes; + for (const char16_t* name : sValidTypeNames) { + nsDependentString validTypeName(name); + if (entryTypes.Contains(validTypeName) && + !validEntryTypes.Contains(validTypeName)) { + validEntryTypes.AppendElement(validTypeName); + } + } + + nsAutoString invalidTypesJoined; + bool addComma = false; + for (const auto& type : entryTypes) { + if (!validEntryTypes.Contains(type)) { + if (addComma) { + invalidTypesJoined.AppendLiteral(", "); + } + addComma = true; + invalidTypesJoined.Append(type); + } + } + + if (!invalidTypesJoined.IsEmpty()) { + ReportUnsupportedTypesErrorToConsole(NS_IsMainThread(), + UnsupportedEntryTypesIgnoredMsgId, + invalidTypesJoined); + } + + /* 3.3.1.5.3 */ + if (validEntryTypes.IsEmpty()) { + nsString errorString; + ReportUnsupportedTypesErrorToConsole( + NS_IsMainThread(), AllEntryTypesIgnoredMsgId, errorString); + return; + } + + /* + * Registered or not, we clear out the list of options, and start fresh + * with the one that we are using here. (3.3.1.5.4,5) + */ + mOptions.Clear(); + mOptions.AppendElement(aOptions); + + } else { + MOZ_ASSERT(mObserverType == ObserverTypeSingle); + bool typeValid = false; + nsString type = maybeType.Value(); + + /* 3.3.1.6.2 */ + for (const char16_t* name : sValidTypeNames) { + nsDependentString validTypeName(name); + if (type == validTypeName) { + typeValid = true; + break; + } + } + + if (!typeValid) { + ReportUnsupportedTypesErrorToConsole( + NS_IsMainThread(), UnsupportedEntryTypesIgnoredMsgId, type); + return; + } + + /* 3.3.1.6.4, 3.3.1.6.4 */ + bool didUpdateOptionsList = false; + nsTArray updatedOptionsList; + for (auto& option : mOptions) { + if (option.mType.WasPassed() && option.mType.Value() == type) { + updatedOptionsList.AppendElement(aOptions); + didUpdateOptionsList = true; + } else { + updatedOptionsList.AppendElement(option); + } + } + if (!didUpdateOptionsList) { + updatedOptionsList.AppendElement(aOptions); + } + mOptions.SwapElements(updatedOptionsList); - if (aOptions.mBuffered) { - for (auto entryType : mEntryTypes) { + /* 3.3.1.6.5 */ + if (maybeBuffered.WasPassed() && maybeBuffered.Value()) { nsTArray> existingEntries; - mPerformance->GetEntriesByType(entryType, existingEntries); + mPerformance->GetEntriesByType(type, existingEntries); if (!existingEntries.IsEmpty()) { mQueuedEntries.AppendElements(existingEntries); } } } - + /* Add ourselves to the list of registered performance + * observers, if necessary. (3.3.1.5.4,5; 3.3.1.6.4) + */ + mPerformance->AddObserver(this); mConnected = true; } +void +PerformanceObserver::GetSupportedEntryTypes(const GlobalObject& aGlobal, JS::MutableHandle aObject) +{ + nsTArray validTypes; + JS::Rooted val(aGlobal.Context()); + + for (const char16_t* name : sValidTypeNames) { + nsString validTypeName(name); + validTypes.AppendElement(validTypeName); + } + + if (!ToJSValue(aGlobal.Context(), validTypes, &val)) { + /* + * If this conversion fails, we don't set a result. + * The spec does not allow us to throw an exception. + */ + return; + } + aObject.set(&val.toObject()); +} + +bool +PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry* aEntry) +{ + for (auto& option : mOptions) { + if (option.mType.WasPassed()) { + if (option.mType.Value() == aEntry->GetEntryType()) { + return true; + } + } else { + if (option.mEntryTypes.Value().Contains(aEntry->GetEntryType())) { + return true; + } + } + } + return false; +} + void PerformanceObserver::Disconnect() { @@ -192,3 +365,10 @@ PerformanceObserver::Disconnect() mConnected = false; } } + +void +PerformanceObserver::TakeRecords(nsTArray>& aRetval) +{ + MOZ_ASSERT(aRetval.IsEmpty()); + aRetval.SwapElements(mQueuedEntries); +} diff --git a/dom/performance/PerformanceObserver.h b/dom/performance/PerformanceObserver.h index 283000d581..25d5f98f6a 100644 --- a/dom/performance/PerformanceObserver.h +++ b/dom/performance/PerformanceObserver.h @@ -55,19 +55,37 @@ public: void Observe(const PerformanceObserverInit& aOptions, mozilla::ErrorResult& aRv); + static void GetSupportedEntryTypes(const GlobalObject& aGlobal, + JS::MutableHandle aObject); void Disconnect(); + void TakeRecords(nsTArray>& aRetval); + void Notify(); void QueueEntry(PerformanceEntry* aEntry); + bool ObservesTypeOfEntry(PerformanceEntry* aEntry); + private: + void ReportUnsupportedTypesErrorToConsole(bool aIsMainThread, + const char* msgId, + const nsString& aInvalidTypes); ~PerformanceObserver(); nsCOMPtr mOwner; RefPtr mCallback; RefPtr mPerformance; nsTArray mEntryTypes; + nsTArray mOptions; + enum { + ObserverTypeUndefined, + ObserverTypeSingle, + ObserverTypeMultiple, + } mObserverType; + /* + * This is also known as registered, in the spec. + */ bool mConnected; nsTArray> mQueuedEntries; }; diff --git a/dom/performance/tests/test_performance_observer.js b/dom/performance/tests/test_performance_observer.js index 9716570e29..767240a99b 100644 --- a/dom/performance/tests/test_performance_observer.js +++ b/dom/performance/tests/test_performance_observer.js @@ -14,25 +14,25 @@ test(t => { var observer = new PerformanceObserver(() => { }); - assert_throws({name: "TypeError"}, function() { + assert_throws({name: "SyntaxError"}, function() { observer.observe(); - }, "observe() should throw TypeError exception if no option specified."); + }, "observe() should throw SyntaxError exception if no option specified."); - assert_throws({name: "TypeError"}, function() { + assert_throws({name: "SyntaxError"}, function() { observer.observe({ unsupportedAttribute: "unsupported" }); - }, "obsrve() should throw TypeError exception if the option has no 'entryTypes' attribute."); + }, "observe() should throw SyntaxError exception if the option has no 'entryTypes' attribute."); assert_throws({name: "TypeError"}, function() { observer.observe({ entryTypes: [] }); - }, "obsrve() should throw TypeError exception if 'entryTypes' attribute is an empty sequence."); + }, "observe() should throw TypeError exception if 'entryTypes' attribute is an empty sequence."); assert_throws({name: "TypeError"}, function() { observer.observe({ entryTypes: null }); - }, "obsrve() should throw TypeError exception if 'entryTypes' attribute is null."); + }, "observe() should throw TypeError exception if 'entryTypes' attribute is null."); assert_throws({name: "TypeError"}, function() { observer.observe({ entryTypes: ["invalid"]}); - }, "obsrve() should throw TypeError exception if 'entryTypes' attribute value is invalid."); + }, "observe() should throw TypeError exception if 'entryTypes' attribute value is invalid."); }, "Test that PerformanceObserver.observe throws exception"); function promiseObserve(test, options) { diff --git a/dom/webidl/PerformanceObserver.webidl b/dom/webidl/PerformanceObserver.webidl index 4cebecbeba..4e08adb34b 100644 --- a/dom/webidl/PerformanceObserver.webidl +++ b/dom/webidl/PerformanceObserver.webidl @@ -8,8 +8,9 @@ */ dictionary PerformanceObserverInit { - required sequence entryTypes; - boolean buffered = false; + sequence entryTypes; + DOMString type; + boolean buffered; }; callback PerformanceObserverCallback = void (PerformanceObserverEntryList entries, PerformanceObserver observer); @@ -18,7 +19,8 @@ callback PerformanceObserverCallback = void (PerformanceObserverEntryList entrie Constructor(PerformanceObserverCallback callback), Exposed=(Window,Worker)] interface PerformanceObserver { - [Throws] - void observe(PerformanceObserverInit options); + [Throws] void observe(optional PerformanceObserverInit options); void disconnect(); + PerformanceEntryList takeRecords(); + static readonly attribute object supportedEntryTypes; }; diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index a7a4929f3b..2f2fa78d42 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -1000,11 +1000,13 @@ private: class ReportErrorToConsoleRunnable final : public WorkerRunnable { const char* mMessage; + const nsTArray mParams; public: // aWorkerPrivate is the worker thread we're on (or the main thread, if null) static void - Report(WorkerPrivate* aWorkerPrivate, const char* aMessage) + Report(WorkerPrivate* aWorkerPrivate, const char* aMessage, + const nsTArray& aParams) { if (aWorkerPrivate) { aWorkerPrivate->AssertIsOnWorkerThread(); @@ -1015,23 +1017,30 @@ public: // Now fire a runnable to do the same on the parent's thread if we can. if (aWorkerPrivate) { RefPtr runnable = - new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage); + new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage, aParams); runnable->Dispatch(); return; } + uint16_t paramCount = aParams.Length(); + const char16_t** params = new const char16_t*[paramCount]; + for (uint16_t i=0; i& aParams) : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount), - mMessage(aMessage) + mMessage(aMessage), mParams(aParams) { } virtual void @@ -1048,7 +1057,7 @@ private: { WorkerPrivate* parent = aWorkerPrivate->GetParent(); MOZ_ASSERT_IF(!parent, NS_IsMainThread()); - Report(parent, mMessage); + Report(parent, mMessage, mParams); return true; } }; @@ -6022,13 +6031,22 @@ WorkerPrivate::ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult, // static void WorkerPrivate::ReportErrorToConsole(const char* aMessage) +{ + nsTArray emptyParams; + WorkerPrivate::ReportErrorToConsole(aMessage, emptyParams); +} + +// static +void +WorkerPrivate::ReportErrorToConsole(const char* aMessage, + const nsTArray& aParams) { WorkerPrivate* wp = nullptr; if (!NS_IsMainThread()) { wp = GetCurrentThreadWorkerPrivate(); } - ReportErrorToConsoleRunnable::Report(wp, aMessage); + ReportErrorToConsoleRunnable::Report(wp, aMessage, aParams); } int32_t diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 0a71420047..351f5458f1 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -1206,6 +1206,9 @@ public: static void ReportErrorToConsole(const char* aMessage); + static void + ReportErrorToConsole(const char* aMessage, const nsTArray& aParams); + int32_t SetTimeout(JSContext* aCx, nsIScriptTimeoutHandler* aHandler, int32_t aTimeout, bool aIsInterval, -- cgit v1.2.3 From 39d52370dc5b1695a5bd7eba505ffc6d39d31509 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sun, 6 Aug 2023 22:28:22 -0500 Subject: Issue #2026 - Follow-up: Support Big(U)Int64Array in crypto.getRandomValues. https://bugzilla.mozilla.org/show_bug.cgi?id=1718932 --- dom/base/Crypto.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dom/base/Crypto.cpp b/dom/base/Crypto.cpp index 4226832f4b..863c26c902 100644 --- a/dom/base/Crypto.cpp +++ b/dom/base/Crypto.cpp @@ -75,6 +75,8 @@ Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray, case js::Scalar::Uint16: case js::Scalar::Int32: case js::Scalar::Uint32: + case js::Scalar::BigInt64: + case js::Scalar::BigUint64: break; default: aRv.Throw(NS_ERROR_DOM_TYPE_MISMATCH_ERR); -- cgit v1.2.3 From 9dda91e5cb765d0f6d9fdd598fd514a0d28eae1f Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sun, 6 Aug 2023 22:35:51 -0500 Subject: Issue #2285 - Ensure we don't try to treat non-DOM-Node event targets as such, https://bugzilla.mozilla.org/show_bug.cgi?id=1440809 Fix crash on https://beeper.notion.site --- dom/base/FragmentOrElement.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index ccfe960cb7..3438c0c733 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -1022,7 +1022,12 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) nsContentUtils::Retarget(relatedTargetAsNode, this); nsCOMPtr targetInKnownToBeHandledScope = FindChromeAccessOnlySubtreeOwner(aVisitor.mTargetInKnownToBeHandledScope); - if (nsContentUtils::ContentIsShadowIncludingDescendantOf( + // If aVisitor.mTargetInKnownToBeHandledScope wasn't nsINode, + // targetInKnownToBeHandledScope will be null. This may happen when + // dispatching event to Window object in a content page and + // propagating the event to a chrome Element. + if (targetInKnownToBeHandledScope && + nsContentUtils::ContentIsShadowIncludingDescendantOf( this, targetInKnownToBeHandledScope->SubtreeRoot())) { // Part of step 11.4. // "If target's root is a shadow-including inclusive ancestor of -- cgit v1.2.3 From d9aa76f5c2bd150af3b284cdde1a3da20120b51b Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sun, 6 Aug 2023 23:22:09 -0500 Subject: No Issue - Fix building with --enable-js-lto Needed to include jscntxt.h due to de-unified building. --- js/src/builtin/intl/CommonFunctions.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/src/builtin/intl/CommonFunctions.h b/js/src/builtin/intl/CommonFunctions.h index c6856c9b6d..8c538a489b 100644 --- a/js/src/builtin/intl/CommonFunctions.h +++ b/js/src/builtin/intl/CommonFunctions.h @@ -18,6 +18,8 @@ #include "js/Vector.h" #include "vm/String.h" +#include "jscntxt.h" + namespace JS { class Value; } class JSObject; @@ -159,4 +161,4 @@ CallICU(JSContext* cx, const ICUStringFunction& strFn) } // namespace js -#endif /* builtin_intl_CommonFunctions_h */ \ No newline at end of file +#endif /* builtin_intl_CommonFunctions_h */ -- cgit v1.2.3 From 41f38134f1983db63b9cc6c7bc457a7418af12b6 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 6 Aug 2023 18:08:59 +0200 Subject: Issue #2172 - add missing RegExpShared TraceKind declaration --- js/src/gc/Tracer.cpp | 4 ++++ js/src/vm/RegExpObject.h | 3 +++ 2 files changed, 7 insertions(+) diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index ea7f613e38..7be4543029 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -344,6 +344,10 @@ JS_GetTraceThingInfo(char* buf, size_t bufsize, JSTracer* trc, void* thing, name = "object_group"; break; + case JS::TraceKind::RegExpShared: + name = "reg_exp_shared"; + break; + default: name = "INVALID"; break; diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 14ec8509ee..5247731112 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -259,6 +259,9 @@ class RegExpShared : public gc::TenuredCell static bool dumpBytecode(JSContext* cx, MutableHandleRegExpShared res, bool match_only, HandleLinearString input); #endif + + public: + static const JS::TraceKind TraceKind = JS::TraceKind::RegExpShared; }; class RegExpCompartment -- cgit v1.2.3 From edfb29e1a250167ea2c014991f2673f2d91db509 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 6 Aug 2023 20:51:59 +0200 Subject: Issue #2172 - ensure pointers in RegExpShared are updated after GC compacting --- js/src/jsgc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 5c2835cca9..c849bacc8c 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2323,7 +2323,8 @@ static const AllocKinds UpdatePhaseMisc { AllocKind::ACCESSOR_SHAPE, AllocKind::OBJECT_GROUP, AllocKind::STRING, - AllocKind::JITCODE + AllocKind::JITCODE, + AllocKind::REGEXP_SHARED }; static const AllocKinds UpdatePhaseObjects { -- cgit v1.2.3 From 3b32a66cb6c6cabf21b41ac4d5662f33ba1e80c3 Mon Sep 17 00:00:00 2001 From: Martok Date: Wed, 9 Aug 2023 22:41:30 +0200 Subject: Issue #2172 - add null zone sanity checks --- js/src/gc/Marking.cpp | 3 ++- js/src/vm/Runtime.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 13ec5b0c05..1376df5a4d 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -788,7 +788,8 @@ ShouldMark(GCMarker* gcmarker, JSObject* obj) // Don't mark things outside a zone if we are in a per-zone GC. It is // faster to check our own arena, which we can do since we know that // the object is tenured. - return obj->asTenured().zone()->shouldMarkInZone(); + Zone* zone = obj->asTenured().zone(); + return (zone && zone->shouldMarkInZone()); } template diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index ceb7a498b0..053b7c44b0 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -877,6 +877,9 @@ js::CurrentThreadCanAccessRuntime(const JSRuntime* rt) bool js::CurrentThreadCanAccessZone(Zone* zone) { + if (!zone) + return false; + if (CurrentThreadCanAccessRuntime(zone->runtime_)) return true; -- cgit v1.2.3 From cd98dce5fa3e75b0d36cef7fac8bdf55941465b0 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 13 Aug 2023 22:04:54 +0200 Subject: Issue #2284 - Add exception for flex/grid items resolving percentages against parent. This folds in BZ bugs 1578586 and 1092007 part 2. --- layout/generic/ReflowInput.cpp | 15 +++++++--- layout/generic/ReflowInput.h | 7 +++++ layout/generic/nsFlexContainerFrame.cpp | 50 +++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/layout/generic/ReflowInput.cpp b/layout/generic/ReflowInput.cpp index 1af7e798ef..4f5424dc44 100644 --- a/layout/generic/ReflowInput.cpp +++ b/layout/generic/ReflowInput.cpp @@ -226,6 +226,7 @@ ReflowInput::ReflowInput( mFlags.mAssumingHScrollbar = mFlags.mAssumingVScrollbar = false; mFlags.mIsColumnBalancing = false; mFlags.mIsFlexContainerMeasuringHeight = false; + mFlags.mTreatBSizeAsIndefinite = false; mFlags.mDummyParentReflowInput = false; mFlags.mShrinkWrap = !!(aFlags & COMPUTE_SIZE_SHRINK_WRAP); mFlags.mUseAutoBSize = !!(aFlags & COMPUTE_SIZE_USE_AUTO_BSIZE); @@ -2041,6 +2042,10 @@ ReflowInput::ComputeContainingBlockRectangle( WritingMode wm = aContainingBlockRI->GetWritingMode(); + if (aContainingBlockRI->mFlags.mTreatBSizeAsIndefinite) { + cbSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + } + // mFrameType for abs-pos tables is NS_CSS_FRAME_TYPE_BLOCK, so we need to // special case them here. if (NS_FRAME_GET_TYPE(mFrameType) == NS_CSS_FRAME_TYPE_ABSOLUTE || @@ -2077,12 +2082,13 @@ ReflowInput::ComputeContainingBlockRectangle( } } else { // an element in quirks mode gets a containing block based on looking for a - // parent with a non-auto height if the element has a percent height - // Note: We don't emulate this quirk for percents in calc() or in - // vertical writing modes. + // parent with a non-auto height if the element has a percent height. + // Note: We don't emulate this quirk for percents in calc(), or in vertical + // writing modes, or if the containing block is a flex or grid item. if (!wm.IsVertical() && NS_AUTOHEIGHT == cbSize.BSize(wm)) { if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() && + !aContainingBlockRI->mFrame->IsFlexOrGridItem() && mStylePosition->mHeight.GetUnit() == eStyleUnit_Percent) { cbSize.BSize(wm) = CalcQuirkContainingBlockHeight(aContainingBlockRI); } @@ -2218,7 +2224,8 @@ ReflowInput::InitConstraints(nsPresContext* aPresContext, // in quirks mode, get the cb height using the special quirk method if (!wm.IsVertical() && eCompatibility_NavQuirks == aPresContext->CompatibilityMode()) { - if (!IS_TABLE_CELL(fType)) { + if (!IS_TABLE_CELL(fType) && + !cbrs->mFrame->IsFlexOrGridItem()) { cbSize.BSize(wm) = CalcQuirkContainingBlockHeight(cbrs); if (cbSize.BSize(wm) == NS_AUTOHEIGHT) { blockSizeUnit = eStyleUnit_Auto; diff --git a/layout/generic/ReflowInput.h b/layout/generic/ReflowInput.h index a70549d8eb..52c90e6eeb 100644 --- a/layout/generic/ReflowInput.h +++ b/layout/generic/ReflowInput.h @@ -206,6 +206,13 @@ public: uint32_t mIsFlexContainerMeasuringHeight:1; // nsFlexContainerFrame is // reflowing this child to // measure its intrinsic height. + uint32_t mTreatBSizeAsIndefinite:1; // If this flag is set, the BSize of this frame should be considered + // indefinite for the purposes of percent resolution on child frames (we + // should behave as if ComputedBSize() were NS_INTRINSIC_ISIZE when doing + // percent resolution against this.ComputedBSize()). For example: flex + // items may have their ComputedBSize() resolved ahead-of-time by their + // flex container, and yet their BSize might have to be considered + // indefinite per https://drafts.csswg.org/css-flexbox/#definite-sizes uint32_t mDummyParentReflowInput:1; // a "fake" reflow state made // in order to be the parent // of a real one diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 18a0643f14..d60308f424 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -557,6 +557,8 @@ public: return mFlexShrink * mFlexBaseSize; } + bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; } + const AspectRatio IntrinsicRatio() const { return mIntrinsicRatio; } bool HasIntrinsicRatio() const { return !!mIntrinsicRatio; } @@ -799,6 +801,9 @@ protected: // Does this item need to resolve a min-[width|height]:auto (in main-axis). bool mNeedsMinSizeAutoResolution; + // Should we take care to treat this item's resolved BSize as indefinite? + bool mTreatBSizeAsIndefinite; + const WritingMode mWM; // The flex item's writing mode. uint8_t mAlignSelf; // My "align-self" computed value (with "auto" // swapped out for parent"s "align-items" value, @@ -1990,6 +1995,40 @@ FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, mAlignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; } + // Our main-size is considered definite if any of these are true: + // (a) flex container has definite main size. + // (b) flex item has a definite flex basis and is fully inflexible + // (NOTE: We don't actually check "fully inflexible" because webcompat + // may not agree with that restriction...) + // + // Hence, we need to take care to treat the final main-size as *indefinite* + // if none of these conditions are satisfied. + + // The flex item's main size is its BSize, and is considered definite under + // certain conditions laid out for definite flex-item main-sizes in the spec. + if (aAxisTracker.IsMainAxisHorizontal() || + (containerRS->ComputedBSize() != NS_UNCONSTRAINEDSIZE && + !containerRS->mFlags.mTreatBSizeAsIndefinite)) { + // The flex *container* has a definite main-size (either by being + // row-oriented [and using its own inline size which is by definition + // definite, or by being column-oriented and having a definite + // block-size). The spec says this means all of the flex items' + // post-flexing main sizes should *also* be treated as definite. + mTreatBSizeAsIndefinite = false; + } else if (aFlexBaseSize != NS_UNCONSTRAINEDSIZE) { + // The flex item has a definite flex basis, which we'll treat as making + // its main-size definite. + // XXXdholbert Technically the spec requires the flex item to *also* be + // fully inflexible in order to have its size treated as definite in this + // scenario, but no browser implements that additional restriction, so + // it's not clear that this additional requirement would be + // web-compatible... + mTreatBSizeAsIndefinite = false; + } else { + // Otherwise, we have to treat the item's BSize as indefinite. + mTreatBSizeAsIndefinite = true; + } + SetFlexBaseSizeAndMainSize(aFlexBaseSize); CheckForMinSizeAuto(aFlexItemReflowInput, aAxisTracker); @@ -2054,6 +2093,7 @@ FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, mIsStretched(false), mIsStrut(true), // (this is the constructor for making struts, after all) mNeedsMinSizeAutoResolution(false), + mTreatBSizeAsIndefinite(false), mWM(aContainerWM), mAlignSelf(NS_STYLE_ALIGN_FLEX_START) { @@ -4463,6 +4503,9 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, childReflowInput.SetComputedWidth(item->GetMainSize()); } else { childReflowInput.SetComputedHeight(item->GetMainSize()); + if (item->TreatBSizeAsIndefinite()) { + childReflowInput.mFlags.mTreatBSizeAsIndefinite = true; + } } } @@ -4802,6 +4845,9 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, } else { childReflowInput.SetComputedHeight(aItem.GetMainSize()); didOverrideComputedHeight = true; + if (aItem.TreatBSizeAsIndefinite()) { + childReflowInput.mFlags.mTreatBSizeAsIndefinite = true; + } } // Override reflow state's computed cross-size if either: @@ -4819,6 +4865,10 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, childReflowInput.SetComputedWidth(aItem.GetCrossSize()); didOverrideComputedWidth = true; } else { + // Note that in the above cases we don't need to worry about the BSize + // needing to be treated as indefinite, because this is for cases where + // the block size would always be considered definite (or where its + // definiteness would be irrelevant). childReflowInput.SetComputedHeight(aItem.GetCrossSize()); didOverrideComputedHeight = true; } -- cgit v1.2.3 From 2a6187709b4abe2a375811bc50d2ea160d9f2d18 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 13 Aug 2023 22:06:13 +0200 Subject: Issue #2284 - Update reftest for behaviour change. I've verified this against Edge-latest. --- layout/reftests/bugs/1128354-1-ref.html | 17 +++++++++++++++-- layout/reftests/bugs/1128354-1.html | 23 ++++++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/layout/reftests/bugs/1128354-1-ref.html b/layout/reftests/bugs/1128354-1-ref.html index a559370f22..a90b26e281 100644 --- a/layout/reftests/bugs/1128354-1-ref.html +++ b/layout/reftests/bugs/1128354-1-ref.html @@ -13,10 +13,21 @@ } .flexInnerHoriz { - display: flex; + height: -moz-fit-content; + margin: 0 0 auto 0; background: pink; } - + .height50pct { + height: -moz-fit-content; + margin: 0 0 auto 0; + background: brown; + } + .height0pct { + height: -moz-fit-content; + margin: 0 0 auto 0; + background: yellow; + } + .spacer { background: lightblue; height: 200px; @@ -28,6 +39,8 @@
text
+
fifty
+
zero
diff --git a/layout/reftests/bugs/1128354-1.html b/layout/reftests/bugs/1128354-1.html index c83bcf8b25..7c6dc87272 100644 --- a/layout/reftests/bugs/1128354-1.html +++ b/layout/reftests/bugs/1128354-1.html @@ -14,11 +14,26 @@ .flexInnerHoriz { display: flex; - height: 100%; /* If you delete this line, then pink area is stretched - to have its height match blue area. */ + /* This percent should not be resolvable, because our parent's + * height is indefinite (because our parent is a flex item with an + * indefinite flex basis, in an indefinite-main-sized flex container). + * So we just end up with our content height. + */ + height: 100%; background: pink; } - + .height50pct { + /* This percent should not be resolvable, for the same reason as above. + */ + height: 50%; + background: brown; + } + .height0pct { + /* This percent should not be resolvable, for the same reason as above. + */ + height: 0%; + background: yellow; + } .spacer { background: lightblue; height: 200px; @@ -30,6 +45,8 @@
text
+
fifty
+
zero
-- cgit v1.2.3 From 53b548e31ab64d3a723f44f63b99af044ba53147 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 13 Aug 2023 22:09:27 +0200 Subject: Issue #2284 - Adjust some in-content CSS to account for flex definite-sizing change. As of this change, flex items in a vertical flex container will sometimes be considered to have "indefinite" sizes, i.e. percent sizes in them will no longer resolve. To work around this, they need to provide a definite flex-basis (e.g. as part of the "flex" shorthand property) if we want percent sizes to resolve (instead of being treated as "auto") inside of them. --- toolkit/themes/shared/in-content/info-pages.inc.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/themes/shared/in-content/info-pages.inc.css b/toolkit/themes/shared/in-content/info-pages.inc.css index 4889602a09..29fcbe1ea8 100644 --- a/toolkit/themes/shared/in-content/info-pages.inc.css +++ b/toolkit/themes/shared/in-content/info-pages.inc.css @@ -96,7 +96,7 @@ ul { /* Trees */ .tree-container { margin-top: 1.2em; - flex-grow: 1; + flex: 1 0px; min-height: 12em; } -- cgit v1.2.3 From d16b014115b610e2f741c0f51a61b5643397bcba Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 14 Aug 2023 13:06:15 -0500 Subject: No Issue - Fix building WebRTC/Basilisk with --enable-debug A bogus MOZ_ASSERT was added at some point, this should make it work. --- media/webrtc/signaling/src/media-conduit/VideoConduit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index e6db06a685..37dcf05005 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -105,7 +105,7 @@ WebrtcVideoConduit::~WebrtcVideoConduit() // Release AudioConduit first by dropping reference on MainThread, where it expects to be SyncTo(nullptr); - MOZ_ASSERT(!mSendStream && !mRecvStream, "Call DeleteStreams prior to ~WebrtcVideoConduit."); + MOZ_ASSERT(!mPtrViEBase, "Call DeleteStreams prior to ~WebrtcVideoConduit."); } bool WebrtcVideoConduit::SetLocalSSRC(unsigned int ssrc) -- cgit v1.2.3 From a0fb8c06de8d4fc97bf38c36affcdc03ab5d8d82 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 14 Aug 2023 13:08:41 -0500 Subject: Issue #2255 - Disable untested code path in Stopwatch.(h|cpp). https://bugzilla.mozilla.org/show_bug.cgi?id=1325299 Multiprocessor support in Windows returned bogus times causing debug asserts. This may or may not be related to the issue but found it trying to debug it. --- js/src/vm/Stopwatch.cpp | 6 ++++-- js/src/vm/Stopwatch.h | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/js/src/vm/Stopwatch.cpp b/js/src/vm/Stopwatch.cpp index 46841b27f8..2c267d446d 100644 --- a/js/src/vm/Stopwatch.cpp +++ b/js/src/vm/Stopwatch.cpp @@ -409,7 +409,8 @@ AutoStopwatch::getCycles(JSRuntime* runtime) const cpuid_t inline AutoStopwatch::getCPU() const { -#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA +// Temporary disable untested code path. Issue #2255 +#if 0 //defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA PROCESSOR_NUMBER proc; GetCurrentProcessorNumberEx(&proc); @@ -423,7 +424,8 @@ AutoStopwatch::getCPU() const bool inline AutoStopwatch::isSameCPU(const cpuid_t& a, const cpuid_t& b) const { -#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA +// Temporary disable untested code path. Issue #2255 +#if 0 //defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA return a.group_ == b.group_ && a.number_ == b.number_; #else return true; diff --git a/js/src/vm/Stopwatch.h b/js/src/vm/Stopwatch.h index f3985c53aa..84da265a25 100644 --- a/js/src/vm/Stopwatch.h +++ b/js/src/vm/Stopwatch.h @@ -271,7 +271,8 @@ struct PerformanceMonitoring { uint64_t highestTimestampCounter_; }; -#if WINVER >= 0x0600 +// Temporary disable untested code path. Issue #2255 +#if 0 // WINVER >= 0x0600 struct cpuid_t { uint16_t group_; uint8_t number_; -- cgit v1.2.3 From 369ba2e7d6dd91328e1e83a037daadf8f4b497f4 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 14 Aug 2023 19:56:06 -0500 Subject: Issue #2266 - Part 1 - Allow MSVC and potentially GCC to use Address Sanitizer on Windows. Check for WINNT instead of CLANG-CL, except for the Blacklist that isn't supported by MSVC. Neither CLANG-CL nor MSVC require -fsanitize=address on the link phase. --- build/autoconf/sanitize.m4 | 6 +++--- build/moz.build | 8 +++++--- build/unix/moz.build | 3 --- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/build/autoconf/sanitize.m4 b/build/autoconf/sanitize.m4 index 3193d5c624..8b18cd5bc7 100644 --- a/build/autoconf/sanitize.m4 +++ b/build/autoconf/sanitize.m4 @@ -13,7 +13,7 @@ MOZ_ARG_ENABLE_BOOL(address-sanitizer, MOZ_ASAN= ) if test -n "$MOZ_ASAN"; then MOZ_LLVM_HACKS=1 - if test -n "$CLANG_CL"; then + if test "$OS_ARCH" = "WINNT"; then # Look for the ASan runtime binary if test "$CPU_ARCH" = "x86_64"; then MOZ_CLANG_RT_ASAN_LIB=clang_rt.asan_dynamic-x86_64.dll @@ -27,14 +27,14 @@ if test -n "$MOZ_ASAN"; then fi AC_SUBST(MOZ_CLANG_RT_ASAN_LIB_PATH) # Suppressing errors in recompiled code. - if test "$OS_ARCH" = "WINNT"; then + if test -n "$CLANG_CL"; then CFLAGS="-fsanitize-blacklist=$_topsrcdir/build/sanitizers/asan_blacklist_win.txt $CFLAGS" CXXFLAGS="-fsanitize-blacklist=$_topsrcdir/build/sanitizers/asan_blacklist_win.txt $CXXFLAGS" fi fi CFLAGS="-fsanitize=address $CFLAGS" CXXFLAGS="-fsanitize=address $CXXFLAGS" - if test -z "$CLANG_CL"; then + if test "$OS_ARCH" != "WINNT"; then LDFLAGS="-fsanitize=address $LDFLAGS" fi AC_DEFINE(MOZ_ASAN) diff --git a/build/moz.build b/build/moz.build index 6567dd944c..f3801976eb 100644 --- a/build/moz.build +++ b/build/moz.build @@ -58,9 +58,11 @@ FINAL_TARGET_FILES += ['/.gdbinit'] FINAL_TARGET_PP_FILES += ['.gdbinit_python.in'] OBJDIR_FILES += ['!/dist/bin/.gdbinit_python'] -# Install the clang-cl runtime library for ASAN next to the binaries we produce. -if CONFIG['MOZ_ASAN'] and CONFIG['CLANG_CL']: - FINAL_TARGET_FILES += ['%' + CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']] +# Install the clang runtime library for ASAN next to the binaries we produce. +if CONFIG['MOZ_ASAN'] and CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']: + FINAL_TARGET_FILES += [CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']] +if CONFIG['LLVM_SYMBOLIZER']: + FINAL_TARGET_FILES += [CONFIG['LLVM_SYMBOLIZER']] if CONFIG['MOZ_APP_BASENAME']: FINAL_TARGET_PP_FILES += ['application.ini'] diff --git a/build/unix/moz.build b/build/unix/moz.build index 88c933295c..cd455b7a4d 100644 --- a/build/unix/moz.build +++ b/build/unix/moz.build @@ -9,9 +9,6 @@ if CONFIG['MOZ_LIBSTDCXX_TARGET_VERSION'] or CONFIG['MOZ_LIBSTDCXX_HOST_VERSION' if CONFIG['USE_ELF_HACK']: DIRS += ['elfhack'] -if CONFIG['LLVM_SYMBOLIZER']: - FINAL_TARGET_FILES += ['/' + CONFIG['LLVM_SYMBOLIZER']] - SDK_FILES.bin += [ 'run-mozilla.sh', ] -- cgit v1.2.3 From ff81f4969c4c6394e9553fdcd442a5ea21e8a7b1 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 14 Aug 2023 23:40:46 -0500 Subject: Issue #2266 - Part 2 - Add Leak and Undefined Behavior Sanitizer support. Also fix building on FreeBSD and Linux, from the previous commit. --- build/autoconf/sanitize.m4 | 34 ++++++++++++++++++++++++++++++++++ build/moz.build | 2 +- build/moz.configure/old.configure | 2 ++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/build/autoconf/sanitize.m4 b/build/autoconf/sanitize.m4 index 8b18cd5bc7..4f038ccb6f 100644 --- a/build/autoconf/sanitize.m4 +++ b/build/autoconf/sanitize.m4 @@ -80,6 +80,40 @@ if test -n "$MOZ_TSAN"; then fi AC_SUBST(MOZ_TSAN) +dnl ======================================================== +dnl = Use Leak Sanitizer +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(leak-sanitizer, +[ --enable-leak-sanitizer Enable Leak Sanitizer (default=no)], + MOZ_LSAN=1, + MOZ_LSAN= ) +if test -n "$MOZ_LSAN"; then + MOZ_LLVM_HACKS=1 + LDFLAGS="-fsanitize=leak $LDFLAGS" + AC_DEFINE(MOZ_LSAN) + MOZ_PATH_PROG(LLVM_SYMBOLIZER, llvm-symbolizer) +fi +AC_SUBST(MOZ_LSAN) + +dnl ======================================================== +dnl = Use Undefined Behavior Sanitizer +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(undefined-sanitizer, +[ --enable-undefined-sanitizer Enable Undefined Behavior Sanitizer (default=no)], + MOZ_UBSAN=1, + MOZ_UBSAN= ) +if test -n "$MOZ_UBSAN"; then + MOZ_LLVM_HACKS=1 + CFLAGS="-fsanitize=undefined $CFLAGS" + CXXFLAGS="-fsanitize=undefined $CXXFLAGS" + if test -z "$CLANG_CL"; then + LDFLAGS="-fsanitize=undefined $LDFLAGS" + fi + AC_DEFINE(MOZ_UBSAN) + MOZ_PATH_PROG(LLVM_SYMBOLIZER, llvm-symbolizer) +fi +AC_SUBST(MOZ_UBSAN) + # The LLVM symbolizer is used by all sanitizers AC_SUBST(LLVM_SYMBOLIZER) diff --git a/build/moz.build b/build/moz.build index f3801976eb..1f1c7474cd 100644 --- a/build/moz.build +++ b/build/moz.build @@ -62,7 +62,7 @@ OBJDIR_FILES += ['!/dist/bin/.gdbinit_python'] if CONFIG['MOZ_ASAN'] and CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']: FINAL_TARGET_FILES += [CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']] if CONFIG['LLVM_SYMBOLIZER']: - FINAL_TARGET_FILES += [CONFIG['LLVM_SYMBOLIZER']] + FINAL_TARGET_FILES += ['/' + CONFIG['LLVM_SYMBOLIZER']] if CONFIG['MOZ_APP_BASENAME']: FINAL_TARGET_PP_FILES += ['application.ini'] diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure index 3d3873eae8..091efc398d 100644 --- a/build/moz.configure/old.configure +++ b/build/moz.configure/old.configure @@ -197,6 +197,7 @@ def old_configure_options(*options): '--enable-ios-target', '--enable-jitspew', '--enable-js-lto', + '--enable-leak-sanitizer', '--enable-libjpeg-turbo', '--enable-libproxy', '--enable-llvm-hacks', @@ -246,6 +247,7 @@ def old_configure_options(*options): '--enable-thread-sanitizer', '--enable-trace-logging', '--enable-ui-locale', + '--enable-undefined-sanitizer', '--enable-universalchardet', '--enable-updater', '--enable-url-classifier', -- cgit v1.2.3 From 6d65f1d192a7b8810fa43dae189d302de2d0cf2f Mon Sep 17 00:00:00 2001 From: athenian200 Date: Fri, 18 Aug 2023 10:29:49 -0500 Subject: Issue #2290 - Hunspell must be built into libxul on SunOS. --- extensions/spellcheck/hunspell/src/moz.build | 15 +++++++++++---- toolkit/library/moz.build | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/extensions/spellcheck/hunspell/src/moz.build b/extensions/spellcheck/hunspell/src/moz.build index 09764ec711..01f49ddd03 100644 --- a/extensions/spellcheck/hunspell/src/moz.build +++ b/extensions/spellcheck/hunspell/src/moz.build @@ -19,11 +19,18 @@ SOURCES += [ DEFINES['BUILDING_LIBHUNSPELL'] = True -SharedLibrary('hunspell') +# Hunspell must be built into libxul on SunOS. See UXP Issue #2290. +if CONFIG['OS_TARGET'] == 'SunOS': -USE_LIBS += [ - 'mozglue' -] + FINAL_LIBRARY = 'xul' + +else: + + SharedLibrary('hunspell') + + USE_LIBS += [ + 'mozglue' + ] LOCAL_INCLUDES += [ '../glue', diff --git a/toolkit/library/moz.build b/toolkit/library/moz.build index a80cf1dc73..d274cfa70b 100644 --- a/toolkit/library/moz.build +++ b/toolkit/library/moz.build @@ -196,7 +196,7 @@ if CONFIG['MOZ_SYSTEM_JPEG']: if CONFIG['MOZ_SYSTEM_HUNSPELL']: OS_LIBS += CONFIG['MOZ_HUNSPELL_LIBS'] -else: +elif CONFIG['OS_TARGET'] != 'SunOS': USE_LIBS += [ 'hunspell' ] if not CONFIG['MOZ_TREE_PIXMAN']: -- cgit v1.2.3 From 178a43839dcdbb6645c09cb2e5905eb11427a375 Mon Sep 17 00:00:00 2001 From: Matheus Marinho Date: Fri, 18 Aug 2023 23:23:51 -0300 Subject: Add --enable-irc and --enable-inspector to old.configure --- build/moz.configure/old.configure | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure index 091efc398d..0a3b84be7b 100644 --- a/build/moz.configure/old.configure +++ b/build/moz.configure/old.configure @@ -307,6 +307,8 @@ def old_configure_options(*options): '--enable-ldap', '--enable-mapi', '--enable-calendar', + '--enable-inspector', + '--enable-irc', '--enable-incomplete-external-linkage', '--enable-mailnews', '--enable-mailnews-oauth2', -- cgit v1.2.3 From 8ac09570720439b0788d93ffd860cc9f34d9ce9e Mon Sep 17 00:00:00 2001 From: Matheus Marinho Date: Fri, 25 Aug 2023 17:18:15 -0300 Subject: Bug 1274518 - Add IPDL preprocessing support Co-authored-by: Kai-Zhen Li --- python/mozbuild/mozbuild/backend/common.py | 31 +++++++++++++++++++--- python/mozbuild/mozbuild/backend/recursivemake.py | 24 +++++++++++++---- python/mozbuild/mozbuild/backend/tup.py | 4 +-- python/mozbuild/mozbuild/compilation/database.py | 4 +-- python/mozbuild/mozbuild/frontend/context.py | 7 +++++ python/mozbuild/mozbuild/frontend/data.py | 12 +++++++++ python/mozbuild/mozbuild/frontend/emitter.py | 2 ++ .../test/backend/data/ipdl_sources/bar/moz.build | 4 +++ .../test/backend/data/ipdl_sources/foo/moz.build | 4 +++ .../mozbuild/test/backend/test_recursivemake.py | 6 ++--- .../test/frontend/data/ipdl_sources/bar/moz.build | 4 +++ .../test/frontend/data/ipdl_sources/foo/moz.build | 4 +++ .../mozbuild/test/frontend/test_emitter.py | 11 +++++++- 13 files changed, 100 insertions(+), 17 deletions(-) diff --git a/python/mozbuild/mozbuild/backend/common.py b/python/mozbuild/mozbuild/backend/common.py index a90aa1e5d5..351c6dbc81 100644 --- a/python/mozbuild/mozbuild/backend/common.py +++ b/python/mozbuild/mozbuild/backend/common.py @@ -29,6 +29,7 @@ from mozbuild.frontend.data import ( FinalTargetFiles, GeneratedEventWebIDLFile, GeneratedWebIDLFile, + PreprocessedIPDLFile, PreprocessedTestWebIDLFile, PreprocessedWebIDLFile, SharedLibrary, @@ -215,6 +216,21 @@ class BinariesCollection(object): self.shared_libraries = [] self.programs = [] +class IPDLCollection(object): + """Collects IPDL files during the build.""" + + def __init__(self): + self.sources = set() + self.preprocessed_sources = set() + + def all_sources(self): + return self.sources | self.preprocessed_sources + + def all_regular_sources(self): + return self.sources + + def all_preprocessed_sources(self): + return self.preprocessed_sources class CommonBackend(BuildBackend): """Holds logic common to all build backends.""" @@ -225,7 +241,7 @@ class CommonBackend(BuildBackend): self._webidls = WebIDLCollection() self._binaries = BinariesCollection() self._configs = set() - self._ipdl_sources = set() + self._ipdls = IPDLCollection() def consume_object(self, obj): self._configs.add(obj.config) @@ -270,6 +286,10 @@ class CommonBackend(BuildBackend): self._webidls.generated_sources.add(mozpath.join(obj.srcdir, obj.basename)) + elif isinstance(obj, PreprocessedIPDLFile): + self._ipdls.preprocessed_sources.add(mozpath.join( + obj.srcdir, obj.basename)) + elif isinstance(obj, PreprocessedWebIDLFile): self._webidls.preprocessed_sources.add(mozpath.join( obj.srcdir, obj.basename)) @@ -278,7 +298,7 @@ class CommonBackend(BuildBackend): self._webidls.example_interfaces.add(obj.name) elif isinstance(obj, IPDLFile): - self._ipdl_sources.add(mozpath.join(obj.srcdir, obj.basename)) + self._ipdls.sources.add(mozpath.join(obj.srcdir, obj.basename)) elif isinstance(obj, UnifiedSources): if obj.have_unified_mapping: @@ -305,7 +325,9 @@ class CommonBackend(BuildBackend): self._handle_webidl_collection(self._webidls) - sorted_ipdl_sources = list(sorted(self._ipdl_sources)) + sorted_ipdl_sources = list(sorted(self._ipdls.all_sources())) + sorted_nonstatic_ipdl_sources = list(sorted(self._ipdls.all_preprocessed_sources())) + sorted_static_ipdl_sources = list(sorted(self._ipdls.all_regular_sources())) def files_from(ipdl): base = mozpath.basename(ipdl) @@ -328,7 +350,8 @@ class CommonBackend(BuildBackend): files_per_unified_file=16)) self._write_unified_files(unified_source_mapping, ipdl_dir, poison_windows_h=False) - self._handle_ipdl_sources(ipdl_dir, sorted_ipdl_sources, unified_source_mapping) + self._handle_ipdl_sources(ipdl_dir, sorted_ipdl_sources, sorted_nonstatic_ipdl_sources, + sorted_static_ipdl_sources, unified_source_mapping) for config in self._configs: self.backend_input_files.add(config.source) diff --git a/python/mozbuild/mozbuild/backend/recursivemake.py b/python/mozbuild/mozbuild/backend/recursivemake.py index 4a37b29663..7f3b36c597 100644 --- a/python/mozbuild/mozbuild/backend/recursivemake.py +++ b/python/mozbuild/mozbuild/backend/recursivemake.py @@ -1350,18 +1350,32 @@ class RecursiveMakeBackend(CommonBackend): self._makefile_out_count += 1 - def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, - unified_ipdl_cppsrcs_mapping): + def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, sorted_nonstatic_ipdl_sources, + sorted_static_ipdl_sources, unified_ipdl_cppsrcs_mapping): # Write out a master list of all IPDL source files. mk = Makefile() - mk.add_statement('ALL_IPDLSRCS := %s' % ' '.join(sorted_ipdl_sources)) + sorted_nonstatic_ipdl_basenames = list() + for source in sorted_nonstatic_ipdl_sources: + basename = os.path.basename(source) + sorted_nonstatic_ipdl_basenames.append(basename) + rule = mk.create_rule([basename]) + rule.add_dependencies([source]) + rule.add_commands([ + '$(RM) $@', + '$(call py_action,preprocessor,$(DEFINES) $(ACDEFINES) ' + '$< -o $@)' + ]) + + mk.add_statement('ALL_IPDLSRCS := %s %s' % (' '.join(sorted_nonstatic_ipdl_basenames), + ' '.join(sorted_static_ipdl_sources))) self._add_unified_build_rules(mk, unified_ipdl_cppsrcs_mapping, unified_files_makefile_variable='CPPSRCS') - mk.add_statement('IPDLDIRS := %s' % ' '.join(sorted(set(mozpath.dirname(p) - for p in self._ipdl_sources)))) + # Preprocessed ipdl files are generated in ipdl_dir. + mk.add_statement('IPDLDIRS := %s %s' % (ipdl_dir, ' '.join(sorted(set(mozpath.dirname(p) + for p in sorted_static_ipdl_sources))))) with self._write_file(mozpath.join(ipdl_dir, 'ipdlsrcs.mk')) as ipdls: mk.dump(ipdls, removal_guard=False) diff --git a/python/mozbuild/mozbuild/backend/tup.py b/python/mozbuild/mozbuild/backend/tup.py index 0f7250eb01..b148532c89 100644 --- a/python/mozbuild/mozbuild/backend/tup.py +++ b/python/mozbuild/mozbuild/backend/tup.py @@ -298,8 +298,8 @@ class TupOnly(CommonBackend, PartialBackend): outputs=[output], ) - def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, - unified_ipdl_cppsrcs_mapping): + def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, sorted_nonstatic_ipdl_sources, + sorted_static_ipdl_sources, unified_ipdl_cppsrcs_mapping): # TODO: This isn't implemented yet in the tup backend, but it is called # by the CommonBackend. pass diff --git a/python/mozbuild/mozbuild/compilation/database.py b/python/mozbuild/mozbuild/compilation/database.py index 4193e1bcf4..20b09df488 100644 --- a/python/mozbuild/mozbuild/compilation/database.py +++ b/python/mozbuild/mozbuild/compilation/database.py @@ -177,8 +177,8 @@ class CompileDBBackend(CommonBackend): def _handle_idl_manager(self, idl_manager): pass - def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, - unified_ipdl_cppsrcs_mapping): + def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, sorted_nonstatic_ipdl_sources, + sorted_static_ipdl_sources, unified_ipdl_cppsrcs_mapping): for f in unified_ipdl_cppsrcs_mapping: self._build_db_line(ipdl_dir, None, self.environment, f[0], '.cpp') diff --git a/python/mozbuild/mozbuild/frontend/context.py b/python/mozbuild/mozbuild/frontend/context.py index 7c44a6836a..46f6d12cb5 100644 --- a/python/mozbuild/mozbuild/frontend/context.py +++ b/python/mozbuild/mozbuild/frontend/context.py @@ -1388,6 +1388,13 @@ VARIABLES = { not use this flag. """), + 'PREPROCESSED_IPDL_SOURCES': (StrictOrderingOnAppendList, list, + """Preprocessed IPDL source files. + + These files will be preprocessed, then parsed and converted to + ``.cpp`` files. + """), + 'IPDL_SOURCES': (StrictOrderingOnAppendList, list, """IPDL source files. diff --git a/python/mozbuild/mozbuild/frontend/data.py b/python/mozbuild/mozbuild/frontend/data.py index 83d7b122ab..c6ed4f65ef 100644 --- a/python/mozbuild/mozbuild/frontend/data.py +++ b/python/mozbuild/mozbuild/frontend/data.py @@ -218,6 +218,18 @@ class IPDLFile(ContextDerived): self.basename = path +class PreprocessedIPDLFile(ContextDerived): + """Describes an individual .ipdl source file that requires preprocessing.""" + + __slots__ = ( + 'basename', + ) + + def __init__(self, context, path): + ContextDerived.__init__(self, context) + + self.basename = path + class WebIDLFile(ContextDerived): """Describes an individual .webidl source file.""" diff --git a/python/mozbuild/mozbuild/frontend/emitter.py b/python/mozbuild/mozbuild/frontend/emitter.py index 78d92c4236..faa41786ac 100644 --- a/python/mozbuild/mozbuild/frontend/emitter.py +++ b/python/mozbuild/mozbuild/frontend/emitter.py @@ -54,6 +54,7 @@ from .data import ( ObjdirFiles, ObjdirPreprocessedFiles, PerSourceFlag, + PreprocessedIPDLFile, PreprocessedTestWebIDLFile, PreprocessedWebIDLFile, Program, @@ -862,6 +863,7 @@ class TreeMetadataEmitter(LoggingMixin): ('GENERATED_EVENTS_WEBIDL_FILES', GeneratedEventWebIDLFile), ('GENERATED_WEBIDL_FILES', GeneratedWebIDLFile), ('IPDL_SOURCES', IPDLFile), + ('PREPROCESSED_IPDL_SOURCES', PreprocessedIPDLFile), ('PREPROCESSED_TEST_WEBIDL_FILES', PreprocessedTestWebIDLFile), ('PREPROCESSED_WEBIDL_FILES', PreprocessedWebIDLFile), ('TEST_WEBIDL_FILES', TestWebIDLFile), diff --git a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build index eb37725230..126854c383 100644 --- a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build +++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build @@ -3,6 +3,10 @@ # 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/. +PREPROCESSED_IPDL_SOURCES += [ + 'bar1.ipdl', +] + IPDL_SOURCES += [ 'bar.ipdl', 'bar2.ipdlh', diff --git a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build index 84d40ac1d3..4047c31216 100644 --- a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build +++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build @@ -3,6 +3,10 @@ # 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/. +PREPROCESSED_IPDL_SOURCES += [ + 'foo1.ipdl', +] + IPDL_SOURCES += [ 'foo.ipdl', 'foo2.ipdlh', diff --git a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py index 87f50f4973..a592ab55a3 100644 --- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py +++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py @@ -656,7 +656,7 @@ class TestRecursiveMakeBackend(BackendTester): self.assertEqual(m, m2) def test_ipdl_sources(self): - """Test that IPDL_SOURCES are written to ipdlsrcs.mk correctly.""" + """Test that PREPROCESSED_IPDL_SOURCES and IPDL_SOURCES are written to ipdlsrcs.mk correctly.""" env = self._consume('ipdl_sources', RecursiveMakeBackend) manifest_path = mozpath.join(env.topobjdir, @@ -667,9 +667,9 @@ class TestRecursiveMakeBackend(BackendTester): topsrcdir = env.topsrcdir.replace(os.sep, '/') expected = [ - "ALL_IPDLSRCS := %s/bar/bar.ipdl %s/bar/bar2.ipdlh %s/foo/foo.ipdl %s/foo/foo2.ipdlh" % tuple([topsrcdir] * 4), + "ALL_IPDLSRCS := bar1.ipdl foo1.ipdl %s/bar/bar.ipdl %s/bar/bar2.ipdlh %s/foo/foo.ipdl %s/foo/foo2.ipdlh" % tuple([topsrcdir] * 4), "CPPSRCS := UnifiedProtocols0.cpp", - "IPDLDIRS := %s/bar %s/foo" % (topsrcdir, topsrcdir), + "IPDLDIRS := %s/ipc/ipdl %s/bar %s/foo" % (env.topobjdir, topsrcdir, topsrcdir), ] found = [str for str in lines if str.startswith(('ALL_IPDLSRCS', diff --git a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build index eb37725230..126854c383 100644 --- a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build +++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build @@ -3,6 +3,10 @@ # 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/. +PREPROCESSED_IPDL_SOURCES += [ + 'bar1.ipdl', +] + IPDL_SOURCES += [ 'bar.ipdl', 'bar2.ipdlh', diff --git a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build index 84d40ac1d3..4047c31216 100644 --- a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build +++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build @@ -3,6 +3,10 @@ # 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/. +PREPROCESSED_IPDL_SOURCES += [ + 'foo1.ipdl', +] + IPDL_SOURCES += [ 'foo.ipdl', 'foo2.ipdlh', diff --git a/python/mozbuild/mozbuild/test/frontend/test_emitter.py b/python/mozbuild/mozbuild/test/frontend/test_emitter.py index 191727381d..e1e3fa5ce2 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py +++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py @@ -30,6 +30,7 @@ from mozbuild.frontend.data import ( JARManifest, LinkageMultipleRustLibrariesError, LocalInclude, + PreprocessedIPDLFile, Program, SdkFiles, SharedLibrary, @@ -688,9 +689,12 @@ class TestEmitterBasic(unittest.TestCase): objs = self.read_topsrcdir(reader) ipdls = [] + nonstatic_ipdls = [] for o in objs: if isinstance(o, IPDLFile): ipdls.append('%s/%s' % (o.relativedir, o.basename)) + elif isinstance(o, PreprocessedIPDLFile): + nonstatic_ipdls.append('%s/%s' % (o.relativedir, o.basename)) expected = [ 'bar/bar.ipdl', @@ -699,7 +703,12 @@ class TestEmitterBasic(unittest.TestCase): 'foo/foo2.ipdlh', ] - self.assertEqual(ipdls, expected) + expected = [ + 'bar/bar1.ipdl', + 'foo/foo1.ipdl', + ] + + self.assertEqual(nonstatic_ipdls, expected) def test_local_includes(self): """Test that LOCAL_INCLUDES is emitted correctly.""" -- cgit v1.2.3 From 92ecaaeaa90e87655ce58ad560e7621cb8001c4d Mon Sep 17 00:00:00 2001 From: Martok Date: Tue, 29 Aug 2023 22:58:21 +0200 Subject: Issue #2298 - Carry private-ness of names through ParseNodeHandler --- js/src/frontend/FullParseHandler.h | 4 ++++ js/src/frontend/ParseNode.h | 4 ++++ js/src/frontend/SyntaxParseHandler.h | 12 +++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index e27b9f540a..d2c8bbc2df 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -965,6 +965,10 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return node->isKind(PNK_NAME); } + bool isPrivateName(Node node) { + return node->isKind(PNK_NAME) && node->as().isPrivateName(); + } + bool isArgumentsAnyParentheses(Node node, ExclusiveContext* cx) { return node->isKind(PNK_NAME) && node->as().atom() == cx->names().arguments; } diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index b1fcab0735..439a6d8937 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -953,6 +953,10 @@ class NameNode : public ParseNode JSAtom* atom() const { return pn_u.name.atom; } + + bool isPrivateName() const { + return atom()->asPropertyName()->latin1OrTwoByteChar(0) == '#'; + } ParseNode* initializer() const { return pn_u.name.initOrStmt; diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 818530cfe9..80ca13ce45 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -104,6 +104,9 @@ class SyntaxParseHandler // Node representing the "async" name, which may actually be a // contextual keyword. NodePotentialAsyncKeyword, + + // Node representing a private name. Handled mostly like NodeUnparenthesizedName. + NodePrivateName, // Valuable for recognizing potential destructuring patterns. NodeUnparenthesizedArray, @@ -212,6 +215,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return NodePotentialAsyncKeyword; if (name == cx->names().eval) return NodeUnparenthesizedEvalName; + if (name->length() >= 1 && name->latin1OrTwoByteChar(0) == '#') + return NodePrivateName; return NodeUnparenthesizedName; } @@ -614,7 +619,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return node == NodeUnparenthesizedArgumentsName || node == NodeUnparenthesizedEvalName || node == NodeUnparenthesizedName || - node == NodePotentialAsyncKeyword; + node == NodePotentialAsyncKeyword || + node == NodePrivateName; } bool isNameAnyParentheses(Node node) { @@ -625,6 +631,10 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) node == NodeParenthesizedName; } + bool isPrivateName(Node node) { + return node == NodePrivateName; + } + bool isArgumentsAnyParentheses(Node node, ExclusiveContext* cx) { return node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName; } -- cgit v1.2.3 From 8b9d69f912cc52fb8a4b007041922bad8e131e82 Mon Sep 17 00:00:00 2001 From: Martok Date: Tue, 29 Aug 2023 23:51:32 +0200 Subject: Issue #2298 - Support '#x in obj' ergonomic brand check syntax --- js/src/frontend/BytecodeEmitter.cpp | 19 ++++++++++++++++--- js/src/frontend/Parser.cpp | 24 +++++++++++++++++++++++- js/src/js.msg | 1 + 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index cb249c254c..462edfd91e 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -7754,10 +7754,23 @@ bool BytecodeEmitter::emitLeftAssociative(ListNode* node) { // Left-associative operator chain. - if (!emitTree(node->head())) - return false; JSOp op = node->getOp(); - ParseNode* nextExpr = node->head()->pn_next; + ParseNode* headExpr = node->head(); + if (op == JSOP_IN && headExpr->isKind(PNK_NAME) && headExpr->as().isPrivateName()) { + // {Goanna} The only way a "naked" private name can show up as the leftmost side of an in-chain + // is from an ergonomic brand check (`this.#x in ...` would be a PNK_DOT child node). + // Instead of going through the emitTree machinery, we pretend that this identifier + // reference is actually a string, which allows us to use the JSOP_IN interpreter routines. + // This erroneously doesn't call updateLineNumberNotes, but this is not a big issue: + // the begin pos is correct as we're on the start of the current tree, the end is on the + // same line anyway. + if (!emitAtomOp(headExpr->as().atom(), JSOP_STRING)) + return false; + } else { + if (!emitTree(headExpr)) + return false; + } + ParseNode* nextExpr = headExpr->pn_next; do { if (!emitTree(nextExpr)) return false; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 9da893f94f..4e3181e06b 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8949,6 +8949,15 @@ Parser::orExpr1(InHandling inHandling, YieldHandling yieldHandling if (!tokenStream.getToken(&tok)) return null(); + // Ensure that if we have a private name lhs we are legally constructing a + // `#x in obj` expression: + if (handler.isPrivateName(pn)) { + if (tok != TOK_IN) { + error(JSMSG_ILLEGAL_PRIVATE_NAME); + return null(); + } + } + ParseNodeKind pnk; if (tok == TOK_IN ? inHandling == InAllowed : TokenKindIsBinaryOp(tok)) { // We're definitely not in a destructuring context, so report any @@ -8985,7 +8994,20 @@ Parser::orExpr1(InHandling inHandling, YieldHandling yieldHandling // If we have not detected a mixing error at this point, record that // we have an unparenthesized expression, in case we have one later. unparenthesizedExpression = EnforcedParentheses::CoalesceExpr; - break; + break; + case TOK_IN: + // if the LHS is a private name, and the operator is In, + // ensure we're construcing an ergonomic brand check of + // '#x in y', rather than having a higher precedence operator + // like + cause a different reduction, such as + // 1 + #x in y. + if (handler.isPrivateName(pn)) { + if (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(PNK_IN)) { + error(JSMSG_ILLEGAL_PRIVATE_NAME); + return null(); + } + } + break; default: // Do nothing in other cases. break; diff --git a/js/src/js.msg b/js/src/js.msg index 1cb571d189..340cfd6ad2 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -364,6 +364,7 @@ MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot b MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain") MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes") MSG_DEF(JSMSG_FIELDS_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "fields are not currently supported") +MSG_DEF(JSMSG_ILLEGAL_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "private names aren't valid in this context") // asm.js MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}") -- cgit v1.2.3 From 45ec6b3a8663995e2f0049436add2dfbe214071e Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 30 Aug 2023 10:52:52 +0200 Subject: Issue #2293 - Add preferences to disable CSS animation/transition props. This resolves #2293 --- layout/style/nsCSSPropList.h | 28 ++++++++++++++-------------- modules/libpref/init/all.js | 6 ++++++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index f2903273e5..0694817515 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -366,14 +366,14 @@ CSS_PROP_SHORTHAND( animation, Animation, CSS_PROPERTY_PARSE_FUNCTION, - "") + "layout.css.animation.enabled") CSS_PROP_DISPLAY( animation-delay, animation_delay, AnimationDelay, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_TIME, // used by list parsing nullptr, CSS_PROP_NO_OFFSET, @@ -384,7 +384,7 @@ CSS_PROP_DISPLAY( AnimationDirection, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD, // used by list parsing kAnimationDirectionKTable, CSS_PROP_NO_OFFSET, @@ -395,7 +395,7 @@ CSS_PROP_DISPLAY( AnimationDuration, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_TIME | VARIANT_NONNEGATIVE_DIMENSION, // used by list parsing nullptr, CSS_PROP_NO_OFFSET, @@ -406,7 +406,7 @@ CSS_PROP_DISPLAY( AnimationFillMode, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD, // used by list parsing kAnimationFillModeKTable, CSS_PROP_NO_OFFSET, @@ -420,7 +420,7 @@ CSS_PROP_DISPLAY( // http://lists.w3.org/Archives/Public/www-style/2011Mar/0355.html CSS_PROPERTY_VALUE_NONNEGATIVE | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD | VARIANT_NUMBER, // used by list parsing kAnimationIterationCountKTable, CSS_PROP_NO_OFFSET, @@ -431,7 +431,7 @@ CSS_PROP_DISPLAY( AnimationName, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", // FIXME: The spec should say something about 'inherit' and 'initial' // not being allowed. VARIANT_NONE | VARIANT_IDENTIFIER_NO_INHERIT, // used by list parsing @@ -444,7 +444,7 @@ CSS_PROP_DISPLAY( AnimationPlayState, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD, // used by list parsing kAnimationPlayStateKTable, CSS_PROP_NO_OFFSET, @@ -455,7 +455,7 @@ CSS_PROP_DISPLAY( AnimationTimingFunction, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD | VARIANT_TIMING_FUNCTION, // used by list parsing kTransitionTimingFunctionKTable, CSS_PROP_NO_OFFSET, @@ -4277,14 +4277,14 @@ CSS_PROP_SHORTHAND( transition, Transition, CSS_PROPERTY_PARSE_FUNCTION, - "") + "layout.css.transition.enabled") CSS_PROP_DISPLAY( transition-delay, transition_delay, TransitionDelay, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.transition.enabled", VARIANT_TIME, // used by list parsing nullptr, CSS_PROP_NO_OFFSET, @@ -4295,7 +4295,7 @@ CSS_PROP_DISPLAY( TransitionDuration, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.transition.enabled", VARIANT_TIME | VARIANT_NONNEGATIVE_DIMENSION, // used by list parsing nullptr, CSS_PROP_NO_OFFSET, @@ -4306,7 +4306,7 @@ CSS_PROP_DISPLAY( TransitionProperty, CSS_PROPERTY_PARSE_FUNCTION | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.transition.enabled", VARIANT_IDENTIFIER | VARIANT_NONE | VARIANT_ALL, // used only in shorthand nullptr, CSS_PROP_NO_OFFSET, @@ -4317,7 +4317,7 @@ CSS_PROP_DISPLAY( TransitionTimingFunction, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.transition.enabled", VARIANT_KEYWORD | VARIANT_TIMING_FUNCTION, // used by list parsing kTransitionTimingFunctionKTable, CSS_PROP_NO_OFFSET, diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 267cb39d6d..9aada9bb90 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2448,6 +2448,12 @@ pref("layout.css.mix-blend-mode.enabled", true); // Is support for isolation enabled? pref("layout.css.isolation.enabled", true); +// Is support for CSS animation properties enabled? +pref("layout.css.animation.enabled", true); + +// Is support for CSS transition properties enabled? +pref("layout.css.transition.enabled", true); + // Is support for CSS Filters enabled? pref("layout.css.filters.enabled", true); -- cgit v1.2.3 From 7fdc866b5a259b209337b9550c902b1bb1e7aa65 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 30 Aug 2023 12:48:43 +0200 Subject: [XPCOM] Add xll files to the executables list. --- xpcom/io/nsLocalFileWin.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp index 42bd83d45e..e5a0131a77 100644 --- a/xpcom/io/nsLocalFileWin.cpp +++ b/xpcom/io/nsLocalFileWin.cpp @@ -3086,7 +3086,8 @@ nsLocalFile::IsExecutable(bool* aResult) "ws", "wsc", "wsf", - "wsh" + "wsh", + "xll" // MS Excel dynamic link library }; nsDependentSubstring ext = Substring(path, dotIdx + 1); for (size_t i = 0; i < ArrayLength(executableExts); ++i) { -- cgit v1.2.3 From 226f97e2f44b1bb2a9ad602fd7f5ef2c5a0dd5ac Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 30 Aug 2023 16:38:42 +0200 Subject: [network] Add locking around access to WebSocketChannel::mPMCECompressor --- netwerk/protocol/websocket/WebSocketChannel.cpp | 80 +++++++++++++++---------- netwerk/protocol/websocket/WebSocketChannel.h | 2 + 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index 058dc46417..4b087a8bf2 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -1191,6 +1191,7 @@ WebSocketChannel::WebSocketChannel() : mBufferSize(kIncomingBufferInitialSize), mCurrentOut(nullptr), mCurrentOutSent(0), + mCompressorMutex("WebSocketChannel::mCompressorMutex"), mDynamicOutputSize(0), mDynamicOutput(nullptr), mPrivateBrowsing(false), @@ -1627,6 +1628,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count) if (rsvBits) { // PMCE sets RSV1 bit in the first fragment when the non-control frame // is deflated + MutexAutoLock lock(mCompressorMutex); if (mPMCECompressor && rsvBits == kRsv1Bit && mFragmentAccumulator == 0 && !(opcode & kControlFrameMask)) { mPMCECompressor->SetMessageDeflated(); @@ -1700,25 +1702,28 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count) LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n", opcode)); } else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) { - bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated(); - LOG(("WebSocketChannel:: %stext frame received\n", - isDeflated ? "deflated " : "")); - if (mListenerMT) { nsCString utf8Data; - if (isDeflated) { - rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data); - if (NS_FAILED(rv)) { - return rv; - } - LOG(("WebSocketChannel:: message successfully inflated " - "[origLength=%d, newLength=%d]\n", payloadLength, - utf8Data.Length())); - } else { - if (!utf8Data.Assign((const char *)payload, payloadLength, - mozilla::fallible)) { - return NS_ERROR_OUT_OF_MEMORY; + { // lockscope + MutexAutoLock lock(mCompressorMutex); + bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated(); + LOG(("WebSocketChannel:: %stext frame received\n", + isDeflated ? "deflated " : "")); + + if (isDeflated) { + rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data); + if (NS_FAILED(rv)) { + return rv; + } + LOG(("WebSocketChannel:: message successfully inflated " + "[origLength=%d, newLength=%d]\n", payloadLength, + utf8Data.Length())); + } else { + if (!utf8Data.Assign((const char *)payload, payloadLength, + mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } } } @@ -1835,25 +1840,28 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count) mService->FrameReceived(mSerial, mInnerWindowID, frame.forget()); } } else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) { - bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated(); - LOG(("WebSocketChannel:: %sbinary frame received\n", - isDeflated ? "deflated " : "")); - if (mListenerMT) { nsCString binaryData; - if (isDeflated) { - rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData); - if (NS_FAILED(rv)) { - return rv; - } - LOG(("WebSocketChannel:: message successfully inflated " - "[origLength=%d, newLength=%d]\n", payloadLength, - binaryData.Length())); - } else { - if (!binaryData.Assign((const char *)payload, payloadLength, - mozilla::fallible)) { - return NS_ERROR_OUT_OF_MEMORY; + { //lockscope + MutexAutoLock lock(mCompressorMutex); + bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated(); + LOG(("WebSocketChannel:: %sbinary frame received\n", + isDeflated ? "deflated " : "")); + + if (isDeflated) { + rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData); + if (NS_FAILED(rv)) { + return rv; + } + LOG(("WebSocketChannel:: message successfully inflated " + "[origLength=%d, newLength=%d]\n", payloadLength, + binaryData.Length())); + } else { + if (!binaryData.Assign((const char *)payload, payloadLength, + mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } } } @@ -2156,6 +2164,7 @@ WebSocketChannel::PrimeNewOutgoingMessage() } // deflate the payload if PMCE is negotiated + MutexAutoLock lock(mCompressorMutex); if (mPMCECompressor && (msgType == kMsgTypeString || msgType == kMsgTypeBinaryString)) { if (mCurrentOut->DeflatePayload(mPMCECompressor)) { @@ -2447,7 +2456,10 @@ WebSocketChannel::StopSession(nsresult reason) mCancelable = nullptr; } - mPMCECompressor = nullptr; + { + MutexAutoLock lock(mCompressorMutex); + mPMCECompressor = nullptr; + } if (!mCalledOnStop) { mCalledOnStop = 1; @@ -2702,6 +2714,7 @@ WebSocketChannel::HandleExtensions() serverMaxWindowBits = 15; } + MutexAutoLock lock(mCompressorMutex); mPMCECompressor = new PMCECompression(clientNoContextTakeover, clientMaxWindowBits, serverMaxWindowBits); @@ -3678,6 +3691,7 @@ WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport, serverMaxWindowBits = 15; } + MutexAutoLock lock(mCompressorMutex); mPMCECompressor = new PMCECompression(serverNoContextTakeover, serverMaxWindowBits, clientMaxWindowBits); diff --git a/netwerk/protocol/websocket/WebSocketChannel.h b/netwerk/protocol/websocket/WebSocketChannel.h index 879027b2ad..691de4102c 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.h +++ b/netwerk/protocol/websocket/WebSocketChannel.h @@ -18,6 +18,7 @@ #include "nsIChannelEventSink.h" #include "nsIHttpChannelInternal.h" #include "nsIStringStream.h" +#include "mozilla/Mutex.h" #include "BaseWebSocketChannel.h" #include "nsCOMPtr.h" @@ -288,6 +289,7 @@ private: uint32_t mHdrOutToSend; uint8_t *mHdrOut; uint8_t mOutHeader[kCopyBreak + 16]; + Mutex mCompressorMutex; nsAutoPtr mPMCECompressor; uint32_t mDynamicOutputSize; uint8_t *mDynamicOutput; -- cgit v1.2.3 From ca691c8d2dbd00957722028b5bc8322721879d86 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 30 Aug 2023 17:46:49 +0200 Subject: [DOM] Deny web notifications if principal can't resolve. --- dom/notification/Notification.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index 9a7c833794..5439a4fe59 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -1688,6 +1688,11 @@ Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv) } nsCOMPtr principal = sop->GetPrincipal(); + if (!principal) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return NotificationPermission::Denied; + } + return GetPermissionInternal(principal, aRv); } -- cgit v1.2.3 From f20e3f4dd16285c9b484a8abb144f2a51d93ac07 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 30 Aug 2023 18:59:32 +0200 Subject: [network] Hold a strong ref to mChannel in OpenConn --- netwerk/protocol/websocket/WebSocketChannel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index 4b087a8bf2..9fca09a087 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -520,7 +520,7 @@ private: ~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); } nsCString mAddress; - WebSocketChannel *mChannel; + RefPtr mChannel; }; void ConnectNext(nsCString &hostName) -- cgit v1.2.3 From 7de2732e7f68e55a24ee9628752a5a620d0eac6e Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 30 Aug 2023 19:30:17 +0200 Subject: [DOM] Make IORunnable::mFilePickerParent into a RefPtr. --- dom/ipc/FilePickerParent.cpp | 1 + dom/ipc/FilePickerParent.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dom/ipc/FilePickerParent.cpp b/dom/ipc/FilePickerParent.cpp index 1438e374fe..0271890758 100644 --- a/dom/ipc/FilePickerParent.cpp +++ b/dom/ipc/FilePickerParent.cpp @@ -286,6 +286,7 @@ FilePickerParent::RecvOpen(const int16_t& aSelectedType, } } + MOZ_ASSERT(!mCallback); mCallback = new FilePickerShownCallback(this); mFilePicker->Open(mCallback); diff --git a/dom/ipc/FilePickerParent.h b/dom/ipc/FilePickerParent.h index 332007a715..7ab2f7dab1 100644 --- a/dom/ipc/FilePickerParent.h +++ b/dom/ipc/FilePickerParent.h @@ -69,7 +69,7 @@ class FilePickerParent : public PFilePickerParent private: virtual ~FilePickerShownCallback() {} - FilePickerParent* mFilePickerParent; + RefPtr mFilePickerParent; }; private: @@ -78,7 +78,7 @@ class FilePickerParent : public PFilePickerParent // This runnable is used to do some I/O operation on a separate thread. class IORunnable : public Runnable { - FilePickerParent* mFilePickerParent; + RefPtr mFilePickerParent; nsTArray> mFiles; nsTArray mResults; nsCOMPtr mEventTarget; -- cgit v1.2.3 From 7a4b8501505e9ed433db0b5a675bc38398b74bb8 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 31 Aug 2023 10:02:50 +0200 Subject: [gfx] Check if we have a valid texture before trying to delete it. --- gfx/gl/SharedSurfaceGL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gfx/gl/SharedSurfaceGL.cpp b/gfx/gl/SharedSurfaceGL.cpp index adb4429ae8..2639d64afa 100644 --- a/gfx/gl/SharedSurfaceGL.cpp +++ b/gfx/gl/SharedSurfaceGL.cpp @@ -94,7 +94,8 @@ SharedSurface_Basic::~SharedSurface_Basic() mGL->fDeleteFramebuffers(1, &mFB); if (mOwnsTex) - mGL->fDeleteTextures(1, &mTex); + if (mTex) + mGL->fDeleteTextures(1, &mTex); } -- cgit v1.2.3 From ee97a5dad40fb8d207b717cb2a0d487f54dd5f1d Mon Sep 17 00:00:00 2001 From: Moonchild Date: Fri, 1 Sep 2023 09:54:03 +0200 Subject: [DOM] Follow-up: fix refcounting in FilePickerParent. --- dom/ipc/FilePickerParent.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dom/ipc/FilePickerParent.h b/dom/ipc/FilePickerParent.h index 7ab2f7dab1..89c472cb8a 100644 --- a/dom/ipc/FilePickerParent.h +++ b/dom/ipc/FilePickerParent.h @@ -27,8 +27,12 @@ class FilePickerParent : public PFilePickerParent , mMode(aMode) {} + private: virtual ~FilePickerParent(); + public: + NS_INLINE_DECL_REFCOUNTING(FilePickerParent) + void Done(int16_t aResult); struct BlobImplOrString -- cgit v1.2.3