Files
palemoon27/js/src/jit/x64/CodeGenerator-x64.cpp
T
roytam1 cc394d8cae import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1253094, part 8 - Stop using DebugOnly for class/struct members in uriloader/. r=bz (15566e1146)
- Bug 1253094, part 9 - Stop using DebugOnly for class/struct members in xpcom/. r=froydnj (9fb881be79)
- Bug 1253094, part 10 - Stop using DebugOnly for class/struct members in memory/. r=njn (5fd563e632)
- Bug 1248843 - Make it clearer that DebugOnly uses up space even in optimized, non-DEBUG builds. r=Waldo (8e5e6e6a01)
- Bug 1253094, part 11 - Make DebugOnly a MOZ_STACK_CLASS. r=Waldo (7cee0c3c03)
- Bug 1246116 - BaldrMonkey: Wasm validation for block and loop. r=luke (0da84fb8fe)
- Bug 1252498 - Baldr: add Wasm object behind pref, default off (r=jorendorff) (b554912a96)
- Bug 1256988 - Fix #endif comments for MOZ_WIDGET_GTK. r=chmanchester (28928d1d58)
- Bug 724538 - Regenerate Unicode property data with updated script. r=emk (50e43bb897)
- Bug 1232665 - initialize class members: mLastPrefLang and mLastPrefFirstFont. r=jfkthame (8a62f92809)
- Bug 1248248 - Don't break glyph run for orientation mismatch before a cluster-extender. r=xidorn (f114f65903)
- Bug 1252432 part 1 - Implement wasm i32.wrap. r=luke (54d1e634b6)
- Bug 1252432 part 2 - Implement wasm i64.extend_s and i64.extend_u. r=bbouvier (d673455188)
- Bug 1252432 part 3 - Implement wasm i64.trunc_s and i64.trunc_u. r=sunfish (41dd8d7272)
- Bug 1253115 - BaldrMonkey: Convert AsmJSHeapAccess offsets to unsigned. r=luke (5cb02e4832)
- Bug 1253115 - BaldrMonkey: Refactor AsmJS load/store infrastructure. r=luke (ef75bae281)
- Bug 1243583 - ensure transition events are dispatched to all the relevant subdocuments, r=dholbert (05026b75bb)
- Bug 1240985 - IPC fuzzer (r=gabor) (e825e77187)
- Bug 1248750 - Eliminate intentional IPC crashes (r=dvander) (0ace690c3b)
- Bug 1242609 - Implement PeekMessage to get some messages earlier. r=billm (4985fc8394)
- Bug 1257314 - Properly lock in IPC PeekMessages. r=dvander a=topcrash (6fe1db48f4)
- Bug 1242609 - Use PeekMessages to get the most recent DisplayPort request. r=kats (12374eafba)
- Bug 1254471 - Fix MessageChannel.cpp error unused variable transaction. r=billm (6a74186673)
- Bug 1251482 - Remove remaining references to MOZILLA_XPCOMRT_API from xpcom. r=froydnj (b691ca31f4)
- Bug 1251473 - Remove libxpcomrt library. r=froydnj (faed80b0ed)
- Bug 1249787 - BaldrMonkey: Add the testcase, which was mistakenly omitted from the main push. r=luke (1ef533365a)
- Bug 1250556: Require Store value expression to have the opcode's type; r=sunfish (b8363b4fc6)
- Bug 1250955: Guard against unimplemented i64 opcodes; r=jandem (98689ea7da)
- Bug 1253137 - Baldr: update version uint32 to match BinaryEncoding.md (r=sunfish) (c75d60370a)
- Bug 1253681 - BaldrMonkey: Update to the current official opcode encodings. r=luke (2e69d5780b)
- Bug 1252019: Don't patch profiling entries for the BadIndirectCall exit; r=luke (6f336d796c)
- Bug 1253137 - Baldr: update section header structure to match BinaryEncoding.md, part 1 (r=sunfish) (cd3e204373)
- Bug 1246116 - BaldrMonkey: Wasm validation for block and loop. r=luke (c594d15189)
- Bug 1253137 - Baldr: update memory exports to match BinaryEncoding.md (r=sunfish) (73fd37ee3b)
2024-02-12 09:50:49 +08:00

1190 lines
40 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/x64/CodeGenerator-x64.h"
#include "mozilla/MathAlgorithms.h"
#include "jit/IonCaches.h"
#include "jit/MIR.h"
#include "jsscriptinlines.h"
#include "jit/MacroAssembler-inl.h"
#include "jit/shared/CodeGenerator-shared-inl.h"
using namespace js;
using namespace js::jit;
CodeGeneratorX64::CodeGeneratorX64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
: CodeGeneratorX86Shared(gen, graph, masm)
{
}
ValueOperand
CodeGeneratorX64::ToValue(LInstruction* ins, size_t pos)
{
return ValueOperand(ToRegister(ins->getOperand(pos)));
}
ValueOperand
CodeGeneratorX64::ToOutValue(LInstruction* ins)
{
return ValueOperand(ToRegister(ins->getDef(0)));
}
ValueOperand
CodeGeneratorX64::ToTempValue(LInstruction* ins, size_t pos)
{
return ValueOperand(ToRegister(ins->getTemp(pos)));
}
FrameSizeClass
FrameSizeClass::FromDepth(uint32_t frameDepth)
{
return FrameSizeClass::None();
}
FrameSizeClass
FrameSizeClass::ClassLimit()
{
return FrameSizeClass(0);
}
uint32_t
FrameSizeClass::frameSize() const
{
MOZ_CRASH("x64 does not use frame size classes");
}
void
CodeGeneratorX64::visitValue(LValue* value)
{
LDefinition* reg = value->getDef(0);
masm.moveValue(value->value(), ToRegister(reg));
}
void
CodeGeneratorX64::visitBox(LBox* box)
{
const LAllocation* in = box->getOperand(0);
const LDefinition* result = box->getDef(0);
if (IsFloatingPointType(box->type())) {
ScratchDoubleScope scratch(masm);
FloatRegister reg = ToFloatRegister(in);
if (box->type() == MIRType_Float32) {
masm.convertFloat32ToDouble(reg, scratch);
reg = scratch;
}
masm.vmovq(reg, ToRegister(result));
} else {
masm.boxValue(ValueTypeFromMIRType(box->type()), ToRegister(in), ToRegister(result));
}
}
void
CodeGeneratorX64::visitUnbox(LUnbox* unbox)
{
MUnbox* mir = unbox->mir();
if (mir->fallible()) {
const ValueOperand value = ToValue(unbox, LUnbox::Input);
Assembler::Condition cond;
switch (mir->type()) {
case MIRType_Int32:
cond = masm.testInt32(Assembler::NotEqual, value);
break;
case MIRType_Boolean:
cond = masm.testBoolean(Assembler::NotEqual, value);
break;
case MIRType_Object:
cond = masm.testObject(Assembler::NotEqual, value);
break;
case MIRType_String:
cond = masm.testString(Assembler::NotEqual, value);
break;
case MIRType_Symbol:
cond = masm.testSymbol(Assembler::NotEqual, value);
break;
default:
MOZ_CRASH("Given MIRType cannot be unboxed.");
}
bailoutIf(cond, unbox->snapshot());
}
Operand input = ToOperand(unbox->getOperand(LUnbox::Input));
Register result = ToRegister(unbox->output());
switch (mir->type()) {
case MIRType_Int32:
masm.unboxInt32(input, result);
break;
case MIRType_Boolean:
masm.unboxBoolean(input, result);
break;
case MIRType_Object:
masm.unboxObject(input, result);
break;
case MIRType_String:
masm.unboxString(input, result);
break;
case MIRType_Symbol:
masm.unboxSymbol(input, result);
break;
default:
MOZ_CRASH("Given MIRType cannot be unboxed.");
}
}
void
CodeGeneratorX64::visitCompareB(LCompareB* lir)
{
MCompare* mir = lir->mir();
const ValueOperand lhs = ToValue(lir, LCompareB::Lhs);
const LAllocation* rhs = lir->rhs();
const Register output = ToRegister(lir->output());
MOZ_ASSERT(mir->jsop() == JSOP_STRICTEQ || mir->jsop() == JSOP_STRICTNE);
// Load boxed boolean in ScratchReg.
ScratchRegisterScope scratch(masm);
if (rhs->isConstant())
masm.moveValue(rhs->toConstant()->toJSValue(), scratch);
else
masm.boxValue(JSVAL_TYPE_BOOLEAN, ToRegister(rhs), scratch);
// Perform the comparison.
masm.cmpPtr(lhs.valueReg(), scratch);
masm.emitSet(JSOpToCondition(mir->compareType(), mir->jsop()), output);
}
void
CodeGeneratorX64::visitCompareBAndBranch(LCompareBAndBranch* lir)
{
MCompare* mir = lir->cmpMir();
const ValueOperand lhs = ToValue(lir, LCompareBAndBranch::Lhs);
const LAllocation* rhs = lir->rhs();
MOZ_ASSERT(mir->jsop() == JSOP_STRICTEQ || mir->jsop() == JSOP_STRICTNE);
// Load boxed boolean in ScratchReg.
ScratchRegisterScope scratch(masm);
if (rhs->isConstant())
masm.moveValue(rhs->toConstant()->toJSValue(), scratch);
else
masm.boxValue(JSVAL_TYPE_BOOLEAN, ToRegister(rhs), scratch);
// Perform the comparison.
masm.cmpPtr(lhs.valueReg(), scratch);
emitBranch(JSOpToCondition(mir->compareType(), mir->jsop()), lir->ifTrue(), lir->ifFalse());
}
void
CodeGeneratorX64::visitCompareBitwise(LCompareBitwise* lir)
{
MCompare* mir = lir->mir();
const ValueOperand lhs = ToValue(lir, LCompareBitwise::LhsInput);
const ValueOperand rhs = ToValue(lir, LCompareBitwise::RhsInput);
const Register output = ToRegister(lir->output());
MOZ_ASSERT(IsEqualityOp(mir->jsop()));
masm.cmpPtr(lhs.valueReg(), rhs.valueReg());
masm.emitSet(JSOpToCondition(mir->compareType(), mir->jsop()), output);
}
void
CodeGeneratorX64::visitCompareBitwiseAndBranch(LCompareBitwiseAndBranch* lir)
{
MCompare* mir = lir->cmpMir();
const ValueOperand lhs = ToValue(lir, LCompareBitwiseAndBranch::LhsInput);
const ValueOperand rhs = ToValue(lir, LCompareBitwiseAndBranch::RhsInput);
MOZ_ASSERT(mir->jsop() == JSOP_EQ || mir->jsop() == JSOP_STRICTEQ ||
mir->jsop() == JSOP_NE || mir->jsop() == JSOP_STRICTNE);
masm.cmpPtr(lhs.valueReg(), rhs.valueReg());
emitBranch(JSOpToCondition(mir->compareType(), mir->jsop()), lir->ifTrue(), lir->ifFalse());
}
void
CodeGeneratorX64::visitCompare64(LCompare64* lir)
{
MCompare* mir = lir->mir();
MOZ_ASSERT(mir->compareType() == MCompare::Compare_Int64 ||
mir->compareType() == MCompare::Compare_UInt64);
Register lhs = ToRegister(lir->getOperand(0));
const LAllocation* rhs = lir->getOperand(1);
if (rhs->isConstant())
masm.cmpPtr(lhs, ImmWord(ToInt64(rhs)));
else
masm.cmpPtr(lhs, ToOperand(rhs));
bool isSigned = mir->compareType() == MCompare::Compare_Int64;
masm.emitSet(JSOpToCondition(lir->jsop(), isSigned), ToRegister(lir->output()));
}
void
CodeGeneratorX64::visitCompare64AndBranch(LCompare64AndBranch* lir)
{
MCompare* mir = lir->cmpMir();
MOZ_ASSERT(mir->compareType() == MCompare::Compare_Int64 ||
mir->compareType() == MCompare::Compare_UInt64);
Register lhs = ToRegister(lir->getOperand(0));
const LAllocation* rhs = lir->getOperand(1);
if (rhs->isConstant())
masm.cmpPtr(lhs, ImmWord(ToInt64(rhs)));
else
masm.cmpPtr(lhs, ToOperand(rhs));
bool isSigned = mir->compareType() == MCompare::Compare_Int64;
emitBranch(JSOpToCondition(lir->jsop(), isSigned), lir->ifTrue(), lir->ifFalse());
}
void
CodeGeneratorX64::visitBitOpI64(LBitOpI64* lir)
{
Register lhs = ToRegister(lir->getOperand(0));
const LAllocation* rhs = lir->getOperand(1);
switch (lir->bitop()) {
case JSOP_BITOR:
if (rhs->isConstant())
masm.or64(Imm64(ToInt64(rhs)), Register64(lhs));
else
masm.orq(ToOperand(rhs), lhs);
break;
case JSOP_BITXOR:
if (rhs->isConstant())
masm.xor64(Imm64(ToInt64(rhs)), Register64(lhs));
else
masm.xorq(ToOperand(rhs), lhs);
break;
case JSOP_BITAND:
if (rhs->isConstant())
masm.and64(Imm64(ToInt64(rhs)), Register64(lhs));
else
masm.andq(ToOperand(rhs), lhs);
break;
default:
MOZ_CRASH("unexpected binary opcode");
}
}
void
CodeGeneratorX64::visitShiftI64(LShiftI64* lir)
{
Register lhs = ToRegister(lir->getOperand(0));
const LAllocation* rhs = lir->getOperand(1);
if (rhs->isConstant()) {
int32_t shift = ToInt32(rhs) & 0x3F;
switch (lir->bitop()) {
case JSOP_LSH:
if (shift)
masm.shlq(Imm32(shift), lhs);
break;
case JSOP_RSH:
if (shift)
masm.sarq(Imm32(shift), lhs);
break;
case JSOP_URSH:
if (shift)
masm.shrq(Imm32(shift), lhs);
break;
default:
MOZ_CRASH("Unexpected shift op");
}
} else {
MOZ_ASSERT(ToRegister(rhs) == ecx);
switch (lir->bitop()) {
case JSOP_LSH:
masm.shlq_cl(lhs);
break;
case JSOP_RSH:
masm.sarq_cl(lhs);
break;
case JSOP_URSH:
masm.shrq_cl(lhs);
break;
default:
MOZ_CRASH("Unexpected shift op");
}
}
}
void
CodeGeneratorX64::visitAddI64(LAddI64* lir)
{
Register lhs = ToRegister(lir->getOperand(0));
const LAllocation* rhs = lir->getOperand(1);
MOZ_ASSERT(ToRegister(lir->getDef(0)) == lhs);
if (rhs->isConstant())
masm.addPtr(ImmWord(ToInt64(rhs)), lhs);
else
masm.addq(ToOperand(rhs), lhs);
}
void
CodeGeneratorX64::visitSubI64(LSubI64* lir)
{
Register lhs = ToRegister(lir->getOperand(0));
const LAllocation* rhs = lir->getOperand(1);
MOZ_ASSERT(ToRegister(lir->getDef(0)) == lhs);
if (rhs->isConstant())
masm.subPtr(ImmWord(ToInt64(rhs)), lhs);
else
masm.subq(ToOperand(rhs), lhs);
}
void
CodeGeneratorX64::visitMulI64(LMulI64* lir)
{
Register lhs = ToRegister(lir->getOperand(0));
const LAllocation* rhs = lir->getOperand(1);
MOZ_ASSERT(ToRegister(lir->getDef(0)) == lhs);
if (rhs->isConstant()) {
int64_t constant = ToInt64(rhs);
switch (constant) {
case -1:
masm.negq(lhs);
return;
case 0:
masm.xorl(lhs, lhs);
return;
case 1:
// nop
return;
case 2:
masm.addq(lhs, lhs);
return;
default:
if (constant > 0) {
// Use shift if constant is power of 2.
int32_t shift = mozilla::FloorLog2(constant);
if (int64_t(1 << shift) == constant) {
masm.shlq(Imm32(shift), lhs);
return;
}
}
masm.mul64(Imm64(constant), Register64(lhs));
}
} else {
masm.imulq(ToOperand(rhs), lhs);
}
}
void
CodeGeneratorX64::visitDivOrModI64(LDivOrModI64* lir)
{
Register lhs = ToRegister(lir->lhs());
Register rhs = ToRegister(lir->rhs());
Register output = ToRegister(lir->output());
MOZ_ASSERT_IF(lhs != rhs, rhs != rax);
MOZ_ASSERT(rhs != rdx);
MOZ_ASSERT_IF(output == rax, ToRegister(lir->remainder()) == rdx);
MOZ_ASSERT_IF(output == rdx, ToRegister(lir->remainder()) == rax);
Label done;
// Put the lhs in rax.
if (lhs != rax)
masm.mov(lhs, rax);
// Handle divide by zero. For now match asm.js and return 0, but
// eventually this should trap.
if (lir->canBeDivideByZero()) {
Label nonZero;
masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero);
masm.xorl(output, output);
masm.jump(&done);
masm.bind(&nonZero);
}
// Handle an integer overflow exception from INT64_MIN / -1. Eventually
// signed integer division should trap, instead of returning the
// LHS (INT64_MIN).
if (lir->canBeNegativeOverflow()) {
Label notmin;
masm.branchPtr(Assembler::NotEqual, lhs, ImmWord(INT64_MIN), &notmin);
masm.branchPtr(Assembler::NotEqual, rhs, ImmWord(-1), &notmin);
if (lir->mir()->isMod()) {
masm.xorl(output, output);
} else {
if (lhs != output)
masm.mov(lhs, output);
}
masm.jump(&done);
masm.bind(&notmin);
}
// Sign extend the lhs into rdx to make rdx:rax.
masm.cqo();
masm.idivq(rhs);
masm.bind(&done);
}
void
CodeGeneratorX64::visitUDivOrMod64(LUDivOrMod64* lir)
{
Register lhs = ToRegister(lir->lhs());
Register rhs = ToRegister(lir->rhs());
Register output = ToRegister(lir->output());
MOZ_ASSERT_IF(lhs != rhs, rhs != rax);
MOZ_ASSERT(rhs != rdx);
MOZ_ASSERT_IF(output == rax, ToRegister(lir->remainder()) == rdx);
MOZ_ASSERT_IF(output == rdx, ToRegister(lir->remainder()) == rax);
// Put the lhs in rax.
if (lhs != rax)
masm.mov(lhs, rax);
Label done;
// Prevent divide by zero. For now match asm.js and return 0, but
// eventually this should trap.
if (lir->canBeDivideByZero()) {
Label nonZero;
masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero);
masm.xorl(output, output);
masm.jump(&done);
masm.bind(&nonZero);
}
// Zero extend the lhs into rdx to make (rdx:rax).
masm.xorl(rdx, rdx);
masm.udivq(rhs);
masm.bind(&done);
}
void
CodeGeneratorX64::visitAsmJSUInt32ToDouble(LAsmJSUInt32ToDouble* lir)
{
masm.convertUInt32ToDouble(ToRegister(lir->input()), ToFloatRegister(lir->output()));
}
void
CodeGeneratorX64::visitAsmJSUInt32ToFloat32(LAsmJSUInt32ToFloat32* lir)
{
masm.convertUInt32ToFloat32(ToRegister(lir->input()), ToFloatRegister(lir->output()));
}
void
CodeGeneratorX64::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins)
{
MOZ_CRASH("NYI");
}
void
CodeGeneratorX64::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins)
{
MOZ_CRASH("NYI");
}
void
CodeGeneratorX64::visitAsmJSCall(LAsmJSCall* ins)
{
emitAsmJSCall(ins);
}
void
CodeGeneratorX64::memoryBarrier(MemoryBarrierBits barrier)
{
if (barrier & MembarStoreLoad)
masm.storeLoadFence();
}
void
CodeGeneratorX64::loadSimd(Scalar::Type type, unsigned numElems, const Operand& srcAddr,
FloatRegister out)
{
switch (type) {
case Scalar::Float32x4: {
switch (numElems) {
// In memory-to-register mode, movss zeroes out the high lanes.
case 1: masm.loadFloat32(srcAddr, out); break;
// See comment above, which also applies to movsd.
case 2: masm.loadDouble(srcAddr, out); break;
case 4: masm.loadUnalignedFloat32x4(srcAddr, out); break;
default: MOZ_CRASH("unexpected size for partial load");
}
break;
}
case Scalar::Int32x4: {
switch (numElems) {
// In memory-to-register mode, movd zeroes out the high lanes.
case 1: masm.vmovd(srcAddr, out); break;
// See comment above, which also applies to movq.
case 2: masm.vmovq(srcAddr, out); break;
case 4: masm.loadUnalignedInt32x4(srcAddr, out); break;
default: MOZ_CRASH("unexpected size for partial load");
}
break;
}
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:
case Scalar::MaxTypedArrayViewType:
MOZ_CRASH("should only handle SIMD types");
}
}
void
CodeGeneratorX64::emitSimdLoad(LAsmJSLoadHeap* ins)
{
const MAsmJSLoadHeap* mir = ins->mir();
Scalar::Type type = mir->accessType();
FloatRegister out = ToFloatRegister(ins->output());
const LAllocation* ptr = ins->ptr();
Operand srcAddr = ptr->isBogus()
? Operand(HeapReg, mir->offset())
: Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
uint32_t maybeCmpOffset = maybeEmitThrowingAsmJSBoundsCheck(mir, mir, ptr);
unsigned numElems = mir->numSimdElems();
if (numElems == 3) {
MOZ_ASSERT(type == Scalar::Int32x4 || type == Scalar::Float32x4);
Operand srcAddrZ =
ptr->isBogus()
? Operand(HeapReg, 2 * sizeof(float) + mir->offset())
: Operand(HeapReg, ToRegister(ptr), TimesOne, 2 * sizeof(float) + mir->offset());
// Load XY
uint32_t before = masm.size();
loadSimd(type, 2, srcAddr, out);
uint32_t after = masm.size();
verifyHeapAccessDisassembly(before, after, /*isLoad=*/true, type, 2, srcAddr,
*ins->output()->output());
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw, maybeCmpOffset));
// Load Z (W is zeroed)
// This is still in bounds, as we've checked with a manual bounds check
// or we had enough space for sure when removing the bounds check.
before = after;
loadSimd(type, 1, srcAddrZ, ScratchSimd128Reg);
after = masm.size();
verifyHeapAccessDisassembly(before, after, /*isLoad=*/true, type, 1, srcAddrZ,
LFloatReg(ScratchSimd128Reg));
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw,
wasm::HeapAccess::NoLengthCheck, 8));
// Move ZW atop XY
masm.vmovlhps(ScratchSimd128Reg, out, out);
} else {
uint32_t before = masm.size();
loadSimd(type, numElems, srcAddr, out);
uint32_t after = masm.size();
verifyHeapAccessDisassembly(before, after, /*isLoad=*/true, type, numElems, srcAddr, *ins->output()->output());
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw, maybeCmpOffset));
}
if (maybeCmpOffset != wasm::HeapAccess::NoLengthCheck)
cleanupAfterAsmJSBoundsCheckBranch(mir, ToRegister(ptr));
}
void
CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap* ins)
{
const MAsmJSLoadHeap* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
if (Scalar::isSimdType(accessType))
return emitSimdLoad(ins);
const LAllocation* ptr = ins->ptr();
const LDefinition* out = ins->output();
Operand srcAddr = ptr->isBogus()
? Operand(HeapReg, mir->offset())
: Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
memoryBarrier(mir->barrierBefore());
OutOfLineLoadTypedArrayOutOfBounds* ool;
uint32_t maybeCmpOffset = maybeEmitAsmJSLoadBoundsCheck(mir, ins, &ool);
uint32_t before = masm.size();
switch (accessType) {
case Scalar::Int8: masm.movsbl(srcAddr, ToRegister(out)); break;
case Scalar::Uint8: masm.movzbl(srcAddr, ToRegister(out)); break;
case Scalar::Int16: masm.movswl(srcAddr, ToRegister(out)); break;
case Scalar::Uint16: masm.movzwl(srcAddr, ToRegister(out)); break;
case Scalar::Int32:
case Scalar::Uint32: masm.movl(srcAddr, ToRegister(out)); break;
case Scalar::Float32: masm.loadFloat32(srcAddr, ToFloatRegister(out)); break;
case Scalar::Float64: masm.loadDouble(srcAddr, ToFloatRegister(out)); break;
case Scalar::Float32x4:
case Scalar::Int32x4: MOZ_CRASH("SIMD loads should be handled in emitSimdLoad");
case Scalar::Uint8Clamped:
case Scalar::MaxTypedArrayViewType:
MOZ_CRASH("unexpected array type");
}
uint32_t after = masm.size();
verifyHeapAccessDisassembly(before, after, /*isLoad=*/true, accessType, 0, srcAddr, *out->output());
if (ool) {
cleanupAfterAsmJSBoundsCheckBranch(mir, ToRegister(ptr));
masm.bind(ool->rejoin());
}
memoryBarrier(mir->barrierAfter());
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::CarryOn, maybeCmpOffset));
}
void
CodeGeneratorX64::storeSimd(Scalar::Type type, unsigned numElems, FloatRegister in,
const Operand& dstAddr)
{
switch (type) {
case Scalar::Float32x4: {
switch (numElems) {
// In memory-to-register mode, movss zeroes out the high lanes.
case 1: masm.storeFloat32(in, dstAddr); break;
// See comment above, which also applies to movsd.
case 2: masm.storeDouble(in, dstAddr); break;
case 4: masm.storeUnalignedFloat32x4(in, dstAddr); break;
default: MOZ_CRASH("unexpected size for partial load");
}
break;
}
case Scalar::Int32x4: {
switch (numElems) {
// In memory-to-register mode, movd zeroes out the high lanes.
case 1: masm.vmovd(in, dstAddr); break;
// See comment above, which also applies to movq.
case 2: masm.vmovq(in, dstAddr); break;
case 4: masm.storeUnalignedInt32x4(in, dstAddr); break;
default: MOZ_CRASH("unexpected size for partial load");
}
break;
}
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:
case Scalar::MaxTypedArrayViewType:
MOZ_CRASH("should only handle SIMD types");
}
}
void
CodeGeneratorX64::emitSimdStore(LAsmJSStoreHeap* ins)
{
const MAsmJSStoreHeap* mir = ins->mir();
Scalar::Type type = mir->accessType();
FloatRegister in = ToFloatRegister(ins->value());
const LAllocation* ptr = ins->ptr();
Operand dstAddr = ptr->isBogus()
? Operand(HeapReg, mir->offset())
: Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
uint32_t maybeCmpOffset = maybeEmitThrowingAsmJSBoundsCheck(mir, mir, ptr);
unsigned numElems = mir->numSimdElems();
if (numElems == 3) {
MOZ_ASSERT(type == Scalar::Int32x4 || type == Scalar::Float32x4);
Operand dstAddrZ =
ptr->isBogus()
? Operand(HeapReg, 2 * sizeof(float) + mir->offset())
: Operand(HeapReg, ToRegister(ptr), TimesOne, 2 * sizeof(float) + mir->offset());
// It's possible that the Z could be out of bounds when the XY is in
// bounds. To avoid storing the XY before the exception is thrown, we
// store the Z first, and record its offset in the HeapAccess so
// that the signal handler knows to check the bounds of the full
// access, rather than just the Z.
masm.vmovhlps(in, ScratchSimd128Reg, ScratchSimd128Reg);
uint32_t before = masm.size();
storeSimd(type, 1, ScratchSimd128Reg, dstAddrZ);
uint32_t after = masm.size();
verifyHeapAccessDisassembly(before, after, /*isLoad=*/false, type, 1, dstAddrZ,
LFloatReg(ScratchSimd128Reg));
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw, maybeCmpOffset, 8));
// Store XY
before = after;
storeSimd(type, 2, in, dstAddr);
after = masm.size();
verifyHeapAccessDisassembly(before, after, /*isLoad=*/false, type, 2, dstAddr, *ins->value());
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw));
} else {
uint32_t before = masm.size();
storeSimd(type, numElems, in, dstAddr);
uint32_t after = masm.size();
verifyHeapAccessDisassembly(before, after, /*isLoad=*/false, type, numElems, dstAddr, *ins->value());
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw, maybeCmpOffset));
}
if (maybeCmpOffset != wasm::HeapAccess::NoLengthCheck)
cleanupAfterAsmJSBoundsCheckBranch(mir, ToRegister(ptr));
}
void
CodeGeneratorX64::visitAsmJSStoreHeap(LAsmJSStoreHeap* ins)
{
const MAsmJSStoreHeap* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
if (Scalar::isSimdType(accessType))
return emitSimdStore(ins);
const LAllocation* value = ins->value();
const LAllocation* ptr = ins->ptr();
Operand dstAddr = ptr->isBogus()
? Operand(HeapReg, mir->offset())
: Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
memoryBarrier(mir->barrierBefore());
Label* rejoin;
uint32_t maybeCmpOffset = maybeEmitAsmJSStoreBoundsCheck(mir, ins, &rejoin);
uint32_t before = masm.size();
if (value->isConstant()) {
switch (accessType) {
case Scalar::Int8:
case Scalar::Uint8: masm.movb(Imm32(ToInt32(value)), dstAddr); break;
case Scalar::Int16:
case Scalar::Uint16: masm.movw(Imm32(ToInt32(value)), dstAddr); break;
case Scalar::Int32:
case Scalar::Uint32: masm.movl(Imm32(ToInt32(value)), dstAddr); break;
case Scalar::Float32:
case Scalar::Float64:
case Scalar::Float32x4:
case Scalar::Int32x4:
case Scalar::Uint8Clamped:
case Scalar::MaxTypedArrayViewType:
MOZ_CRASH("unexpected array type");
}
} else {
switch (accessType) {
case Scalar::Int8:
case Scalar::Uint8: masm.movb(ToRegister(value), dstAddr); break;
case Scalar::Int16:
case Scalar::Uint16: masm.movw(ToRegister(value), dstAddr); break;
case Scalar::Int32:
case Scalar::Uint32: masm.movl(ToRegister(value), dstAddr); break;
case Scalar::Float32: masm.storeFloat32(ToFloatRegister(value), dstAddr); break;
case Scalar::Float64: masm.storeDouble(ToFloatRegister(value), dstAddr); break;
case Scalar::Float32x4:
case Scalar::Int32x4: MOZ_CRASH("SIMD stores must be handled in emitSimdStore");
case Scalar::Uint8Clamped:
case Scalar::MaxTypedArrayViewType:
MOZ_CRASH("unexpected array type");
}
}
uint32_t after = masm.size();
verifyHeapAccessDisassembly(before, after, /*isLoad=*/false, accessType, 0, dstAddr, *value);
if (rejoin) {
cleanupAfterAsmJSBoundsCheckBranch(mir, ToRegister(ptr));
masm.bind(rejoin);
}
memoryBarrier(mir->barrierAfter());
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::CarryOn, maybeCmpOffset));
}
void
CodeGeneratorX64::visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins)
{
MAsmJSCompareExchangeHeap* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
const LAllocation* ptr = ins->ptr();
MOZ_ASSERT(ins->addrTemp()->isBogusTemp());
MOZ_ASSERT(ptr->isRegister());
BaseIndex srcAddr(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
Register oldval = ToRegister(ins->oldValue());
Register newval = ToRegister(ins->newValue());
// Note that we can't use the same machinery as normal asm.js loads/stores
// since signal-handler bounds checking is not yet implemented for atomic accesses.
uint32_t maybeCmpOffset = wasm::HeapAccess::NoLengthCheck;
if (mir->needsBoundsCheck()) {
maybeCmpOffset = masm.cmp32WithPatch(ToRegister(ptr), Imm32(-mir->endOffset())).offset();
masm.j(Assembler::Above, wasm::JumpTarget::OutOfBounds);
}
uint32_t before = masm.size();
masm.compareExchangeToTypedIntArray(accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
srcAddr,
oldval,
newval,
InvalidReg,
ToAnyRegister(ins->output()));
MOZ_ASSERT(mir->offset() == 0,
"The AsmJS signal handler doesn't yet support emulating "
"atomic accesses in the case of a fault from an unwrapped offset");
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw, maybeCmpOffset));
}
void
CodeGeneratorX64::visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins)
{
MAsmJSAtomicExchangeHeap* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
const LAllocation* ptr = ins->ptr();
MOZ_ASSERT(ins->addrTemp()->isBogusTemp());
MOZ_ASSERT(ptr->isRegister());
MOZ_ASSERT(accessType <= Scalar::Uint32);
BaseIndex srcAddr(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
Register value = ToRegister(ins->value());
// Note that we can't use the same machinery as normal asm.js loads/stores
// since signal-handler bounds checking is not yet implemented for atomic accesses.
uint32_t maybeCmpOffset = wasm::HeapAccess::NoLengthCheck;
if (mir->needsBoundsCheck()) {
maybeCmpOffset = masm.cmp32WithPatch(ToRegister(ptr), Imm32(-mir->endOffset())).offset();
masm.j(Assembler::Above, wasm::JumpTarget::OutOfBounds);
}
uint32_t before = masm.size();
masm.atomicExchangeToTypedIntArray(accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
srcAddr,
value,
InvalidReg,
ToAnyRegister(ins->output()));
MOZ_ASSERT(mir->offset() == 0,
"The AsmJS signal handler doesn't yet support emulating "
"atomic accesses in the case of a fault from an unwrapped offset");
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw, maybeCmpOffset));
}
void
CodeGeneratorX64::visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins)
{
MOZ_ASSERT(ins->mir()->hasUses());
MOZ_ASSERT(ins->addrTemp()->isBogusTemp());
MAsmJSAtomicBinopHeap* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
Register ptrReg = ToRegister(ins->ptr());
Register temp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
const LAllocation* value = ins->value();
AtomicOp op = mir->operation();
BaseIndex srcAddr(HeapReg, ptrReg, TimesOne, mir->offset());
// Note that we can't use the same machinery as normal asm.js loads/stores
// since signal-handler bounds checking is not yet implemented for atomic accesses.
uint32_t maybeCmpOffset = wasm::HeapAccess::NoLengthCheck;
if (mir->needsBoundsCheck()) {
maybeCmpOffset = masm.cmp32WithPatch(ptrReg, Imm32(-mir->endOffset())).offset();
masm.j(Assembler::Above, wasm::JumpTarget::OutOfBounds);
}
uint32_t before = masm.size();
if (value->isConstant()) {
atomicBinopToTypedIntArray(op, accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
Imm32(ToInt32(value)),
srcAddr,
temp,
InvalidReg,
ToAnyRegister(ins->output()));
} else {
atomicBinopToTypedIntArray(op, accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
ToRegister(value),
srcAddr,
temp,
InvalidReg,
ToAnyRegister(ins->output()));
}
MOZ_ASSERT(mir->offset() == 0,
"The AsmJS signal handler doesn't yet support emulating "
"atomic accesses in the case of a fault from an unwrapped offset");
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw, maybeCmpOffset));
}
void
CodeGeneratorX64::visitAsmJSAtomicBinopHeapForEffect(LAsmJSAtomicBinopHeapForEffect* ins)
{
MOZ_ASSERT(!ins->mir()->hasUses());
MOZ_ASSERT(ins->addrTemp()->isBogusTemp());
MAsmJSAtomicBinopHeap* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
Register ptrReg = ToRegister(ins->ptr());
const LAllocation* value = ins->value();
AtomicOp op = mir->operation();
BaseIndex srcAddr(HeapReg, ptrReg, TimesOne, mir->offset());
// Note that we can't use the same machinery as normal asm.js loads/stores
// since signal-handler bounds checking is not yet implemented for atomic accesses.
uint32_t maybeCmpOffset = wasm::HeapAccess::NoLengthCheck;
if (mir->needsBoundsCheck()) {
maybeCmpOffset = masm.cmp32WithPatch(ptrReg, Imm32(-mir->endOffset())).offset();
masm.j(Assembler::Above, wasm::JumpTarget::OutOfBounds);
}
uint32_t before = masm.size();
if (value->isConstant())
atomicBinopToTypedIntArray(op, accessType, Imm32(ToInt32(value)), srcAddr);
else
atomicBinopToTypedIntArray(op, accessType, ToRegister(value), srcAddr);
MOZ_ASSERT(mir->offset() == 0,
"The AsmJS signal handler doesn't yet support emulating "
"atomic accesses in the case of a fault from an unwrapped offset");
masm.append(wasm::HeapAccess(before, wasm::HeapAccess::Throw, maybeCmpOffset));
}
void
CodeGeneratorX64::visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar* ins)
{
MAsmJSLoadGlobalVar* mir = ins->mir();
MIRType type = mir->type();
MOZ_ASSERT(IsNumberType(type) || IsSimdType(type));
CodeOffset label;
switch (type) {
case MIRType_Int32:
label = masm.loadRipRelativeInt32(ToRegister(ins->output()));
break;
case MIRType_Float32:
label = masm.loadRipRelativeFloat32(ToFloatRegister(ins->output()));
break;
case MIRType_Double:
label = masm.loadRipRelativeDouble(ToFloatRegister(ins->output()));
break;
// Aligned access: code is aligned on PageSize + there is padding
// before the global data section.
case MIRType_Int32x4:
case MIRType_Bool32x4:
label = masm.loadRipRelativeInt32x4(ToFloatRegister(ins->output()));
break;
case MIRType_Float32x4:
label = masm.loadRipRelativeFloat32x4(ToFloatRegister(ins->output()));
break;
default:
MOZ_CRASH("unexpected type in visitAsmJSLoadGlobalVar");
}
masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset()));
}
void
CodeGeneratorX64::visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar* ins)
{
MAsmJSStoreGlobalVar* mir = ins->mir();
MIRType type = mir->value()->type();
MOZ_ASSERT(IsNumberType(type) || IsSimdType(type));
CodeOffset label;
switch (type) {
case MIRType_Int32:
label = masm.storeRipRelativeInt32(ToRegister(ins->value()));
break;
case MIRType_Float32:
label = masm.storeRipRelativeFloat32(ToFloatRegister(ins->value()));
break;
case MIRType_Double:
label = masm.storeRipRelativeDouble(ToFloatRegister(ins->value()));
break;
// Aligned access: code is aligned on PageSize + there is padding
// before the global data section.
case MIRType_Int32x4:
case MIRType_Bool32x4:
label = masm.storeRipRelativeInt32x4(ToFloatRegister(ins->value()));
break;
case MIRType_Float32x4:
label = masm.storeRipRelativeFloat32x4(ToFloatRegister(ins->value()));
break;
default:
MOZ_CRASH("unexpected type in visitAsmJSStoreGlobalVar");
}
masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset()));
}
void
CodeGeneratorX64::visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr* ins)
{
MAsmJSLoadFuncPtr* mir = ins->mir();
Register index = ToRegister(ins->index());
Register tmp = ToRegister(ins->temp());
Register out = ToRegister(ins->output());
if (mir->hasLimit()) {
masm.branch32(Assembler::Condition::AboveOrEqual, index, Imm32(mir->limit()),
wasm::JumpTarget::OutOfBounds);
}
if (mir->alwaysThrow()) {
masm.jump(wasm::JumpTarget::BadIndirectCall);
} else {
CodeOffset label = masm.leaRipRelative(tmp);
masm.loadPtr(Operand(tmp, index, TimesEight, 0), out);
masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset()));
}
}
void
CodeGeneratorX64::visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc* ins)
{
MAsmJSLoadFFIFunc* mir = ins->mir();
CodeOffset label = masm.loadRipRelativeInt64(ToRegister(ins->output()));
masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset()));
}
void
CodeGeneratorX64::visitTruncateDToInt32(LTruncateDToInt32* ins)
{
FloatRegister input = ToFloatRegister(ins->input());
Register output = ToRegister(ins->output());
// On x64, branchTruncateDouble uses vcvttsd2sq. Unlike the x86
// implementation, this should handle most doubles and we can just
// call a stub if it fails.
emitTruncateDouble(input, output, ins->mir());
}
void
CodeGeneratorX64::visitTruncateFToInt32(LTruncateFToInt32* ins)
{
FloatRegister input = ToFloatRegister(ins->input());
Register output = ToRegister(ins->output());
// On x64, branchTruncateFloat32 uses vcvttss2sq. Unlike the x86
// implementation, this should handle most floats and we can just
// call a stub if it fails.
emitTruncateFloat32(input, output, ins->mir());
}
void
CodeGeneratorX64::visitWrapInt64ToInt32(LWrapInt64ToInt32* lir)
{
const LAllocation* input = lir->getOperand(0);
Register output = ToRegister(lir->output());
masm.movl(ToOperand(input), output);
}
void
CodeGeneratorX64::visitExtendInt32ToInt64(LExtendInt32ToInt64* lir)
{
const LAllocation* input = lir->getOperand(0);
Register output = ToRegister(lir->output());
if (lir->mir()->isUnsigned())
masm.movl(ToOperand(input), output);
else
masm.movslq(ToOperand(input), output);
}
void
CodeGeneratorX64::visitTruncateToInt64(LTruncateToInt64* lir)
{
FloatRegister input = ToFloatRegister(lir->input());
Register output = ToRegister(lir->output());
MIRType inputType = lir->mir()->input()->type();
bool isUnsigned = lir->mir()->isUnsigned();
// We should trap on invalid inputs, but for now we just return
// 0x8000000000000000. Note that we can remove some unnecessary jumps
// once we get rid of this trap Label.
Label trap;
Label done;
if (isUnsigned) {
FloatRegister tempDouble = ToFloatRegister(lir->temp());
// If the input < INT64_MAX, vcvttsd2sq will do the right thing, so
// we use it directly. Else, we subtract INT64_MAX, convert to int64,
// and then add INT64_MAX to the result.
if (inputType == MIRType_Double) {
Label isLarge;
masm.loadConstantDouble(double(0x8000000000000000), ScratchDoubleReg);
masm.branchDouble(Assembler::DoubleGreaterThanOrEqual, input, ScratchDoubleReg, &isLarge);
masm.vcvttsd2sq(input, output);
masm.branchTestPtr(Assembler::Signed, output, output, &trap);
masm.jump(&done);
masm.bind(&isLarge);
masm.moveDouble(input, tempDouble);
masm.subDouble(ScratchDoubleReg, tempDouble);
masm.vcvttsd2sq(tempDouble, output);
masm.branchTestPtr(Assembler::Signed, output, output, &trap);
masm.or64(Imm64(0x8000000000000000), Register64(output));
masm.jump(&done);
} else {
MOZ_ASSERT(inputType == MIRType_Float32);
Label isLarge;
masm.loadConstantFloat32(float(0x8000000000000000), ScratchDoubleReg);
masm.branchFloat(Assembler::DoubleGreaterThanOrEqual, input, ScratchDoubleReg, &isLarge);
masm.vcvttss2sq(input, output);
masm.branchTestPtr(Assembler::Signed, output, output, &trap);
masm.jump(&done);
masm.bind(&isLarge);
masm.moveFloat32(input, tempDouble);
masm.vsubss(ScratchDoubleReg, tempDouble, tempDouble);
masm.vcvttss2sq(tempDouble, output);
masm.branchTestPtr(Assembler::Signed, output, output, &trap);
masm.or64(Imm64(0x8000000000000000), Register64(output));
masm.jump(&done);
}
} else {
if (inputType == MIRType_Double) {
masm.vcvttsd2sq(input, output);
masm.cmpq(Imm32(1), output);
masm.j(Assembler::Overflow, &trap);
masm.jump(&done);
} else {
MOZ_ASSERT(inputType == MIRType_Float32);
masm.vcvttss2sq(input, output);
masm.cmpq(Imm32(1), output);
masm.j(Assembler::Overflow, &trap);
masm.jump(&done);
}
}
masm.bind(&trap);
masm.movePtr(ImmWord(0x8000000000000000), output);
masm.bind(&done);
}