blob: 2e3478437ffa0a7237cbb4a2880613c88b3e4b59 [file] [log] [blame]
/*
* Copyright (C) 2015-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.
*/
#include "config.h"
#include "B3CheckSpecial.h"
#if ENABLE(B3_JIT)
#include "AirGenerationContext.h"
#include "AirInstInlines.h"
#include "B3StackmapGenerationParams.h"
#include "B3ValueInlines.h"
#include "CCallHelpers.h"
namespace JSC { namespace B3 {
using Inst = Air::Inst;
using Arg = Air::Arg;
using GenerationContext = Air::GenerationContext;
namespace {
unsigned numB3Args(Kind kind)
{
switch (kind.opcode()) {
case CheckAdd:
case CheckSub:
case CheckMul:
return 2;
case Check:
return 1;
default:
RELEASE_ASSERT_NOT_REACHED();
return 0;
}
}
unsigned numB3Args(Value* value)
{
return numB3Args(value->kind());
}
unsigned numB3Args(Inst& inst)
{
return numB3Args(inst.origin);
}
} // anonymous namespace
CheckSpecial::Key::Key(const Inst& inst)
{
m_kind = inst.kind;
m_numArgs = inst.args.size();
m_stackmapRole = SameAsRep;
}
void CheckSpecial::Key::dump(PrintStream& out) const
{
out.print(m_kind, "(", m_numArgs, ",", m_stackmapRole, ")");
}
CheckSpecial::CheckSpecial(Air::Kind kind, unsigned numArgs, RoleMode stackmapRole)
: m_checkKind(kind)
, m_stackmapRole(stackmapRole)
, m_numCheckArgs(numArgs)
{
ASSERT(isDefinitelyTerminal(kind.opcode));
}
CheckSpecial::CheckSpecial(const CheckSpecial::Key& key)
: CheckSpecial(key.kind(), key.numArgs(), key.stackmapRole())
{
}
CheckSpecial::~CheckSpecial()
{
}
Inst CheckSpecial::hiddenBranch(const Inst& inst) const
{
Inst hiddenBranch(m_checkKind, inst.origin);
hiddenBranch.args.reserveInitialCapacity(m_numCheckArgs);
for (unsigned i = 0; i < m_numCheckArgs; ++i)
hiddenBranch.args.append(inst.args[i + 1]);
ASSERT(hiddenBranch.isTerminal());
return hiddenBranch;
}
void CheckSpecial::forEachArg(Inst& inst, const ScopedLambda<Inst::EachArgCallback>& callback)
{
using namespace Air;
std::optional<Width> optionalDefArgWidth;
Inst hidden = hiddenBranch(inst);
hidden.forEachArg(
[&] (Arg& arg, Arg::Role role, Bank bank, Width width) {
if (Arg::isAnyDef(role) && role != Arg::Scratch) {
ASSERT(!optionalDefArgWidth); // There can only be one Def'ed arg.
optionalDefArgWidth = width;
}
unsigned index = &arg - &hidden.args[0];
callback(inst.args[1 + index], role, bank, width);
});
std::optional<unsigned> firstRecoverableIndex;
if (m_checkKind.opcode == BranchAdd32 || m_checkKind.opcode == BranchAdd64)
firstRecoverableIndex = 1;
forEachArgImpl(numB3Args(inst), m_numCheckArgs + 1, inst, m_stackmapRole, firstRecoverableIndex, callback, optionalDefArgWidth);
}
bool CheckSpecial::isValid(Inst& inst)
{
return hiddenBranch(inst).isValidForm()
&& isValidImpl(numB3Args(inst), m_numCheckArgs + 1, inst)
&& inst.args.size() - m_numCheckArgs - 1 == inst.origin->numChildren() - numB3Args(inst);
}
bool CheckSpecial::admitsStack(Inst& inst, unsigned argIndex)
{
if (argIndex >= 1 && argIndex < 1 + m_numCheckArgs)
return hiddenBranch(inst).admitsStack(argIndex - 1);
return admitsStackImpl(numB3Args(inst), m_numCheckArgs + 1, inst, argIndex);
}
bool CheckSpecial::admitsExtendedOffsetAddr(Inst& inst, unsigned argIndex)
{
if (argIndex >= 1 && argIndex < 1 + m_numCheckArgs)
return false;
return admitsStack(inst, argIndex);
}
std::optional<unsigned> CheckSpecial::shouldTryAliasingDef(Inst& inst)
{
if (std::optional<unsigned> branchDef = hiddenBranch(inst).shouldTryAliasingDef())
return *branchDef + 1;
return std::nullopt;
}
CCallHelpers::Jump CheckSpecial::generate(Inst& inst, CCallHelpers& jit, GenerationContext& context)
{
using namespace Air;
CCallHelpers::Jump fail = hiddenBranch(inst).generate(jit, context);
ASSERT(fail.isSet());
StackmapValue* value = inst.origin->as<StackmapValue>();
ASSERT(value);
Vector<ValueRep> reps = repsImpl(context, numB3Args(inst), m_numCheckArgs + 1, inst);
// Set aside the args that are relevant to undoing the operation. This is because we don't want to
// capture all of inst in the closure below.
Vector<Arg, 3> args;
for (unsigned i = 0; i < m_numCheckArgs; ++i)
args.append(inst.args[1 + i]);
context.latePaths.append(
createSharedTask<GenerationContext::LatePathFunction>(
[=, this] (CCallHelpers& jit, GenerationContext& context) {
fail.link(&jit);
// If necessary, undo the operation.
switch (m_checkKind.opcode) {
case BranchAdd32:
if ((m_numCheckArgs == 4 && args[1] == args[2] && args[2] == args[3])
|| (m_numCheckArgs == 3 && args[1] == args[2])) {
// This is ugly, but that's fine - we won't have to do this very often.
ASSERT(args[1].isGPR());
GPRReg valueGPR = args[1].gpr();
GPRReg scratchGPR = CCallHelpers::selectScratchGPR(valueGPR);
jit.pushToSave(scratchGPR);
jit.setCarry(scratchGPR);
jit.lshift32(CCallHelpers::TrustedImm32(31), scratchGPR);
jit.urshift32(CCallHelpers::TrustedImm32(1), valueGPR);
jit.or32(scratchGPR, valueGPR);
jit.popToRestore(scratchGPR);
break;
}
if (m_numCheckArgs == 4) {
if (args[1] == args[3])
Inst(Sub32, nullptr, args[2], args[3]).generate(jit, context);
else if (args[2] == args[3])
Inst(Sub32, nullptr, args[1], args[3]).generate(jit, context);
} else if (m_numCheckArgs == 3)
Inst(Sub32, nullptr, args[1], args[2]).generate(jit, context);
break;
case BranchAdd64:
if ((m_numCheckArgs == 4 && args[1] == args[2] && args[2] == args[3])
|| (m_numCheckArgs == 3 && args[1] == args[2])) {
// This is ugly, but that's fine - we won't have to do this very often.
ASSERT(args[1].isGPR());
GPRReg valueGPR = args[1].gpr();
GPRReg scratchGPR = CCallHelpers::selectScratchGPR(valueGPR);
jit.pushToSave(scratchGPR);
jit.setCarry(scratchGPR);
jit.lshift64(CCallHelpers::TrustedImm32(63), scratchGPR);
jit.urshift64(CCallHelpers::TrustedImm32(1), valueGPR);
jit.or64(scratchGPR, valueGPR);
jit.popToRestore(scratchGPR);
break;
}
if (m_numCheckArgs == 4) {
if (args[1] == args[3])
Inst(Sub64, nullptr, args[2], args[3]).generate(jit, context);
else if (args[2] == args[3])
Inst(Sub64, nullptr, args[1], args[3]).generate(jit, context);
} else if (m_numCheckArgs == 3)
Inst(Sub64, nullptr, args[1], args[2]).generate(jit, context);
break;
case BranchSub32:
Inst(Add32, nullptr, args[1], args[2]).generate(jit, context);
break;
case BranchSub64:
Inst(Add64, nullptr, args[1], args[2]).generate(jit, context);
break;
case BranchNeg32:
Inst(Neg32, nullptr, args[1]).generate(jit, context);
break;
case BranchNeg64:
Inst(Neg64, nullptr, args[1]).generate(jit, context);
break;
default:
break;
}
value->m_generator->run(jit, StackmapGenerationParams(value, reps, context));
}));
return CCallHelpers::Jump(); // As far as Air thinks, we are not a terminal.
}
void CheckSpecial::dumpImpl(PrintStream& out) const
{
out.print(m_checkKind, "(", m_numCheckArgs, ",", m_stackmapRole, ")");
}
void CheckSpecial::deepDumpImpl(PrintStream& out) const
{
out.print("B3::CheckValue lowered to ", m_checkKind, " with ", m_numCheckArgs, " args.");
}
} } // namespace JSC::B3
#endif // ENABLE(B3_JIT)