blob: e98afea86762140521e5cfc85e5e7ecd551b3441 [file] [log] [blame]
/*
* Copyright (C) 2016-2017 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(B3_JIT)
#include "AirCode.h"
#include "AirGenerationContext.h"
#include "AirInst.h"
#include "AirSpecial.h"
#include "B3ValueInlines.h"
#include "B3WasmBoundsCheckValue.h"
namespace JSC { namespace B3 { namespace Air {
// This defines the behavior of custom instructions - i.e. those whose behavior cannot be
// described using AirOpcode.opcodes. If you define an opcode as "custom Foo" in that file, then
// you will need to create a "struct FooCustom" here that implements the custom behavior
// methods.
//
// The customizability granted by the custom instruction mechanism is strictly less than what
// you get using the Patch instruction and implementing a Special. However, that path requires
// allocating a Special object and ensuring that it's the first operand. For many instructions,
// that is not as convenient as using Custom, which makes the instruction look like any other
// instruction. Note that both of those extra powers of the Patch instruction happen because we
// special-case that instruction in many phases and analyses. Non-special-cased behaviors of
// Patch are implemented using the custom instruction mechanism.
//
// Specials are still more flexible if you need to list extra clobbered registers and you'd like
// that to be expressed as a bitvector rather than an arglist. They are also more flexible if
// you need to carry extra state around with the instruction. Also, Specials mean that you
// always have access to Code& even in methods that don't take a GenerationContext.
// Definition of Patch instruction. Patch is used to delegate the behavior of the instruction to the
// Special object, which will be the first argument to the instruction.
struct PatchCustom {
static void forEachArg(Inst& inst, ScopedLambda<Inst::EachArgCallback> lambda)
{
// This is basically bogus, but it works for analyses that model Special as an
// immediate.
lambda(inst.args[0], Arg::Use, GP, pointerWidth());
inst.args[0].special()->forEachArg(inst, lambda);
}
template<typename... Arguments>
static bool isValidFormStatic(Arguments...)
{
return false;
}
static bool isValidForm(Inst& inst);
static bool admitsStack(Inst& inst, unsigned argIndex)
{
if (!argIndex)
return false;
return inst.args[0].special()->admitsStack(inst, argIndex);
}
static bool admitsExtendedOffsetAddr(Inst& inst, unsigned argIndex)
{
if (!argIndex)
return false;
return inst.args[0].special()->admitsExtendedOffsetAddr(inst, argIndex);
}
static Optional<unsigned> shouldTryAliasingDef(Inst& inst)
{
return inst.args[0].special()->shouldTryAliasingDef(inst);
}
static bool isTerminal(Inst& inst)
{
return inst.args[0].special()->isTerminal(inst);
}
static bool hasNonArgEffects(Inst& inst)
{
return inst.args[0].special()->hasNonArgEffects(inst);
}
static bool hasNonArgNonControlEffects(Inst& inst)
{
return inst.args[0].special()->hasNonArgNonControlEffects(inst);
}
static CCallHelpers::Jump generate(
Inst& inst, CCallHelpers& jit, GenerationContext& context)
{
return inst.args[0].special()->generate(inst, jit, context);
}
};
template<typename Subtype>
struct CommonCustomBase {
static bool hasNonArgEffects(Inst& inst)
{
return Subtype::isTerminal(inst) || Subtype::hasNonArgNonControlEffects(inst);
}
};
// Definition of CCall instruction. CCall is used for hot path C function calls. It's lowered to a
// Patch with an Air CCallSpecial along with code to marshal instructions. The lowering happens
// before register allocation, so that the register allocator sees the clobbers.
struct CCallCustom : public CommonCustomBase<CCallCustom> {
template<typename Functor>
static void forEachArg(Inst& inst, const Functor& functor)
{
Value* value = inst.origin;
unsigned index = 0;
functor(inst.args[index++], Arg::Use, GP, pointerWidth()); // callee
if (value->type() != Void) {
functor(
inst.args[index++], Arg::Def,
bankForType(value->type()),
widthForType(value->type()));
}
for (unsigned i = 1; i < value->numChildren(); ++i) {
Value* child = value->child(i);
functor(
inst.args[index++], Arg::Use,
bankForType(child->type()),
widthForType(child->type()));
}
}
template<typename... Arguments>
static bool isValidFormStatic(Arguments...)
{
return false;
}
static bool isValidForm(Inst&);
static bool admitsStack(Inst&, unsigned)
{
return true;
}
static bool admitsExtendedOffsetAddr(Inst&, unsigned)
{
return false;
}
static bool isTerminal(Inst&)
{
return false;
}
static bool hasNonArgNonControlEffects(Inst&)
{
return true;
}
// This just crashes, since we expect C calls to be lowered before generation.
static CCallHelpers::Jump generate(Inst&, CCallHelpers&, GenerationContext&);
};
struct ColdCCallCustom : CCallCustom {
template<typename Functor>
static void forEachArg(Inst& inst, const Functor& functor)
{
// This is just like a call, but uses become cold.
CCallCustom::forEachArg(
inst,
[&] (Arg& arg, Arg::Role role, Bank bank, Width width) {
functor(arg, Arg::cooled(role), bank, width);
});
}
};
struct ShuffleCustom : public CommonCustomBase<ShuffleCustom> {
template<typename Functor>
static void forEachArg(Inst& inst, const Functor& functor)
{
unsigned limit = inst.args.size() / 3 * 3;
for (unsigned i = 0; i < limit; i += 3) {
Arg& src = inst.args[i + 0];
Arg& dst = inst.args[i + 1];
Arg& widthArg = inst.args[i + 2];
Width width = widthArg.width();
Bank bank = src.isGP() && dst.isGP() ? GP : FP;
functor(src, Arg::Use, bank, width);
functor(dst, Arg::Def, bank, width);
functor(widthArg, Arg::Use, GP, Width8);
}
}
template<typename... Arguments>
static bool isValidFormStatic(Arguments...)
{
return false;
}
static bool isValidForm(Inst&);
static bool admitsStack(Inst&, unsigned index)
{
switch (index % 3) {
case 0:
case 1:
return true;
default:
return false;
}
}
static bool admitsExtendedOffsetAddr(Inst&, unsigned)
{
return false;
}
static bool isTerminal(Inst&)
{
return false;
}
static bool hasNonArgNonControlEffects(Inst&)
{
return false;
}
static CCallHelpers::Jump generate(Inst&, CCallHelpers&, GenerationContext&);
};
struct EntrySwitchCustom : public CommonCustomBase<EntrySwitchCustom> {
template<typename Func>
static void forEachArg(Inst&, const Func&)
{
}
template<typename... Arguments>
static bool isValidFormStatic(Arguments...)
{
return !sizeof...(Arguments);
}
static bool isValidForm(Inst& inst)
{
return inst.args.isEmpty();
}
static bool admitsStack(Inst&, unsigned)
{
return false;
}
static bool admitsExtendedOffsetAddr(Inst&, unsigned)
{
return false;
}
static bool isTerminal(Inst&)
{
return true;
}
static bool hasNonArgNonControlEffects(Inst&)
{
return false;
}
static CCallHelpers::Jump generate(Inst&, CCallHelpers&, GenerationContext&)
{
// This should never be reached because we should have lowered EntrySwitch before
// generation.
UNREACHABLE_FOR_PLATFORM();
return CCallHelpers::Jump();
}
};
struct WasmBoundsCheckCustom : public CommonCustomBase<WasmBoundsCheckCustom> {
template<typename Func>
static void forEachArg(Inst& inst, const Func& functor)
{
functor(inst.args[0], Arg::Use, GP, Width64);
functor(inst.args[1], Arg::Use, GP, Width64);
}
template<typename... Arguments>
static bool isValidFormStatic(Arguments...)
{
return false;
}
static bool isValidForm(Inst&);
static bool admitsStack(Inst&, unsigned)
{
return false;
}
static bool admitsExtendedOffsetAddr(Inst&, unsigned)
{
return false;
}
static bool isTerminal(Inst&)
{
return false;
}
static bool hasNonArgNonControlEffects(Inst&)
{
return true;
}
static CCallHelpers::Jump generate(Inst& inst, CCallHelpers& jit, GenerationContext& context)
{
WasmBoundsCheckValue* value = inst.origin->as<WasmBoundsCheckValue>();
CCallHelpers::Jump outOfBounds = Inst(Air::Branch64, value, Arg::relCond(CCallHelpers::AboveOrEqual), inst.args[0], inst.args[1]).generate(jit, context);
context.latePaths.append(createSharedTask<GenerationContext::LatePathFunction>(
[outOfBounds, value] (CCallHelpers& jit, Air::GenerationContext& context) {
outOfBounds.link(&jit);
switch (value->boundsType()) {
case WasmBoundsCheckValue::Type::Pinned:
context.code->wasmBoundsCheckGenerator()->run(jit, value->bounds().pinnedSize);
break;
case WasmBoundsCheckValue::Type::Maximum:
context.code->wasmBoundsCheckGenerator()->run(jit, InvalidGPRReg);
break;
}
}));
// We said we were not a terminal.
return CCallHelpers::Jump();
}
};
} } } // namespace JSC::B3::Air
#endif // ENABLE(B3_JIT)