Compare commits

...
Author SHA1 Message Date
Moonchild 510d86ddfb Issue #2730 - Part 2: Add support for reading [u]int64 values from a BigInt.
Provides an inline assembly implementation of `BigInt::toUint64()` resp.
`BigInt::toInt64()`.

Also adds a template class for GC things that have 32-bit length and
32-bit flags stored at the beginning of the GC thing.
This was necessary to follow Mozilla's implementation that builds on
a big rewrite of code depending on this generalization of JSString and
BigInt in the hunt for code-deduping-complexity-increase.
Not ideal since it's only used for BigInt as a result, but
reverse-refactoring of the necessary code referencing JSString would have
been worse.
2025-04-18 16:10:19 +02:00
Moonchild 3527ad788c Issue #2730 - Part 1: Avoid crashes for TypedObjects using bigint properties. 2025-04-18 00:31:01 +02:00
15 changed files with 281 additions and 35 deletions

No files matched your search

+94
View File
@@ -1377,6 +1377,100 @@ TenuredCell::isAligned() const
static const int32_t ChunkLocationOffsetFromLastByte =
int32_t(gc::ChunkLocationOffset) - int32_t(gc::ChunkMask);
// Base class for GC things that have 32-bit length and 32-bit flags fields
// stored at the beginning (currently BigInt).
//
// First word of a Cell has additional requirements from GC and normally
// would store a pointer. If a single word isn't large enough, the length
// is stored separately.
//
// 32 0
// ------------------
// | Length | Flags |
// ------------------
//
// The low bits of the flags word (see NumFlagBitsReservedForGC) are reserved
// for GC. Derived classes must ensure they don't use these flags for non-GC
// purposes.
template <class BaseCell>
class CellWithLengthAndFlags : public BaseCell {
static_assert(std::is_same<BaseCell, Cell>::value ||
std::is_same<BaseCell, TenuredCell>::value,
"BaseCell must be either Cell or TenuredCell");
// NOTE: This word can also be used for temporary storage, see
// setTemporaryGCUnsafeData.
uintptr_t flags_;
#if JS_BITS_PER_WORD == 32
// Additional storage for length if |flags_| is too small to fit both.
uint32_t length_;
#endif
protected:
static constexpr size_t NumFlagBitsReservedForGC = 2; //Cell::ReservedBits;
uint32_t lengthField() const {
#if JS_BITS_PER_WORD == 32
return length_;
#else
return uint32_t(flags_ >> 32);
#endif
}
uint32_t flagsField() const { return uint32_t(flags_); }
void setFlagBit(uint32_t flag) { flags_ |= uintptr_t(flag); }
void clearFlagBit(uint32_t flag) { flags_ &= ~uintptr_t(flag); }
void toggleFlagBit(uint32_t flag) { flags_ ^= uintptr_t(flag); }
void setLengthAndFlags(uint32_t len, uint32_t flags) {
#if JS_BITS_PER_WORD == 32
flags_ = flags;
length_ = len;
#else
flags_ = (uint64_t(len) << 32) | uint64_t(flags);
#endif
}
// Sub classes can store temporary data in the flags word. This is not GC safe
// and users must ensure flags/length are never checked (including by asserts)
// while this data is stored. Use of this method is strongly discouraged!
void setTemporaryGCUnsafeData(uintptr_t data) { flags_ = data; }
// To get back the data, values to safely re-initialize clobbered flags
// must be provided.
uintptr_t unsetTemporaryGCUnsafeData(uint32_t len, uint32_t flags) {
uintptr_t data = flags_;
setLengthAndFlags(len, flags);
return data;
}
// Offsets for direct field from jit code. A number of places directly
// access 32-bit length and flags fields so do endian trickery here.
#if JS_BITS_PER_WORD == 32
static constexpr size_t offsetOfFlags() {
return offsetof(CellWithLengthAndFlags, flags_);
}
static constexpr size_t offsetOfLength() {
return offsetof(CellWithLengthAndFlags, length_);
}
#elif MOZ_LITTLE_ENDIAN
static constexpr size_t offsetOfFlags() {
return offsetof(CellWithLengthAndFlags, flags_);
}
static constexpr size_t offsetOfLength() {
return offsetof(CellWithLengthAndFlags, flags_) + sizeof(uint32_t);
}
#else
static constexpr size_t offsetOfFlags() {
return offsetof(CellWithLengthAndFlags, flags_) + sizeof(uint32_t);
}
static constexpr size_t offsetOfLength() {
return offsetof(CellWithLengthAndFlags, flags_);
}
#endif
};
} /* namespace gc */
} /* namespace js */
+7
View File
@@ -336,6 +336,13 @@ GetPropIRGenerator::tryAttachTypedObject(CacheIRWriter& writer, HandleObject obj
if (!fieldDescr->is<SimpleTypeDescr>())
return true;
if (fieldDescr->is<ScalarTypeDescr>()) {
Scalar::Type type = fieldDescr->as<ScalarTypeDescr>().type();
if (Scalar::isBigIntType(type)) {
return true;
}
}
Shape* shape = typedObj->maybeShape();
TypedThingLayout layout = GetTypedThingLayout(shape->getObjectClass());
+21
View File
@@ -9417,6 +9417,11 @@ IonBuilder::checkTypedObjectIndexInBounds(uint32_t elemSize,
return indexAsByteOffset->add(index, AssertedCast<int32_t>(elemSize));
}
static bool CheckTypedObjectSupportedType(Scalar::Type type) {
// FIXME: Enable TypedArray JIT optimizations for Big(U)Int64Array
return !Scalar::isBigIntType(type);
}
bool
IonBuilder::getElemTryScalarElemOfTypedObject(bool* emitted,
MDefinition* obj,
@@ -9431,6 +9436,10 @@ IonBuilder::getElemTryScalarElemOfTypedObject(bool* emitted,
ScalarTypeDescr::Type elemType = elemPrediction.scalarType();
MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType));
if (!CheckTypedObjectSupportedType(elemType)) {
return true;
}
LinearSum indexAsByteOffset(alloc());
if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset))
return true;
@@ -10523,6 +10532,10 @@ IonBuilder::setElemTryScalarElemOfTypedObject(bool* emitted,
ScalarTypeDescr::Type elemType = elemPrediction.scalarType();
MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType));
if (!CheckTypedObjectSupportedType(elemType)) {
return true;
}
LinearSum indexAsByteOffset(alloc());
if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset))
return true;
@@ -12110,6 +12123,10 @@ IonBuilder::getPropTryScalarPropOfTypedObject(bool* emitted, MDefinition* typedO
// Must always be loading the same scalar type
Scalar::Type fieldType = fieldPrediction.scalarType();
if (!CheckTypedObjectSupportedType(fieldType)) {
return true;
}
// Don't optimize if the typed object's underlying buffer may be detached.
TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER))
@@ -13196,6 +13213,10 @@ IonBuilder::setPropTryScalarPropOfTypedObject(bool* emitted,
// Must always be loading the same scalar type
Scalar::Type fieldType = fieldPrediction.scalarType();
if (!CheckTypedObjectSupportedType(fieldType)) {
return true;
}
// Don't optimize if the typed object's underlying buffer may be detached.
TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER))
+68
View File
@@ -1328,6 +1328,74 @@ MacroAssembler::loadStringChar(Register str, Register index, Register output)
bind(&done);
}
void MacroAssembler::loadBigInt64(Register bigInt, Register64 dest) {
// This code follows the implementation of |BigInt::toUint64()|. We're also
// using it for inline callers of |BigInt::toInt64()|, which works, because
// all supported Jit architectures use a two's complement representation for
// int64 values, which means the WrapToSigned call in toInt64() is a no-op.
Label done, nonZero;
branch32(Assembler::NotEqual, Address(bigInt, BigInt::offsetOfLength()),
Imm32(0), &nonZero);
{
move64(Imm64(0), dest);
jump(&done);
}
bind(&nonZero);
#ifdef JS_PUNBOX64
Register digits = dest.reg;
#else
Register digits = dest.high;
#endif
MOZ_ASSERT(digits != bigInt);
Label isInline, inlineDone;
branch32(Assembler::NonZero, Address(bigInt, BigInt::offsetOfLength()),
Imm32(int32_t(BigInt::nonInlineDigitsLengthMask())),
&isInline);
load64(Address(bigInt, BigInt::nonInlineDigitsLengthMask()), dest);
jump(&inlineDone);
bind(&isInline);
// Load the inline digits.
computeEffectiveAddress(Address(bigInt, BigInt::offsetOfInlineDigits()),
digits);
bind(&inlineDone);
#if JS_PUNBOX64
// Load the first digit into the destination register.
load64(Address(digits, 0), dest);
#else
// Load the first digit into the destination register's low value.
load32(Address(digits, 0), dest.low);
// And conditionally load the second digit into the high value register.
Label twoDigits, digitsDone;
branch32(Assembler::GreaterThan, Address(bigInt, BigInt::offsetOfLength()),
Imm32(1), &twoDigits);
{
move32(Imm32(0), dest.high);
jump(&digitsDone);
}
{
bind(&twoDigits);
load32(Address(digits, sizeof(BigInt::Digit)), dest.high);
}
bind(&digitsDone);
#endif
branchTest32(Assembler::Zero, Address(bigInt, BigInt::offsetOfFlags()),
Imm32(BigInt::signBitMask()), &done);
neg64(dest);
bind(&done);
}
static void
BailoutReportOverRecursed(JSContext* cx)
{
+6 -1
View File
@@ -835,7 +835,7 @@ class MacroAssembler : public MacroAssemblerSpecific
inline void dec32(RegisterOrInt32Constant* key);
inline void neg32(Register reg) PER_SHARED_ARCH;
inline void neg64(Register64 reg) DEFINED_ON(x86, x64, arm, mips32, mips64);
inline void neg64(Register64 reg) PER_SHARED_ARCH;
inline void negateFloat(FloatRegister reg) PER_SHARED_ARCH;
@@ -1441,6 +1441,11 @@ class MacroAssembler : public MacroAssemblerSpecific
void loadStringChars(Register str, Register dest);
void loadStringChar(Register str, Register index, Register output);
/**
* Load the first [u]int64 value from |bigInt| into |dest|.
*/
void loadBigInt64(Register bigInt, Register64 dest);
void loadJSContext(Register dest) {
movePtr(ImmPtr(GetJitContext()->runtime->getJSContext()), dest);
}
+1 -2
View File
@@ -3773,8 +3773,7 @@ MacroAssemblerARMCompat::testBigIntTruthy(bool truthy, const ValueOperand& value
ScratchRegisterScope scratch(asMasm());
SecondScratchRegisterScope scratch2(asMasm());
ma_dtr(IsLoad, bi, Imm32(BigInt::offsetOfLengthSignAndReservedBits()),
scratch, scratch2);
ma_dtr(IsLoad, bi, Imm32(BigInt::offsetOfDigitLength()), scratch, scratch2);
as_cmp(scratch, Imm8(0));
return truthy ? Assembler::NotEqual : Assembler::Equal;
}
+10
View File
@@ -1077,11 +1077,21 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM
store32(src.high, Address(address.base, address.offset + INT64HIGH_OFFSET));
}
void store64(Register64 src, const BaseIndex& address) {
store32(src.low, Address(address.base, address.offset + INT64LOW_OFFSET));
store32(src.high, Address(address.base, address.offset + INT64HIGH_OFFSET));
}
void store64(Imm64 imm, Address address) {
store32(imm.low(), Address(address.base, address.offset + INT64LOW_OFFSET));
store32(imm.hi(), Address(address.base, address.offset + INT64HIGH_OFFSET));
}
void store64(Imm64 imm, const BaseIndex& address) {
store32(imm.low(), Address(address.base, address.offset + INT64LOW_OFFSET));
store32(imm.hi(), Address(address.base, address.offset + INT64HIGH_OFFSET));
}
void storePtr(ImmWord imm, const Address& address);
void storePtr(ImmWord imm, const BaseIndex& address);
void storePtr(ImmPtr imm, const Address& address);
@@ -504,6 +504,12 @@ MacroAssembler::neg32(Register reg)
Negs(ARMRegister(reg, 32), Operand(ARMRegister(reg, 32)));
}
void MacroAssembler::neg64(Register64 reg) { negPtr(reg.reg); }
void MacroAssembler::negPtr(Register reg) {
Negs(ARMRegister(reg, 64), Operand(ARMRegister(reg, 64)));
}
void
MacroAssembler::negateFloat(FloatRegister reg)
{
+9
View File
@@ -907,6 +907,14 @@ class MacroAssemblerCompat : public vixl::MacroAssembler
storePtr(src.reg, address);
}
void store64(Register64 src, const BaseIndex& address) {
storePtr(src.reg, address);
}
void store64(Imm64 imm, const BaseIndex& address) {
storePtr(ImmWord(imm.value), address);
}
// StackPointer manipulation.
template <typename T> void addToStackPtr(T t);
template <typename T> void addStackPtrTo(T t);
@@ -2302,3 +2310,4 @@ typedef MacroAssemblerCompat MacroAssemblerSpecific;
} // namespace js
#endif // jit_arm64_MacroAssembler_arm64_h
void store64(Register64 src, const BaseIndex& address) {
@@ -972,7 +972,7 @@ void MacroAssembler::branchTestBigIntTruthy(bool b, const ValueOperand& value, L
{
Register bi = value.payloadReg();
SecondScratchRegisterScope scratch2(*this);
ma_lw(scratch2, Address(bi, BigInt::offsetOfLengthSignAndReservedBits()));
ma_lw(scratch2, Address(bi, BigInt::offsetOfDigitLength()));
ma_b(scratch2, Imm32(0), label, b ? NotEqual : Equal);
}
@@ -633,8 +633,8 @@ void MacroAssembler::branchTestBigIntTruthy(bool b, const ValueOperand& value, L
{
SecondScratchRegisterScope scratch2(*this);
unboxBigInt(value, scratch2);
loadPtr(Address(scratch2, BigInt::offsetOfLengthSignAndReservedBits()), scratch2);
ma_b(scratch2, ImmWord(0), label, b ? NotEqual : Equal);
load32(Address(scratch2, BigInt::offsetOfDigitLength()), scratch2);
ma_b(scratch2, Imm32(0), label, b ? NotEqual : Equal);
}
void
+7 -2
View File
@@ -663,9 +663,15 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared
void store64(Register64 src, Address address) {
storePtr(src.reg, address);
}
void store64(Register64 src, const BaseIndex& address) {
storePtr(src.reg, address);
}
void store64(Imm64 imm, Address address) {
storePtr(ImmWord(imm.value), address);
}
void store64(Imm64 imm, const BaseIndex& address) {
storePtr(ImmWord(imm.value), address);
}
void splitTag(Register src, Register dest) {
if (src != dest)
@@ -920,8 +926,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared
Condition testBigIntTruthy(bool truthy, const ValueOperand& value) {
ScratchRegisterScope scratch(asMasm());
unboxBigInt(value, scratch);
cmpPtr(Operand(scratch, BigInt::offsetOfLengthSignAndReservedBits()),
ImmWord(0));
cmp32(Operand(scratch, BigInt::offsetOfDigitLength()), Imm32(0));
return truthy ? Assembler::NotEqual : Assembler::Equal;
}
+1 -2
View File
@@ -810,8 +810,7 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared
}
Condition testBigIntTruthy(bool truthy, const ValueOperand& value) {
Register bi = value.payloadReg();
cmpPtr(Operand(bi, BigInt::offsetOfLengthSignAndReservedBits()),
ImmWord(0));
cmp32(Operand(bi, BigInt::offsetOfDigitLength()), Imm32(0));
return truthy ? Assembler::NotEqual : Assembler::Equal;
}
+9 -9
View File
@@ -123,9 +123,9 @@ static inline unsigned DigitLeadingZeroes(BigInt::Digit x) {
: mozilla::CountLeadingZeroes64(x);
}
BigInt* BigInt::createUninitialized(ExclusiveContext* cx, size_t length,
BigInt* BigInt::createUninitialized(ExclusiveContext* cx, size_t digitLength,
bool isNegative) {
if (length > MaxDigitLength) {
if (digitLength > MaxDigitLength) {
if (cx->isJSContext()) {
JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr,
JSMSG_BIGINT_TOO_LARGE);
@@ -134,8 +134,8 @@ BigInt* BigInt::createUninitialized(ExclusiveContext* cx, size_t length,
}
UniquePtr<Digit[], JS::FreePolicy> heapDigits;
if (length > InlineDigitsLength) {
heapDigits = cx->make_pod_array<Digit>(length);
if (digitLength > InlineDigitsLength) {
heapDigits = cx->make_pod_array<Digit>(digitLength);
if (!heapDigits) {
return nullptr;
}
@@ -148,9 +148,9 @@ BigInt* BigInt::createUninitialized(ExclusiveContext* cx, size_t length,
return nullptr;
}
x->lengthSignAndReservedBits_ =
(length << LengthShift) | (isNegative ? SignBit : 0);
MOZ_ASSERT(x->digitLength() == length);
x->setLengthAndFlags(digitLength, isNegative ? SignBit : 0);
MOZ_ASSERT(x->digitLength() == digitLength);
MOZ_ASSERT(x->isNegative() == isNegative);
if (heapDigits) {
@@ -211,7 +211,7 @@ BigInt* BigInt::neg(ExclusiveContext* cx, HandleBigInt x) {
if (!result) {
return nullptr;
}
result->lengthSignAndReservedBits_ ^= SignBit;
result->toggleFlagBit(SignBit);
return result;
}
@@ -1612,7 +1612,7 @@ BigInt* BigInt::createFromInt64(ExclusiveContext* cx, int64_t n) {
}
if (n < 0) {
res->lengthSignAndReservedBits_ |= SignBit;
res->setFlagBit(SignBit);
}
MOZ_ASSERT(res->isNegative() == (n < 0));
+39 -16
View File
@@ -39,18 +39,21 @@ bool XDRBigInt(XDRState<mode>* xdr, MutableHandle<JS::BigInt*> bi);
namespace JS {
class BigInt final : public js::gc::TenuredCell {
class BigInt final
: public js::gc::CellWithLengthAndFlags<js::gc::TenuredCell> {
using Base = js::gc::CellWithLengthAndFlags<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;
// The low NumFlagBitsReservedForGC flag bits are reserved.
static constexpr uintptr_t SignBit = JS_BIT(Base::NumFlagBitsReservedForGC);
static constexpr size_t InlineDigitsLength =
(js_gc_MinCellSize - sizeof(uintptr_t)) / sizeof(Digit);
(js_gc_MinCellSize - sizeof(Base)) / sizeof(Digit);
uintptr_t lengthSignAndReservedBits_;
// Note: 32-bit length and flags fields are inherited from
// CellWithLengthAndFlags.
// The digit storage starts with the least significant digit (little-endian
// digit order). Byte order within a digit is of course native endian.
@@ -62,8 +65,11 @@ class BigInt final : public js::gc::TenuredCell {
public:
static const JS::TraceKind TraceKind = JS::TraceKind::BigInt;
size_t digitLength() const {
return lengthSignAndReservedBits_ >> LengthShift;
size_t digitLength() const { return lengthField(); }
// Offset for direct access from JIT code.
static constexpr size_t offsetOfDigitLength() {
return Base::offsetOfLength();
}
bool hasInlineDigits() const { return digitLength() <= InlineDigitsLength; }
@@ -78,12 +84,7 @@ class BigInt final : public js::gc::TenuredCell {
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_);
}
bool isNegative() const { return flagsField() & SignBit; }
void initializeDigitsToZero();
@@ -92,7 +93,7 @@ class BigInt final : public js::gc::TenuredCell {
js::HashNumber hash();
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
static BigInt* createUninitialized(js::ExclusiveContext* cx, size_t length,
static BigInt* createUninitialized(js::ExclusiveContext* cx, size_t digitLength,
bool isNegative);
static BigInt* createFromDouble(js::ExclusiveContext* cx, double d);
static BigInt* createFromUint64(js::ExclusiveContext* cx, uint64_t n);
@@ -354,7 +355,29 @@ class BigInt final : public js::gc::TenuredCell {
BigInt() = delete;
BigInt(const BigInt& other) = delete;
void operator=(const BigInt& other) = delete;
};
private:
// To help avoid writing Spectre-unsafe code, we only allow MacroAssembler to
// call the methods below.
friend class js::jit::MacroAssembler;
// Make offset accessors accessible to the MacroAssembler.
using Base::offsetOfFlags;
using Base::offsetOfLength;
static size_t offsetOfInlineDigits() {
return offsetof(BigInt, inlineDigits_);
}
static size_t offsetOfHeapDigits() { return offsetof(BigInt, heapDigits_); }
static constexpr size_t nonInlineDigitsLengthMask() {
static_assert(mozilla::IsPowerOfTwo(InlineDigitsLength),
"inline digits length is a power of two");
return ~(InlineDigitsLength - 1) & ~InlineDigitsLength;
}
static constexpr size_t signBitMask() { return SignBit; }};
static_assert(
sizeof(BigInt) >= js_gc_MinCellSize,