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