Mypal/js/src/wasm/WasmStubs.cpp

1152 lines
45 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:
*
* Copyright 2015 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wasm/WasmStubs.h"
#include "mozilla/ArrayUtils.h"
#include "wasm/WasmCode.h"
#include "wasm/WasmIonCompile.h"
#include "jit/MacroAssembler-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::ArrayLength;
static void
AssertStackAlignment(MacroAssembler& masm, uint32_t alignment, uint32_t addBeforeAssert = 0)
{
MOZ_ASSERT((sizeof(Frame) + masm.framePushed() + addBeforeAssert) % alignment == 0);
masm.assertStackAlignment(alignment, addBeforeAssert);
}
static unsigned
StackDecrementForCall(MacroAssembler& masm, uint32_t alignment, unsigned bytesToPush)
{
return StackDecrementForCall(alignment, sizeof(Frame) + masm.framePushed(), bytesToPush);
}
template <class VectorT>
static unsigned
StackArgBytes(const VectorT& args)
{
ABIArgIter<VectorT> iter(args);
while (!iter.done())
iter++;
return iter.stackBytesConsumedSoFar();
}
template <class VectorT>
static unsigned
StackDecrementForCall(MacroAssembler& masm, uint32_t alignment, const VectorT& args,
unsigned extraBytes = 0)
{
return StackDecrementForCall(masm, alignment, StackArgBytes(args) + extraBytes);
}
#if defined(JS_CODEGEN_ARM)
// The ARM system ABI also includes d15 & s31 in the non volatile float registers.
// Also exclude lr (a.k.a. r14) as we preserve it manually)
static const LiveRegisterSet NonVolatileRegs =
LiveRegisterSet(GeneralRegisterSet(Registers::NonVolatileMask&
~(uint32_t(1) << Registers::lr)),
FloatRegisterSet(FloatRegisters::NonVolatileMask
| (1ULL << FloatRegisters::d15)
| (1ULL << FloatRegisters::s31)));
#else
static const LiveRegisterSet NonVolatileRegs =
LiveRegisterSet(GeneralRegisterSet(Registers::NonVolatileMask),
FloatRegisterSet(FloatRegisters::NonVolatileMask));
#endif
#if defined(JS_CODEGEN_MIPS32)
// Mips is using one more double slot due to stack alignment for double values.
// Look at MacroAssembler::PushRegsInMask(RegisterSet set)
static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) +
NonVolatileRegs.fpus().getPushSizeInBytes() +
sizeof(double);
#elif defined(JS_CODEGEN_NONE)
static const unsigned FramePushedAfterSave = 0;
#else
static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t)
+ NonVolatileRegs.fpus().getPushSizeInBytes();
#endif
static const unsigned FramePushedForEntrySP = FramePushedAfterSave + sizeof(void*);
// Generate a stub that enters wasm from a C++ caller via the native ABI. The
// signature of the entry point is Module::ExportFuncPtr. The exported wasm
// function has an ABI derived from its specific signature, so this function
// must map from the ABI of ExportFuncPtr to the export's signature's ABI.
Offsets
wasm::GenerateEntry(MacroAssembler& masm, const FuncExport& fe)
{
masm.haltingAlign(CodeAlignment);
Offsets offsets;
offsets.begin = masm.currentOffset();
// Save the return address if it wasn't already saved by the call insn.
#if defined(JS_CODEGEN_ARM)
masm.push(lr);
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
masm.push(ra);
#endif
// Save all caller non-volatile registers before we clobber them here and in
// the asm.js callee (which does not preserve non-volatile registers).
masm.setFramePushed(0);
masm.PushRegsInMask(NonVolatileRegs);
MOZ_ASSERT(masm.framePushed() == FramePushedAfterSave);
// Put the 'argv' argument into a non-argument/return/TLS register so that
// we can use 'argv' while we fill in the arguments for the asm.js callee.
Register argv = ABINonArgReturnReg0;
Register scratch = ABINonArgReturnReg1;
// Read the arguments of wasm::ExportFuncPtr according to the native ABI.
// The entry stub's frame is only 1 word, not the usual 2 for wasm::Frame.
const unsigned argBase = sizeof(void*) + masm.framePushed();
ABIArgGenerator abi;
ABIArg arg;
// arg 1: ExportArg*
arg = abi.next(MIRType::Pointer);
if (arg.kind() == ABIArg::GPR)
masm.movePtr(arg.gpr(), argv);
else
masm.loadPtr(Address(masm.getStackPointer(), argBase + arg.offsetFromArgBase()), argv);
// Arg 2: TlsData*
arg = abi.next(MIRType::Pointer);
if (arg.kind() == ABIArg::GPR)
masm.movePtr(arg.gpr(), WasmTlsReg);
else
masm.loadPtr(Address(masm.getStackPointer(), argBase + arg.offsetFromArgBase()), WasmTlsReg);
// Setup pinned registers that are assumed throughout wasm code.
masm.loadWasmPinnedRegsFromTls();
// Save 'argv' on the stack so that we can recover it after the call. Use
// a second non-argument/return register as temporary scratch.
masm.Push(argv);
// Save the stack pointer in the WasmActivation right before dynamically
// aligning the stack so that it may be recovered on return or throw.
MOZ_ASSERT(masm.framePushed() == FramePushedForEntrySP);
masm.loadWasmActivationFromTls(scratch);
masm.storeStackPtr(Address(scratch, WasmActivation::offsetOfEntrySP()));
// Dynamically align the stack since ABIStackAlignment is not necessarily
// WasmStackAlignment. We'll use entrySP to recover the original stack
// pointer on return.
masm.andToStackPtr(Imm32(~(WasmStackAlignment - 1)));
// Bump the stack for the call.
masm.reserveStack(AlignBytes(StackArgBytes(fe.sig().args()), WasmStackAlignment));
// Copy parameters out of argv and into the registers/stack-slots specified by
// the system ABI.
for (ABIArgValTypeIter iter(fe.sig().args()); !iter.done(); iter++) {
unsigned argOffset = iter.index() * sizeof(ExportArg);
Address src(argv, argOffset);
MIRType type = iter.mirType();
switch (iter->kind()) {
case ABIArg::GPR:
if (type == MIRType::Int32)
masm.load32(src, iter->gpr());
else if (type == MIRType::Int64)
masm.load64(src, iter->gpr64());
break;
#ifdef JS_CODEGEN_REGISTER_PAIR
case ABIArg::GPR_PAIR:
if (type == MIRType::Int64)
masm.load64(src, iter->gpr64());
else
MOZ_CRASH("wasm uses hardfp for function calls.");
break;
#endif
case ABIArg::FPU: {
static_assert(sizeof(ExportArg) >= jit::Simd128DataSize,
"ExportArg must be big enough to store SIMD values");
switch (type) {
case MIRType::Int8x16:
case MIRType::Int16x8:
case MIRType::Int32x4:
case MIRType::Bool8x16:
case MIRType::Bool16x8:
case MIRType::Bool32x4:
masm.loadUnalignedSimd128Int(src, iter->fpu());
break;
case MIRType::Float32x4:
masm.loadUnalignedSimd128Float(src, iter->fpu());
break;
case MIRType::Double:
masm.loadDouble(src, iter->fpu());
break;
case MIRType::Float32:
masm.loadFloat32(src, iter->fpu());
break;
default:
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected FPU type");
break;
}
break;
}
case ABIArg::Stack:
switch (type) {
case MIRType::Int32:
masm.load32(src, scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(), iter->offsetFromArgBase()));
break;
case MIRType::Int64: {
Register sp = masm.getStackPointer();
#if JS_BITS_PER_WORD == 32
masm.load32(Address(src.base, src.offset + INT64LOW_OFFSET), scratch);
masm.store32(scratch, Address(sp, iter->offsetFromArgBase() + INT64LOW_OFFSET));
masm.load32(Address(src.base, src.offset + INT64HIGH_OFFSET), scratch);
masm.store32(scratch, Address(sp, iter->offsetFromArgBase() + INT64HIGH_OFFSET));
#else
Register64 scratch64(scratch);
masm.load64(src, scratch64);
masm.store64(scratch64, Address(sp, iter->offsetFromArgBase()));
#endif
break;
}
case MIRType::Double:
masm.loadDouble(src, ScratchDoubleReg);
masm.storeDouble(ScratchDoubleReg,
Address(masm.getStackPointer(), iter->offsetFromArgBase()));
break;
case MIRType::Float32:
masm.loadFloat32(src, ScratchFloat32Reg);
masm.storeFloat32(ScratchFloat32Reg,
Address(masm.getStackPointer(), iter->offsetFromArgBase()));
break;
case MIRType::Int8x16:
case MIRType::Int16x8:
case MIRType::Int32x4:
case MIRType::Bool8x16:
case MIRType::Bool16x8:
case MIRType::Bool32x4:
masm.loadUnalignedSimd128Int(src, ScratchSimd128Reg);
masm.storeAlignedSimd128Int(
ScratchSimd128Reg, Address(masm.getStackPointer(), iter->offsetFromArgBase()));
break;
case MIRType::Float32x4:
masm.loadUnalignedSimd128Float(src, ScratchSimd128Reg);
masm.storeAlignedSimd128Float(
ScratchSimd128Reg, Address(masm.getStackPointer(), iter->offsetFromArgBase()));
break;
default:
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected stack arg type");
}
break;
}
}
// Call into the real function.
masm.assertStackAlignment(WasmStackAlignment);
masm.call(CallSiteDesc(CallSiteDesc::Func), fe.funcIndex());
// Recover the stack pointer value before dynamic alignment.
masm.loadWasmActivationFromTls(scratch);
masm.loadStackPtr(Address(scratch, WasmActivation::offsetOfEntrySP()));
masm.setFramePushed(FramePushedForEntrySP);
// Recover the 'argv' pointer which was saved before aligning the stack.
masm.Pop(argv);
// Store the return value in argv[0]
switch (fe.sig().ret()) {
case ExprType::Void:
break;
case ExprType::I32:
masm.store32(ReturnReg, Address(argv, 0));
break;
case ExprType::I64:
masm.store64(ReturnReg64, Address(argv, 0));
break;
case ExprType::F32:
if (!JitOptions.wasmTestMode)
masm.canonicalizeFloat(ReturnFloat32Reg);
masm.storeFloat32(ReturnFloat32Reg, Address(argv, 0));
break;
case ExprType::F64:
if (!JitOptions.wasmTestMode)
masm.canonicalizeDouble(ReturnDoubleReg);
masm.storeDouble(ReturnDoubleReg, Address(argv, 0));
break;
case ExprType::I8x16:
case ExprType::I16x8:
case ExprType::I32x4:
case ExprType::B8x16:
case ExprType::B16x8:
case ExprType::B32x4:
// We don't have control on argv alignment, do an unaligned access.
masm.storeUnalignedSimd128Int(ReturnSimd128Reg, Address(argv, 0));
break;
case ExprType::F32x4:
// We don't have control on argv alignment, do an unaligned access.
masm.storeUnalignedSimd128Float(ReturnSimd128Reg, Address(argv, 0));
break;
case ExprType::Limit:
MOZ_CRASH("Limit");
}
// Restore clobbered non-volatile registers of the caller.
masm.PopRegsInMask(NonVolatileRegs);
MOZ_ASSERT(masm.framePushed() == 0);
masm.move32(Imm32(true), ReturnReg);
masm.ret();
offsets.end = masm.currentOffset();
return offsets;
}
static void
StackCopy(MacroAssembler& masm, MIRType type, Register scratch, Address src, Address dst)
{
if (type == MIRType::Int32) {
masm.load32(src, scratch);
masm.store32(scratch, dst);
} else if (type == MIRType::Int64) {
#if JS_BITS_PER_WORD == 32
masm.load32(Address(src.base, src.offset + INT64LOW_OFFSET), scratch);
masm.store32(scratch, Address(dst.base, dst.offset + INT64LOW_OFFSET));
masm.load32(Address(src.base, src.offset + INT64HIGH_OFFSET), scratch);
masm.store32(scratch, Address(dst.base, dst.offset + INT64HIGH_OFFSET));
#else
Register64 scratch64(scratch);
masm.load64(src, scratch64);
masm.store64(scratch64, dst);
#endif
} else if (type == MIRType::Float32) {
masm.loadFloat32(src, ScratchFloat32Reg);
masm.storeFloat32(ScratchFloat32Reg, dst);
} else {
MOZ_ASSERT(type == MIRType::Double);
masm.loadDouble(src, ScratchDoubleReg);
masm.storeDouble(ScratchDoubleReg, dst);
}
}
typedef bool ToValue;
static void
FillArgumentArray(MacroAssembler& masm, const ValTypeVector& args, unsigned argOffset,
unsigned offsetToCallerStackArgs, Register scratch, ToValue toValue)
{
for (ABIArgValTypeIter i(args); !i.done(); i++) {
Address dst(masm.getStackPointer(), argOffset + i.index() * sizeof(Value));
MIRType type = i.mirType();
switch (i->kind()) {
case ABIArg::GPR:
if (type == MIRType::Int32) {
if (toValue)
masm.storeValue(JSVAL_TYPE_INT32, i->gpr(), dst);
else
masm.store32(i->gpr(), dst);
} else if (type == MIRType::Int64) {
// We can't box int64 into Values (yet).
if (toValue)
masm.breakpoint();
else
masm.store64(i->gpr64(), dst);
} else {
MOZ_CRASH("unexpected input type?");
}
break;
#ifdef JS_CODEGEN_REGISTER_PAIR
case ABIArg::GPR_PAIR:
if (type == MIRType::Int64)
masm.store64(i->gpr64(), dst);
else
MOZ_CRASH("wasm uses hardfp for function calls.");
break;
#endif
case ABIArg::FPU: {
MOZ_ASSERT(IsFloatingPointType(type));
FloatRegister srcReg = i->fpu();
if (type == MIRType::Double) {
if (toValue) {
// Preserve the NaN pattern in the input.
masm.moveDouble(srcReg, ScratchDoubleReg);
srcReg = ScratchDoubleReg;
masm.canonicalizeDouble(srcReg);
}
masm.storeDouble(srcReg, dst);
} else {
MOZ_ASSERT(type == MIRType::Float32);
if (toValue) {
// JS::Values can't store Float32, so convert to a Double.
masm.convertFloat32ToDouble(srcReg, ScratchDoubleReg);
masm.canonicalizeDouble(ScratchDoubleReg);
masm.storeDouble(ScratchDoubleReg, dst);
} else {
// Preserve the NaN pattern in the input.
masm.moveFloat32(srcReg, ScratchFloat32Reg);
masm.canonicalizeFloat(ScratchFloat32Reg);
masm.storeFloat32(ScratchFloat32Reg, dst);
}
}
break;
}
case ABIArg::Stack: {
Address src(masm.getStackPointer(), offsetToCallerStackArgs + i->offsetFromArgBase());
if (toValue) {
if (type == MIRType::Int32) {
masm.load32(src, scratch);
masm.storeValue(JSVAL_TYPE_INT32, scratch, dst);
} else if (type == MIRType::Int64) {
// We can't box int64 into Values (yet).
masm.breakpoint();
} else {
MOZ_ASSERT(IsFloatingPointType(type));
if (type == MIRType::Float32) {
masm.loadFloat32(src, ScratchFloat32Reg);
masm.convertFloat32ToDouble(ScratchFloat32Reg, ScratchDoubleReg);
} else {
masm.loadDouble(src, ScratchDoubleReg);
}
masm.canonicalizeDouble(ScratchDoubleReg);
masm.storeDouble(ScratchDoubleReg, dst);
}
} else {
StackCopy(masm, type, scratch, src, dst);
}
break;
}
}
}
}
// Generate a wrapper function with the standard intra-wasm call ABI which simply
// calls an import. This wrapper function allows any import to be treated like a
// normal wasm function for the purposes of exports and table calls. In
// particular, the wrapper function provides:
// - a table entry, so JS imports can be put into tables
// - normal (non-)profiling entries, so that, if the import is re-exported,
// an entry stub can be generated and called without any special cases
FuncOffsets
wasm::GenerateImportFunction(jit::MacroAssembler& masm, const FuncImport& fi, SigIdDesc sigId)
{
masm.setFramePushed(0);
unsigned tlsBytes = sizeof(void*);
unsigned framePushed = StackDecrementForCall(masm, WasmStackAlignment, fi.sig().args(), tlsBytes);
FuncOffsets offsets;
GenerateFunctionPrologue(masm, framePushed, sigId, &offsets);
// The argument register state is already setup by our caller. We just need
// to be sure not to clobber it before the call.
Register scratch = ABINonArgReg0;
// Copy our frame's stack arguments to the callee frame's stack argument.
unsigned offsetToCallerStackArgs = sizeof(Frame) + masm.framePushed();
ABIArgValTypeIter i(fi.sig().args());
for (; !i.done(); i++) {
if (i->kind() != ABIArg::Stack)
continue;
Address src(masm.getStackPointer(), offsetToCallerStackArgs + i->offsetFromArgBase());
Address dst(masm.getStackPointer(), i->offsetFromArgBase());
StackCopy(masm, i.mirType(), scratch, src, dst);
}
// Save the TLS register so it can be restored later.
uint32_t tlsStackOffset = i.stackBytesConsumedSoFar();
masm.storePtr(WasmTlsReg, Address(masm.getStackPointer(), tlsStackOffset));
// Call the import exit stub.
CallSiteDesc desc(CallSiteDesc::Dynamic);
masm.wasmCallImport(desc, CalleeDesc::import(fi.tlsDataOffset()));
// Restore the TLS register and pinned regs, per wasm function ABI.
masm.loadPtr(Address(masm.getStackPointer(), tlsStackOffset), WasmTlsReg);
masm.loadWasmPinnedRegsFromTls();
GenerateFunctionEpilogue(masm, framePushed, &offsets);
masm.wasmEmitTrapOutOfLineCode();
offsets.end = masm.currentOffset();
return offsets;
}
// Generate a stub that is called via the internal ABI derived from the
// signature of the import and calls into an appropriate callImport C++
// function, having boxed all the ABI arguments into a homogeneous Value array.
ProfilingOffsets
wasm::GenerateImportInterpExit(MacroAssembler& masm, const FuncImport& fi, uint32_t funcImportIndex,
Label* throwLabel)
{
masm.setFramePushed(0);
// Argument types for Module::callImport_*:
static const MIRType typeArray[] = { MIRType::Pointer, // Instance*
MIRType::Pointer, // funcImportIndex
MIRType::Int32, // argc
MIRType::Pointer }; // argv
MIRTypeVector invokeArgTypes;
MOZ_ALWAYS_TRUE(invokeArgTypes.append(typeArray, ArrayLength(typeArray)));
// At the point of the call, the stack layout shall be (sp grows to the left):
// | stack args | padding | Value argv[] | padding | retaddr | caller stack args |
// The padding between stack args and argv ensures that argv is aligned. The
// padding between argv and retaddr ensures that sp is aligned.
unsigned argOffset = AlignBytes(StackArgBytes(invokeArgTypes), sizeof(double));
unsigned argBytes = Max<size_t>(1, fi.sig().args().length()) * sizeof(Value);
unsigned framePushed = StackDecrementForCall(masm, ABIStackAlignment, argOffset + argBytes);
ProfilingOffsets offsets;
GenerateExitPrologue(masm, framePushed, ExitReason::ImportInterp, &offsets);
// Fill the argument array.
unsigned offsetToCallerStackArgs = sizeof(Frame) + masm.framePushed();
Register scratch = ABINonArgReturnReg0;
FillArgumentArray(masm, fi.sig().args(), argOffset, offsetToCallerStackArgs, scratch, ToValue(false));
// Prepare the arguments for the call to Module::callImport_*.
ABIArgMIRTypeIter i(invokeArgTypes);
// argument 0: Instance*
Address instancePtr(WasmTlsReg, offsetof(TlsData, instance));
if (i->kind() == ABIArg::GPR) {
masm.loadPtr(instancePtr, i->gpr());
} else {
masm.loadPtr(instancePtr, scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(), i->offsetFromArgBase()));
}
i++;
// argument 1: funcImportIndex
if (i->kind() == ABIArg::GPR)
masm.mov(ImmWord(funcImportIndex), i->gpr());
else
masm.store32(Imm32(funcImportIndex), Address(masm.getStackPointer(), i->offsetFromArgBase()));
i++;
// argument 2: argc
unsigned argc = fi.sig().args().length();
if (i->kind() == ABIArg::GPR)
masm.mov(ImmWord(argc), i->gpr());
else
masm.store32(Imm32(argc), Address(masm.getStackPointer(), i->offsetFromArgBase()));
i++;
// argument 3: argv
Address argv(masm.getStackPointer(), argOffset);
if (i->kind() == ABIArg::GPR) {
masm.computeEffectiveAddress(argv, i->gpr());
} else {
masm.computeEffectiveAddress(argv, scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(), i->offsetFromArgBase()));
}
i++;
MOZ_ASSERT(i.done());
// Make the call, test whether it succeeded, and extract the return value.
AssertStackAlignment(masm, ABIStackAlignment);
switch (fi.sig().ret()) {
case ExprType::Void:
masm.call(SymbolicAddress::CallImport_Void);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
break;
case ExprType::I32:
masm.call(SymbolicAddress::CallImport_I32);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.load32(argv, ReturnReg);
break;
case ExprType::I64:
masm.call(SymbolicAddress::CallImport_I64);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.load64(argv, ReturnReg64);
break;
case ExprType::F32:
masm.call(SymbolicAddress::CallImport_F64);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.loadDouble(argv, ReturnDoubleReg);
masm.convertDoubleToFloat32(ReturnDoubleReg, ReturnFloat32Reg);
break;
case ExprType::F64:
masm.call(SymbolicAddress::CallImport_F64);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.loadDouble(argv, ReturnDoubleReg);
break;
case ExprType::I8x16:
case ExprType::I16x8:
case ExprType::I32x4:
case ExprType::F32x4:
case ExprType::B8x16:
case ExprType::B16x8:
case ExprType::B32x4:
MOZ_CRASH("SIMD types shouldn't be returned from a FFI");
case ExprType::Limit:
MOZ_CRASH("Limit");
}
// The native ABI preserves the TLS, heap and global registers since they
// are non-volatile.
MOZ_ASSERT(NonVolatileRegs.has(WasmTlsReg));
#if defined(JS_CODEGEN_X64) || \
defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
MOZ_ASSERT(NonVolatileRegs.has(HeapReg));
#endif
#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
MOZ_ASSERT(NonVolatileRegs.has(GlobalReg));
#endif
GenerateExitEpilogue(masm, framePushed, ExitReason::ImportInterp, &offsets);
offsets.end = masm.currentOffset();
return offsets;
}
static const unsigned SavedTlsReg = sizeof(void*);
// Generate a stub that is called via the internal ABI derived from the
// signature of the import and calls into a compatible JIT function,
// having boxed all the ABI arguments into the JIT stack frame layout.
ProfilingOffsets
wasm::GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLabel)
{
masm.setFramePushed(0);
// JIT calls use the following stack layout (sp grows to the left):
// | retaddr | descriptor | callee | argc | this | arg1..N |
// After the JIT frame, the global register (if present) is saved since the
// JIT's ABI does not preserve non-volatile regs. Also, unlike most ABIs,
// the JIT ABI requires that sp be JitStackAlignment-aligned *after* pushing
// the return address.
static_assert(WasmStackAlignment >= JitStackAlignment, "subsumes");
unsigned sizeOfRetAddr = sizeof(void*);
unsigned jitFrameBytes = 3 * sizeof(void*) + (1 + fi.sig().args().length()) * sizeof(Value);
unsigned totalJitFrameBytes = sizeOfRetAddr + jitFrameBytes + SavedTlsReg;
unsigned jitFramePushed = StackDecrementForCall(masm, JitStackAlignment, totalJitFrameBytes) -
sizeOfRetAddr;
ProfilingOffsets offsets;
GenerateExitPrologue(masm, jitFramePushed, ExitReason::ImportJit, &offsets);
// 1. Descriptor
size_t argOffset = 0;
uint32_t descriptor = MakeFrameDescriptor(jitFramePushed, JitFrame_Entry,
JitFrameLayout::Size());
masm.storePtr(ImmWord(uintptr_t(descriptor)), Address(masm.getStackPointer(), argOffset));
argOffset += sizeof(size_t);
// 2. Callee
Register callee = ABINonArgReturnReg0; // live until call
Register scratch = ABINonArgReturnReg1; // repeatedly clobbered
// 2.1. Get callee
masm.loadWasmGlobalPtr(fi.tlsDataOffset() + offsetof(FuncImportTls, obj), callee);
// 2.2. Save callee
masm.storePtr(callee, Address(masm.getStackPointer(), argOffset));
argOffset += sizeof(size_t);
// 2.3. Load callee executable entry point
masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
masm.loadBaselineOrIonNoArgCheck(callee, callee, nullptr);
// 3. Argc
unsigned argc = fi.sig().args().length();
masm.storePtr(ImmWord(uintptr_t(argc)), Address(masm.getStackPointer(), argOffset));
argOffset += sizeof(size_t);
// 4. |this| value
masm.storeValue(UndefinedValue(), Address(masm.getStackPointer(), argOffset));
argOffset += sizeof(Value);
// 5. Fill the arguments
unsigned offsetToCallerStackArgs = jitFramePushed + sizeof(Frame);
FillArgumentArray(masm, fi.sig().args(), argOffset, offsetToCallerStackArgs, scratch, ToValue(true));
argOffset += fi.sig().args().length() * sizeof(Value);
MOZ_ASSERT(argOffset == jitFrameBytes);
// 6. Jit code will clobber all registers, even non-volatiles. WasmTlsReg
// must be kept live for the benefit of the epilogue, so push it on the
// stack so that it can be restored before the epilogue.
static_assert(SavedTlsReg == sizeof(void*), "stack frame accounting");
masm.storePtr(WasmTlsReg, Address(masm.getStackPointer(), jitFrameBytes));
{
// Enable Activation.
//
// This sequence requires two registers, and needs to preserve the
// 'callee' register, so there are three live registers.
MOZ_ASSERT(callee == WasmIonExitRegCallee);
Register cx = WasmIonExitRegE0;
Register act = WasmIonExitRegE1;
// JitActivation* act = cx->activation();
masm.movePtr(SymbolicAddress::Context, cx);
masm.loadPtr(Address(cx, JSContext::offsetOfActivation()), act);
// act.active_ = true;
masm.store8(Imm32(1), Address(act, JitActivation::offsetOfActiveUint8()));
// cx->jitActivation = act;
masm.storePtr(act, Address(cx, offsetof(JSContext, jitActivation)));
// cx->profilingActivation_ = act;
masm.storePtr(act, Address(cx, JSContext::offsetOfProfilingActivation()));
}
AssertStackAlignment(masm, JitStackAlignment, sizeOfRetAddr);
masm.callJitNoProfiler(callee);
AssertStackAlignment(masm, JitStackAlignment, sizeOfRetAddr);
{
// Disable Activation.
//
// This sequence needs three registers, and must preserve the JSReturnReg_Data and
// JSReturnReg_Type, so there are five live registers.
MOZ_ASSERT(JSReturnReg_Data == WasmIonExitRegReturnData);
MOZ_ASSERT(JSReturnReg_Type == WasmIonExitRegReturnType);
Register cx = WasmIonExitRegD0;
Register act = WasmIonExitRegD1;
Register tmp = WasmIonExitRegD2;
// JitActivation* act = cx->activation();
masm.movePtr(SymbolicAddress::Context, cx);
masm.loadPtr(Address(cx, JSContext::offsetOfActivation()), act);
// cx->jitTop = act->prevJitTop_;
masm.loadPtr(Address(act, JitActivation::offsetOfPrevJitTop()), tmp);
masm.storePtr(tmp, Address(cx, offsetof(JSContext, jitTop)));
// cx->jitActivation = act->prevJitActivation_;
masm.loadPtr(Address(act, JitActivation::offsetOfPrevJitActivation()), tmp);
masm.storePtr(tmp, Address(cx, offsetof(JSContext, jitActivation)));
// cx->profilingActivation = act->prevProfilingActivation_;
masm.loadPtr(Address(act, Activation::offsetOfPrevProfiling()), tmp);
masm.storePtr(tmp, Address(cx, JSContext::offsetOfProfilingActivation()));
// act->active_ = false;
masm.store8(Imm32(0), Address(act, JitActivation::offsetOfActiveUint8()));
}
// As explained above, the frame was aligned for the JIT ABI such that
// (sp + sizeof(void*)) % JitStackAlignment == 0
// But now we possibly want to call one of several different C++ functions,
// so subtract the sizeof(void*) so that sp is aligned for an ABI call.
static_assert(ABIStackAlignment <= JitStackAlignment, "subsumes");
masm.reserveStack(sizeOfRetAddr);
unsigned nativeFramePushed = masm.framePushed();
AssertStackAlignment(masm, ABIStackAlignment);
masm.branchTestMagic(Assembler::Equal, JSReturnOperand, throwLabel);
Label oolConvert;
switch (fi.sig().ret()) {
case ExprType::Void:
break;
case ExprType::I32:
masm.convertValueToInt32(JSReturnOperand, ReturnDoubleReg, ReturnReg, &oolConvert,
/* -0 check */ false);
break;
case ExprType::I64:
// We don't expect int64 to be returned from Ion yet, because of a
// guard in callImport.
masm.breakpoint();
break;
case ExprType::F32:
masm.convertValueToFloat(JSReturnOperand, ReturnFloat32Reg, &oolConvert);
break;
case ExprType::F64:
masm.convertValueToDouble(JSReturnOperand, ReturnDoubleReg, &oolConvert);
break;
case ExprType::I8x16:
case ExprType::I16x8:
case ExprType::I32x4:
case ExprType::F32x4:
case ExprType::B8x16:
case ExprType::B16x8:
case ExprType::B32x4:
MOZ_CRASH("SIMD types shouldn't be returned from an import");
case ExprType::Limit:
MOZ_CRASH("Limit");
}
Label done;
masm.bind(&done);
// Ion code does not respect the system ABI's callee-saved register
// conventions so reload any assumed-non-volatile registers. Note that the
// reserveStack(sizeOfRetAddr) above means that the stack pointer is at a
// different offset than when WasmTlsReg was stored.
masm.loadPtr(Address(masm.getStackPointer(), jitFrameBytes + sizeOfRetAddr), WasmTlsReg);
GenerateExitEpilogue(masm, masm.framePushed(), ExitReason::ImportJit, &offsets);
if (oolConvert.used()) {
masm.bind(&oolConvert);
masm.setFramePushed(nativeFramePushed);
// Coercion calls use the following stack layout (sp grows to the left):
// | args | padding | Value argv[1] | padding | exit Frame |
MIRTypeVector coerceArgTypes;
JS_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer));
unsigned offsetToCoerceArgv = AlignBytes(StackArgBytes(coerceArgTypes), sizeof(Value));
MOZ_ASSERT(nativeFramePushed >= offsetToCoerceArgv + sizeof(Value));
AssertStackAlignment(masm, ABIStackAlignment);
// Store return value into argv[0]
masm.storeValue(JSReturnOperand, Address(masm.getStackPointer(), offsetToCoerceArgv));
// argument 0: argv
ABIArgMIRTypeIter i(coerceArgTypes);
Address argv(masm.getStackPointer(), offsetToCoerceArgv);
if (i->kind() == ABIArg::GPR) {
masm.computeEffectiveAddress(argv, i->gpr());
} else {
masm.computeEffectiveAddress(argv, scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(), i->offsetFromArgBase()));
}
i++;
MOZ_ASSERT(i.done());
// Call coercion function
AssertStackAlignment(masm, ABIStackAlignment);
switch (fi.sig().ret()) {
case ExprType::I32:
masm.call(SymbolicAddress::CoerceInPlace_ToInt32);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.unboxInt32(Address(masm.getStackPointer(), offsetToCoerceArgv), ReturnReg);
break;
case ExprType::F64:
masm.call(SymbolicAddress::CoerceInPlace_ToNumber);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.loadDouble(Address(masm.getStackPointer(), offsetToCoerceArgv), ReturnDoubleReg);
break;
case ExprType::F32:
masm.call(SymbolicAddress::CoerceInPlace_ToNumber);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
masm.loadDouble(Address(masm.getStackPointer(), offsetToCoerceArgv), ReturnDoubleReg);
masm.convertDoubleToFloat32(ReturnDoubleReg, ReturnFloat32Reg);
break;
default:
MOZ_CRASH("Unsupported convert type");
}
masm.jump(&done);
masm.setFramePushed(0);
}
MOZ_ASSERT(masm.framePushed() == 0);
offsets.end = masm.currentOffset();
return offsets;
}
// Generate a stub that calls into ReportTrap with the right trap reason.
// This stub is called with ABIStackAlignment by a trap out-of-line path. A
// profiling prologue/epilogue is used so that stack unwinding picks up the
// current WasmActivation. Unwinding will begin at the caller of this trap exit.
ProfilingOffsets
wasm::GenerateTrapExit(MacroAssembler& masm, Trap trap, Label* throwLabel)
{
masm.haltingAlign(CodeAlignment);
masm.setFramePushed(0);
MIRTypeVector args;
MOZ_ALWAYS_TRUE(args.append(MIRType::Int32));
uint32_t framePushed = StackDecrementForCall(masm, ABIStackAlignment, args);
ProfilingOffsets offsets;
GenerateExitPrologue(masm, framePushed, ExitReason::Trap, &offsets);
ABIArgMIRTypeIter i(args);
if (i->kind() == ABIArg::GPR)
masm.move32(Imm32(int32_t(trap)), i->gpr());
else
masm.store32(Imm32(int32_t(trap)), Address(masm.getStackPointer(), i->offsetFromArgBase()));
i++;
MOZ_ASSERT(i.done());
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::ReportTrap);
masm.jump(throwLabel);
GenerateExitEpilogue(masm, framePushed, ExitReason::Trap, &offsets);
offsets.end = masm.currentOffset();
return offsets;
}
// Generate a stub which is only used by the signal handlers to handle out of
// bounds access by experimental SIMD.js and Atomics and unaligned accesses on
// ARM. This stub is executed by direct PC transfer from the faulting memory
// access and thus the stack depth is unknown. Since WasmActivation::fp is not
// set before calling the error reporter, the current wasm activation will be
// lost. This stub should be removed when SIMD.js and Atomics are moved to wasm
// and given proper traps and when we use a non-faulting strategy for unaligned
// ARM access.
static Offsets
GenerateGenericMemoryAccessTrap(MacroAssembler& masm, SymbolicAddress reporter, Label* throwLabel)
{
masm.haltingAlign(CodeAlignment);
Offsets offsets;
offsets.begin = masm.currentOffset();
// sp can be anything at this point, so ensure it is aligned when calling
// into C++. We unconditionally jump to throw so don't worry about
// restoring sp.
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
if (ShadowStackSpace)
masm.subFromStackPtr(Imm32(ShadowStackSpace));
masm.call(reporter);
masm.jump(throwLabel);
offsets.end = masm.currentOffset();
return offsets;
}
Offsets
wasm::GenerateOutOfBoundsExit(MacroAssembler& masm, Label* throwLabel)
{
return GenerateGenericMemoryAccessTrap(masm, SymbolicAddress::ReportOutOfBounds, throwLabel);
}
Offsets
wasm::GenerateUnalignedExit(MacroAssembler& masm, Label* throwLabel)
{
return GenerateGenericMemoryAccessTrap(masm, SymbolicAddress::ReportUnalignedAccess, throwLabel);
}
static const LiveRegisterSet AllRegsExceptSP(
GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
FloatRegisterSet(FloatRegisters::AllMask));
// The async interrupt-callback exit is called from arbitrarily-interrupted wasm
// code. That means we must first save *all* registers and restore *all*
// registers (except the stack pointer) when we resume. The address to resume to
// (assuming that js::HandleExecutionInterrupt doesn't indicate that the
// execution should be aborted) is stored in WasmActivation::resumePC_.
// Unfortunately, loading this requires a scratch register which we don't have
// after restoring all registers. To hack around this, push the resumePC on the
// stack so that it can be popped directly into PC.
Offsets
wasm::GenerateInterruptExit(MacroAssembler& masm, Label* throwLabel)
{
masm.haltingAlign(CodeAlignment);
Offsets offsets;
offsets.begin = masm.currentOffset();
#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
// Be very careful here not to perturb the machine state before saving it
// to the stack. In particular, add/sub instructions may set conditions in
// the flags register.
masm.push(Imm32(0)); // space for resumePC
masm.pushFlags(); // after this we are safe to use sub
masm.setFramePushed(0); // set to zero so we can use masm.framePushed() below
masm.PushRegsInMask(AllRegsExceptSP); // save all GP/FP registers (except SP)
Register scratch = ABINonArgReturnReg0;
// Store resumePC into the reserved space.
masm.loadWasmActivationFromSymbolicAddress(scratch);
masm.loadPtr(Address(scratch, WasmActivation::offsetOfResumePC()), scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(), masm.framePushed() + sizeof(void*)));
// We know that StackPointer is word-aligned, but not necessarily
// stack-aligned, so we need to align it dynamically.
masm.moveStackPtrTo(ABINonVolatileReg);
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
if (ShadowStackSpace)
masm.subFromStackPtr(Imm32(ShadowStackSpace));
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::HandleExecutionInterrupt);
masm.branchIfFalseBool(ReturnReg, throwLabel);
// Restore the StackPointer to its position before the call.
masm.moveToStackPtr(ABINonVolatileReg);
// Restore the machine state to before the interrupt.
masm.PopRegsInMask(AllRegsExceptSP); // restore all GP/FP registers (except SP)
masm.popFlags(); // after this, nothing that sets conditions
masm.ret(); // pop resumePC into PC
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
// Reserve space to store resumePC and HeapReg.
masm.subFromStackPtr(Imm32(2 * sizeof(intptr_t)));
// set to zero so we can use masm.framePushed() below.
masm.setFramePushed(0);
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
// save all registers,except sp. After this stack is alligned.
masm.PushRegsInMask(AllRegsExceptSP);
// Save the stack pointer in a non-volatile register.
masm.moveStackPtrTo(s0);
// Align the stack.
masm.ma_and(StackPointer, StackPointer, Imm32(~(ABIStackAlignment - 1)));
// Store resumePC into the reserved space.
masm.loadWasmActivationFromSymbolicAddress(IntArgReg0);
masm.loadPtr(Address(IntArgReg0, WasmActivation::offsetOfResumePC()), IntArgReg1);
masm.storePtr(IntArgReg1, Address(s0, masm.framePushed()));
// Store HeapReg into the reserved space.
masm.storePtr(HeapReg, Address(s0, masm.framePushed() + sizeof(intptr_t)));
# ifdef USES_O32_ABI
// MIPS ABI requires rewserving stack for registes $a0 to $a3.
masm.subFromStackPtr(Imm32(4 * sizeof(intptr_t)));
# endif
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::HandleExecutionInterrupt);
# ifdef USES_O32_ABI
masm.addToStackPtr(Imm32(4 * sizeof(intptr_t)));
# endif
masm.branchIfFalseBool(ReturnReg, throwLabel);
// This will restore stack to the address before the call.
masm.moveToStackPtr(s0);
masm.PopRegsInMask(AllRegsExceptSP);
// Pop resumePC into PC. Clobber HeapReg to make the jump and restore it
// during jump delay slot.
masm.loadPtr(Address(StackPointer, 0), HeapReg);
// Reclaim the reserve space.
masm.addToStackPtr(Imm32(2 * sizeof(intptr_t)));
masm.as_jr(HeapReg);
masm.loadPtr(Address(StackPointer, -sizeof(intptr_t)), HeapReg);
#elif defined(JS_CODEGEN_ARM)
masm.setFramePushed(0); // set to zero so we can use masm.framePushed() below
// Save all GPR, except the stack pointer.
masm.PushRegsInMask(LiveRegisterSet(
GeneralRegisterSet(Registers::AllMask & ~(1<<Registers::sp)),
FloatRegisterSet(uint32_t(0))));
// Save both the APSR and FPSCR in non-volatile registers.
masm.as_mrs(r4);
masm.as_vmrs(r5);
// Save the stack pointer in a non-volatile register.
masm.mov(sp,r6);
// Align the stack.
masm.as_bic(sp, sp, Imm8(7));
// Store resumePC into the return PC stack slot.
masm.loadWasmActivationFromSymbolicAddress(IntArgReg0);
masm.loadPtr(Address(IntArgReg0, WasmActivation::offsetOfResumePC()), IntArgReg1);
masm.storePtr(IntArgReg1, Address(r6, 14 * sizeof(uint32_t*)));
// Save all FP registers
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
masm.PushRegsInMask(LiveRegisterSet(GeneralRegisterSet(0),
FloatRegisterSet(FloatRegisters::AllDoubleMask)));
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::HandleExecutionInterrupt);
masm.branchIfFalseBool(ReturnReg, throwLabel);
// Restore the machine state to before the interrupt. this will set the pc!
// Restore all FP registers
masm.PopRegsInMask(LiveRegisterSet(GeneralRegisterSet(0),
FloatRegisterSet(FloatRegisters::AllDoubleMask)));
masm.mov(r6,sp);
masm.as_vmsr(r5);
masm.as_msr(r4);
// Restore all GP registers
masm.startDataTransferM(IsLoad, sp, IA, WriteBack);
masm.transferReg(r0);
masm.transferReg(r1);
masm.transferReg(r2);
masm.transferReg(r3);
masm.transferReg(r4);
masm.transferReg(r5);
masm.transferReg(r6);
masm.transferReg(r7);
masm.transferReg(r8);
masm.transferReg(r9);
masm.transferReg(r10);
masm.transferReg(r11);
masm.transferReg(r12);
masm.transferReg(lr);
masm.finishDataTransfer();
masm.ret();
#elif defined(JS_CODEGEN_ARM64)
MOZ_CRASH();
#elif defined (JS_CODEGEN_NONE)
MOZ_CRASH();
#else
# error "Unknown architecture!"
#endif
offsets.end = masm.currentOffset();
return offsets;
}
// Generate a stub that restores the stack pointer to what it was on entry to
// the wasm activation, sets the return register to 'false' and then executes a
// return which will return from this wasm activation to the caller. This stub
// should only be called after the caller has reported an error (or, in the case
// of the interrupt stub, intends to interrupt execution).
Offsets
wasm::GenerateThrowStub(MacroAssembler& masm, Label* throwLabel)
{
masm.haltingAlign(CodeAlignment);
masm.bind(throwLabel);
Offsets offsets;
offsets.begin = masm.currentOffset();
// We are about to pop all frames in this WasmActivation. Set fp to null to
// maintain the invariant that fp is either null or pointing to a valid
// frame.
Register scratch = ABINonArgReturnReg0;
masm.loadWasmActivationFromSymbolicAddress(scratch);
masm.storePtr(ImmWord(0), Address(scratch, WasmActivation::offsetOfFP()));
masm.setFramePushed(FramePushedForEntrySP);
masm.loadStackPtr(Address(scratch, WasmActivation::offsetOfEntrySP()));
masm.Pop(scratch);
masm.PopRegsInMask(NonVolatileRegs);
MOZ_ASSERT(masm.framePushed() == 0);
masm.mov(ImmWord(0), ReturnReg);
masm.ret();
offsets.end = masm.currentOffset();
return offsets;
}