blob: 10eb8cd4e7336e0ef24dc9be504b7507ce83c752 [file] [log] [blame]
/*
* Copyright (C) 2016-2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if ENABLE(WEBASSEMBLY)
#include "AirCode.h"
#include "AllowMacroScratchRegisterUsage.h"
#include "B3ArgumentRegValue.h"
#include "B3BasicBlock.h"
#include "B3Const64Value.h"
#include "B3ConstrainedValue.h"
#include "B3MemoryValue.h"
#include "B3PatchpointValue.h"
#include "B3Procedure.h"
#include "B3StackmapGenerationParams.h"
#include "CallFrame.h"
#include "LinkBuffer.h"
#include "RegisterSet.h"
#include "WasmFormat.h"
#include "WasmSignature.h"
namespace JSC { namespace Wasm {
typedef unsigned (*NextOffset)(unsigned currentOffset);
template<unsigned headerSize, NextOffset updateOffset>
class CallingConvention {
public:
CallingConvention(Vector<Reg>&& gprArgs, Vector<Reg>&& fprArgs, RegisterSet&& calleeSaveRegisters)
: m_gprArgs(WTFMove(gprArgs))
, m_fprArgs(WTFMove(fprArgs))
, m_calleeSaveRegisters(WTFMove(calleeSaveRegisters))
{
}
private:
B3::ValueRep marshallArgumentImpl(const Vector<Reg>& regArgs, size_t& count, size_t& stackOffset) const
{
if (count < regArgs.size())
return B3::ValueRep::reg(regArgs[count++]);
count++;
B3::ValueRep result = B3::ValueRep::stackArgument(stackOffset);
stackOffset = updateOffset(stackOffset);
return result;
}
B3::ValueRep marshallArgument(B3::Type type, size_t& gpArgumentCount, size_t& fpArgumentCount, size_t& stackOffset) const
{
switch (type) {
case B3::Int32:
case B3::Int64:
return marshallArgumentImpl(m_gprArgs, gpArgumentCount, stackOffset);
case B3::Float:
case B3::Double:
return marshallArgumentImpl(m_fprArgs, fpArgumentCount, stackOffset);
case B3::Void:
break;
}
RELEASE_ASSERT_NOT_REACHED();
}
public:
static unsigned headerSizeInBytes() { return headerSize; }
void setupFrameInPrologue(CodeLocationDataLabelPtr<WasmEntryPtrTag>* calleeMoveLocation, B3::Procedure& proc, B3::Origin origin, B3::BasicBlock* block) const
{
static_assert(CallFrameSlot::callee * sizeof(Register) < headerSize, "We rely on this here for now.");
static_assert(CallFrameSlot::codeBlock * sizeof(Register) < headerSize, "We rely on this here for now.");
B3::PatchpointValue* getCalleePatchpoint = block->appendNew<B3::PatchpointValue>(proc, B3::Int64, origin);
getCalleePatchpoint->resultConstraint = B3::ValueRep::SomeRegister;
getCalleePatchpoint->effects = B3::Effects::none();
getCalleePatchpoint->setGenerator(
[=] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
GPRReg result = params[0].gpr();
MacroAssembler::DataLabelPtr moveLocation = jit.moveWithPatch(MacroAssembler::TrustedImmPtr(nullptr), result);
jit.addLinkTask([calleeMoveLocation, moveLocation] (LinkBuffer& linkBuffer) {
*calleeMoveLocation = linkBuffer.locationOf<WasmEntryPtrTag>(moveLocation);
});
});
B3::Value* framePointer = block->appendNew<B3::Value>(proc, B3::FramePointer, origin);
B3::Value* offsetOfCallee = block->appendNew<B3::Const64Value>(proc, origin, CallFrameSlot::callee * sizeof(Register));
block->appendNew<B3::MemoryValue>(proc, B3::Store, origin,
getCalleePatchpoint,
block->appendNew<B3::Value>(proc, B3::Add, origin, framePointer, offsetOfCallee));
// FIXME: We shouldn't have to store zero into the CodeBlock* spot in the call frame,
// but there are places that interpret non-null CodeBlock slot to mean a valid CodeBlock.
// When doing unwinding, we'll need to verify that the entire runtime is OK with a non-null
// CodeBlock not implying that the CodeBlock is valid.
// https://bugs.webkit.org/show_bug.cgi?id=165321
B3::Value* offsetOfCodeBlock = block->appendNew<B3::Const64Value>(proc, origin, CallFrameSlot::codeBlock * sizeof(Register));
block->appendNew<B3::MemoryValue>(proc, B3::Store, origin,
block->appendNew<B3::Const64Value>(proc, origin, 0),
block->appendNew<B3::Value>(proc, B3::Add, origin, framePointer, offsetOfCodeBlock));
}
template<typename Functor>
void loadArguments(const Signature& signature, B3::Procedure& proc, B3::BasicBlock* block, B3::Origin origin, const Functor& functor) const
{
B3::Value* framePointer = block->appendNew<B3::Value>(proc, B3::FramePointer, origin);
size_t gpArgumentCount = 0;
size_t fpArgumentCount = 0;
size_t stackOffset = headerSize;
for (size_t i = 0; i < signature.argumentCount(); ++i) {
B3::Type type = toB3Type(signature.argument(i));
B3::Value* argument;
B3::ValueRep rep = marshallArgument(type, gpArgumentCount, fpArgumentCount, stackOffset);
if (rep.isReg()) {
argument = block->appendNew<B3::ArgumentRegValue>(proc, origin, rep.reg());
if (type == B3::Int32 || type == B3::Float)
argument = block->appendNew<B3::Value>(proc, B3::Trunc, origin, argument);
} else {
ASSERT(rep.isStackArgument());
B3::Value* address = block->appendNew<B3::Value>(proc, B3::Add, origin, framePointer,
block->appendNew<B3::Const64Value>(proc, origin, rep.offsetFromSP()));
argument = block->appendNew<B3::MemoryValue>(proc, B3::Load, type, origin, address);
}
functor(argument, i);
}
}
// It's expected that the pachpointFunctor sets the generator for the call operation.
template<typename Functor>
B3::Value* setupCall(B3::Procedure& proc, B3::BasicBlock* block, B3::Origin origin, const Vector<B3::Value*>& arguments, B3::Type returnType, const Functor& patchpointFunctor) const
{
size_t gpArgumentCount = 0;
size_t fpArgumentCount = 0;
size_t stackOffset = headerSize - sizeof(CallerFrameAndPC);
Vector<B3::ConstrainedValue> constrainedArguments;
for (B3::Value* argument : arguments) {
B3::ValueRep rep = marshallArgument(argument->type(), gpArgumentCount, fpArgumentCount, stackOffset);
constrainedArguments.append(B3::ConstrainedValue(argument, rep));
}
proc.requestCallArgAreaSizeInBytes(WTF::roundUpToMultipleOf(stackAlignmentBytes(), stackOffset));
B3::PatchpointValue* patchpoint = block->appendNew<B3::PatchpointValue>(proc, returnType, origin);
patchpoint->clobberEarly(RegisterSet::macroScratchRegisters());
patchpoint->clobberLate(RegisterSet::volatileRegistersForJSCall());
patchpointFunctor(patchpoint);
patchpoint->appendVector(constrainedArguments);
switch (returnType) {
case B3::Void:
return nullptr;
case B3::Float:
case B3::Double:
patchpoint->resultConstraint = B3::ValueRep::reg(FPRInfo::returnValueFPR);
break;
case B3::Int32:
case B3::Int64:
patchpoint->resultConstraint = B3::ValueRep::reg(GPRInfo::returnValueGPR);
break;
}
return patchpoint;
}
const Vector<Reg> m_gprArgs;
const Vector<Reg> m_fprArgs;
const RegisterSet m_calleeSaveRegisters;
const RegisterSet m_callerSaveRegisters;
};
// FIXME: Share more code with CallingConvention above:
// https://bugs.webkit.org/show_bug.cgi?id=194065
template<unsigned headerSize, NextOffset updateOffset>
class CallingConventionAir {
public:
CallingConventionAir(Vector<Reg>&& gprArgs, Vector<Reg>&& fprArgs, RegisterSet&& calleeSaveRegisters)
: m_gprArgs(WTFMove(gprArgs))
, m_fprArgs(WTFMove(fprArgs))
, m_calleeSaveRegisters(WTFMove(calleeSaveRegisters))
{
RegisterSet scratch = RegisterSet::allGPRs();
scratch.exclude(RegisterSet::macroScratchRegisters());
scratch.exclude(RegisterSet::reservedHardwareRegisters());
scratch.exclude(RegisterSet::stackRegisters());
for (Reg reg : m_gprArgs)
scratch.clear(reg);
for (Reg reg : m_calleeSaveRegisters)
scratch.clear(reg);
for (Reg reg : scratch)
m_scratchGPRs.append(reg);
RELEASE_ASSERT(m_scratchGPRs.size() >= 2);
}
GPRReg prologueScratch(size_t i) const { return m_scratchGPRs[i].gpr(); }
private:
template <typename RegFunc, typename StackFunc>
void marshallArgumentImpl(const Vector<Reg>& regArgs, size_t& count, size_t& stackOffset, const RegFunc& regFunc, const StackFunc& stackFunc) const
{
if (count < regArgs.size()) {
regFunc(regArgs[count++]);
return;
}
count++;
stackFunc(stackOffset);
stackOffset = updateOffset(stackOffset);
}
template <typename RegFunc, typename StackFunc>
void marshallArgument(Type type, size_t& gpArgumentCount, size_t& fpArgumentCount, size_t& stackOffset, const RegFunc& regFunc, const StackFunc& stackFunc) const
{
switch (type) {
case Type::I32:
case Type::I64:
case Type::Anyref:
marshallArgumentImpl(m_gprArgs, gpArgumentCount, stackOffset, regFunc, stackFunc);
break;
case Type::F32:
case Type::F64:
marshallArgumentImpl(m_fprArgs, fpArgumentCount, stackOffset, regFunc, stackFunc);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
}
}
public:
static unsigned headerSizeInBytes() { return headerSize; }
template<typename Functor>
void loadArguments(const Signature& signature, const Functor& functor) const
{
size_t gpArgumentCount = 0;
size_t fpArgumentCount = 0;
size_t stackOffset = headerSize;
for (size_t i = 0; i < signature.argumentCount(); ++i) {
marshallArgument(signature.argument(i), gpArgumentCount, fpArgumentCount, stackOffset,
[&] (Reg reg) {
functor(B3::Air::Tmp(reg), i);
},
[&] (size_t stackOffset) {
functor(B3::Air::Arg::addr(B3::Air::Tmp(GPRInfo::callFrameRegister), stackOffset), i);
});
}
}
// It's expected that the pachpointFunctor sets the generator for the call operation.
template<typename Functor>
void setupCall(B3::Air::Code& code, Type returnType, B3::PatchpointValue* patchpoint, const Vector<B3::Air::Tmp>& args, const Functor& functor) const
{
size_t gpArgumentCount = 0;
size_t fpArgumentCount = 0;
size_t stackOffset = headerSize - sizeof(CallerFrameAndPC);
for (auto tmp : args) {
marshallArgument(tmp.isGP() ? Type::I64 : Type::F64, gpArgumentCount, fpArgumentCount, stackOffset,
[&] (Reg reg) {
functor(tmp, B3::ValueRep::reg(reg));
},
[&] (size_t stackOffset) {
functor(tmp, B3::ValueRep::stackArgument(stackOffset));
});
}
code.requestCallArgAreaSizeInBytes(WTF::roundUpToMultipleOf(stackAlignmentBytes(), stackOffset));
patchpoint->clobberEarly(RegisterSet::macroScratchRegisters());
patchpoint->clobberLate(RegisterSet::volatileRegistersForJSCall());
switch (returnType) {
case Type::Void:
break;
case Type::F32:
case Type::F64:
patchpoint->resultConstraint = B3::ValueRep::reg(FPRInfo::returnValueFPR);
break;
case Type::I32:
case Type::I64:
case Type::Anyref:
patchpoint->resultConstraint = B3::ValueRep::reg(GPRInfo::returnValueGPR);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
}
}
const Vector<Reg> m_gprArgs;
const Vector<Reg> m_fprArgs;
Vector<Reg> m_scratchGPRs;
const RegisterSet m_calleeSaveRegisters;
const RegisterSet m_callerSaveRegisters;
};
inline unsigned nextJSCOffset(unsigned currentOffset)
{
return currentOffset + sizeof(Register);
}
constexpr unsigned jscHeaderSize = ExecState::headerSizeInRegisters * sizeof(Register);
using JSCCallingConvention = CallingConvention<jscHeaderSize, nextJSCOffset>;
using WasmCallingConvention = JSCCallingConvention;
const JSCCallingConvention& jscCallingConvention();
const WasmCallingConvention& wasmCallingConvention();
using JSCCallingConventionAir = CallingConventionAir<jscHeaderSize, nextJSCOffset>;
using WasmCallingConventionAir = JSCCallingConventionAir;
const JSCCallingConventionAir& jscCallingConventionAir();
const WasmCallingConventionAir& wasmCallingConventionAir();
} } // namespace JSC::Wasm
#endif // ENABLE(WEBASSEMBLY)