diff --git a/JSTests/stress/dataview-jit-bigint64.js b/JSTests/stress/dataview-jit-bigint64.js new file mode 100644 index 0000000000000..f6a644826db9c --- /dev/null +++ b/JSTests/stress/dataview-jit-bigint64.js @@ -0,0 +1,222 @@ +// Correctness test for DataView BigInt64/BigUint64 DFG/FTL intrinsics. +function assert(cond, msg) { + if (!cond) + throw new Error("FAIL: " + msg); +} + +// Reference implementations via byte-level access. +function refGetBigUint64(bytes, offset, littleEndian) { + let v = 0n; + for (let i = 0; i < 8; i++) { + const b = BigInt(bytes[offset + (littleEndian ? 7 - i : i)]); + v = (v << 8n) | b; + } + return v; +} +function refGetBigInt64(bytes, offset, littleEndian) { + const u = refGetBigUint64(bytes, offset, littleEndian); + return u >= (1n << 63n) ? u - (1n << 64n) : u; +} +function refSetBigUint64(bytes, offset, value, littleEndian) { + let v = ((value % (1n << 64n)) + (1n << 64n)) % (1n << 64n); + for (let i = 0; i < 8; i++) { + const shift = BigInt(8 * (littleEndian ? i : 7 - i)); + bytes[offset + i] = Number((v >> shift) & 0xffn); + } +} + +// --- get tests: constant endianness (true / false / omitted) and variable --- +function testGetLE(view, offset) { return [view.getBigInt64(offset, true), view.getBigUint64(offset, true)]; } +function testGetBE(view, offset) { return [view.getBigInt64(offset, false), view.getBigUint64(offset, false)]; } +function testGetDefault(view, offset) { return [view.getBigInt64(offset), view.getBigUint64(offset)]; } +function testGetVar(view, offset, le) { return [view.getBigInt64(offset, le), view.getBigUint64(offset, le)]; } +noInline(testGetLE); +noInline(testGetBE); +noInline(testGetDefault); +noInline(testGetVar); + +function testSetLE(view, offset, v1, v2) { view.setBigInt64(offset, v1, true); view.setBigUint64(offset + 8, v2, true); } +function testSetBE(view, offset, v1, v2) { view.setBigInt64(offset, v1, false); view.setBigUint64(offset + 8, v2, false); } +function testSetDefault(view, offset, v1, v2) { view.setBigInt64(offset, v1); view.setBigUint64(offset + 8, v2); } +function testSetVar(view, offset, v1, v2, le) { view.setBigInt64(offset, v1, le); view.setBigUint64(offset + 8, v2, le); } +noInline(testSetLE); +noInline(testSetBE); +noInline(testSetDefault); +noInline(testSetVar); + +const SIZE = 64; +const bytes = new Uint8Array(SIZE); +const view = new DataView(bytes.buffer); + +const interestingValues = [ + 0n, 1n, -1n, 2n, 255n, 256n, 0x7fn, + 0x7fffffffn, 0x80000000n, 0xffffffffn, 0x100000000n, + (1n << 63n) - 1n, 1n << 63n, (1n << 64n) - 1n, + -(1n << 63n), -(1n << 63n) + 1n, + 0x0102030405060708n, 0xf1f2f3f4f5f6f7f8n, + // multi-digit BigInts (wrap modulo 2^64) + (1n << 64n) + 5n, (1n << 128n) + 7n, -((1n << 64n) + 5n), -((1n << 100n) + 123n), + (1n << 64n), -(1n << 64n), +]; + +function toInt64(v) { + let u = ((v % (1n << 64n)) + (1n << 64n)) % (1n << 64n); + return u >= (1n << 63n) ? u - (1n << 64n) : u; +} +function toUint64(v) { + return ((v % (1n << 64n)) + (1n << 64n)) % (1n << 64n); +} + +const ITERATIONS = 20000; +for (let i = 0; i < ITERATIONS; i++) { + const value = interestingValues[i % interestingValues.length]; + const offset = (i * 3) % (SIZE - 16); + + for (const le of [true, false]) { + // set via intrinsic path, check bytes against reference + testSetVar(view, offset, value, value, le); + const expected = new Uint8Array(16); + refSetBigUint64(expected, 0, value, le); + refSetBigUint64(expected, 8, value, le); + for (let j = 0; j < 16; j++) + assert(bytes[offset + j] === expected[j], `setVar le=${le} value=${value} byte ${j}: ${bytes[offset + j]} != ${expected[j]}`); + + // get via intrinsic path, check against reference + const [i64v, u64v] = testGetVar(view, offset, le); + assert(i64v === toInt64(value), `getVar i64 le=${le} value=${value}: ${i64v}`); + assert(u64v === toUint64(value), `getVar u64 le=${le} value=${value}: ${u64v}`); + } + + // constant-endianness paths + testSetLE(view, offset, value, value); + let [a, b] = testGetLE(view, offset); + assert(a === toInt64(value), `LE i64 ${value}: ${a}`); + assert(b === toUint64(value), `LE u64 ${value}: ${b}`); + + testSetBE(view, offset, value, value); + [a, b] = testGetBE(view, offset); + assert(a === toInt64(value), `BE i64 ${value}: ${a}`); + assert(b === toUint64(value), `BE u64 ${value}: ${b}`); + + // default (big-endian per spec) + testSetDefault(view, offset, value, value); + [a, b] = testGetDefault(view, offset); + assert(a === toInt64(value), `default i64 ${value}: ${a}`); + assert(b === toUint64(value), `default u64 ${value}: ${b}`); + assert(view.getBigUint64(offset, false) === toUint64(value), `default is big-endian ${value}`); +} + +// cross-check against two-uint32 decomposition +for (let i = 0; i < ITERATIONS; i++) { + const value = interestingValues[i % interestingValues.length]; + testSetLE(view, 0, value, value); + const lo = BigInt(view.getUint32(0, true)); + const hi = BigInt(view.getUint32(4, true)); + assert(((hi << 32n) | lo) === toUint64(value), `uint32 cross-check ${value}`); +} + +// --- out-of-bounds must throw RangeError even once optimized --- +function oobGet(view, offset) { return view.getBigUint64(offset, true); } +function oobSet(view, offset) { view.setBigUint64(offset, 1n, true); } +noInline(oobGet); +noInline(oobSet); +for (let i = 0; i < ITERATIONS; i++) { + oobGet(view, 0); + oobSet(view, 0); +} +for (const badOffset of [SIZE - 7, SIZE, -1, 0x7fffffff]) { + let threw = false; + try { oobGet(view, badOffset); } catch (e) { threw = e instanceof RangeError; } + assert(threw, `getBigUint64(${badOffset}) should throw RangeError`); + threw = false; + try { oobSet(view, badOffset); } catch (e) { threw = e instanceof RangeError; } + assert(threw, `setBigUint64(${badOffset}) should throw RangeError`); +} + +// --- non-BigInt value to set must throw TypeError after optimization --- +function setAny(view, offset, v) { view.setBigUint64(offset, v, true); } +noInline(setAny); +for (let i = 0; i < ITERATIONS; i++) + setAny(view, 0, 42n); +for (const bad of [42, 1.5, null, undefined, Symbol("x")]) { + let threw = false; + try { setAny(view, 0, bad); } catch (e) { threw = e instanceof TypeError; } + assert(threw, `setBigUint64 with ${String(bad)} should throw TypeError`); +} +{ + let threw = false; + try { setAny(view, 0, {}); } catch (e) { threw = e instanceof SyntaxError; } + assert(threw, "setBigUint64 with {} should throw SyntaxError"); +} +// string and boolean convert via ToBigInt without throwing +setAny(view, 0, "42"); +assert(view.getBigUint64(0, true) === 42n, "string value converts"); +setAny(view, 0, true); +assert(view.getBigUint64(0, true) === 1n, "boolean value converts"); +// after the exits, bigint values must still work +setAny(view, 0, 7n); +assert(view.getBigUint64(0, true) === 7n, "set after exits"); + +// --- number value to get offset coercion & detached buffer --- +{ + const buf = new ArrayBuffer(16); + const v = new DataView(buf); + v.setBigUint64(0, 0x1122334455667788n, true); + transferArrayBuffer(buf); + let threw = false; + try { oobGet(v, 0); } catch (e) { threw = e instanceof TypeError || e instanceof RangeError; } + assert(threw, "get on detached buffer should throw"); + threw = false; + try { oobSet(v, 0); } catch (e) { threw = e instanceof TypeError || e instanceof RangeError; } + assert(threw, "set on detached buffer should throw"); +} + +// --- resizable ArrayBuffer --- +{ + const rab = new ArrayBuffer(32, { maxByteLength: 64 }); + const v = new DataView(rab); + function rget(v, o) { return v.getBigUint64(o, true); } + function rset(v, o, val) { v.setBigUint64(o, val, true); } + noInline(rget); + noInline(rset); + for (let i = 0; i < ITERATIONS; i++) { + rset(v, 16, BigInt(i)); + assert(rget(v, 16) === BigInt(i), "resizable basic"); + } + rab.resize(16); + let threw = false; + try { rget(v, 16); } catch (e) { threw = true; } + assert(threw, "get past shrunk resizable buffer should throw"); + rab.resize(64); + rset(v, 48, 99n); + assert(rget(v, 48) === 99n, "grown resizable buffer"); +} + +// --- GC stress: results are real, independent BigInts --- +function gcGet(view, offset) { return view.getBigUint64(offset, true); } +noInline(gcGet); +view.setBigUint64(0, 0xdeadbeefcafebaben, true); +{ + const keep = []; + for (let i = 0; i < 50000; i++) { + keep.push(gcGet(view, 0)); + if (keep.length > 64) + keep.shift(); + if ((i % 10000) === 0) + fullGC(); + } + for (const k of keep) + assert(k === 0xdeadbeefcafebaben, "gc stress value"); +} + +// --- negative zero-adjacent and zero BigInt handling in set fast path --- +function setZero(view) { view.setBigUint64(0, 0n, true); view.setBigInt64(8, 0n, true); } +noInline(setZero); +view.setBigUint64(0, ~0n & ((1n << 64n) - 1n), true); +view.setBigUint64(8, ~0n & ((1n << 64n) - 1n), true); +for (let i = 0; i < ITERATIONS; i++) + setZero(view); +assert(view.getBigUint64(0, true) === 0n, "zero set u64"); +assert(view.getBigInt64(8, true) === 0n, "zero set i64"); + +print("PASS"); diff --git a/Source/JavaScriptCore/b3/B3AbstractHeapRepository.h b/Source/JavaScriptCore/b3/B3AbstractHeapRepository.h index 6404ef84d3977..34d58121a5968 100644 --- a/Source/JavaScriptCore/b3/B3AbstractHeapRepository.h +++ b/Source/JavaScriptCore/b3/B3AbstractHeapRepository.h @@ -92,7 +92,9 @@ namespace JSC::B3 { macro(JSArrayBufferView_length, JSArrayBufferView::offsetOfLength(), Mutability::Mutable) \ macro(JSArrayBufferView_mode, JSArrayBufferView::offsetOfMode(), Mutability::Mutable) \ macro(JSArrayBufferView_vector, JSArrayBufferView::offsetOfVector(), Mutability::Mutable) \ - macro(JSBigInt_length, JSBigInt::offsetOfLength(), Mutability::Immutable) \ + macro(JSBigInt_length, JSBigInt::offsetOfLength(), Mutability::Mutable) \ + macro(JSBigInt_hash, JSBigInt::offsetOfHash(), Mutability::Mutable) \ + macro(JSBigInt_data0, JSBigInt::offsetOfData(), Mutability::Mutable) \ macro(JSBoundFunction_targetFunction, JSBoundFunction::offsetOfTargetFunction(), Mutability::Mutable) \ macro(JSBoundFunction_boundThis, JSBoundFunction::offsetOfBoundThis(), Mutability::Mutable) \ macro(JSBoundFunction_boundArg0, JSBoundFunction::offsetOfBoundArgs() + sizeof(WriteBarrier) * 0, Mutability::Mutable) \ diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h index 2acbc4bfd89cd..dbaf1510837f7 100644 --- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h +++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h @@ -5974,12 +5974,14 @@ bool AbstractInterpreter::executeEffects(unsigned clobberLimi DataViewData data = node->dataViewData(); if (data.byteSize < 4) setNonCellTypeForNode(node, SpecInt32Only); - else { - ASSERT(data.byteSize == 4); + else if (data.byteSize == 4) { if (data.isSigned) setNonCellTypeForNode(node, SpecInt32Only); else setNonCellTypeForNode(node, SpecInt52Any); + } else { + ASSERT(data.byteSize == 8); + setTypeForNode(node, SpecBigInt); } break; } diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index 18a22b16d54ca..70c41bb9161b9 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -4863,7 +4863,9 @@ auto ByteCodeParser::handleIntrinsicCall(Node* callee, Operand resultOperand, Ca case DataViewGetUint32: case DataViewGetFloat16: case DataViewGetFloat32: - case DataViewGetFloat64: { + case DataViewGetFloat64: + case DataViewGetBigInt64: + case DataViewGetBigUint64: { if (!is64Bit()) return CallOptimizationResult::DidNothing; @@ -4919,6 +4921,13 @@ auto ByteCodeParser::handleIntrinsicCall(Node* callee, Operand resultOperand, Ca byteSize = 8; op = DataViewGetFloat; break; + + case DataViewGetBigInt64: + isSigned = true; + [[fallthrough]]; + case DataViewGetBigUint64: + byteSize = 8; + break; default: RELEASE_ASSERT_NOT_REACHED(); } @@ -4966,9 +4975,17 @@ auto ByteCodeParser::handleIntrinsicCall(Node* callee, Operand resultOperand, Ca case DataViewSetUint32: case DataViewSetFloat16: case DataViewSetFloat32: - case DataViewSetFloat64: { + case DataViewSetFloat64: + case DataViewSetBigInt64: + case DataViewSetBigUint64: { if (!is64Bit()) return CallOptimizationResult::DidNothing; +#if USE(BIGINT32) + // The value child is speculated as a heap BigInt, which would always + // exit when small BigInts are boxed as BigInt32. + if (intrinsic == DataViewSetBigInt64 || intrinsic == DataViewSetBigUint64) + return CallOptimizationResult::DidNothing; +#endif if (argumentCountIncludingThis < 3) return CallOptimizationResult::DidNothing; @@ -5019,6 +5036,12 @@ auto ByteCodeParser::handleIntrinsicCall(Node* callee, Operand resultOperand, Ca isFloatingPoint = true; byteSize = 8; break; + case DataViewSetBigInt64: + isSigned = true; + [[fallthrough]]; + case DataViewSetBigUint64: + byteSize = 8; + break; default: RELEASE_ASSERT_NOT_REACHED(); } diff --git a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp index a2f4adc3c9c14..436cb505ab341 100644 --- a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp +++ b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp @@ -264,7 +264,6 @@ bool doesGC(Graph& graph, Node* node) case FilterSetPrivateBrandStatus: case DateGetInt32OrNaN: case DateGetTime: - case DataViewGetInt: case DataViewGetFloat: case DataViewSet: case PutByOffset: @@ -534,6 +533,10 @@ bool doesGC(Graph& graph, Node* node) case GlobalIsNaN: return node->child1().useKind() == UntypedUse; + case DataViewGetInt: + // getBigInt64/getBigUint64 allocate a BigInt for the result. + return node->dataViewData().byteSize == 8; + case CallNumberConstructor: switch (node->child1().useKind()) { case BigInt32Use: diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp index 3871f97cef1c9..9f668d4864c08 100644 --- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp @@ -3668,6 +3668,10 @@ class FixupPhase : public Phase { else node->setResult(NodeResultInt52); break; + case 8: + // getBigInt64/getBigUint64: the result is a BigInt JSValue. + ASSERT(node->result() == NodeResultJS); + break; default: RELEASE_ASSERT_NOT_REACHED(); } @@ -3697,6 +3701,11 @@ class FixupPhase : public Phase { else fixEdge(valueToStore); break; + case 8: + // setBigInt64/setBigUint64: the value is wrapped modulo 2^64, + // so the heap BigInt's low digit (with sign applied) suffices. + fixEdge(valueToStore); + break; } } break; diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp index 8c19646ba8fb7..a000d51018306 100644 --- a/Source/JavaScriptCore/dfg/DFGOperations.cpp +++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp @@ -6387,6 +6387,15 @@ JSC_DEFINE_JIT_OPERATION(operationInt64ToBigInt, EncodedJSValue, (JSGlobalObject OPERATION_RETURN(scope, JSValue::encode(JSBigInt::makeHeapBigIntOrBigInt32(globalObject, value))); } +JSC_DEFINE_JIT_OPERATION(operationUint64ToBigInt, EncodedJSValue, (JSGlobalObject* globalObject, uint64_t value)) +{ + VM& vm = globalObject->vm(); + CallFrame* callFrame = DECLARE_CALL_FRAME(vm); + JITOperationPrologueCallFrameTracer tracer(vm, callFrame); + auto scope = DECLARE_THROW_SCOPE(vm); + OPERATION_RETURN(scope, JSValue::encode(JSBigInt::makeHeapBigIntOrBigInt32(globalObject, value))); +} + JSC_DEFINE_JIT_OPERATION(operationThrowDFG, void, (JSGlobalObject* globalObject, EncodedJSValue valueToThrow)) { VM& vm = globalObject->vm(); diff --git a/Source/JavaScriptCore/dfg/DFGOperations.h b/Source/JavaScriptCore/dfg/DFGOperations.h index d750fef9c82d5..db6d11ca232b3 100644 --- a/Source/JavaScriptCore/dfg/DFGOperations.h +++ b/Source/JavaScriptCore/dfg/DFGOperations.h @@ -496,6 +496,7 @@ JSC_DECLARE_JIT_OPERATION(operationDateGetTimezoneOffset, EncodedJSValue, (VM*, JSC_DECLARE_JIT_OPERATION(operationDateGetYear, EncodedJSValue, (VM*, DateInstance*)); JSC_DECLARE_JIT_OPERATION(operationInt64ToBigInt, EncodedJSValue, (JSGlobalObject*, int64_t)); +JSC_DECLARE_JIT_OPERATION(operationUint64ToBigInt, EncodedJSValue, (JSGlobalObject*, uint64_t)); JSC_DECLARE_JIT_OPERATION(operationProcessTypeProfilerLogDFG, void, (VM*)); diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp index 7ee9603b5dd71..59b86ffb24c9e 100644 --- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp +++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp @@ -6431,6 +6431,29 @@ void SpeculativeJIT::compile(Node* node) strictInt52Result(t2, node); break; } + case 8: { + load64(baseIndex, t2); + + if (data.isLittleEndian == TriState::False) + byteSwap64(t2); + else if (data.isLittleEndian == TriState::Indeterminate) { + RELEASE_ASSERT(isLittleEndianGPR != InvalidGPRReg); + auto isLittleEndian = branchTest32(NonZero, isLittleEndianGPR, TrustedImm32(1)); + byteSwap64(t2); + isLittleEndian.link(this); + } + + flushRegisters(); + GPRFlushedCallResult result(this); + GPRReg resultGPR = result.gpr(); + if (data.isSigned) + callOperation(operationInt64ToBigInt, resultGPR, LinkableConstant::globalObject(*this, node), t2); + else + callOperation(operationUint64ToBigInt, resultGPR, LinkableConstant::globalObject(*this, node), t2); + exceptionCheck(); + jsValueResult(resultGPR, node); + break; + } default: RELEASE_ASSERT_NOT_REACHED(); } @@ -6558,6 +6581,7 @@ void SpeculativeJIT::compile(Node* node) std::optional int52Value; std::optional doubleValue; std::optional int32Value; + std::optional bigIntValue; std::optional fprTemporary; GPRReg valueGPR = InvalidGPRReg; FPRReg valueFPR = InvalidFPRReg; @@ -6583,6 +6607,11 @@ void SpeculativeJIT::compile(Node* node) int52Value.emplace(this, valueEdge); valueGPR = int52Value->gpr(); break; + case HeapBigIntUse: + bigIntValue.emplace(this, valueEdge); + valueGPR = bigIntValue->gpr(); + speculateHeapBigInt(valueEdge, valueGPR); + break; default: RELEASE_ASSERT_NOT_REACHED(); } @@ -6773,6 +6802,38 @@ void SpeculativeJIT::compile(Node* node) break; } + case 8: { + RELEASE_ASSERT(valueEdge.useKind() == HeapBigIntUse); + RELEASE_ASSERT(valueGPR != InvalidGPRReg); + + // setBigInt64/setBigUint64 wrap the value modulo 2^64, so the + // low digit of the heap BigInt (with the sign applied) suffices. + toBigInt64(valueGPR, t3); + + auto emitLittleEndianCode = [&] { + store64(t3, baseIndex); + }; + auto emitBigEndianCode = [&] { + byteSwap64(t3); + store64(t3, baseIndex); + }; + + if (data.isLittleEndian == TriState::False) + emitBigEndianCode(); + else if (data.isLittleEndian == TriState::True) + emitLittleEndianCode(); + else { + RELEASE_ASSERT(isLittleEndianGPR != InvalidGPRReg); + auto isBigEndian = branchTest32(Zero, isLittleEndianGPR, TrustedImm32(1)); + emitLittleEndianCode(); + auto done = jump(); + isBigEndian.link(this); + emitBigEndianCode(); + done.link(this); + } + + break; + } default: RELEASE_ASSERT_NOT_REACHED(); } diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp index 6204924103ad0..890e4ccb0d72d 100644 --- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp +++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp @@ -21461,6 +21461,65 @@ IGNORE_CLANG_WARNINGS_END break; } + case 8: { + LValue loadedValue = m_out.load64(pointer); + + if (data.isLittleEndian == TriState::False) + loadedValue = byteSwap64(loadedValue); + else if (data.isLittleEndian == TriState::Indeterminate) { + auto emitLittleEndianCode = [&] { + return loadedValue; + }; + auto emitBigEndianCode = [&] { + return byteSwap64(loadedValue); + }; + + loadedValue = emitCodeBasedOnEndiannessBranch(isLittleEndian, emitLittleEndianCode, emitBigEndianCode); + } + + JSGlobalObject* globalObject = m_graph.globalObjectFor(m_origin.semantic); + + // Fast path: inline-allocate a one-digit heap BigInt. The zero + // case is represented canonically with length 0; the extra digit + // slot stays within the allocated cell, so storing it is safe. + LBasicBlock slowPath = m_out.newBlock(); + LBasicBlock continuation = m_out.newBlock(); + + LValue isNegative = nullptr; + LValue digit = loadedValue; + if (data.isSigned) { + isNegative = m_out.lessThan(loadedValue, m_out.constInt64(0)); + digit = m_out.select(isNegative, m_out.neg(loadedValue), loadedValue); + } + + Allocator cellAllocator = allocatorForConcurrently(vm(), JSBigInt::allocationSize(1), AllocatorForMode::AllocatorIfExists); + LValue fastBigInt = allocateCell(m_out.constIntPtr(cellAllocator.localAllocator()), vm().bigIntStructure.get(), slowPath); + if (data.isSigned) { + LValue flags = m_out.load8ZeroExt32(fastBigInt, m_heaps.JSCell_typeInfoFlags); + LValue newFlags = m_out.select(isNegative, m_out.bitOr(flags, m_out.constInt32(TypeInfoPerCellBit)), flags); + m_out.store32As8(newFlags, fastBigInt, m_heaps.JSCell_typeInfoFlags); + } + m_out.store32(m_out.select(m_out.notZero64(digit), m_out.constInt32(1), m_out.constInt32(0)), fastBigInt, m_heaps.JSBigInt_length); + m_out.store32(m_out.constInt32(0), fastBigInt, m_heaps.JSBigInt_hash); + m_out.store64(digit, fastBigInt, m_heaps.JSBigInt_data0); + mutatorFence(); + ValueFromBlock fastResult = m_out.anchor(fastBigInt); + m_out.jump(continuation); + + LBasicBlock lastNext = m_out.appendTo(slowPath, continuation); + LValue slowResultValue; + if (data.isSigned) + slowResultValue = vmCall(Int64, operationInt64ToBigInt, weakPointer(globalObject), loadedValue); + else + slowResultValue = vmCall(Int64, operationUint64ToBigInt, weakPointer(globalObject), loadedValue); + ValueFromBlock slowResult = m_out.anchor(slowResultValue); + m_out.jump(continuation); + + m_out.appendTo(continuation, lastNext); + setJSValue(m_out.phi(Int64, fastResult, slowResult)); + + break; + } default: RELEASE_ASSERT_NOT_REACHED(); } @@ -21582,6 +21641,9 @@ IGNORE_CLANG_WARNINGS_END case Int52RepUse: valueToStore = lowStrictInt52(valueEdge); break; + case HeapBigIntUse: + valueToStore = lowHeapBigInt(valueEdge); + break; default: RELEASE_ASSERT_NOT_REACHED(); } @@ -21727,6 +21789,47 @@ IGNORE_CLANG_WARNINGS_END break; } + case 8: { + RELEASE_ASSERT(valueEdge.useKind() == HeapBigIntUse); + + // setBigInt64/setBigUint64 wrap the value modulo 2^64, so the + // low digit of the heap BigInt (with the sign applied) suffices. + LBasicBlock nonZeroLength = m_out.newBlock(); + LBasicBlock continuation = m_out.newBlock(); + + LValue length = m_out.load32NonNegative(valueToStore, m_heaps.JSBigInt_length); + ValueFromBlock zeroValue = m_out.anchor(m_out.constInt64(0)); + m_out.branch(m_out.isZero32(length), unsure(continuation), unsure(nonZeroLength)); + + LBasicBlock lastNext = m_out.appendTo(nonZeroLength, continuation); + LValue digit = m_out.load64(valueToStore, m_heaps.JSBigInt_data0); + LValue isNegative = m_out.testNonZero32( + m_out.load8ZeroExt32(valueToStore, m_heaps.JSCell_typeInfoFlags), + m_out.constInt32(TypeInfoPerCellBit)); + ValueFromBlock nonZeroValue = m_out.anchor(m_out.select(isNegative, m_out.neg(digit), digit)); + m_out.jump(continuation); + + m_out.appendTo(continuation, lastNext); + LValue int64Value = m_out.phi(Int64, zeroValue, nonZeroValue); + + auto emitLittleEndianCode = [&] () -> LValue { + m_out.store64(int64Value, pointer); + return nullptr; + }; + auto emitBigEndianCode = [&] () -> LValue { + m_out.store64(byteSwap64(int64Value), pointer); + return nullptr; + }; + + if (data.isLittleEndian == TriState::False) + emitBigEndianCode(); + else if (data.isLittleEndian == TriState::True) + emitLittleEndianCode(); + else + emitCodeBasedOnEndiannessBranch(isLittleEndian, emitLittleEndianCode, emitBigEndianCode); + + break; + } default: RELEASE_ASSERT_NOT_REACHED(); } diff --git a/Source/JavaScriptCore/runtime/Intrinsic.h b/Source/JavaScriptCore/runtime/Intrinsic.h index da4a099cdb8e8..d5aa58bf1d6a5 100644 --- a/Source/JavaScriptCore/runtime/Intrinsic.h +++ b/Source/JavaScriptCore/runtime/Intrinsic.h @@ -276,6 +276,8 @@ namespace JSC { macro(DataViewGetFloat16) \ macro(DataViewGetFloat32) \ macro(DataViewGetFloat64) \ + macro(DataViewGetBigInt64) \ + macro(DataViewGetBigUint64) \ macro(DataViewSetInt8) \ macro(DataViewSetUint8) \ macro(DataViewSetInt16) \ @@ -285,6 +287,8 @@ namespace JSC { macro(DataViewSetFloat16) \ macro(DataViewSetFloat32) \ macro(DataViewSetFloat64) \ + macro(DataViewSetBigInt64) \ + macro(DataViewSetBigUint64) \ \ macro(WasmFunctionIntrinsic) \ diff --git a/Source/JavaScriptCore/runtime/JSBigInt.h b/Source/JavaScriptCore/runtime/JSBigInt.h index 5a6d9e9b81ca7..a1c240d51fa2e 100644 --- a/Source/JavaScriptCore/runtime/JSBigInt.h +++ b/Source/JavaScriptCore/runtime/JSBigInt.h @@ -104,6 +104,11 @@ class JSBigInt final : public JSCell { return OBJECT_OFFSETOF(JSBigInt, m_length); } + static constexpr size_t offsetOfHash() + { + return OBJECT_OFFSETOF(JSBigInt, m_hash); + } + static constexpr size_t offsetOfData() { return WTF::roundUpToMultipleOf(sizeof(JSBigInt)); diff --git a/Source/JavaScriptCore/runtime/JSDataViewPrototype.cpp b/Source/JavaScriptCore/runtime/JSDataViewPrototype.cpp index 12ea054d88f94..c9dfb5071d3c9 100644 --- a/Source/JavaScriptCore/runtime/JSDataViewPrototype.cpp +++ b/Source/JavaScriptCore/runtime/JSDataViewPrototype.cpp @@ -48,8 +48,8 @@ namespace JSC { getFloat16 dataViewProtoFuncGetFloat16 DontEnum|Function 1 DataViewGetFloat16 getFloat32 dataViewProtoFuncGetFloat32 DontEnum|Function 1 DataViewGetFloat32 getFloat64 dataViewProtoFuncGetFloat64 DontEnum|Function 1 DataViewGetFloat64 - getBigInt64 dataViewProtoFuncGetBigInt64 DontEnum|Function 1 - getBigUint64 dataViewProtoFuncGetBigUint64 DontEnum|Function 1 + getBigInt64 dataViewProtoFuncGetBigInt64 DontEnum|Function 1 DataViewGetBigInt64 + getBigUint64 dataViewProtoFuncGetBigUint64 DontEnum|Function 1 DataViewGetBigUint64 setInt8 dataViewProtoFuncSetInt8 DontEnum|Function 2 DataViewSetInt8 setUint8 dataViewProtoFuncSetUint8 DontEnum|Function 2 DataViewSetUint8 setInt16 dataViewProtoFuncSetInt16 DontEnum|Function 2 DataViewSetInt16 @@ -59,8 +59,8 @@ namespace JSC { setFloat16 dataViewProtoFuncSetFloat16 DontEnum|Function 2 DataViewSetFloat16 setFloat32 dataViewProtoFuncSetFloat32 DontEnum|Function 2 DataViewSetFloat32 setFloat64 dataViewProtoFuncSetFloat64 DontEnum|Function 2 DataViewSetFloat64 - setBigInt64 dataViewProtoFuncSetBigInt64 DontEnum|Function 2 - setBigUint64 dataViewProtoFuncSetBigUint64 DontEnum|Function 2 + setBigInt64 dataViewProtoFuncSetBigInt64 DontEnum|Function 2 DataViewSetBigInt64 + setBigUint64 dataViewProtoFuncSetBigUint64 DontEnum|Function 2 DataViewSetBigUint64 buffer dataViewProtoGetterBuffer DontEnum|ReadOnly|CustomAccessor byteOffset dataViewProtoGetterByteOffset DontEnum|ReadOnly|CustomAccessor @end