| /* |
| * 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) |