/* -*- 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/x86/MacroAssembler-x86.h" #include "mozilla/Alignment.h" #include "mozilla/Casting.h" #include "jit/Bailouts.h" #include "jit/BaselineFrame.h" #include "jit/JitFrames.h" #include "jit/MacroAssembler.h" #include "jit/MoveEmitter.h" #include "jsscriptinlines.h" #include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; static const double TO_DOUBLE_HIGH_SCALE = 0x100000000; bool MacroAssemblerX86::convertUInt64ToDoubleNeedsTemp() { return HasSSE3(); } void MacroAssemblerX86::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp) { // SUBPD needs SSE2, HADDPD needs SSE3. if (!HasSSE3()) { MOZ_ASSERT(temp == Register::Invalid()); // Zero the dest register to break dependencies, see convertInt32ToDouble. zeroDouble(dest); asMasm().Push(src.high); asMasm().Push(src.low); fild(Operand(esp, 0)); Label notNegative; asMasm().branch32(Assembler::NotSigned, src.high, Imm32(0), ¬Negative); double add_constant = 18446744073709551616.0; // 2^64 store64(Imm64(mozilla::BitwiseCast(add_constant)), Address(esp, 0)); fld(Operand(esp, 0)); faddp(); bind(¬Negative); fstp(Operand(esp, 0)); vmovsd(Address(esp, 0), dest); asMasm().freeStack(2*sizeof(intptr_t)); return; } // Following operation uses entire 128-bit of dest XMM register. // Currently higher 64-bit is free when we have access to lower 64-bit. MOZ_ASSERT(dest.size() == 8); FloatRegister dest128 = FloatRegister(dest.encoding(), FloatRegisters::Simd128); // Assume that src is represented as following: // src = 0x HHHHHHHH LLLLLLLL // Move src to dest (=dest128) and ScratchInt32x4Reg (=scratch): // dest = 0x 00000000 00000000 00000000 LLLLLLLL // scratch = 0x 00000000 00000000 00000000 HHHHHHHH vmovd(src.low, dest128); vmovd(src.high, ScratchSimd128Reg); // Unpack and interleave dest and scratch to dest: // dest = 0x 00000000 00000000 HHHHHHHH LLLLLLLL vpunpckldq(ScratchSimd128Reg, dest128, dest128); // Unpack and interleave dest and a constant C1 to dest: // C1 = 0x 00000000 00000000 45300000 43300000 // dest = 0x 45300000 HHHHHHHH 43300000 LLLLLLLL // here, each 64-bit part of dest represents following double: // HI(dest) = 0x 1.00000HHHHHHHH * 2**84 == 2**84 + 0x HHHHHHHH 00000000 // LO(dest) = 0x 1.00000LLLLLLLL * 2**52 == 2**52 + 0x 00000000 LLLLLLLL // See convertUInt64ToDouble for the details. static const int32_t CST1[4] = { 0x43300000, 0x45300000, 0x0, 0x0, }; loadConstantSimd128Int(SimdConstant::CreateX4(CST1), ScratchSimd128Reg); vpunpckldq(ScratchSimd128Reg, dest128, dest128); // Subtract a constant C2 from dest, for each 64-bit part: // C2 = 0x 45300000 00000000 43300000 00000000 // here, each 64-bit part of C2 represents following double: // HI(C2) = 0x 1.0000000000000 * 2**84 == 2**84 // LO(C2) = 0x 1.0000000000000 * 2**52 == 2**52 // after the operation each 64-bit part of dest represents following: // HI(dest) = double(0x HHHHHHHH 00000000) // LO(dest) = double(0x 00000000 LLLLLLLL) static const int32_t CST2[4] = { 0x0, 0x43300000, 0x0, 0x45300000, }; loadConstantSimd128Int(SimdConstant::CreateX4(CST2), ScratchSimd128Reg); vsubpd(ScratchSimd128Reg, dest128, dest128); // Add HI(dest) and LO(dest) in double and store it into LO(dest), // LO(dest) = double(0x HHHHHHHH 00000000) + double(0x 00000000 LLLLLLLL) // = double(0x HHHHHHHH LLLLLLLL) // = double(src) vhaddpd(dest128, dest128); } void MacroAssemblerX86::loadConstantDouble(wasm::RawF64 d, FloatRegister dest) { if (maybeInlineDouble(d, dest)) return; Double* dbl = getDouble(d); if (!dbl) return; masm.vmovsd_mr(nullptr, dest.encoding()); propagateOOM(dbl->uses.append(CodeOffset(masm.size()))); } void MacroAssemblerX86::loadConstantDouble(double d, FloatRegister dest) { loadConstantDouble(wasm::RawF64(d), dest); } void MacroAssemblerX86::loadConstantFloat32(wasm::RawF32 f, FloatRegister dest) { if (maybeInlineFloat(f, dest)) return; Float* flt = getFloat(f); if (!flt) return; masm.vmovss_mr(nullptr, dest.encoding()); propagateOOM(flt->uses.append(CodeOffset(masm.size()))); } void MacroAssemblerX86::loadConstantFloat32(float f, FloatRegister dest) { loadConstantFloat32(wasm::RawF32(f), dest); } void MacroAssemblerX86::loadConstantSimd128Int(const SimdConstant& v, FloatRegister dest) { if (maybeInlineSimd128Int(v, dest)) return; SimdData* i4 = getSimdData(v); if (!i4) return; masm.vmovdqa_mr(nullptr, dest.encoding()); propagateOOM(i4->uses.append(CodeOffset(masm.size()))); } void MacroAssemblerX86::loadConstantSimd128Float(const SimdConstant& v, FloatRegister dest) { if (maybeInlineSimd128Float(v, dest)) return; SimdData* f4 = getSimdData(v); if (!f4) return; masm.vmovaps_mr(nullptr, dest.encoding()); propagateOOM(f4->uses.append(CodeOffset(masm.size()))); } void MacroAssemblerX86::finish() { if (!doubles_.empty()) masm.haltingAlign(sizeof(double)); for (const Double& d : doubles_) { CodeOffset cst(masm.currentOffset()); for (CodeOffset use : d.uses) addCodeLabel(CodeLabel(use, cst)); masm.int64Constant(d.value); if (!enoughMemory_) return; } if (!floats_.empty()) masm.haltingAlign(sizeof(float)); for (const Float& f : floats_) { CodeOffset cst(masm.currentOffset()); for (CodeOffset use : f.uses) addCodeLabel(CodeLabel(use, cst)); masm.int32Constant(f.value); if (!enoughMemory_) return; } // SIMD memory values must be suitably aligned. if (!simds_.empty()) masm.haltingAlign(SimdMemoryAlignment); for (const SimdData& v : simds_) { CodeOffset cst(masm.currentOffset()); for (CodeOffset use : v.uses) addCodeLabel(CodeLabel(use, cst)); masm.simd128Constant(v.value.bytes()); if (!enoughMemory_) return; } } void MacroAssemblerX86::handleFailureWithHandlerTail(void* handler) { // Reserve space for exception information. subl(Imm32(sizeof(ResumeFromException)), esp); movl(esp, eax); // Call the handler. asMasm().setupUnalignedABICall(ecx); asMasm().passABIArg(eax); asMasm().callWithABI(handler); Label entryFrame; Label catch_; Label finally; Label return_; Label bailout; loadPtr(Address(esp, offsetof(ResumeFromException, kind)), eax); asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame); asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_CATCH), &catch_); asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_FINALLY), &finally); asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_); asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout); breakpoint(); // Invalid kind. // No exception handler. Load the error value, load the new stack pointer // and return from the entry frame. bind(&entryFrame); moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand); loadPtr(Address(esp, offsetof(ResumeFromException, stackPointer)), esp); ret(); // If we found a catch handler, this must be a baseline frame. Restore state // and jump to the catch block. bind(&catch_); loadPtr(Address(esp, offsetof(ResumeFromException, target)), eax); loadPtr(Address(esp, offsetof(ResumeFromException, framePointer)), ebp); loadPtr(Address(esp, offsetof(ResumeFromException, stackPointer)), esp); jmp(Operand(eax)); // If we found a finally block, this must be a baseline frame. Push // two values expected by JSOP_RETSUB: BooleanValue(true) and the // exception. bind(&finally); ValueOperand exception = ValueOperand(ecx, edx); loadValue(Address(esp, offsetof(ResumeFromException, exception)), exception); loadPtr(Address(esp, offsetof(ResumeFromException, target)), eax); loadPtr(Address(esp, offsetof(ResumeFromException, framePointer)), ebp); loadPtr(Address(esp, offsetof(ResumeFromException, stackPointer)), esp); pushValue(BooleanValue(true)); pushValue(exception); jmp(Operand(eax)); // Only used in debug mode. Return BaselineFrame->returnValue() to the caller. bind(&return_); loadPtr(Address(esp, offsetof(ResumeFromException, framePointer)), ebp); loadPtr(Address(esp, offsetof(ResumeFromException, stackPointer)), esp); loadValue(Address(ebp, BaselineFrame::reverseOffsetOfReturnValue()), JSReturnOperand); movl(ebp, esp); pop(ebp); // If profiling is enabled, then update the lastProfilingFrame to refer to caller // frame before returning. { Label skipProfilingInstrumentation; // Test if profiler enabled. AbsoluteAddress addressOfEnabled(GetJitContext()->runtime->spsProfiler().addressOfEnabled()); asMasm().branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); profilerExitFrame(); bind(&skipProfilingInstrumentation); } ret(); // If we are bailing out to baseline to handle an exception, jump to // the bailout tail stub. bind(&bailout); loadPtr(Address(esp, offsetof(ResumeFromException, bailoutInfo)), ecx); movl(Imm32(BAILOUT_RETURN_OK), eax); jmp(Operand(esp, offsetof(ResumeFromException, target))); } void MacroAssemblerX86::profilerEnterFrame(Register framePtr, Register scratch) { AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation()); loadPtr(activation, scratch); storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame())); storePtr(ImmPtr(nullptr), Address(scratch, JitActivation::offsetOfLastProfilingCallSite())); } void MacroAssemblerX86::profilerExitFrame() { jmp(GetJitContext()->runtime->jitRuntime()->getProfilerExitFrameTail()); } MacroAssembler& MacroAssemblerX86::asMasm() { return *static_cast(this); } const MacroAssembler& MacroAssemblerX86::asMasm() const { return *static_cast(this); } void MacroAssembler::subFromStackPtr(Imm32 imm32) { if (imm32.value) { // On windows, we cannot skip very far down the stack without touching the // memory pages in-between. This is a corner-case code for situations where the // Ion frame data for a piece of code is very large. To handle this special case, // for frames over 1k in size we allocate memory on the stack incrementally, touching // it as we go. uint32_t amountLeft = imm32.value; while (amountLeft > 4096) { subl(Imm32(4096), StackPointer); store32(Imm32(0), Address(StackPointer, 0)); amountLeft -= 4096; } subl(Imm32(amountLeft), StackPointer); } } //{{{ check_macroassembler_style // =============================================================== // ABI function calls. void MacroAssembler::setupUnalignedABICall(Register scratch) { setupABICall(); dynamicAlignment_ = true; movl(esp, scratch); andl(Imm32(~(ABIStackAlignment - 1)), esp); push(scratch); } void MacroAssembler::callWithABIPre(uint32_t* stackAdjust, bool callFromWasm) { MOZ_ASSERT(inCall_); uint32_t stackForCall = abiArgs_.stackBytesConsumedSoFar(); if (dynamicAlignment_) { // sizeof(intptr_t) accounts for the saved stack pointer pushed by // setupUnalignedABICall. stackForCall += ComputeByteAlignment(stackForCall + sizeof(intptr_t), ABIStackAlignment); } else { uint32_t alignmentAtPrologue = callFromWasm ? sizeof(wasm::Frame) : 0; stackForCall += ComputeByteAlignment(stackForCall + framePushed() + alignmentAtPrologue, ABIStackAlignment); } *stackAdjust = stackForCall; reserveStack(stackForCall); // Position all arguments. { enoughMemory_ &= moveResolver_.resolve(); if (!enoughMemory_) return; MoveEmitter emitter(*this); emitter.emit(moveResolver_); emitter.finish(); } assertStackAlignment(ABIStackAlignment); } void MacroAssembler::callWithABIPost(uint32_t stackAdjust, MoveOp::Type result) { freeStack(stackAdjust); if (result == MoveOp::DOUBLE) { reserveStack(sizeof(double)); fstp(Operand(esp, 0)); loadDouble(Operand(esp, 0), ReturnDoubleReg); freeStack(sizeof(double)); } else if (result == MoveOp::FLOAT32) { reserveStack(sizeof(float)); fstp32(Operand(esp, 0)); loadFloat32(Operand(esp, 0), ReturnFloat32Reg); freeStack(sizeof(float)); } if (dynamicAlignment_) pop(esp); #ifdef DEBUG MOZ_ASSERT(inCall_); inCall_ = false; #endif } void MacroAssembler::callWithABINoProfiler(Register fun, MoveOp::Type result) { uint32_t stackAdjust; callWithABIPre(&stackAdjust); call(fun); callWithABIPost(stackAdjust, result); } void MacroAssembler::callWithABINoProfiler(const Address& fun, MoveOp::Type result) { uint32_t stackAdjust; callWithABIPre(&stackAdjust); call(fun); callWithABIPost(stackAdjust, result); } // =============================================================== // Branch functions void MacroAssembler::branchPtrInNurseryChunk(Condition cond, Register ptr, Register temp, Label* label) { MOZ_ASSERT(temp != InvalidReg); // A temp register is required for x86. MOZ_ASSERT(ptr != temp); movePtr(ptr, temp); branchPtrInNurseryChunkImpl(cond, temp, label); } void MacroAssembler::branchPtrInNurseryChunk(Condition cond, const Address& address, Register temp, Label* label) { MOZ_ASSERT(temp != InvalidReg); // A temp register is required for x86. loadPtr(address, temp); branchPtrInNurseryChunkImpl(cond, temp, label); } void MacroAssembler::branchPtrInNurseryChunkImpl(Condition cond, Register ptr, Label* label) { MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); orPtr(Imm32(gc::ChunkMask), ptr); branch32(cond, Address(ptr, gc::ChunkLocationOffsetFromLastByte), Imm32(int32_t(gc::ChunkLocation::Nursery)), label); } void MacroAssembler::branchValueIsNurseryObject(Condition cond, const Address& address, Register temp, Label* label) { MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); Label done; branchTestObject(Assembler::NotEqual, address, cond == Assembler::Equal ? &done : label); branchPtrInNurseryChunk(cond, address, temp, label); bind(&done); } void MacroAssembler::branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, Label* label) { MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); Label done; branchTestObject(Assembler::NotEqual, value, cond == Assembler::Equal ? &done : label); branchPtrInNurseryChunk(cond, value.payloadReg(), temp, label); bind(&done); } void MacroAssembler::branchTestValue(Condition cond, const ValueOperand& lhs, const Value& rhs, Label* label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); if (rhs.isGCThing()) cmpPtr(lhs.payloadReg(), ImmGCPtr(rhs.toGCThing())); else cmpPtr(lhs.payloadReg(), ImmWord(rhs.toNunboxPayload())); if (cond == Equal) { Label done; j(NotEqual, &done); { cmp32(lhs.typeReg(), Imm32(rhs.toNunboxTag())); j(Equal, label); } bind(&done); } else { j(NotEqual, label); cmp32(lhs.typeReg(), Imm32(rhs.toNunboxTag())); j(NotEqual, label); } } // ======================================================================== // Memory access primitives. template void MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, MIRType valueType, const T& dest, MIRType slotType) { if (valueType == MIRType::Double) { storeDouble(value.reg().typedReg().fpu(), dest); return; } // Store the type tag if needed. if (valueType != slotType) storeTypeTag(ImmType(ValueTypeFromMIRType(valueType)), Operand(dest)); // Store the payload. if (value.constant()) storePayload(value.value(), Operand(dest)); else storePayload(value.reg().typedReg().gpr(), Operand(dest)); } template void MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, MIRType valueType, const Address& dest, MIRType slotType); template void MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, MIRType valueType, const BaseIndex& dest, MIRType slotType); // wasm specific methods, used in both the wasm baseline compiler and ion. void MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, Operand srcAddr, AnyRegister out) { memoryBarrier(access.barrierBefore()); size_t loadOffset = size(); switch (access.type()) { case Scalar::Int8: movsblWithPatch(srcAddr, out.gpr()); break; case Scalar::Uint8: movzblWithPatch(srcAddr, out.gpr()); break; case Scalar::Int16: movswlWithPatch(srcAddr, out.gpr()); break; case Scalar::Uint16: movzwlWithPatch(srcAddr, out.gpr()); break; case Scalar::Int32: case Scalar::Uint32: movlWithPatch(srcAddr, out.gpr()); break; case Scalar::Float32: vmovssWithPatch(srcAddr, out.fpu()); break; case Scalar::Float64: vmovsdWithPatch(srcAddr, out.fpu()); break; case Scalar::Float32x4: switch (access.numSimdElems()) { // In memory-to-register mode, movss zeroes out the high lanes. case 1: vmovssWithPatch(srcAddr, out.fpu()); break; // See comment above, which also applies to movsd. case 2: vmovsdWithPatch(srcAddr, out.fpu()); break; case 4: vmovupsWithPatch(srcAddr, out.fpu()); break; default: MOZ_CRASH("unexpected size for partial load"); } break; case Scalar::Int32x4: switch (access.numSimdElems()) { // In memory-to-register mode, movd zeroes out the high lanes. case 1: vmovdWithPatch(srcAddr, out.fpu()); break; // See comment above, which also applies to movq. case 2: vmovqWithPatch(srcAddr, out.fpu()); break; case 4: vmovdquWithPatch(srcAddr, out.fpu()); break; default: MOZ_CRASH("unexpected size for partial load"); } break; case Scalar::Int8x16: MOZ_ASSERT(access.numSimdElems() == 16, "unexpected partial load"); vmovdquWithPatch(srcAddr, out.fpu()); break; case Scalar::Int16x8: MOZ_ASSERT(access.numSimdElems() == 8, "unexpected partial load"); vmovdquWithPatch(srcAddr, out.fpu()); break; case Scalar::Int64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected type"); } append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); memoryBarrier(access.barrierAfter()); } void MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAddr, Register64 out) { MOZ_ASSERT(!access.isAtomic()); MOZ_ASSERT(!access.isSimd()); size_t loadOffset = size(); switch (access.type()) { case Scalar::Int8: MOZ_ASSERT(out == Register64(edx, eax)); movsblWithPatch(srcAddr, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); cdq(); break; case Scalar::Uint8: movzblWithPatch(srcAddr, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); xorl(out.high, out.high); break; case Scalar::Int16: MOZ_ASSERT(out == Register64(edx, eax)); movswlWithPatch(srcAddr, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); cdq(); break; case Scalar::Uint16: movzwlWithPatch(srcAddr, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); xorl(out.high, out.high); break; case Scalar::Int32: MOZ_ASSERT(out == Register64(edx, eax)); movlWithPatch(srcAddr, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); cdq(); break; case Scalar::Uint32: movlWithPatch(srcAddr, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); xorl(out.high, out.high); break; case Scalar::Int64: if (srcAddr.kind() == Operand::MEM_ADDRESS32) { Operand low(PatchedAbsoluteAddress(uint32_t(srcAddr.address()) + INT64LOW_OFFSET)); Operand high(PatchedAbsoluteAddress(uint32_t(srcAddr.address()) + INT64HIGH_OFFSET)); movlWithPatch(low, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); loadOffset = size(); movlWithPatch(high, out.high); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); } else { MOZ_ASSERT(srcAddr.kind() == Operand::MEM_REG_DISP); Address addr = srcAddr.toAddress(); Operand low(addr.base, addr.offset + INT64LOW_OFFSET); Operand high(addr.base, addr.offset + INT64HIGH_OFFSET); if (addr.base != out.low) { movlWithPatch(low, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); loadOffset = size(); movlWithPatch(high, out.high); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); } else { MOZ_ASSERT(addr.base != out.high); movlWithPatch(high, out.high); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); loadOffset = size(); movlWithPatch(low, out.low); append(wasm::MemoryPatch(size())); append(access, loadOffset, framePushed()); } } break; case Scalar::Float32: case Scalar::Float64: case Scalar::Float32x4: case Scalar::Int8x16: case Scalar::Int16x8: case Scalar::Int32x4: MOZ_CRASH("non-int64 loads should use load()"); case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); } } void MacroAssembler::wasmStore(const wasm::MemoryAccessDesc& access, AnyRegister value, Operand dstAddr) { memoryBarrier(access.barrierBefore()); size_t storeOffset = size(); switch (access.type()) { case Scalar::Int8: case Scalar::Uint8Clamped: case Scalar::Uint8: movbWithPatch(value.gpr(), dstAddr); break; case Scalar::Int16: case Scalar::Uint16: movwWithPatch(value.gpr(), dstAddr); break; case Scalar::Int32: case Scalar::Uint32: movlWithPatch(value.gpr(), dstAddr); break; case Scalar::Float32: vmovssWithPatch(value.fpu(), dstAddr); break; case Scalar::Float64: vmovsdWithPatch(value.fpu(), dstAddr); break; case Scalar::Float32x4: switch (access.numSimdElems()) { // In memory-to-register mode, movss zeroes out the high lanes. case 1: vmovssWithPatch(value.fpu(), dstAddr); break; // See comment above, which also applies to movsd. case 2: vmovsdWithPatch(value.fpu(), dstAddr); break; case 4: vmovupsWithPatch(value.fpu(), dstAddr); break; default: MOZ_CRASH("unexpected size for partial load"); } break; case Scalar::Int32x4: switch (access.numSimdElems()) { // In memory-to-register mode, movd zeroes out the high lanes. case 1: vmovdWithPatch(value.fpu(), dstAddr); break; // See comment above, which also applies to movsd. case 2: vmovqWithPatch(value.fpu(), dstAddr); break; case 4: vmovdquWithPatch(value.fpu(), dstAddr); break; default: MOZ_CRASH("unexpected size for partial load"); } break; case Scalar::Int8x16: MOZ_ASSERT(access.numSimdElems() == 16, "unexpected partial store"); vmovdquWithPatch(value.fpu(), dstAddr); break; case Scalar::Int16x8: MOZ_ASSERT(access.numSimdElems() == 8, "unexpected partial store"); vmovdquWithPatch(value.fpu(), dstAddr); break; case Scalar::Int64: MOZ_CRASH("Should be handled in storeI64."); case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected type"); } append(wasm::MemoryPatch(size())); append(access, storeOffset, framePushed()); memoryBarrier(access.barrierAfter()); } void MacroAssembler::wasmStoreI64(const wasm::MemoryAccessDesc& access, Register64 value, Operand dstAddr) { MOZ_ASSERT(!access.isAtomic()); MOZ_ASSERT(!access.isSimd()); size_t storeOffset = size(); if (dstAddr.kind() == Operand::MEM_ADDRESS32) { Operand low(PatchedAbsoluteAddress(uint32_t(dstAddr.address()) + INT64LOW_OFFSET)); Operand high(PatchedAbsoluteAddress(uint32_t(dstAddr.address()) + INT64HIGH_OFFSET)); movlWithPatch(value.low, low); append(wasm::MemoryPatch(size())); append(access, storeOffset, framePushed()); storeOffset = size(); movlWithPatch(value.high, high); append(wasm::MemoryPatch(size())); append(access, storeOffset, framePushed()); } else { MOZ_ASSERT(dstAddr.kind() == Operand::MEM_REG_DISP); Address addr = dstAddr.toAddress(); Operand low(addr.base, addr.offset + INT64LOW_OFFSET); Operand high(addr.base, addr.offset + INT64HIGH_OFFSET); if (addr.base != value.low) { movlWithPatch(value.low, low); append(wasm::MemoryPatch(size())); append(access, storeOffset, framePushed()); storeOffset = size(); movlWithPatch(value.high, high); append(wasm::MemoryPatch(size())); append(access, storeOffset, framePushed()); } else { MOZ_ASSERT(addr.base != value.high); movlWithPatch(value.high, high); append(wasm::MemoryPatch(size())); append(access, storeOffset, framePushed()); storeOffset = size(); movlWithPatch(value.low, low); append(wasm::MemoryPatch(size())); append(access, storeOffset, framePushed()); } } } void MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry) { Label done; vcvttsd2si(input, output); branch32(Assembler::Condition::NotSigned, output, Imm32(0), &done); loadConstantDouble(double(int32_t(0x80000000)), ScratchDoubleReg); addDouble(input, ScratchDoubleReg); vcvttsd2si(ScratchDoubleReg, output); branch32(Assembler::Condition::Signed, output, Imm32(0), oolEntry); or32(Imm32(0x80000000), output); bind(&done); } void MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry) { Label done; vcvttss2si(input, output); branch32(Assembler::Condition::NotSigned, output, Imm32(0), &done); loadConstantFloat32(float(int32_t(0x80000000)), ScratchFloat32Reg); addFloat32(input, ScratchFloat32Reg); vcvttss2si(ScratchFloat32Reg, output); branch32(Assembler::Condition::Signed, output, Imm32(0), oolEntry); or32(Imm32(0x80000000), output); bind(&done); } //}}} check_macroassembler_style void MacroAssemblerX86::convertInt64ToDouble(Register64 input, FloatRegister output) { // Zero the output register to break dependencies, see convertInt32ToDouble. zeroDouble(output); asMasm().Push(input.high); asMasm().Push(input.low); fild(Operand(esp, 0)); fstp(Operand(esp, 0)); vmovsd(Address(esp, 0), output); asMasm().freeStack(2*sizeof(intptr_t)); } void MacroAssemblerX86::convertInt64ToFloat32(Register64 input, FloatRegister output) { convertInt64ToDouble(input, output); convertDoubleToFloat32(output, output); } void MacroAssemblerX86::convertUInt64ToFloat32(Register64 input, FloatRegister output, Register temp) { convertUInt64ToDouble(input, output.asDouble(), temp); convertDoubleToFloat32(output, output); } void MacroAssemblerX86::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry, Label* oolRejoin, FloatRegister tempReg) { Label fail, convert; Register temp = output.high; // Make sure input fits in (u)int64. asMasm().reserveStack(2 * sizeof(int32_t)); asMasm().storeDouble(input, Operand(esp, 0)); asMasm().branchDoubleNotInInt64Range(Address(esp, 0), temp, &fail); jump(&convert); // Handle failure in ool. bind(&fail); asMasm().freeStack(2 * sizeof(int32_t)); jump(oolEntry); bind(oolRejoin); asMasm().reserveStack(2 * sizeof(int32_t)); asMasm().storeDouble(input, Operand(esp, 0)); // Convert the double/float to int64. bind(&convert); asMasm().truncateDoubleToInt64(Address(esp, 0), Address(esp, 0), temp); // Load value into int64 register. load64(Address(esp, 0), output); asMasm().freeStack(2 * sizeof(int32_t)); } void MacroAssemblerX86::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry, Label* oolRejoin, FloatRegister tempReg) { Label fail, convert; Register temp = output.high; // Make sure input fits in (u)int64. asMasm().reserveStack(2 * sizeof(int32_t)); asMasm().storeFloat32(input, Operand(esp, 0)); asMasm().branchFloat32NotInInt64Range(Address(esp, 0), temp, &fail); jump(&convert); // Handle failure in ool. bind(&fail); asMasm().freeStack(2 * sizeof(int32_t)); jump(oolEntry); bind(oolRejoin); asMasm().reserveStack(2 * sizeof(int32_t)); asMasm().storeFloat32(input, Operand(esp, 0)); // Convert the double/float to int64. bind(&convert); asMasm().truncateFloat32ToInt64(Address(esp, 0), Address(esp, 0), temp); // Load value into int64 register. load64(Address(esp, 0), output); asMasm().freeStack(2 * sizeof(int32_t)); } void MacroAssemblerX86::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry, Label* oolRejoin, FloatRegister tempReg) { Label fail, convert; Register temp = output.high; // Make sure input fits in (u)int64. asMasm().reserveStack(2 * sizeof(int32_t)); asMasm().storeDouble(input, Operand(esp, 0)); asMasm().branchDoubleNotInUInt64Range(Address(esp, 0), temp, &fail); jump(&convert); // Handle failure in ool. bind(&fail); asMasm().freeStack(2 * sizeof(int32_t)); jump(oolEntry); bind(oolRejoin); asMasm().reserveStack(2 * sizeof(int32_t)); asMasm().storeDouble(input, Operand(esp, 0)); // Convert the double/float to int64. bind(&convert); asMasm().truncateDoubleToUInt64(Address(esp, 0), Address(esp, 0), temp, tempReg); // Load value into int64 register. load64(Address(esp, 0), output); asMasm().freeStack(2 * sizeof(int32_t)); } void MacroAssemblerX86::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry, Label* oolRejoin, FloatRegister tempReg) { Label fail, convert; Register temp = output.high; // Make sure input fits in (u)int64. asMasm().reserveStack(2 * sizeof(int32_t)); asMasm().storeFloat32(input, Operand(esp, 0)); asMasm().branchFloat32NotInUInt64Range(Address(esp, 0), temp, &fail); jump(&convert); // Handle failure in ool. bind(&fail); asMasm().freeStack(2 * sizeof(int32_t)); jump(oolEntry); bind(oolRejoin); asMasm().reserveStack(2 * sizeof(int32_t)); asMasm().storeFloat32(input, Operand(esp, 0)); // Convert the double/float to int64. bind(&convert); asMasm().truncateFloat32ToUInt64(Address(esp, 0), Address(esp, 0), temp, tempReg); // Load value into int64 register. load64(Address(esp, 0), output); asMasm().freeStack(2 * sizeof(int32_t)); }