|  | /* | 
|  | * Copyright (C) 2015-2019 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 "B3LowerToAir.h" | 
|  |  | 
|  | #if ENABLE(B3_JIT) | 
|  |  | 
|  | #include "AirBlockInsertionSet.h" | 
|  | #include "AirCode.h" | 
|  | #include "AirHelpers.h" | 
|  | #include "AirInsertionSet.h" | 
|  | #include "AirInstInlines.h" | 
|  | #include "AirPrintSpecial.h" | 
|  | #include "B3ArgumentRegValue.h" | 
|  | #include "B3AtomicValue.h" | 
|  | #include "B3BlockWorklist.h" | 
|  | #include "B3CCallValue.h" | 
|  | #include "B3CheckSpecial.h" | 
|  | #include "B3Commutativity.h" | 
|  | #include "B3Dominators.h" | 
|  | #include "B3ExtractValue.h" | 
|  | #include "B3FenceValue.h" | 
|  | #include "B3MemoryValueInlines.h" | 
|  | #include "B3PatchpointSpecial.h" | 
|  | #include "B3PatchpointValue.h" | 
|  | #include "B3PhaseScope.h" | 
|  | #include "B3PhiChildren.h" | 
|  | #include "B3Procedure.h" | 
|  | #include "B3ProcedureInlines.h" | 
|  | #include "B3SlotBaseValue.h" | 
|  | #include "B3UpsilonValue.h" | 
|  | #include "B3UseCounts.h" | 
|  | #include "B3ValueInlines.h" | 
|  | #include "B3Variable.h" | 
|  | #include "B3VariableValue.h" | 
|  | #include "B3WasmAddressValue.h" | 
|  | #include <wtf/IndexMap.h> | 
|  | #include <wtf/IndexSet.h> | 
|  | #include <wtf/StdLibExtras.h> | 
|  |  | 
|  | #if !ASSERT_ENABLED | 
|  | IGNORE_RETURN_TYPE_WARNINGS_BEGIN | 
|  | #endif | 
|  |  | 
|  | namespace JSC { namespace B3 { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | namespace B3LowerToAirInternal { | 
|  | static constexpr bool verbose = false; | 
|  | } | 
|  |  | 
|  | using Arg = Air::Arg; | 
|  | using Inst = Air::Inst; | 
|  | using Code = Air::Code; | 
|  | using Tmp = Air::Tmp; | 
|  |  | 
|  | using Air::moveForType; | 
|  | using Air::relaxedMoveForType; | 
|  |  | 
|  | // FIXME: We wouldn't need this if Air supported Width modifiers in Air::Kind. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=169247 | 
|  | #define OPCODE_FOR_WIDTH(opcode, width) ( \ | 
|  | (width) == Width8 ? Air::opcode ## 8 : \ | 
|  | (width) == Width16 ? Air::opcode ## 16 :    \ | 
|  | (width) == Width32 ? Air::opcode ## 32 :    \ | 
|  | Air::opcode ## 64) | 
|  | #define OPCODE_FOR_CANONICAL_WIDTH(opcode, width) ( \ | 
|  | (width) == Width64 ? Air::opcode ## 64 : Air::opcode ## 32) | 
|  |  | 
|  | class LowerToAir { | 
|  | public: | 
|  | LowerToAir(Procedure& procedure) | 
|  | : m_valueToTmp(procedure.values().size()) | 
|  | , m_phiToTmp(procedure.values().size()) | 
|  | , m_blockToBlock(procedure.size()) | 
|  | , m_useCounts(procedure) | 
|  | , m_phiChildren(procedure) | 
|  | , m_dominators(procedure.dominators()) | 
|  | , m_procedure(procedure) | 
|  | , m_code(procedure.code()) | 
|  | , m_blockInsertionSet(m_code) | 
|  | #if CPU(X86) || CPU(X86_64) | 
|  | , m_eax(X86Registers::eax) | 
|  | , m_ecx(X86Registers::ecx) | 
|  | , m_edx(X86Registers::edx) | 
|  | #endif | 
|  | { | 
|  | } | 
|  |  | 
|  | void run() | 
|  | { | 
|  | using namespace Air; | 
|  | for (B3::BasicBlock* block : m_procedure) | 
|  | m_blockToBlock[block] = m_code.addBlock(block->frequency()); | 
|  |  | 
|  | auto ensureTupleTmps = [&] (Value* tupleValue, auto& hashTable) { | 
|  | hashTable.ensure(tupleValue, [&] { | 
|  | const auto tuple = m_procedure.tupleForType(tupleValue->type()); | 
|  | Vector<Tmp> tmps(tuple.size()); | 
|  |  | 
|  | for (unsigned i = 0; i < tuple.size(); ++i) | 
|  | tmps[i] = tmpForType(tuple[i]); | 
|  | return tmps; | 
|  | }); | 
|  | }; | 
|  |  | 
|  | for (Value* value : m_procedure.values()) { | 
|  | switch (value->opcode()) { | 
|  | case Phi: { | 
|  | if (value->type().isTuple()) { | 
|  | ensureTupleTmps(value, m_tuplePhiToTmps); | 
|  | ensureTupleTmps(value, m_tupleValueToTmps); | 
|  | break; | 
|  | } | 
|  |  | 
|  | m_phiToTmp[value] = m_code.newTmp(value->resultBank()); | 
|  | if (B3LowerToAirInternal::verbose) | 
|  | dataLog("Phi tmp for ", *value, ": ", m_phiToTmp[value], "\n"); | 
|  | break; | 
|  | } | 
|  | case Get: | 
|  | case Patchpoint: | 
|  | case BottomTuple: { | 
|  | if (value->type().isTuple()) | 
|  | ensureTupleTmps(value, m_tupleValueToTmps); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | for (Variable* variable : m_procedure.variables()) { | 
|  | auto addResult = m_variableToTmps.add(variable, Vector<Tmp, 1>(m_procedure.resultCount(variable->type()))); | 
|  | ASSERT(addResult.isNewEntry); | 
|  | for (unsigned i = 0; i < m_procedure.resultCount(variable->type()); ++i) | 
|  | addResult.iterator->value[i] = tmpForType(m_procedure.typeAtOffset(variable->type(), i)); | 
|  | } | 
|  |  | 
|  | // Figure out which blocks are not rare. | 
|  | m_fastWorklist.push(m_procedure[0]); | 
|  | while (B3::BasicBlock* block = m_fastWorklist.pop()) { | 
|  | for (B3::FrequentedBlock& successor : block->successors()) { | 
|  | if (!successor.isRare()) | 
|  | m_fastWorklist.push(successor.block()); | 
|  | } | 
|  | } | 
|  |  | 
|  | m_procedure.resetValueOwners(); // Used by crossesInterference(). | 
|  |  | 
|  | // Lower defs before uses on a global level. This is a good heuristic to lock down a | 
|  | // hoisted address expression before we duplicate it back into the loop. | 
|  | for (B3::BasicBlock* block : m_procedure.blocksInPreOrder()) { | 
|  | m_block = block; | 
|  |  | 
|  | m_isRare = !m_fastWorklist.saw(block); | 
|  |  | 
|  | if (B3LowerToAirInternal::verbose) | 
|  | dataLog("Lowering Block ", *block, ":\n"); | 
|  |  | 
|  | // Make sure that the successors are set up correctly. | 
|  | for (B3::FrequentedBlock successor : block->successors()) { | 
|  | m_blockToBlock[block]->successors().append( | 
|  | Air::FrequentedBlock(m_blockToBlock[successor.block()], successor.frequency())); | 
|  | } | 
|  |  | 
|  | // Process blocks in reverse order so we see uses before defs. That's what allows us | 
|  | // to match patterns effectively. | 
|  | for (unsigned i = block->size(); i--;) { | 
|  | m_index = i; | 
|  | m_value = block->at(i); | 
|  | if (m_locked.contains(m_value)) | 
|  | continue; | 
|  | m_insts.append(Vector<Inst>()); | 
|  | if (B3LowerToAirInternal::verbose) | 
|  | dataLog("Lowering ", deepDump(m_procedure, m_value), ":\n"); | 
|  | lower(); | 
|  | if (B3LowerToAirInternal::verbose) { | 
|  | for (Inst& inst : m_insts.last()) | 
|  | dataLog("    ", inst, "\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | finishAppendingInstructions(m_blockToBlock[block]); | 
|  | } | 
|  |  | 
|  | m_blockInsertionSet.execute(); | 
|  |  | 
|  | Air::InsertionSet insertionSet(m_code); | 
|  | for (Inst& inst : m_prologue) | 
|  | insertionSet.insertInst(0, WTFMove(inst)); | 
|  | insertionSet.execute(m_code[0]); | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool shouldCopyPropagate(Value* value) | 
|  | { | 
|  | switch (value->opcode()) { | 
|  | case Trunc: | 
|  | case Identity: | 
|  | case Opaque: | 
|  | return true; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ArgPromise { | 
|  | WTF_MAKE_NONCOPYABLE(ArgPromise); | 
|  | public: | 
|  | ArgPromise() { } | 
|  |  | 
|  | ArgPromise(const Arg& arg, Value* valueToLock = nullptr) | 
|  | : m_arg(arg) | 
|  | , m_value(valueToLock) | 
|  | { | 
|  | } | 
|  |  | 
|  | void swap(ArgPromise& other) | 
|  | { | 
|  | std::swap(m_arg, other.m_arg); | 
|  | std::swap(m_value, other.m_value); | 
|  | std::swap(m_wasConsumed, other.m_wasConsumed); | 
|  | std::swap(m_wasWrapped, other.m_wasWrapped); | 
|  | std::swap(m_traps, other.m_traps); | 
|  | } | 
|  |  | 
|  | ArgPromise(ArgPromise&& other) | 
|  | { | 
|  | swap(other); | 
|  | } | 
|  |  | 
|  | ArgPromise& operator=(ArgPromise&& other) | 
|  | { | 
|  | swap(other); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | ~ArgPromise() | 
|  | { | 
|  | if (m_wasConsumed) | 
|  | RELEASE_ASSERT(m_wasWrapped); | 
|  | } | 
|  |  | 
|  | void setTraps(bool value) | 
|  | { | 
|  | m_traps = value; | 
|  | } | 
|  |  | 
|  | static ArgPromise tmp(Value* value) | 
|  | { | 
|  | ArgPromise result; | 
|  | result.m_value = value; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | explicit operator bool() const { return m_arg || m_value; } | 
|  |  | 
|  | Arg::Kind kind() const | 
|  | { | 
|  | if (!m_arg && m_value) | 
|  | return Arg::Tmp; | 
|  | return m_arg.kind(); | 
|  | } | 
|  |  | 
|  | const Arg& peek() const | 
|  | { | 
|  | return m_arg; | 
|  | } | 
|  |  | 
|  | Arg consume(LowerToAir& lower) | 
|  | { | 
|  | m_wasConsumed = true; | 
|  | if (!m_arg && m_value) | 
|  | return lower.tmp(m_value); | 
|  | if (m_value) | 
|  | lower.commitInternal(m_value); | 
|  | return m_arg; | 
|  | } | 
|  |  | 
|  | template<typename... Args> | 
|  | Inst inst(Args&&... args) | 
|  | { | 
|  | Inst result(std::forward<Args>(args)...); | 
|  | result.kind.effects |= m_traps; | 
|  | m_wasWrapped = true; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | private: | 
|  | // Three forms: | 
|  | // Everything null: invalid. | 
|  | // Arg non-null, value null: just use the arg, nothing special. | 
|  | // Arg null, value non-null: it's a tmp, pin it when necessary. | 
|  | // Arg non-null, value non-null: use the arg, lock the value. | 
|  | Arg m_arg; | 
|  | Value* m_value { nullptr }; | 
|  | bool m_wasConsumed { false }; | 
|  | bool m_wasWrapped { false }; | 
|  | bool m_traps { false }; | 
|  | }; | 
|  |  | 
|  | // Consider using tmpPromise() in cases where you aren't sure that you want to pin the value yet. | 
|  | // Here are three canonical ways of using tmp() and tmpPromise(): | 
|  | // | 
|  | // Idiom #1: You know that you want a tmp() and you know that it will be valid for the | 
|  | // instruction you're emitting. | 
|  | // | 
|  | //     append(Foo, tmp(bar)); | 
|  | // | 
|  | // Idiom #2: You don't know if you want to use a tmp() because you haven't determined if the | 
|  | // instruction will accept it, so you query first. Note that the call to tmp() happens only after | 
|  | // you are sure that you will use it. | 
|  | // | 
|  | //     if (isValidForm(Foo, Arg::Tmp)) | 
|  | //         append(Foo, tmp(bar)) | 
|  | // | 
|  | // Idiom #3: Same as Idiom #2, but using tmpPromise. Notice that this calls consume() only after | 
|  | // it's sure it will use the tmp. That's deliberate. Also note that you're required to pass any | 
|  | // Inst you create with consumed promises through that promise's inst() function. | 
|  | // | 
|  | //     ArgPromise promise = tmpPromise(bar); | 
|  | //     if (isValidForm(Foo, promise.kind())) | 
|  | //         append(promise.inst(Foo, promise.consume(*this))) | 
|  | // | 
|  | // In both idiom #2 and idiom #3, we don't pin the value to a temporary except when we actually | 
|  | // emit the instruction. Both tmp() and tmpPromise().consume(*this) will pin it. Pinning means | 
|  | // that we will henceforth require that the value of 'bar' is generated as a separate | 
|  | // instruction. We don't want to pin the value to a temporary if we might change our minds, and | 
|  | // pass an address operand representing 'bar' to Foo instead. | 
|  | // | 
|  | // Because tmp() pins, the following is not an idiom you should use: | 
|  | // | 
|  | //     Tmp tmp = this->tmp(bar); | 
|  | //     if (isValidForm(Foo, tmp.kind())) | 
|  | //         append(Foo, tmp); | 
|  | // | 
|  | // That's because if isValidForm() returns false, you will have already pinned the 'bar' to a | 
|  | // temporary. You might later want to try to do something like loadPromise(), and that will fail. | 
|  | // This arises in operations that have both a Addr,Tmp and Tmp,Addr forms. The following code | 
|  | // seems right, but will actually fail to ever match the Tmp,Addr form because by then, the right | 
|  | // value is already pinned. | 
|  | // | 
|  | //     auto tryThings = [this] (const Arg& left, const Arg& right) { | 
|  | //         if (isValidForm(Foo, left.kind(), right.kind())) | 
|  | //             return Inst(Foo, m_value, left, right); | 
|  | //         return Inst(); | 
|  | //     }; | 
|  | //     if (Inst result = tryThings(loadAddr(left), tmp(right))) | 
|  | //         return result; | 
|  | //     if (Inst result = tryThings(tmp(left), loadAddr(right))) // this never succeeds. | 
|  | //         return result; | 
|  | //     return Inst(Foo, m_value, tmp(left), tmp(right)); | 
|  | // | 
|  | // If you imagine that loadAddr(value) is just loadPromise(value).consume(*this), then this code | 
|  | // will run correctly - it will generate OK code - but the second form is never matched. | 
|  | // loadAddr(right) will never succeed because it will observe that 'right' is already pinned. | 
|  | // Of course, it's exactly because of the risky nature of such code that we don't have a | 
|  | // loadAddr() helper and require you to balance ArgPromise's in code like this. Such code will | 
|  | // work fine if written as: | 
|  | // | 
|  | //     auto tryThings = [this] (ArgPromise& left, ArgPromise& right) { | 
|  | //         if (isValidForm(Foo, left.kind(), right.kind())) | 
|  | //             return left.inst(right.inst(Foo, m_value, left.consume(*this), right.consume(*this))); | 
|  | //         return Inst(); | 
|  | //     }; | 
|  | //     if (Inst result = tryThings(loadPromise(left), tmpPromise(right))) | 
|  | //         return result; | 
|  | //     if (Inst result = tryThings(tmpPromise(left), loadPromise(right))) | 
|  | //         return result; | 
|  | //     return Inst(Foo, m_value, tmp(left), tmp(right)); | 
|  | // | 
|  | // Notice that we did use tmp in the fall-back case at the end, because by then, we know for sure | 
|  | // that we want a tmp. But using tmpPromise in the tryThings() calls ensures that doing so | 
|  | // doesn't prevent us from trying loadPromise on the same value. | 
|  | Tmp tmp(Value* value) | 
|  | { | 
|  | Tmp& tmp = m_valueToTmp[value]; | 
|  | if (!tmp) { | 
|  | while (shouldCopyPropagate(value)) | 
|  | value = value->child(0); | 
|  |  | 
|  | if (value->opcode() == FramePointer) | 
|  | return Tmp(GPRInfo::callFrameRegister); | 
|  |  | 
|  | Tmp& realTmp = m_valueToTmp[value]; | 
|  | if (!realTmp) { | 
|  | realTmp = m_code.newTmp(value->resultBank()); | 
|  | if (m_procedure.isFastConstant(value->key())) | 
|  | m_code.addFastTmp(realTmp); | 
|  | if (B3LowerToAirInternal::verbose) | 
|  | dataLog("Tmp for ", *value, ": ", realTmp, "\n"); | 
|  | } | 
|  | tmp = realTmp; | 
|  | } | 
|  | return tmp; | 
|  | } | 
|  |  | 
|  | ArgPromise tmpPromise(Value* value) | 
|  | { | 
|  | return ArgPromise::tmp(value); | 
|  | } | 
|  |  | 
|  | Tmp tmpForType(Type type) | 
|  | { | 
|  | return m_code.newTmp(bankForType(type)); | 
|  | } | 
|  |  | 
|  | const Vector<Tmp>& tmpsForTuple(Value* tupleValue) | 
|  | { | 
|  | ASSERT(tupleValue->type().isTuple()); | 
|  |  | 
|  | switch (tupleValue->opcode()) { | 
|  | case Phi: | 
|  | case Patchpoint: | 
|  | case BottomTuple: { | 
|  | return m_tupleValueToTmps.find(tupleValue)->value; | 
|  | } | 
|  | case Get: | 
|  | case Set: | 
|  | return m_variableToTmps.find(tupleValue->as<VariableValue>()->variable())->value; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | } | 
|  |  | 
|  | bool canBeInternal(Value* value) | 
|  | { | 
|  | // If one of the internal things has already been computed, then we don't want to cause | 
|  | // it to be recomputed again. | 
|  | if (m_valueToTmp[value]) | 
|  | return false; | 
|  |  | 
|  | // We require internals to have only one use - us. It's not clear if this should be numUses() or | 
|  | // numUsingInstructions(). Ideally, it would be numUsingInstructions(), except that it's not clear | 
|  | // if we'd actually do the right thing when matching over such a DAG pattern. For now, it simply | 
|  | // doesn't matter because we don't implement patterns that would trigger this. | 
|  | if (m_useCounts.numUses(value) != 1) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // If you ask canBeInternal() and then construct something from that, and you commit to emitting | 
|  | // that code, then you must commitInternal() on that value. This is tricky, and you only need to | 
|  | // do it if you're pattern matching by hand rather than using the patterns language. Long story | 
|  | // short, you should avoid this by using the pattern matcher to match patterns. | 
|  | void commitInternal(Value* value) | 
|  | { | 
|  | if (value) | 
|  | m_locked.add(value); | 
|  | } | 
|  |  | 
|  | bool crossesInterference(Value* value) | 
|  | { | 
|  | // If it's in a foreign block, then be conservative. We could handle this if we were | 
|  | // willing to do heavier analysis. For example, if we had liveness, then we could label | 
|  | // values as "crossing interference" if they interfere with anything that they are live | 
|  | // across. But, it's not clear how useful this would be. | 
|  | if (value->owner != m_value->owner) | 
|  | return true; | 
|  |  | 
|  | Effects effects = value->effects(); | 
|  |  | 
|  | for (unsigned i = m_index; i--;) { | 
|  | Value* otherValue = m_block->at(i); | 
|  | if (otherValue == value) | 
|  | return false; | 
|  | if (effects.interferes(otherValue->effects())) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | ASSERT_NOT_REACHED(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool isMergeableValue(Value* v, B3::Opcode b3Opcode, bool checkCanBeInternal = false) | 
|  | { | 
|  | if (v->opcode() != b3Opcode) | 
|  | return false; | 
|  | if (checkCanBeInternal && !canBeInternal(v)) | 
|  | return false; | 
|  | if (m_locked.contains(v->child(0))) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template<typename Int, typename = Value::IsLegalOffset<Int>> | 
|  | Arg indexArg(Tmp base, Value* index, unsigned scale, Int offset) | 
|  | { | 
|  | #if CPU(ARM64) | 
|  | // Maybe, the ideal approach is to introduce a decorator (Index@EXT) to the Air operand | 
|  | // to provide an extension opportunity for the specific form under the Air opcode. | 
|  | if (isMergeableValue(index, ZExt32)) | 
|  | return Arg::index(base, tmp(index->child(0)), scale, offset, MacroAssembler::Extend::ZExt32); | 
|  | if (isMergeableValue(index, SExt32)) | 
|  | return Arg::index(base, tmp(index->child(0)), scale, offset, MacroAssembler::Extend::SExt32); | 
|  | #endif | 
|  | return Arg::index(base, tmp(index), scale, offset); | 
|  | } | 
|  |  | 
|  | template<typename Int, typename = Value::IsLegalOffset<Int>> | 
|  | std::optional<unsigned> scaleForShl(Value* shl, Int offset, std::optional<Width> width = std::nullopt) | 
|  | { | 
|  | if (shl->opcode() != Shl) | 
|  | return std::nullopt; | 
|  | if (!shl->child(1)->hasInt32()) | 
|  | return std::nullopt; | 
|  | unsigned logScale = shl->child(1)->asInt32(); | 
|  | if (shl->type() == Int32) | 
|  | logScale &= 31; | 
|  | else | 
|  | logScale &= 63; | 
|  | // Use 64-bit math to perform the shift so that <<32 does the right thing, but then switch | 
|  | // to signed since that's what all of our APIs want. | 
|  | int64_t bigScale = static_cast<uint64_t>(1) << static_cast<uint64_t>(logScale); | 
|  | if (!isRepresentableAs<int32_t>(bigScale)) | 
|  | return std::nullopt; | 
|  | unsigned scale = static_cast<int32_t>(bigScale); | 
|  | if (!Arg::isValidIndexForm(scale, offset, width)) | 
|  | return std::nullopt; | 
|  | return scale; | 
|  | } | 
|  |  | 
|  | // This turns the given operand into an address. | 
|  | template<typename Int, typename = Value::IsLegalOffset<Int>> | 
|  | Arg effectiveAddr(Value* address, Int offset, Width width) | 
|  | { | 
|  | ASSERT(Arg::isValidAddrForm(offset, width)); | 
|  |  | 
|  | auto fallback = [&] () -> Arg { | 
|  | return Arg::addr(tmp(address), offset); | 
|  | }; | 
|  |  | 
|  | static constexpr unsigned lotsOfUses = 10; // This is arbitrary and we should tune it eventually. | 
|  |  | 
|  | // Only match if the address value isn't used in some large number of places. | 
|  | if (m_useCounts.numUses(address) > lotsOfUses) | 
|  | return fallback(); | 
|  |  | 
|  | switch (address->opcode()) { | 
|  | case Add: { | 
|  | Value* left = address->child(0); | 
|  | Value* right = address->child(1); | 
|  |  | 
|  | auto tryIndex = [&] (Value* index, Value* base) -> Arg { | 
|  | std::optional<unsigned> scale = scaleForShl(index, offset, width); | 
|  | if (!scale) | 
|  | return Arg(); | 
|  | if (m_locked.contains(index->child(0)) || m_locked.contains(base)) | 
|  | return Arg(); | 
|  | return indexArg(tmp(base), index->child(0), *scale, offset); | 
|  | }; | 
|  |  | 
|  | if (Arg result = tryIndex(left, right)) | 
|  | return result; | 
|  | if (Arg result = tryIndex(right, left)) | 
|  | return result; | 
|  |  | 
|  | if (m_locked.contains(left) || m_locked.contains(right) | 
|  | || !Arg::isValidIndexForm(1, offset, width)) | 
|  | return fallback(); | 
|  |  | 
|  | return indexArg(tmp(left), right, 1, offset); | 
|  | } | 
|  |  | 
|  | case Shl: { | 
|  | Value* left = address->child(0); | 
|  |  | 
|  | // We'll never see child(1)->isInt32(0), since that would have been reduced. If the shift | 
|  | // amount is greater than 1, then there isn't really anything smart that we could do here. | 
|  | // We avoid using baseless indexes because their encoding isn't particularly efficient. | 
|  | if (m_locked.contains(left) || !address->child(1)->isInt32(1) | 
|  | || !Arg::isValidIndexForm(1, offset, width)) | 
|  | return fallback(); | 
|  |  | 
|  | return indexArg(tmp(left), left, 1, offset); | 
|  | } | 
|  |  | 
|  | case FramePointer: | 
|  | return Arg::addr(Tmp(GPRInfo::callFrameRegister), offset); | 
|  |  | 
|  | case SlotBase: | 
|  | return Arg::stack(address->as<SlotBaseValue>()->slot(), offset); | 
|  |  | 
|  | case WasmAddress: { | 
|  | WasmAddressValue* wasmAddress = address->as<WasmAddressValue>(); | 
|  | Value* pointer = wasmAddress->child(0); | 
|  | if (!Arg::isValidIndexForm(1, offset, width) || m_locked.contains(pointer)) | 
|  | return fallback(); | 
|  |  | 
|  | // FIXME: We should support ARM64 LDR 32-bit addressing, which will | 
|  | // allow us to fuse a Shl ptr, 2 into the address. Additionally, and | 
|  | // perhaps more importantly, it would allow us to avoid a truncating | 
|  | // move. See: https://bugs.webkit.org/show_bug.cgi?id=163465 | 
|  |  | 
|  | return indexArg(Tmp(wasmAddress->pinnedGPR()), pointer, 1, offset); | 
|  | } | 
|  |  | 
|  | default: | 
|  | return fallback(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // This gives you the address of the given Load or Store. If it's not a Load or Store, then | 
|  | // it returns Arg(). | 
|  | Arg addr(Value* memoryValue) | 
|  | { | 
|  | MemoryValue* value = memoryValue->as<MemoryValue>(); | 
|  | if (!value) | 
|  | return Arg(); | 
|  |  | 
|  | if (value->requiresSimpleAddr()) | 
|  | return Arg::simpleAddr(tmp(value->lastChild())); | 
|  |  | 
|  | Value::OffsetType offset = value->offset(); | 
|  | Width width = value->accessWidth(); | 
|  |  | 
|  | Arg result = effectiveAddr(value->lastChild(), offset, width); | 
|  | RELEASE_ASSERT(result.isValidForm(width)); | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | template<typename... Args> | 
|  | Inst trappingInst(bool traps, Args&&... args) | 
|  | { | 
|  | Inst result(std::forward<Args>(args)...); | 
|  | result.kind.effects |= traps; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | template<typename... Args> | 
|  | Inst trappingInst(Value* value, Args&&... args) | 
|  | { | 
|  | return trappingInst(value->traps(), std::forward<Args>(args)...); | 
|  | } | 
|  |  | 
|  | ArgPromise loadPromiseAnyOpcode(Value* loadValue) | 
|  | { | 
|  | RELEASE_ASSERT(loadValue->as<MemoryValue>()); | 
|  | if (!canBeInternal(loadValue)) | 
|  | return Arg(); | 
|  | if (crossesInterference(loadValue)) | 
|  | return Arg(); | 
|  | // On x86, all loads have fences. Doing this kind of instruction selection will move the load, | 
|  | // but that's fine because our interference analysis stops the motion of fences around other | 
|  | // fences. So, any load motion we introduce here would not be observable. | 
|  | if (!isX86() && loadValue->as<MemoryValue>()->hasFence()) | 
|  | return Arg(); | 
|  | Arg loadAddr = addr(loadValue); | 
|  | RELEASE_ASSERT(loadAddr); | 
|  | ArgPromise result(loadAddr, loadValue); | 
|  | if (loadValue->traps()) | 
|  | result.setTraps(true); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | ArgPromise loadPromise(Value* loadValue, B3::Opcode loadOpcode) | 
|  | { | 
|  | if (loadValue->opcode() != loadOpcode) | 
|  | return Arg(); | 
|  | return loadPromiseAnyOpcode(loadValue); | 
|  | } | 
|  |  | 
|  | ArgPromise loadPromise(Value* loadValue) | 
|  | { | 
|  | return loadPromise(loadValue, Load); | 
|  | } | 
|  |  | 
|  | Arg imm(int64_t intValue) | 
|  | { | 
|  | if (Arg::isValidImmForm(intValue)) | 
|  | return Arg::imm(intValue); | 
|  | return Arg(); | 
|  | } | 
|  |  | 
|  | Arg imm(Value* value) | 
|  | { | 
|  | if (value->hasInt()) | 
|  | return imm(value->asInt()); | 
|  | return Arg(); | 
|  | } | 
|  |  | 
|  | Arg bitImm(Value* value) | 
|  | { | 
|  | if (value->hasInt()) { | 
|  | int64_t intValue = value->asInt(); | 
|  | if (Arg::isValidBitImmForm(intValue)) | 
|  | return Arg::bitImm(intValue); | 
|  | } | 
|  | return Arg(); | 
|  | } | 
|  |  | 
|  | Arg bitImm64(Value* value) | 
|  | { | 
|  | if (value->hasInt()) { | 
|  | int64_t intValue = value->asInt(); | 
|  | if (Arg::isValidBitImm64Form(intValue)) | 
|  | return Arg::bitImm64(intValue); | 
|  | } | 
|  | return Arg(); | 
|  | } | 
|  |  | 
|  | Arg zeroReg() | 
|  | { | 
|  | return Arg::zeroReg(); | 
|  | } | 
|  |  | 
|  | Arg immOrTmp(Value* value) | 
|  | { | 
|  | if (Arg result = imm(value)) | 
|  | return result; | 
|  | return tmp(value); | 
|  | } | 
|  |  | 
|  | template<typename Functor> | 
|  | void forEachImmOrTmp(Value* value, const Functor& func) | 
|  | { | 
|  | ASSERT(value->type() != Void); | 
|  | if (!value->type().isTuple()) { | 
|  | func(immOrTmp(value), value->type(), 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const Vector<Type>& tuple = m_procedure.tupleForType(value->type()); | 
|  | const auto& tmps = tmpsForTuple(value); | 
|  | for (unsigned i = 0; i < tuple.size(); ++i) | 
|  | func(tmps[i], tuple[i], i); | 
|  | } | 
|  |  | 
|  | // By convention, we use Oops to mean "I don't know". | 
|  | Air::Opcode tryOpcodeForType( | 
|  | Air::Opcode opcode32, Air::Opcode opcode64, Air::Opcode opcodeDouble, Air::Opcode opcodeFloat, Type type) | 
|  | { | 
|  | Air::Opcode opcode; | 
|  | switch (type.kind()) { | 
|  | case Int32: | 
|  | opcode = opcode32; | 
|  | break; | 
|  | case Int64: | 
|  | opcode = opcode64; | 
|  | break; | 
|  | case Float: | 
|  | opcode = opcodeFloat; | 
|  | break; | 
|  | case Double: | 
|  | opcode = opcodeDouble; | 
|  | break; | 
|  | default: | 
|  | opcode = Air::Oops; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return opcode; | 
|  | } | 
|  |  | 
|  | Air::Opcode tryOpcodeForType(Air::Opcode opcode32, Air::Opcode opcode64, Type type) | 
|  | { | 
|  | return tryOpcodeForType(opcode32, opcode64, Air::Oops, Air::Oops, type); | 
|  | } | 
|  |  | 
|  | Air::Opcode opcodeForType( | 
|  | Air::Opcode opcode32, Air::Opcode opcode64, Air::Opcode opcodeDouble, Air::Opcode opcodeFloat, Type type) | 
|  | { | 
|  | Air::Opcode opcode = tryOpcodeForType(opcode32, opcode64, opcodeDouble, opcodeFloat, type); | 
|  | RELEASE_ASSERT(opcode != Air::Oops); | 
|  | return opcode; | 
|  | } | 
|  |  | 
|  | Air::Opcode opcodeForType(Air::Opcode opcode32, Air::Opcode opcode64, Type type) | 
|  | { | 
|  | return tryOpcodeForType(opcode32, opcode64, Air::Oops, Air::Oops, type); | 
|  | } | 
|  |  | 
|  | template<Air::Opcode opcode32, Air::Opcode opcode64, Air::Opcode opcodeDouble = Air::Oops, Air::Opcode opcodeFloat = Air::Oops> | 
|  | void appendUnOp(Value* value) | 
|  | { | 
|  | Air::Opcode opcode = opcodeForType(opcode32, opcode64, opcodeDouble, opcodeFloat, value->type()); | 
|  |  | 
|  | Tmp result = tmp(m_value); | 
|  |  | 
|  | // Two operand forms like: | 
|  | //     Op a, b | 
|  | // mean something like: | 
|  | //     b = Op a | 
|  |  | 
|  | ArgPromise addr = loadPromise(value); | 
|  | if (isValidForm(opcode, addr.kind(), Arg::Tmp)) { | 
|  | append(addr.inst(opcode, m_value, addr.consume(*this), result)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (isValidForm(opcode, Arg::Tmp, Arg::Tmp)) { | 
|  | append(opcode, tmp(value), result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ASSERT(value->type() == m_value->type()); | 
|  | append(relaxedMoveForType(m_value->type()), tmp(value), result); | 
|  | append(opcode, result); | 
|  | } | 
|  |  | 
|  | Air::Opcode opcodeBasedOnShiftKind(B3::Opcode b3Opcode, | 
|  | Air::Opcode shl32, Air::Opcode shl64, | 
|  | Air::Opcode sshr32, Air::Opcode sshr64, | 
|  | Air::Opcode zshr32, Air::Opcode zshr64) | 
|  | { | 
|  | switch (b3Opcode) { | 
|  | case Shl: | 
|  | return tryOpcodeForType(shl32, shl64, m_value->type()); | 
|  | case SShr: | 
|  | return tryOpcodeForType(sshr32, sshr64, m_value->type()); | 
|  | case ZShr: | 
|  | return tryOpcodeForType(zshr32, zshr64, m_value->type()); | 
|  | default: | 
|  | return Air::Oops; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Call this method when doing two-operand lowering of a commutative operation. You have a choice of | 
|  | // which incoming Value is moved into the result. This will select which one is likely to be most | 
|  | // profitable to use as the result. Doing the right thing can have big performance consequences in tight | 
|  | // kernels. | 
|  | bool preferRightForResult(Value* left, Value* right) | 
|  | { | 
|  | // The default is to move left into result, because that's required for non-commutative instructions. | 
|  | // The value that we want to move into result position is the one that dies here. So, if we're | 
|  | // compiling a commutative operation and we know that actually right is the one that dies right here, | 
|  | // then we can flip things around to help coalescing, which then kills the move instruction. | 
|  | // | 
|  | // But it's more complicated: | 
|  | // - Used-once is a bad estimate of whether the variable dies here. | 
|  | // - A child might be a candidate for coalescing with this value. | 
|  | // | 
|  | // Currently, we have machinery in place to recognize super obvious forms of the latter issue. | 
|  |  | 
|  | // We recognize when a child is a Phi that has this value as one of its children. We're very | 
|  | // conservative about this; for example we don't even consider transitive Phi children. | 
|  | bool leftIsPhiWithThis = m_phiChildren[left].transitivelyUses(m_value); | 
|  | bool rightIsPhiWithThis = m_phiChildren[right].transitivelyUses(m_value); | 
|  |  | 
|  | if (leftIsPhiWithThis != rightIsPhiWithThis) | 
|  | return rightIsPhiWithThis; | 
|  |  | 
|  | if (m_useCounts.numUsingInstructions(right) != 1) | 
|  | return false; | 
|  |  | 
|  | if (m_useCounts.numUsingInstructions(left) != 1) | 
|  | return true; | 
|  |  | 
|  | // The use count might be 1 if the variable is live around a loop. We can guarantee that we | 
|  | // pick the variable that is least likely to suffer this problem if we pick the one that | 
|  | // is closest to us in an idom walk. By convention, we slightly bias this in favor of | 
|  | // returning true. | 
|  |  | 
|  | // We cannot prefer right if right is further away in an idom walk. | 
|  | if (m_dominators.strictlyDominates(right->owner, left->owner)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template<Air::Opcode opcode32, Air::Opcode opcode64, Air::Opcode opcodeDouble, Air::Opcode opcodeFloat, Commutativity commutativity = NotCommutative> | 
|  | void appendBinOp(Value* left, Value* right) | 
|  | { | 
|  | Air::Opcode opcode = opcodeForType(opcode32, opcode64, opcodeDouble, opcodeFloat, left->type()); | 
|  |  | 
|  | Tmp result = tmp(m_value); | 
|  |  | 
|  | // Three-operand forms like: | 
|  | //     Op a, b, c | 
|  | // mean something like: | 
|  | //     c = a Op b | 
|  |  | 
|  | if (isValidForm(opcode, Arg::Imm, Arg::Tmp, Arg::Tmp)) { | 
|  | if (commutativity == Commutative) { | 
|  | if (imm(right)) { | 
|  | append(opcode, imm(right), tmp(left), result); | 
|  | return; | 
|  | } | 
|  | } else { | 
|  | // A non-commutative operation could have an immediate in left. | 
|  | if (imm(left)) { | 
|  | append(opcode, imm(left), tmp(right), result); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (isValidForm(opcode, Arg::BitImm, Arg::Tmp, Arg::Tmp)) { | 
|  | if (commutativity == Commutative) { | 
|  | if (Arg rightArg = bitImm(right)) { | 
|  | append(opcode, rightArg, tmp(left), result); | 
|  | return; | 
|  | } | 
|  | } else { | 
|  | // A non-commutative operation could have an immediate in left. | 
|  | if (Arg leftArg = bitImm(left)) { | 
|  | append(opcode, leftArg, tmp(right), result); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (isValidForm(opcode, Arg::BitImm64, Arg::Tmp, Arg::Tmp)) { | 
|  | if (commutativity == Commutative) { | 
|  | if (Arg rightArg = bitImm64(right)) { | 
|  | append(opcode, rightArg, tmp(left), result); | 
|  | return; | 
|  | } | 
|  | } else { | 
|  | // A non-commutative operation could have an immediate in left. | 
|  | if (Arg leftArg = bitImm64(left)) { | 
|  | append(opcode, leftArg, tmp(right), result); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (imm(right) && isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Tmp)) { | 
|  | append(opcode, tmp(left), imm(right), result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Note that no extant architecture has a three-operand form of binary operations that also | 
|  | // load from memory. If such an abomination did exist, we would handle it somewhere around | 
|  | // here. | 
|  |  | 
|  | // Two-operand forms like: | 
|  | //     Op a, b | 
|  | // mean something like: | 
|  | //     b = b Op a | 
|  |  | 
|  | // At this point, we prefer versions of the operation that have a fused load or an immediate | 
|  | // over three operand forms. | 
|  |  | 
|  | if (left != right) { | 
|  | ArgPromise leftAddr = loadPromise(left); | 
|  | if (isValidForm(opcode, leftAddr.kind(), Arg::Tmp, Arg::Tmp)) { | 
|  | append(leftAddr.inst(opcode, m_value, leftAddr.consume(*this), tmp(right), result)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (commutativity == Commutative) { | 
|  | if (isValidForm(opcode, leftAddr.kind(), Arg::Tmp)) { | 
|  | append(relaxedMoveForType(m_value->type()), tmp(right), result); | 
|  | append(leftAddr.inst(opcode, m_value, leftAddr.consume(*this), result)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | ArgPromise rightAddr = loadPromise(right); | 
|  | if (isValidForm(opcode, Arg::Tmp, rightAddr.kind(), Arg::Tmp)) { | 
|  | append(rightAddr.inst(opcode, m_value, tmp(left), rightAddr.consume(*this), result)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (commutativity == Commutative) { | 
|  | if (isValidForm(opcode, rightAddr.kind(), Arg::Tmp, Arg::Tmp)) { | 
|  | append(rightAddr.inst(opcode, m_value, rightAddr.consume(*this), tmp(left), result)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (isValidForm(opcode, rightAddr.kind(), Arg::Tmp)) { | 
|  | append(relaxedMoveForType(m_value->type()), tmp(left), result); | 
|  | append(rightAddr.inst(opcode, m_value, rightAddr.consume(*this), result)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (imm(right) && isValidForm(opcode, Arg::Imm, Arg::Tmp)) { | 
|  | append(relaxedMoveForType(m_value->type()), tmp(left), result); | 
|  | append(opcode, imm(right), result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | append(opcode, tmp(left), tmp(right), result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (commutativity == Commutative && preferRightForResult(left, right)) { | 
|  | append(relaxedMoveForType(m_value->type()), tmp(right), result); | 
|  | append(opcode, tmp(left), result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | append(relaxedMoveForType(m_value->type()), tmp(left), result); | 
|  | append(opcode, tmp(right), result); | 
|  | } | 
|  |  | 
|  | template<Air::Opcode opcode32, Air::Opcode opcode64, Commutativity commutativity = NotCommutative> | 
|  | void appendBinOp(Value* left, Value* right) | 
|  | { | 
|  | appendBinOp<opcode32, opcode64, Air::Oops, Air::Oops, commutativity>(left, right); | 
|  | } | 
|  |  | 
|  | template<Air::Opcode opcode32, Air::Opcode opcode64> | 
|  | void appendShift(Value* value, Value* amount) | 
|  | { | 
|  | using namespace Air; | 
|  | Air::Opcode opcode = opcodeForType(opcode32, opcode64, value->type()); | 
|  |  | 
|  | if (imm(amount)) { | 
|  | if (isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Tmp)) { | 
|  | append(opcode, tmp(value), imm(amount), tmp(m_value)); | 
|  | return; | 
|  | } | 
|  | if (isValidForm(opcode, Arg::Imm, Arg::Tmp)) { | 
|  | append(Move, tmp(value), tmp(m_value)); | 
|  | append(opcode, imm(amount), tmp(m_value)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | append(opcode, tmp(value), tmp(amount), tmp(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | append(Move, tmp(value), tmp(m_value)); | 
|  | append(Move, tmp(amount), m_ecx); | 
|  | append(opcode, m_ecx, tmp(m_value)); | 
|  | } | 
|  |  | 
|  | template<Air::Opcode opcode32, Air::Opcode opcode64> | 
|  | bool tryAppendStoreUnOp(Value* value) | 
|  | { | 
|  | Air::Opcode opcode = tryOpcodeForType(opcode32, opcode64, value->type()); | 
|  | if (opcode == Air::Oops) | 
|  | return false; | 
|  |  | 
|  | Arg storeAddr = addr(m_value); | 
|  | ASSERT(storeAddr); | 
|  |  | 
|  | ArgPromise loadPromise = this->loadPromise(value); | 
|  | if (loadPromise.peek() != storeAddr) | 
|  | return false; | 
|  |  | 
|  | if (!isValidForm(opcode, storeAddr.kind())) | 
|  | return false; | 
|  |  | 
|  | loadPromise.consume(*this); | 
|  | append(trappingInst(m_value, loadPromise.inst(opcode, m_value, storeAddr))); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template< | 
|  | Air::Opcode opcode32, Air::Opcode opcode64, Commutativity commutativity = NotCommutative> | 
|  | bool tryAppendStoreBinOp(Value* left, Value* right) | 
|  | { | 
|  | RELEASE_ASSERT(m_value->as<MemoryValue>()); | 
|  |  | 
|  | Air::Opcode opcode = tryOpcodeForType(opcode32, opcode64, left->type()); | 
|  | if (opcode == Air::Oops) | 
|  | return false; | 
|  |  | 
|  | if (m_value->as<MemoryValue>()->hasFence()) | 
|  | return false; | 
|  |  | 
|  | Arg storeAddr = addr(m_value); | 
|  | ASSERT(storeAddr); | 
|  |  | 
|  | auto getLoadPromise = [&] (Value* load) -> ArgPromise { | 
|  | switch (m_value->opcode()) { | 
|  | case B3::Store: | 
|  | if (load->opcode() != B3::Load) | 
|  | return ArgPromise(); | 
|  | break; | 
|  | case B3::Store8: | 
|  | if (load->opcode() != B3::Load8Z && load->opcode() != B3::Load8S) | 
|  | return ArgPromise(); | 
|  | break; | 
|  | case B3::Store16: | 
|  | if (load->opcode() != B3::Load16Z && load->opcode() != B3::Load16S) | 
|  | return ArgPromise(); | 
|  | break; | 
|  | default: | 
|  | return ArgPromise(); | 
|  | } | 
|  | return loadPromiseAnyOpcode(load); | 
|  | }; | 
|  |  | 
|  | ArgPromise loadPromise; | 
|  | Value* otherValue = nullptr; | 
|  |  | 
|  | loadPromise = getLoadPromise(left); | 
|  | if (loadPromise.peek() == storeAddr) | 
|  | otherValue = right; | 
|  | else if (commutativity == Commutative) { | 
|  | loadPromise = getLoadPromise(right); | 
|  | if (loadPromise.peek() == storeAddr) | 
|  | otherValue = left; | 
|  | } | 
|  |  | 
|  | if (!otherValue) | 
|  | return false; | 
|  |  | 
|  | if (isValidForm(opcode, Arg::Imm, storeAddr.kind()) && imm(otherValue)) { | 
|  | loadPromise.consume(*this); | 
|  | append(trappingInst(m_value, loadPromise.inst(opcode, m_value, imm(otherValue), storeAddr))); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (!isValidForm(opcode, Arg::Tmp, storeAddr.kind())) | 
|  | return false; | 
|  |  | 
|  | loadPromise.consume(*this); | 
|  | append(trappingInst(m_value, loadPromise.inst(opcode, m_value, tmp(otherValue), storeAddr))); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | Inst createStore(Air::Kind move, Value* value, const Arg& dest) | 
|  | { | 
|  | using namespace Air; | 
|  | if (auto imm_value = imm(value)) { | 
|  | if (!imm_value.value()) { | 
|  | switch (move.opcode) { | 
|  | default: | 
|  | break; | 
|  | case Air::Move32: | 
|  | if (isValidForm(Store32, Arg::ZeroReg, dest.kind()) && dest.isValidForm(Width32)) | 
|  | return Inst(Store32, m_value, zeroReg(), dest); | 
|  | break; | 
|  | case Air::Move: | 
|  | if (isValidForm(Store64, Arg::ZeroReg, dest.kind()) && dest.isValidForm(Width64)) | 
|  | return Inst(Store64, m_value, zeroReg(), dest); | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (isValidForm(move.opcode, Arg::Imm, dest.kind())) | 
|  | return Inst(move, m_value, imm_value, dest); | 
|  | } | 
|  |  | 
|  | return Inst(move, m_value, tmp(value), dest); | 
|  | } | 
|  |  | 
|  | Air::Opcode storeOpcode(Width width, Bank bank) | 
|  | { | 
|  | using namespace Air; | 
|  | switch (width) { | 
|  | case Width8: | 
|  | RELEASE_ASSERT(bank == GP); | 
|  | return Air::Store8; | 
|  | case Width16: | 
|  | RELEASE_ASSERT(bank == GP); | 
|  | return Air::Store16; | 
|  | case Width32: | 
|  | switch (bank) { | 
|  | case GP: | 
|  | return Move32; | 
|  | case FP: | 
|  | return MoveFloat; | 
|  | } | 
|  | break; | 
|  | case Width64: | 
|  | RELEASE_ASSERT(is64Bit()); | 
|  | switch (bank) { | 
|  | case GP: | 
|  | return Move; | 
|  | case FP: | 
|  | return MoveDouble; | 
|  | } | 
|  | break; | 
|  | } | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | } | 
|  |  | 
|  | void appendStore(Value* value, const Arg& dest) | 
|  | { | 
|  | using namespace Air; | 
|  | MemoryValue* memory = value->as<MemoryValue>(); | 
|  | RELEASE_ASSERT(memory->isStore()); | 
|  |  | 
|  | Air::Kind kind; | 
|  | if (memory->hasFence()) { | 
|  | RELEASE_ASSERT(memory->accessBank() == GP); | 
|  |  | 
|  | if (isX86()) { | 
|  | kind = OPCODE_FOR_WIDTH(Xchg, memory->accessWidth()); | 
|  | kind.effects = true; | 
|  | Tmp swapTmp = m_code.newTmp(GP); | 
|  | append(relaxedMoveForType(memory->accessType()), tmp(memory->child(0)), swapTmp); | 
|  | append(kind, swapTmp, dest); | 
|  | return; | 
|  | } | 
|  |  | 
|  | kind = OPCODE_FOR_WIDTH(StoreRel, memory->accessWidth()); | 
|  | } else | 
|  | kind = storeOpcode(memory->accessWidth(), memory->accessBank()); | 
|  |  | 
|  | kind.effects |= memory->traps(); | 
|  |  | 
|  | append(createStore(kind, memory->child(0), dest)); | 
|  | } | 
|  |  | 
|  | template<typename... Arguments> | 
|  | void print(Arguments&&... arguments) | 
|  | { | 
|  | Value* origin = m_value; | 
|  | print(origin, std::forward<Arguments>(arguments)...); | 
|  | } | 
|  |  | 
|  | template<typename... Arguments> | 
|  | void print(Value* origin, Arguments&&... arguments) | 
|  | { | 
|  | auto printList = Printer::makePrintRecordList(arguments...); | 
|  | auto printSpecial = static_cast<Air::PrintSpecial*>(m_code.addSpecial(makeUnique<Air::PrintSpecial>(printList))); | 
|  | Inst inst(Air::Patch, origin, Arg::special(printSpecial)); | 
|  | Printer::appendAirArgs(inst, std::forward<Arguments>(arguments)...); | 
|  | append(WTFMove(inst)); | 
|  | } | 
|  |  | 
|  | template<typename... Arguments> | 
|  | void append(Air::Kind kind, Arguments&&... arguments) | 
|  | { | 
|  | m_insts.last().append(Inst(kind, m_value, std::forward<Arguments>(arguments)...)); | 
|  | } | 
|  |  | 
|  | template<typename... Arguments> | 
|  | void appendTrapping(Air::Kind kind, Arguments&&... arguments) | 
|  | { | 
|  | m_insts.last().append(trappingInst(m_value, kind, m_value, std::forward<Arguments>(arguments)...)); | 
|  | } | 
|  |  | 
|  | void append(Inst&& inst) | 
|  | { | 
|  | m_insts.last().append(WTFMove(inst)); | 
|  | } | 
|  | void append(const Inst& inst) | 
|  | { | 
|  | m_insts.last().append(inst); | 
|  | } | 
|  |  | 
|  | void finishAppendingInstructions(Air::BasicBlock* target) | 
|  | { | 
|  | // Now append the instructions. m_insts contains them in reverse order, so we process | 
|  | // it in reverse. | 
|  | for (unsigned i = m_insts.size(); i--;) { | 
|  | for (Inst& inst : m_insts[i]) | 
|  | target->appendInst(WTFMove(inst)); | 
|  | } | 
|  | m_insts.shrink(0); | 
|  | } | 
|  |  | 
|  | Air::BasicBlock* newBlock() | 
|  | { | 
|  | return m_blockInsertionSet.insertAfter(m_blockToBlock[m_block]); | 
|  | } | 
|  |  | 
|  | // NOTE: This will create a continuation block (`nextBlock`) *after* any blocks you've created using | 
|  | // newBlock(). So, it's preferable to create all of your blocks upfront using newBlock(). Also note | 
|  | // that any code you emit before this will be prepended to the continuation, and any code you emit | 
|  | // after this will be appended to the previous block. | 
|  | void splitBlock(Air::BasicBlock*& previousBlock, Air::BasicBlock*& nextBlock) | 
|  | { | 
|  | Air::BasicBlock* block = m_blockToBlock[m_block]; | 
|  |  | 
|  | previousBlock = block; | 
|  | nextBlock = m_blockInsertionSet.insertAfter(block); | 
|  |  | 
|  | finishAppendingInstructions(nextBlock); | 
|  | nextBlock->successors() = block->successors(); | 
|  | block->successors().clear(); | 
|  |  | 
|  | m_insts.append(Vector<Inst>()); | 
|  | } | 
|  |  | 
|  | template<typename T, typename... Arguments> | 
|  | T* ensureSpecial(T*& field, Arguments&&... arguments) | 
|  | { | 
|  | if (!field) { | 
|  | field = static_cast<T*>( | 
|  | m_code.addSpecial(makeUnique<T>(std::forward<Arguments>(arguments)...))); | 
|  | } | 
|  | return field; | 
|  | } | 
|  |  | 
|  | template<typename... Arguments> | 
|  | CheckSpecial* ensureCheckSpecial(Arguments&&... arguments) | 
|  | { | 
|  | CheckSpecial::Key key(std::forward<Arguments>(arguments)...); | 
|  | auto result = m_checkSpecials.add(key, nullptr); | 
|  | return ensureSpecial(result.iterator->value, key); | 
|  | } | 
|  |  | 
|  | void fillStackmap(Inst& inst, StackmapValue* stackmap, unsigned numSkipped) | 
|  | { | 
|  | for (unsigned i = numSkipped; i < stackmap->numChildren(); ++i) { | 
|  | ConstrainedValue value = stackmap->constrainedChild(i); | 
|  |  | 
|  | Arg arg; | 
|  | switch (value.rep().kind()) { | 
|  | case ValueRep::WarmAny: | 
|  | case ValueRep::ColdAny: | 
|  | case ValueRep::LateColdAny: | 
|  | if (imm(value.value())) | 
|  | arg = imm(value.value()); | 
|  | else if (value.value()->hasInt64()) | 
|  | arg = Arg::bigImm(value.value()->asInt64()); | 
|  | else if (value.value()->hasDouble() && canBeInternal(value.value())) { | 
|  | commitInternal(value.value()); | 
|  | arg = Arg::bigImm(bitwise_cast<int64_t>(value.value()->asDouble())); | 
|  | } else if (value.value()->hasFloat() && canBeInternal(value.value())) { | 
|  | commitInternal(value.value()); | 
|  | arg = Arg::bigImm(static_cast<uint64_t>(bitwise_cast<uint32_t>(value.value()->asFloat()))); | 
|  | } else | 
|  | arg = tmp(value.value()); | 
|  | break; | 
|  | case ValueRep::SomeRegister: | 
|  | case ValueRep::SomeLateRegister: | 
|  | arg = tmp(value.value()); | 
|  | break; | 
|  | case ValueRep::SomeRegisterWithClobber: { | 
|  | Tmp dstTmp = m_code.newTmp(value.value()->resultBank()); | 
|  | append(relaxedMoveForType(value.value()->type()), immOrTmp(value.value()), dstTmp); | 
|  | arg = dstTmp; | 
|  | break; | 
|  | } | 
|  | case ValueRep::LateRegister: | 
|  | case ValueRep::Register: | 
|  | stackmap->earlyClobbered().clear(value.rep().reg()); | 
|  | arg = Tmp(value.rep().reg()); | 
|  | append(relaxedMoveForType(value.value()->type()), immOrTmp(value.value()), arg); | 
|  | break; | 
|  | case ValueRep::StackArgument: | 
|  | arg = Arg::callArg(value.rep().offsetFromSP()); | 
|  | append(trappingInst(m_value, createStore(moveForType(value.value()->type()), value.value(), arg))); | 
|  | break; | 
|  | default: | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | break; | 
|  | } | 
|  | inst.args.append(arg); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Create an Inst to do the comparison specified by the given value. | 
|  | template<typename CompareFunctor, typename TestFunctor, typename CompareDoubleFunctor, typename CompareFloatFunctor> | 
|  | Inst createGenericCompare( | 
|  | Value* value, | 
|  | const CompareFunctor& compare, // Signature: (Width, Arg relCond, Arg, Arg) -> Inst | 
|  | const TestFunctor& test, // Signature: (Width, Arg resCond, Arg, Arg) -> Inst | 
|  | const CompareDoubleFunctor& compareDouble, // Signature: (Arg doubleCond, Arg, Arg) -> Inst | 
|  | const CompareFloatFunctor& compareFloat, // Signature: (Arg doubleCond, Arg, Arg) -> Inst | 
|  | bool inverted = false) | 
|  | { | 
|  | // NOTE: This is totally happy to match comparisons that have already been computed elsewhere | 
|  | // since on most architectures, the cost of branching on a previously computed comparison | 
|  | // result is almost always higher than just doing another fused compare/branch. The only time | 
|  | // it could be worse is if we have a binary comparison and both operands are variables (not | 
|  | // constants), and we encounter register pressure. Even in this case, duplicating the compare | 
|  | // so that we can fuse it to the branch will be more efficient most of the time, since | 
|  | // register pressure is not *that* common. For this reason, this algorithm will always | 
|  | // duplicate the comparison. | 
|  | // | 
|  | // However, we cannot duplicate loads. The canBeInternal() on a load will assume that we | 
|  | // already validated canBeInternal() on all of the values that got us to the load. So, even | 
|  | // if we are sharing a value, we still need to call canBeInternal() for the purpose of | 
|  | // tracking whether we are still in good shape to fuse loads. | 
|  | // | 
|  | // We could even have a chain of compare values that we fuse, and any member of the chain | 
|  | // could be shared. Once any of them are shared, then the shared one's transitive children | 
|  | // cannot be locked (i.e. commitInternal()). But if none of them are shared, then we want to | 
|  | // lock all of them because that's a prerequisite to fusing the loads so that the loads don't | 
|  | // get duplicated. For example, we might have: | 
|  | // | 
|  | //     @tmp1 = LessThan(@a, @b) | 
|  | //     @tmp2 = Equal(@tmp1, 0) | 
|  | //     Branch(@tmp2) | 
|  | // | 
|  | // If either @a or @b are loads, then we want to have locked @tmp1 and @tmp2 so that they | 
|  | // don't emit the loads a second time. But if we had another use of @tmp2, then we cannot | 
|  | // lock @tmp1 (or @a or @b) because then we'll get into trouble when the other values that | 
|  | // try to share @tmp1 with us try to do their lowering. | 
|  | // | 
|  | // There's one more wrinkle. If we don't lock an internal value, then this internal value may | 
|  | // have already separately locked its children. So, if we're not locking a value then we need | 
|  | // to make sure that its children aren't locked. We encapsulate this in two ways: | 
|  | // | 
|  | // canCommitInternal: This variable tells us if the values that we've fused so far are | 
|  | // locked. This means that we're not sharing any of them with anyone. This permits us to fuse | 
|  | // loads. If it's false, then we cannot fuse loads and we also need to ensure that the | 
|  | // children of any values we try to fuse-by-sharing are not already locked. You don't have to | 
|  | // worry about the children locking thing if you use prepareToFuse() before trying to fuse a | 
|  | // sharable value. But, you do need to guard any load fusion by checking if canCommitInternal | 
|  | // is true. | 
|  | // | 
|  | // FusionResult prepareToFuse(value): Call this when you think that you would like to fuse | 
|  | // some value and that value is not a load. It will automatically handle the shared-or-locked | 
|  | // issues and it will clear canCommitInternal if necessary. This will return CannotFuse | 
|  | // (which acts like false) if the value cannot be locked and its children are locked. That's | 
|  | // rare, but you just need to make sure that you do smart things when this happens (i.e. just | 
|  | // use the value rather than trying to fuse it). After you call prepareToFuse(), you can | 
|  | // still change your mind about whether you will actually fuse the value. If you do fuse it, | 
|  | // you need to call commitFusion(value, fusionResult). | 
|  | // | 
|  | // commitFusion(value, fusionResult): Handles calling commitInternal(value) if fusionResult | 
|  | // is FuseAndCommit. | 
|  |  | 
|  | bool canCommitInternal = true; | 
|  |  | 
|  | enum FusionResult { | 
|  | CannotFuse, | 
|  | FuseAndCommit, | 
|  | Fuse | 
|  | }; | 
|  | auto prepareToFuse = [&] (Value* value) -> FusionResult { | 
|  | if (value == m_value) { | 
|  | // It's not actually internal. It's the root value. We're good to go. | 
|  | return Fuse; | 
|  | } | 
|  |  | 
|  | if (canCommitInternal && canBeInternal(value)) { | 
|  | // We are the only users of this value. This also means that the value's children | 
|  | // could not have been locked, since we have now proved that m_value dominates value | 
|  | // in the data flow graph. To only other way to value is from a user of m_value. If | 
|  | // value's children are shared with others, then they could not have been locked | 
|  | // because their use count is greater than 1. If they are only used from value, then | 
|  | // in order for value's children to be locked, value would also have to be locked, | 
|  | // and we just proved that it wasn't. | 
|  | return FuseAndCommit; | 
|  | } | 
|  |  | 
|  | // We're going to try to share value with others. It's possible that some other basic | 
|  | // block had already emitted code for value and then matched over its children and then | 
|  | // locked them, in which case we just want to use value instead of duplicating it. So, we | 
|  | // validate the children. Note that this only arises in linear chains like: | 
|  | // | 
|  | //     BB#1: | 
|  | //         @1 = Foo(...) | 
|  | //         @2 = Bar(@1) | 
|  | //         Jump(#2) | 
|  | //     BB#2: | 
|  | //         @3 = Baz(@2) | 
|  | // | 
|  | // Notice how we could start by generating code for BB#1 and then decide to lock @1 when | 
|  | // generating code for @2, if we have some way of fusing Bar and Foo into a single | 
|  | // instruction. This is legal, since indeed @1 only has one user. The fact that @2 now | 
|  | // has a tmp (i.e. @2 is pinned), canBeInternal(@2) will return false, which brings us | 
|  | // here. In that case, we cannot match over @2 because then we'd hit a hazard if we end | 
|  | // up deciding not to fuse Foo into the fused Baz/Bar. | 
|  | // | 
|  | // Happily, there are only two places where this kind of child validation happens is in | 
|  | // rules that admit sharing, like this and effectiveAddress(). | 
|  | // | 
|  | // N.B. We could probably avoid the need to do value locking if we committed to a well | 
|  | // chosen code generation order. For example, if we guaranteed that all of the users of | 
|  | // a value get generated before that value, then there's no way for the lowering of @3 to | 
|  | // see @1 locked. But we don't want to do that, since this is a greedy instruction | 
|  | // selector and so we want to be able to play with order. | 
|  | for (Value* child : value->children()) { | 
|  | if (m_locked.contains(child)) | 
|  | return CannotFuse; | 
|  | } | 
|  |  | 
|  | // It's safe to share value, but since we're sharing, it means that we aren't locking it. | 
|  | // If we don't lock it, then fusing loads is off limits and all of value's children will | 
|  | // have to go through the sharing path as well. Fusing loads is off limits because the load | 
|  | // could already have been emitted elsehwere - so fusing it here would duplicate the load. | 
|  | // We don't consider that to be a legal optimization. | 
|  | canCommitInternal = false; | 
|  |  | 
|  | return Fuse; | 
|  | }; | 
|  |  | 
|  | auto commitFusion = [&] (Value* value, FusionResult result) { | 
|  | if (result == FuseAndCommit) | 
|  | commitInternal(value); | 
|  | }; | 
|  |  | 
|  | // Chew through any inversions. This loop isn't necessary for comparisons and branches, but | 
|  | // we do need at least one iteration of it for Check. | 
|  | for (;;) { | 
|  | bool shouldInvert = | 
|  | (value->opcode() == BitXor && value->child(1)->hasInt() && (value->child(1)->asInt() == 1) && value->child(0)->returnsBool()) | 
|  | || (value->opcode() == Equal && value->child(1)->isInt(0)); | 
|  | if (!shouldInvert) | 
|  | break; | 
|  |  | 
|  | FusionResult fusionResult = prepareToFuse(value); | 
|  | if (fusionResult == CannotFuse) | 
|  | break; | 
|  | commitFusion(value, fusionResult); | 
|  |  | 
|  | value = value->child(0); | 
|  | inverted = !inverted; | 
|  | } | 
|  |  | 
|  | auto createRelCond = [&] ( | 
|  | MacroAssembler::RelationalCondition relationalCondition, | 
|  | MacroAssembler::DoubleCondition doubleCondition) { | 
|  | Arg relCond = Arg::relCond(relationalCondition).inverted(inverted); | 
|  | Arg doubleCond = Arg::doubleCond(doubleCondition).inverted(inverted); | 
|  | Value* left = value->child(0); | 
|  | Value* right = value->child(1); | 
|  |  | 
|  | if (value->child(0)->type().isInt()) { | 
|  | Arg rightImm = imm(right); | 
|  |  | 
|  | auto tryCompare = [&] ( | 
|  | Width width, ArgPromise&& left, ArgPromise&& right) -> Inst { | 
|  | if (Inst result = compare(width, relCond, left, right)) | 
|  | return result; | 
|  | if (Inst result = compare(width, relCond.flipped(), right, left)) | 
|  | return result; | 
|  | return Inst(); | 
|  | }; | 
|  |  | 
|  | auto tryCompareLoadImm = [&] ( | 
|  | Width width, B3::Opcode loadOpcode, Arg::Signedness signedness) -> Inst { | 
|  | if (rightImm && rightImm.isRepresentableAs(width, signedness)) { | 
|  | if (Inst result = tryCompare(width, loadPromise(left, loadOpcode), rightImm)) { | 
|  | commitInternal(left); | 
|  | return result; | 
|  | } | 
|  | } | 
|  | return Inst(); | 
|  | }; | 
|  |  | 
|  | Width width = value->child(0)->resultWidth(); | 
|  |  | 
|  | if (canCommitInternal) { | 
|  | // First handle compares that involve fewer bits than B3's type system supports. | 
|  | // This is pretty important. For example, we want this to be a single | 
|  | // instruction: | 
|  | // | 
|  | //     @1 = Load8S(...) | 
|  | //     @2 = Const32(...) | 
|  | //     @3 = LessThan(@1, @2) | 
|  | //     Branch(@3) | 
|  |  | 
|  | if (relCond.isSignedCond()) { | 
|  | if (Inst result = tryCompareLoadImm(Width8, Load8S, Arg::Signed)) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (relCond.isUnsignedCond()) { | 
|  | if (Inst result = tryCompareLoadImm(Width8, Load8Z, Arg::Unsigned)) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (relCond.isSignedCond()) { | 
|  | if (Inst result = tryCompareLoadImm(Width16, Load16S, Arg::Signed)) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (relCond.isUnsignedCond()) { | 
|  | if (Inst result = tryCompareLoadImm(Width16, Load16Z, Arg::Unsigned)) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Now handle compares that involve a load and an immediate. | 
|  |  | 
|  | if (Inst result = tryCompareLoadImm(width, Load, Arg::Signed)) | 
|  | return result; | 
|  |  | 
|  | // Now handle compares that involve a load. It's not obvious that it's better to | 
|  | // handle this before the immediate cases or not. Probably doesn't matter. | 
|  |  | 
|  | if (Inst result = tryCompare(width, loadPromise(left), tmpPromise(right))) { | 
|  | commitInternal(left); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (Inst result = tryCompare(width, tmpPromise(left), loadPromise(right))) { | 
|  | commitInternal(right); | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Now handle compares that involve an immediate and a tmp. | 
|  |  | 
|  | if (rightImm && rightImm.isRepresentableAs<int32_t>()) { | 
|  | if (Inst result = tryCompare(width, tmpPromise(left), rightImm)) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Finally, handle comparison between tmps. | 
|  | ArgPromise leftPromise = tmpPromise(left); | 
|  | ArgPromise rightPromise = tmpPromise(right); | 
|  | return compare(width, relCond, leftPromise, rightPromise); | 
|  | } | 
|  |  | 
|  | // Floating point comparisons can't really do anything smart. | 
|  | ArgPromise leftPromise = tmpPromise(left); | 
|  | ArgPromise rightPromise = tmpPromise(right); | 
|  | if (value->child(0)->type() == Float) | 
|  | return compareFloat(doubleCond, leftPromise, rightPromise); | 
|  | return compareDouble(doubleCond, leftPromise, rightPromise); | 
|  | }; | 
|  |  | 
|  | Width width = value->resultWidth(); | 
|  | Arg resCond = Arg::resCond(MacroAssembler::NonZero).inverted(inverted); | 
|  |  | 
|  | auto tryTest = [&] ( | 
|  | Width width, ArgPromise&& left, ArgPromise&& right) -> Inst { | 
|  | if (Inst result = test(width, resCond, left, right)) | 
|  | return result; | 
|  | if (Inst result = test(width, resCond, right, left)) | 
|  | return result; | 
|  | return Inst(); | 
|  | }; | 
|  |  | 
|  | auto attemptFused = [&] () -> Inst { | 
|  | switch (value->opcode()) { | 
|  | case NotEqual: | 
|  | return createRelCond(MacroAssembler::NotEqual, MacroAssembler::DoubleNotEqualOrUnordered); | 
|  | case Equal: | 
|  | return createRelCond(MacroAssembler::Equal, MacroAssembler::DoubleEqualAndOrdered); | 
|  | case LessThan: | 
|  | return createRelCond(MacroAssembler::LessThan, MacroAssembler::DoubleLessThanAndOrdered); | 
|  | case GreaterThan: | 
|  | return createRelCond(MacroAssembler::GreaterThan, MacroAssembler::DoubleGreaterThanAndOrdered); | 
|  | case LessEqual: | 
|  | return createRelCond(MacroAssembler::LessThanOrEqual, MacroAssembler::DoubleLessThanOrEqualAndOrdered); | 
|  | case GreaterEqual: | 
|  | return createRelCond(MacroAssembler::GreaterThanOrEqual, MacroAssembler::DoubleGreaterThanOrEqualAndOrdered); | 
|  | case EqualOrUnordered: | 
|  | // The integer condition is never used in this case. | 
|  | return createRelCond(MacroAssembler::Equal, MacroAssembler::DoubleEqualOrUnordered); | 
|  | case Above: | 
|  | // We use a bogus double condition because these integer comparisons won't got down that | 
|  | // path anyway. | 
|  | return createRelCond(MacroAssembler::Above, MacroAssembler::DoubleEqualAndOrdered); | 
|  | case Below: | 
|  | return createRelCond(MacroAssembler::Below, MacroAssembler::DoubleEqualAndOrdered); | 
|  | case AboveEqual: | 
|  | return createRelCond(MacroAssembler::AboveOrEqual, MacroAssembler::DoubleEqualAndOrdered); | 
|  | case BelowEqual: | 
|  | return createRelCond(MacroAssembler::BelowOrEqual, MacroAssembler::DoubleEqualAndOrdered); | 
|  | case BitAnd: { | 
|  | Value* left = value->child(0); | 
|  | Value* right = value->child(1); | 
|  |  | 
|  | bool hasRightConst; | 
|  | int64_t rightConst; | 
|  | Arg rightImm; | 
|  | Arg rightImm64; | 
|  |  | 
|  | hasRightConst = right->hasInt(); | 
|  | if (hasRightConst) { | 
|  | rightConst = right->asInt(); | 
|  | rightImm = bitImm(right); | 
|  | rightImm64 = bitImm64(right); | 
|  | } | 
|  |  | 
|  | auto tryTestLoadImm = [&] (Width width, Arg::Signedness signedness, B3::Opcode loadOpcode) -> Inst { | 
|  | if (!hasRightConst) | 
|  | return Inst(); | 
|  | // Signed loads will create high bits, so if the immediate has high bits | 
|  | // then we cannot proceed. Consider BitAnd(Load8S(ptr), 0x101). This cannot | 
|  | // be turned into testb (ptr), $1, since if the high bit within that byte | 
|  | // was set then it would be extended to include 0x100. The handling below | 
|  | // won't anticipate this, so we need to catch it here. | 
|  | if (signedness == Arg::Signed | 
|  | && !Arg::isRepresentableAs(width, Arg::Unsigned, rightConst)) | 
|  | return Inst(); | 
|  |  | 
|  | // FIXME: If this is unsigned then we can chop things off of the immediate. | 
|  | // This might make the immediate more legal. Perhaps that's a job for | 
|  | // strength reduction? | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=169248 | 
|  |  | 
|  | if (rightImm) { | 
|  | if (Inst result = tryTest(width, loadPromise(left, loadOpcode), rightImm)) { | 
|  | commitInternal(left); | 
|  | return result; | 
|  | } | 
|  | } | 
|  | if (rightImm64) { | 
|  | if (Inst result = tryTest(width, loadPromise(left, loadOpcode), rightImm64)) { | 
|  | commitInternal(left); | 
|  | return result; | 
|  | } | 
|  | } | 
|  | return Inst(); | 
|  | }; | 
|  |  | 
|  | if (canCommitInternal) { | 
|  | // First handle test's that involve fewer bits than B3's type system supports. | 
|  |  | 
|  | if (Inst result = tryTestLoadImm(Width8, Arg::Unsigned, Load8Z)) | 
|  | return result; | 
|  |  | 
|  | if (Inst result = tryTestLoadImm(Width8, Arg::Signed, Load8S)) | 
|  | return result; | 
|  |  | 
|  | if (Inst result = tryTestLoadImm(Width16, Arg::Unsigned, Load16Z)) | 
|  | return result; | 
|  |  | 
|  | if (Inst result = tryTestLoadImm(Width16, Arg::Signed, Load16S)) | 
|  | return result; | 
|  |  | 
|  | // This allows us to use a 32-bit test for 64-bit BitAnd if the immediate is | 
|  | // representable as an unsigned 32-bit value. The logic involved is the same | 
|  | // as if we were pondering using a 32-bit test for | 
|  | // BitAnd(SExt(Load(ptr)), const), in the sense that in both cases we have | 
|  | // to worry about high bits. So, we use the "Signed" version of this helper. | 
|  | if (Inst result = tryTestLoadImm(Width32, Arg::Signed, Load)) | 
|  | return result; | 
|  |  | 
|  | // This is needed to handle 32-bit test for arbitrary 32-bit immediates. | 
|  | if (Inst result = tryTestLoadImm(width, Arg::Unsigned, Load)) | 
|  | return result; | 
|  |  | 
|  | // Now handle test's that involve a load. | 
|  |  | 
|  | Width width = value->child(0)->resultWidth(); | 
|  | if (Inst result = tryTest(width, loadPromise(left), tmpPromise(right))) { | 
|  | commitInternal(left); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (Inst result = tryTest(width, tmpPromise(left), loadPromise(right))) { | 
|  | commitInternal(right); | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Now handle test's that involve an immediate and a tmp. | 
|  |  | 
|  | if (hasRightConst) { | 
|  | if ((width == Width32 && rightConst == 0xffffffff) | 
|  | || (width == Width64 && rightConst == -1)) { | 
|  | if (Inst result = tryTest(width, tmpPromise(left), tmpPromise(left))) | 
|  | return result; | 
|  | } | 
|  | if (isRepresentableAs<uint32_t>(rightConst)) { | 
|  | if (Inst result = tryTest(Width32, tmpPromise(left), rightImm)) | 
|  | return result; | 
|  | if (Inst result = tryTest(Width32, tmpPromise(left), rightImm64)) | 
|  | return result; | 
|  | } | 
|  | if (Inst result = tryTest(width, tmpPromise(left), rightImm)) | 
|  | return result; | 
|  | if (Inst result = tryTest(width, tmpPromise(left), rightImm64)) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Finally, just do tmp's. | 
|  | return tryTest(width, tmpPromise(left), tmpPromise(right)); | 
|  | } | 
|  | default: | 
|  | return Inst(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | if (FusionResult fusionResult = prepareToFuse(value)) { | 
|  | if (Inst result = attemptFused()) { | 
|  | commitFusion(value, fusionResult); | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (Arg::isValidBitImmForm(-1)) { | 
|  | if (canCommitInternal && value->as<MemoryValue>()) { | 
|  | // Handle things like Branch(Load8Z(value)) | 
|  |  | 
|  | if (Inst result = tryTest(Width8, loadPromise(value, Load8Z), Arg::bitImm(-1))) { | 
|  | commitInternal(value); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (Inst result = tryTest(Width8, loadPromise(value, Load8S), Arg::bitImm(-1))) { | 
|  | commitInternal(value); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (Inst result = tryTest(Width16, loadPromise(value, Load16Z), Arg::bitImm(-1))) { | 
|  | commitInternal(value); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (Inst result = tryTest(Width16, loadPromise(value, Load16S), Arg::bitImm(-1))) { | 
|  | commitInternal(value); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (Inst result = tryTest(width, loadPromise(value), Arg::bitImm(-1))) { | 
|  | commitInternal(value); | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  | ArgPromise leftPromise = tmpPromise(value); | 
|  | ArgPromise rightPromise = Arg::bitImm(-1); | 
|  | if (Inst result = test(width, resCond, leftPromise, rightPromise)) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Sometimes this is the only form of test available. We prefer not to use this because | 
|  | // it's less canonical. | 
|  | ArgPromise leftPromise = tmpPromise(value); | 
|  | ArgPromise rightPromise = tmpPromise(value); | 
|  | return test(width, resCond, leftPromise, rightPromise); | 
|  | } | 
|  |  | 
|  | Inst createBranch(Value* value, bool inverted = false) | 
|  | { | 
|  | using namespace Air; | 
|  | return createGenericCompare( | 
|  | value, | 
|  | [this] ( | 
|  | Width width, const Arg& relCond, | 
|  | ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | switch (width) { | 
|  | case Width8: | 
|  | if (isValidForm(Branch8, Arg::RelCond, left.kind(), right.kind())) { | 
|  | return left.inst(right.inst( | 
|  | Branch8, m_value, relCond, | 
|  | left.consume(*this), right.consume(*this))); | 
|  | } | 
|  | return Inst(); | 
|  | case Width16: | 
|  | return Inst(); | 
|  | case Width32: | 
|  | if (isValidForm(Branch32, Arg::RelCond, left.kind(), right.kind())) { | 
|  | return left.inst(right.inst( | 
|  | Branch32, m_value, relCond, | 
|  | left.consume(*this), right.consume(*this))); | 
|  | } | 
|  | return Inst(); | 
|  | case Width64: | 
|  | if (isValidForm(Branch64, Arg::RelCond, left.kind(), right.kind())) { | 
|  | return left.inst(right.inst( | 
|  | Branch64, m_value, relCond, | 
|  | left.consume(*this), right.consume(*this))); | 
|  | } | 
|  | return Inst(); | 
|  | } | 
|  | ASSERT_NOT_REACHED(); | 
|  | }, | 
|  | [this] ( | 
|  | Width width, const Arg& resCond, | 
|  | ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | switch (width) { | 
|  | case Width8: | 
|  | if (isValidForm(BranchTest8, Arg::ResCond, left.kind(), right.kind())) { | 
|  | return left.inst(right.inst( | 
|  | BranchTest8, m_value, resCond, | 
|  | left.consume(*this), right.consume(*this))); | 
|  | } | 
|  | return Inst(); | 
|  | case Width16: | 
|  | return Inst(); | 
|  | case Width32: | 
|  | if (isValidForm(BranchTest32, Arg::ResCond, left.kind(), right.kind())) { | 
|  | return left.inst(right.inst( | 
|  | BranchTest32, m_value, resCond, | 
|  | left.consume(*this), right.consume(*this))); | 
|  | } | 
|  | return Inst(); | 
|  | case Width64: | 
|  | if (isValidForm(BranchTest64, Arg::ResCond, left.kind(), right.kind())) { | 
|  | return left.inst(right.inst( | 
|  | BranchTest64, m_value, resCond, | 
|  | left.consume(*this), right.consume(*this))); | 
|  | } | 
|  | return Inst(); | 
|  | } | 
|  | ASSERT_NOT_REACHED(); | 
|  | }, | 
|  | [this] (Arg doubleCond, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | if (isValidForm(BranchDouble, Arg::DoubleCond, left.kind(), right.kind())) { | 
|  | return left.inst(right.inst( | 
|  | BranchDouble, m_value, doubleCond, | 
|  | left.consume(*this), right.consume(*this))); | 
|  | } | 
|  | return Inst(); | 
|  | }, | 
|  | [this] (Arg doubleCond, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | if (isValidForm(BranchFloat, Arg::DoubleCond, left.kind(), right.kind())) { | 
|  | return left.inst(right.inst( | 
|  | BranchFloat, m_value, doubleCond, | 
|  | left.consume(*this), right.consume(*this))); | 
|  | } | 
|  | return Inst(); | 
|  | }, | 
|  | inverted); | 
|  | } | 
|  |  | 
|  | Inst createCompare(Value* value, bool inverted = false) | 
|  | { | 
|  | using namespace Air; | 
|  | return createGenericCompare( | 
|  | value, | 
|  | [this] ( | 
|  | Width width, const Arg& relCond, | 
|  | ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | switch (width) { | 
|  | case Width8: | 
|  | case Width16: | 
|  | return Inst(); | 
|  | case Width32: | 
|  | if (isValidForm(Compare32, Arg::RelCond, left.kind(), right.kind(), Arg::Tmp)) { | 
|  | return left.inst(right.inst( | 
|  | Compare32, m_value, relCond, | 
|  | left.consume(*this), right.consume(*this), tmp(m_value))); | 
|  | } | 
|  | return Inst(); | 
|  | case Width64: | 
|  | if (isValidForm(Compare64, Arg::RelCond, left.kind(), right.kind(), Arg::Tmp)) { | 
|  | return left.inst(right.inst( | 
|  | Compare64, m_value, relCond, | 
|  | left.consume(*this), right.consume(*this), tmp(m_value))); | 
|  | } | 
|  | return Inst(); | 
|  | } | 
|  | ASSERT_NOT_REACHED(); | 
|  | }, | 
|  | [this] ( | 
|  | Width width, const Arg& resCond, | 
|  | ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | switch (width) { | 
|  | case Width8: | 
|  | case Width16: | 
|  | return Inst(); | 
|  | case Width32: | 
|  | if (isValidForm(Test32, Arg::ResCond, left.kind(), right.kind(), Arg::Tmp)) { | 
|  | return left.inst(right.inst( | 
|  | Test32, m_value, resCond, | 
|  | left.consume(*this), right.consume(*this), tmp(m_value))); | 
|  | } | 
|  | return Inst(); | 
|  | case Width64: | 
|  | if (isValidForm(Test64, Arg::ResCond, left.kind(), right.kind(), Arg::Tmp)) { | 
|  | return left.inst(right.inst( | 
|  | Test64, m_value, resCond, | 
|  | left.consume(*this), right.consume(*this), tmp(m_value))); | 
|  | } | 
|  | return Inst(); | 
|  | } | 
|  | ASSERT_NOT_REACHED(); | 
|  | }, | 
|  | [this] (const Arg& doubleCond, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | if (isValidForm(CompareDouble, Arg::DoubleCond, left.kind(), right.kind(), Arg::Tmp)) { | 
|  | return left.inst(right.inst( | 
|  | CompareDouble, m_value, doubleCond, | 
|  | left.consume(*this), right.consume(*this), tmp(m_value))); | 
|  | } | 
|  | return Inst(); | 
|  | }, | 
|  | [this] (const Arg& doubleCond, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | if (isValidForm(CompareFloat, Arg::DoubleCond, left.kind(), right.kind(), Arg::Tmp)) { | 
|  | return left.inst(right.inst( | 
|  | CompareFloat, m_value, doubleCond, | 
|  | left.consume(*this), right.consume(*this), tmp(m_value))); | 
|  | } | 
|  | return Inst(); | 
|  | }, | 
|  | inverted); | 
|  | } | 
|  |  | 
|  | struct MoveConditionallyConfig { | 
|  | Air::Opcode moveConditionally32; | 
|  | Air::Opcode moveConditionally64; | 
|  | Air::Opcode moveConditionallyTest32; | 
|  | Air::Opcode moveConditionallyTest64; | 
|  | Air::Opcode moveConditionallyDouble; | 
|  | Air::Opcode moveConditionallyFloat; | 
|  | }; | 
|  | Inst createSelect(const MoveConditionallyConfig& config) | 
|  | { | 
|  | using namespace Air; | 
|  | auto createSelectInstruction = [&] (Air::Opcode opcode, const Arg& condition, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | if (isValidForm(opcode, condition.kind(), left.kind(), right.kind(), Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | Tmp result = tmp(m_value); | 
|  | Tmp thenCase = tmp(m_value->child(1)); | 
|  | Tmp elseCase = tmp(m_value->child(2)); | 
|  | return left.inst(right.inst( | 
|  | opcode, m_value, condition, | 
|  | left.consume(*this), right.consume(*this), thenCase, elseCase, result)); | 
|  | } | 
|  | if (isValidForm(opcode, condition.kind(), left.kind(), right.kind(), Arg::Tmp, Arg::Tmp)) { | 
|  | Tmp result = tmp(m_value); | 
|  | Tmp source = tmp(m_value->child(1)); | 
|  | append(relaxedMoveForType(m_value->type()), tmp(m_value->child(2)), result); | 
|  | return left.inst(right.inst( | 
|  | opcode, m_value, condition, | 
|  | left.consume(*this), right.consume(*this), source, result)); | 
|  | } | 
|  | return Inst(); | 
|  | }; | 
|  |  | 
|  | return createGenericCompare( | 
|  | m_value->child(0), | 
|  | [&] (Width width, const Arg& relCond, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | switch (width) { | 
|  | case Width8: | 
|  | // FIXME: Support these things. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=151504 | 
|  | return Inst(); | 
|  | case Width16: | 
|  | return Inst(); | 
|  | case Width32: | 
|  | return createSelectInstruction(config.moveConditionally32, relCond, left, right); | 
|  | case Width64: | 
|  | return createSelectInstruction(config.moveConditionally64, relCond, left, right); | 
|  | } | 
|  | ASSERT_NOT_REACHED(); | 
|  | }, | 
|  | [&] (Width width, const Arg& resCond, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | switch (width) { | 
|  | case Width8: | 
|  | // FIXME: Support more things. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=151504 | 
|  | return Inst(); | 
|  | case Width16: | 
|  | return Inst(); | 
|  | case Width32: | 
|  | return createSelectInstruction(config.moveConditionallyTest32, resCond, left, right); | 
|  | case Width64: | 
|  | return createSelectInstruction(config.moveConditionallyTest64, resCond, left, right); | 
|  | } | 
|  | ASSERT_NOT_REACHED(); | 
|  | }, | 
|  | [&] (Arg doubleCond, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | return createSelectInstruction(config.moveConditionallyDouble, doubleCond, left, right); | 
|  | }, | 
|  | [&] (Arg doubleCond, ArgPromise& left, ArgPromise& right) -> Inst { | 
|  | return createSelectInstruction(config.moveConditionallyFloat, doubleCond, left, right); | 
|  | }, | 
|  | false); | 
|  | } | 
|  |  | 
|  | bool tryAppendLea() | 
|  | { | 
|  | using namespace Air; | 
|  | Air::Opcode leaOpcode = tryOpcodeForType(Lea32, Lea64, m_value->type()); | 
|  | if (!isValidForm(leaOpcode, Arg::Index, Arg::Tmp)) | 
|  | return false; | 
|  |  | 
|  | // This lets us turn things like this: | 
|  | // | 
|  | //     Add(Add(@x, Shl(@y, $2)), $100) | 
|  | // | 
|  | // Into this: | 
|  | // | 
|  | //     lea 100(%rdi,%rsi,4), %rax | 
|  | // | 
|  | // We have a choice here between committing the internal bits of an index or sharing | 
|  | // them. There are solid arguments for both. | 
|  | // | 
|  | // Sharing: The word on the street is that the cost of a lea is one cycle no matter | 
|  | // what it does. Every experiment I've ever seen seems to confirm this. So, sharing | 
|  | // helps us in situations where Wasm input did this: | 
|  | // | 
|  | //     x = a[i].x; | 
|  | //     y = a[i].y; | 
|  | // | 
|  | // With sharing we would do: | 
|  | // | 
|  | //     leal (%a,%i,4), %tmp | 
|  | //     cmp (%size, %tmp) | 
|  | //     ja _fail | 
|  | //     movl (%base, %tmp), %x | 
|  | //     leal 4(%a,%i,4), %tmp | 
|  | //     cmp (%size, %tmp) | 
|  | //     ja _fail | 
|  | //     movl (%base, %tmp), %y | 
|  | // | 
|  | // In the absence of sharing, we may find ourselves needing separate registers for | 
|  | // the innards of the index. That's relatively unlikely to be a thing due to other | 
|  | // optimizations that we already have, but it could happen | 
|  | // | 
|  | // Committing: The worst case is that there is a complicated graph of additions and | 
|  | // shifts, where each value has multiple uses. In that case, it's better to compute | 
|  | // each one separately from the others since that way, each calculation will use a | 
|  | // relatively nearby tmp as its input. That seems uncommon, but in those cases, | 
|  | // committing is a clear winner: it would result in a simple interference graph | 
|  | // while sharing would result in a complex one. Interference sucks because it means | 
|  | // more time in IRC and it means worse code. | 
|  | // | 
|  | // It's not super clear if any of these corner cases would ever arise. Committing | 
|  | // has the benefit that it's easier to reason about, and protects a much darker | 
|  | // corner case (more interference). | 
|  |  | 
|  | // Here are the things we want to match: | 
|  | // Add(Add(@x, @y), $c) | 
|  | // Add(Shl(@x, $c), @y) | 
|  | // Add(@x, Shl(@y, $c)) | 
|  | // Add(Add(@x, Shl(@y, $c)), $d) | 
|  | // Add(Add(Shl(@x, $c), @y), $d) | 
|  | // | 
|  | // Note that if you do Add(Shl(@x, $c), $d) then we will treat $d as a non-constant and | 
|  | // force it to materialize. You'll get something like this: | 
|  | // | 
|  | // movl $d, %tmp | 
|  | // leal (%tmp,%x,1<<c), %result | 
|  | // | 
|  | // Which is pretty close to optimal and has the nice effect of being able to handle large | 
|  | // constants gracefully. | 
|  |  | 
|  | Value* innerAdd = nullptr; | 
|  |  | 
|  | Value* value = m_value; | 
|  |  | 
|  | // We're going to consume Add(Add(_), $c). If we succeed at consuming it then we have these | 
|  | // patterns left (i.e. in the Add(_)): | 
|  | // | 
|  | // Add(Add(@x, @y), $c) | 
|  | // Add(Add(@x, Shl(@y, $c)), $d) | 
|  | // Add(Add(Shl(@x, $c), @y), $d) | 
|  | // | 
|  | // Otherwise we are looking at these patterns: | 
|  | // | 
|  | // Add(Shl(@x, $c), @y) | 
|  | // Add(@x, Shl(@y, $c)) | 
|  | // | 
|  | // This means that the subsequent code only has to worry about three patterns: | 
|  | // | 
|  | // Add(Shl(@x, $c), @y) | 
|  | // Add(@x, Shl(@y, $c)) | 
|  | // Add(@x, @y) (only if offset != 0) | 
|  | Value::OffsetType offset = 0; | 
|  | if (value->child(1)->isRepresentableAs<Value::OffsetType>() | 
|  | && canBeInternal(value->child(0)) | 
|  | && value->child(0)->opcode() == Add) { | 
|  | innerAdd = value->child(0); | 
|  | offset = static_cast<Value::OffsetType>(value->child(1)->asInt()); | 
|  | value = value->child(0); | 
|  | } | 
|  |  | 
|  | auto tryShl = [&] (Value* shl, Value* other) -> bool { | 
|  | std::optional<unsigned> scale = scaleForShl(shl, offset); | 
|  | if (!scale) | 
|  | return false; | 
|  | if (!canBeInternal(shl)) | 
|  | return false; | 
|  |  | 
|  | ASSERT(!m_locked.contains(shl->child(0))); | 
|  | ASSERT(!m_locked.contains(other)); | 
|  |  | 
|  | append(leaOpcode, indexArg(tmp(other), shl->child(0), *scale, offset), tmp(m_value)); | 
|  | commitInternal(innerAdd); | 
|  | commitInternal(shl); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryShl(value->child(0), value->child(1))) | 
|  | return true; | 
|  | if (tryShl(value->child(1), value->child(0))) | 
|  | return true; | 
|  |  | 
|  | // The remaining pattern is just: | 
|  | // Add(@x, @y) (only if offset != 0) | 
|  | if (!offset) | 
|  | return false; | 
|  | ASSERT(!m_locked.contains(value->child(0))); | 
|  | ASSERT(!m_locked.contains(value->child(1))); | 
|  | append(leaOpcode, indexArg(tmp(value->child(0)), value->child(1), 1, offset), tmp(m_value)); | 
|  | commitInternal(innerAdd); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void appendX86Div(B3::Opcode op) | 
|  | { | 
|  | using namespace Air; | 
|  | Air::Opcode convertToDoubleWord; | 
|  | Air::Opcode div; | 
|  | switch (m_value->type().kind()) { | 
|  | case Int32: | 
|  | convertToDoubleWord = X86ConvertToDoubleWord32; | 
|  | div = X86Div32; | 
|  | break; | 
|  | case Int64: | 
|  | convertToDoubleWord = X86ConvertToQuadWord64; | 
|  | div = X86Div64; | 
|  | break; | 
|  | default: | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ASSERT(op == Div || op == Mod); | 
|  | Tmp result = op == Div ? m_eax : m_edx; | 
|  |  | 
|  | append(Move, tmp(m_value->child(0)), m_eax); | 
|  | append(convertToDoubleWord, m_eax, m_edx); | 
|  | append(div, m_eax, m_edx, tmp(m_value->child(1))); | 
|  | append(Move, result, tmp(m_value)); | 
|  | } | 
|  |  | 
|  | void appendX86UDiv(B3::Opcode op) | 
|  | { | 
|  | using namespace Air; | 
|  | Air::Opcode div = m_value->type() == Int32 ? X86UDiv32 : X86UDiv64; | 
|  |  | 
|  | ASSERT(op == UDiv || op == UMod); | 
|  | Tmp result = op == UDiv ? m_eax : m_edx; | 
|  |  | 
|  | append(Move, tmp(m_value->child(0)), m_eax); | 
|  | append(Xor64, m_edx, m_edx); | 
|  | append(div, m_eax, m_edx, tmp(m_value->child(1))); | 
|  | append(Move, result, tmp(m_value)); | 
|  | } | 
|  |  | 
|  | Air::Opcode loadLinkOpcode(Width width, bool fence) | 
|  | { | 
|  | return fence ? OPCODE_FOR_WIDTH(LoadLinkAcq, width) : OPCODE_FOR_WIDTH(LoadLink, width); | 
|  | } | 
|  |  | 
|  | Air::Opcode storeCondOpcode(Width width, bool fence) | 
|  | { | 
|  | return fence ? OPCODE_FOR_WIDTH(StoreCondRel, width) : OPCODE_FOR_WIDTH(StoreCond, width); | 
|  | } | 
|  |  | 
|  | // This can emit code for the following patterns: | 
|  | // AtomicWeakCAS | 
|  | // BitXor(AtomicWeakCAS, 1) | 
|  | // AtomicStrongCAS | 
|  | // Equal(AtomicStrongCAS, expected) | 
|  | // NotEqual(AtomicStrongCAS, expected) | 
|  | // Branch(AtomicWeakCAS) | 
|  | // Branch(Equal(AtomicStrongCAS, expected)) | 
|  | // Branch(NotEqual(AtomicStrongCAS, expected)) | 
|  | // | 
|  | // It assumes that atomicValue points to the CAS, and m_value points to the instruction being | 
|  | // generated. It assumes that you've consumed everything that needs to be consumed. | 
|  | void appendCAS(Value* atomicValue, bool invert) | 
|  | { | 
|  | using namespace Air; | 
|  | AtomicValue* atomic = atomicValue->as<AtomicValue>(); | 
|  | RELEASE_ASSERT(atomic); | 
|  |  | 
|  | bool isBranch = m_value->opcode() == Branch; | 
|  | bool isStrong = atomic->opcode() == AtomicStrongCAS; | 
|  | bool returnsOldValue = m_value->opcode() == AtomicStrongCAS; | 
|  | bool hasFence = atomic->hasFence(); | 
|  |  | 
|  | Width width = atomic->accessWidth(); | 
|  | Arg address = addr(atomic); | 
|  |  | 
|  | Tmp valueResultTmp; | 
|  | Tmp boolResultTmp; | 
|  | if (returnsOldValue) { | 
|  | RELEASE_ASSERT(!invert); | 
|  | valueResultTmp = tmp(m_value); | 
|  | boolResultTmp = m_code.newTmp(GP); | 
|  | } else if (isBranch) { | 
|  | valueResultTmp = m_code.newTmp(GP); | 
|  | boolResultTmp = m_code.newTmp(GP); | 
|  | } else { | 
|  | valueResultTmp = m_code.newTmp(GP); | 
|  | boolResultTmp = tmp(m_value); | 
|  | } | 
|  |  | 
|  | Tmp successBoolResultTmp; | 
|  | if (isStrong && !isBranch) | 
|  | successBoolResultTmp = m_code.newTmp(GP); | 
|  | else | 
|  | successBoolResultTmp = boolResultTmp; | 
|  |  | 
|  | Tmp expectedValueTmp = tmp(atomic->child(0)); | 
|  | Tmp newValueTmp = tmp(atomic->child(1)); | 
|  |  | 
|  | Air::FrequentedBlock success; | 
|  | Air::FrequentedBlock failure; | 
|  | if (isBranch) { | 
|  | success = m_blockToBlock[m_block]->successor(invert); | 
|  | failure = m_blockToBlock[m_block]->successor(!invert); | 
|  | } | 
|  |  | 
|  | if (isX86()) { | 
|  | append(relaxedMoveForType(atomic->accessType()), immOrTmp(atomic->child(0)), m_eax); | 
|  | if (returnsOldValue) { | 
|  | appendTrapping(OPCODE_FOR_WIDTH(AtomicStrongCAS, width), m_eax, newValueTmp, address); | 
|  | append(relaxedMoveForType(atomic->accessType()), m_eax, valueResultTmp); | 
|  | } else if (isBranch) { | 
|  | appendTrapping(OPCODE_FOR_WIDTH(BranchAtomicStrongCAS, width), Arg::statusCond(MacroAssembler::Success), m_eax, newValueTmp, address); | 
|  | m_blockToBlock[m_block]->setSuccessors(success, failure); | 
|  | } else | 
|  | appendTrapping(OPCODE_FOR_WIDTH(AtomicStrongCAS, width), Arg::statusCond(invert ? MacroAssembler::Failure : MacroAssembler::Success), m_eax, tmp(atomic->child(1)), address, boolResultTmp); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (isARM64E()) { | 
|  | if (isBranch) { | 
|  | switch (width) { | 
|  | case Width8: | 
|  | append(Air::ZeroExtend8To32, expectedValueTmp, expectedValueTmp); | 
|  | break; | 
|  | case Width16: | 
|  | append(Air::ZeroExtend16To32, expectedValueTmp, expectedValueTmp); | 
|  | break; | 
|  | case Width32: | 
|  | case Width64: | 
|  | break; | 
|  | } | 
|  | } | 
|  | append(relaxedMoveForType(atomic->accessType()), expectedValueTmp, valueResultTmp); | 
|  | appendTrapping(OPCODE_FOR_WIDTH(AtomicStrongCAS, width), valueResultTmp, newValueTmp, address); | 
|  | if (returnsOldValue) | 
|  | return; | 
|  | if (isBranch) { | 
|  | switch (width) { | 
|  | case Width8: | 
|  | case Width16: | 
|  | case Width32: | 
|  | appendTrapping(Air::Branch32, Arg::relCond(MacroAssembler::Equal), valueResultTmp, expectedValueTmp); | 
|  | break; | 
|  | case Width64: | 
|  | appendTrapping(Air::Branch64, Arg::relCond(MacroAssembler::Equal), valueResultTmp, expectedValueTmp); | 
|  | break; | 
|  | } | 
|  | m_blockToBlock[m_block]->setSuccessors(success, failure); | 
|  | return; | 
|  | } | 
|  | append(OPCODE_FOR_CANONICAL_WIDTH(Compare, width), Arg::relCond(invert ? MacroAssembler::NotEqual : MacroAssembler::Equal), valueResultTmp, expectedValueTmp, boolResultTmp); | 
|  | return; | 
|  | } | 
|  |  | 
|  | RELEASE_ASSERT(isARM64()); | 
|  | // We wish to emit: | 
|  | // | 
|  | // Block #reloop: | 
|  | //     LoadLink | 
|  | //     Branch NotEqual | 
|  | //   Successors: Then:#fail, Else: #store | 
|  | // Block #store: | 
|  | //     StoreCond | 
|  | //     Xor $1, %result    <--- only if !invert | 
|  | //     Jump | 
|  | //   Successors: #done | 
|  | // Block #fail: | 
|  | //     Move $invert, %result | 
|  | //     Jump | 
|  | //   Successors: #done | 
|  | // Block #done: | 
|  |  | 
|  | Air::BasicBlock* reloopBlock = newBlock(); | 
|  | Air::BasicBlock* storeBlock = newBlock(); | 
|  | Air::BasicBlock* successBlock = nullptr; | 
|  | if (!isBranch && isStrong) | 
|  | successBlock = newBlock(); | 
|  | Air::BasicBlock* failBlock = nullptr; | 
|  | if (!isBranch) { | 
|  | failBlock = newBlock(); | 
|  | failure = failBlock; | 
|  | } | 
|  | Air::BasicBlock* strongFailBlock = nullptr; | 
|  | if (isStrong && hasFence) | 
|  | strongFailBlock = newBlock(); | 
|  | Air::FrequentedBlock comparisonFail = failure; | 
|  | Air::FrequentedBlock weakFail; | 
|  | if (isStrong) { | 
|  | if (hasFence) | 
|  | comparisonFail = strongFailBlock; | 
|  | weakFail = reloopBlock; | 
|  | } else | 
|  | weakFail = failure; | 
|  | Air::BasicBlock* beginBlock; | 
|  | Air::BasicBlock* doneBlock; | 
|  | splitBlock(beginBlock, doneBlock); | 
|  |  | 
|  | append(Air::Jump); | 
|  | beginBlock->setSuccessors(reloopBlock); | 
|  |  | 
|  | reloopBlock->append(trappingInst(m_value, loadLinkOpcode(width, atomic->hasFence()), m_value, address, valueResultTmp)); | 
|  | reloopBlock->append(OPCODE_FOR_CANONICAL_WIDTH(Branch, width), m_value, Arg::relCond(MacroAssembler::NotEqual), valueResultTmp, expectedValueTmp); | 
|  | reloopBlock->setSuccessors(comparisonFail, storeBlock); | 
|  |  | 
|  | storeBlock->append(trappingInst(m_value, storeCondOpcode(width, atomic->hasFence()), m_value, newValueTmp, address, successBoolResultTmp)); | 
|  | if (isBranch) { | 
|  | storeBlock->append(BranchTest32, m_value, Arg::resCond(MacroAssembler::Zero), boolResultTmp, boolResultTmp); | 
|  | storeBlock->setSuccessors(success, weakFail); | 
|  | doneBlock->successors().clear(); | 
|  | RELEASE_ASSERT(!doneBlock->size()); | 
|  | doneBlock->append(Air::Oops, m_value); | 
|  | } else { | 
|  | if (isStrong) { | 
|  | storeBlock->append(BranchTest32, m_value, Arg::resCond(MacroAssembler::Zero), successBoolResultTmp, successBoolResultTmp); | 
|  | storeBlock->setSuccessors(successBlock, reloopBlock); | 
|  |  | 
|  | successBlock->append(Move, m_value, Arg::imm(!invert), boolResultTmp); | 
|  | successBlock->append(Air::Jump, m_value); | 
|  | successBlock->setSuccessors(doneBlock); | 
|  | } else { | 
|  | if (!invert) | 
|  | storeBlock->append(Xor32, m_value, Arg::bitImm(1), boolResultTmp, boolResultTmp); | 
|  |  | 
|  | storeBlock->append(Air::Jump, m_value); | 
|  | storeBlock->setSuccessors(doneBlock); | 
|  | } | 
|  |  | 
|  | failBlock->append(Move, m_value, Arg::imm(invert), boolResultTmp); | 
|  | failBlock->append(Air::Jump, m_value); | 
|  | failBlock->setSuccessors(doneBlock); | 
|  | } | 
|  |  | 
|  | if (isStrong && hasFence) { | 
|  | Tmp tmp = m_code.newTmp(GP); | 
|  | strongFailBlock->append(trappingInst(m_value, storeCondOpcode(width, atomic->hasFence()), m_value, valueResultTmp, address, tmp)); | 
|  | strongFailBlock->append(BranchTest32, m_value, Arg::resCond(MacroAssembler::Zero), tmp, tmp); | 
|  | strongFailBlock->setSuccessors(failure, reloopBlock); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool appendVoidAtomic(Air::Opcode atomicOpcode) | 
|  | { | 
|  | if (m_useCounts.numUses(m_value)) | 
|  | return false; | 
|  |  | 
|  | Arg address = addr(m_value); | 
|  |  | 
|  | if (isValidForm(atomicOpcode, Arg::Imm, address.kind()) && imm(m_value->child(0))) { | 
|  | appendTrapping(atomicOpcode, imm(m_value->child(0)), address); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (isValidForm(atomicOpcode, Arg::Tmp, address.kind())) { | 
|  | appendTrapping(atomicOpcode, tmp(m_value->child(0)), address); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void appendGeneralAtomic(Air::Opcode opcode, Commutativity commutativity = NotCommutative) | 
|  | { | 
|  | using namespace Air; | 
|  | AtomicValue* atomic = m_value->as<AtomicValue>(); | 
|  |  | 
|  | Arg address = addr(m_value); | 
|  | Tmp oldValue = m_code.newTmp(GP); | 
|  | Tmp newValue = opcode == Air::Nop ? tmp(atomic->child(0)) : m_code.newTmp(GP); | 
|  |  | 
|  | // We need a CAS loop or a LL/SC loop. Using prepare/attempt jargon, we want: | 
|  | // | 
|  | // Block #reloop: | 
|  | //     Prepare | 
|  | //     opcode | 
|  | //     Attempt | 
|  | //   Successors: Then:#done, Else:#reloop | 
|  | // Block #done: | 
|  | //     Move oldValue, result | 
|  |  | 
|  | append(relaxedMoveForType(atomic->type()), oldValue, tmp(atomic)); | 
|  |  | 
|  | Air::BasicBlock* reloopBlock = newBlock(); | 
|  | Air::BasicBlock* beginBlock; | 
|  | Air::BasicBlock* doneBlock; | 
|  | splitBlock(beginBlock, doneBlock); | 
|  |  | 
|  | append(Air::Jump); | 
|  | beginBlock->setSuccessors(reloopBlock); | 
|  |  | 
|  | Air::Opcode prepareOpcode; | 
|  | if (isX86()) { | 
|  | switch (atomic->accessWidth()) { | 
|  | case Width8: | 
|  | prepareOpcode = Load8SignedExtendTo32; | 
|  | break; | 
|  | case Width16: | 
|  | prepareOpcode = Load16SignedExtendTo32; | 
|  | break; | 
|  | case Width32: | 
|  | prepareOpcode = Move32; | 
|  | break; | 
|  | case Width64: | 
|  | prepareOpcode = Move; | 
|  | break; | 
|  | } | 
|  | } else { | 
|  | RELEASE_ASSERT(isARM64()); | 
|  | prepareOpcode = loadLinkOpcode(atomic->accessWidth(), atomic->hasFence()); | 
|  | } | 
|  | reloopBlock->append(trappingInst(m_value, prepareOpcode, m_value, address, oldValue)); | 
|  |  | 
|  | if (opcode != Air::Nop) { | 
|  | // FIXME: If we ever have to write this again, we need to find a way to share the code with | 
|  | // appendBinOp. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=169249 | 
|  | if (commutativity == Commutative && imm(atomic->child(0)) && isValidForm(opcode, Arg::Imm, Arg::Tmp, Arg::Tmp)) | 
|  | reloopBlock->append(opcode, m_value, imm(atomic->child(0)), oldValue, newValue); | 
|  | else if (imm(atomic->child(0)) && isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Tmp)) | 
|  | reloopBlock->append(opcode, m_value, oldValue, imm(atomic->child(0)), newValue); | 
|  | else if (commutativity == Commutative && bitImm(atomic->child(0)) && isValidForm(opcode, Arg::BitImm, Arg::Tmp, Arg::Tmp)) | 
|  | reloopBlock->append(opcode, m_value, bitImm(atomic->child(0)), oldValue, newValue); | 
|  | else if (isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Tmp)) | 
|  | reloopBlock->append(opcode, m_value, oldValue, tmp(atomic->child(0)), newValue); | 
|  | else { | 
|  | reloopBlock->append(relaxedMoveForType(atomic->type()), m_value, oldValue, newValue); | 
|  | if (imm(atomic->child(0)) && isValidForm(opcode, Arg::Imm, Arg::Tmp)) | 
|  | reloopBlock->append(opcode, m_value, imm(atomic->child(0)), newValue); | 
|  | else | 
|  | reloopBlock->append(opcode, m_value, tmp(atomic->child(0)), newValue); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (isX86()) { | 
|  | Air::Opcode casOpcode = OPCODE_FOR_WIDTH(BranchAtomicStrongCAS, atomic->accessWidth()); | 
|  | reloopBlock->append(relaxedMoveForType(atomic->type()), m_value, oldValue, m_eax); | 
|  | reloopBlock->append(trappingInst(m_value, casOpcode, m_value, Arg::statusCond(MacroAssembler::Success), m_eax, newValue, address)); | 
|  | } else { | 
|  | RELEASE_ASSERT(isARM64()); | 
|  | Tmp boolResult = m_code.newTmp(GP); | 
|  | reloopBlock->append(trappingInst(m_value, storeCondOpcode(atomic->accessWidth(), atomic->hasFence()), m_value, newValue, address, boolResult)); | 
|  | reloopBlock->append(BranchTest32, m_value, Arg::resCond(MacroAssembler::Zero), boolResult, boolResult); | 
|  | } | 
|  | reloopBlock->setSuccessors(doneBlock, reloopBlock); | 
|  | } | 
|  |  | 
|  | bool tryAppendBitOpWithShift(Value* left, Value* right, Air::Opcode opcode) | 
|  | { | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Imm, Arg::Tmp)) | 
|  | return false; | 
|  | if (!canBeInternal(right) || !imm(right->child(1)) || right->child(1)->asInt() < 0) | 
|  | return false; | 
|  |  | 
|  | uint64_t amount = right->child(1)->asInt(); | 
|  | uint64_t datasize = m_value->type() == Int32 ? 32 : 64; | 
|  | if (amount >= datasize) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(left), tmp(right->child(0)), imm(right->child(1)), tmp(m_value)); | 
|  | commitInternal(right); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void lower() | 
|  | { | 
|  | using namespace Air; | 
|  | switch (m_value->opcode()) { | 
|  | case B3::Nop: { | 
|  | // Yes, we will totally see Nop's because some phases will replaceWithNop() instead of | 
|  | // properly removing things. | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Load: { | 
|  | MemoryValue* memory = m_value->as<MemoryValue>(); | 
|  | Air::Kind kind = moveForType(memory->type()); | 
|  | if (memory->hasFence()) { | 
|  | if (isX86()) | 
|  | kind.effects = true; | 
|  | else { | 
|  | switch (memory->type().kind()) { | 
|  | case Int32: | 
|  | kind = LoadAcq32; | 
|  | break; | 
|  | case Int64: | 
|  | kind = LoadAcq64; | 
|  | break; | 
|  | default: | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Pre-Index Canonical Form: | 
|  | //     address = Add(base, offset)    --->   Move %base %address | 
|  | //     memory = Load(base, offset)           MoveWithIncrement (%address, prefix(offset)) %memory | 
|  | // Post-Index Canonical Form: | 
|  | //     address = Add(base, offset)    --->   Move %base %address | 
|  | //     memory = Load(base, 0)                MoveWithIncrement (%address, postfix(offset)) %memory | 
|  | auto tryAppendIncrementAddress = [&] () -> bool { | 
|  | Air::Opcode opcode = tryOpcodeForType(MoveWithIncrement32, MoveWithIncrement64, memory->type()); | 
|  | if (!isValidForm(opcode, Arg::PreIndex, Arg::Tmp) || !m_index) | 
|  | return false; | 
|  | Value* address = m_block->at(m_index - 1); | 
|  | if (address->opcode() != Add || address->type() != Int64) | 
|  | return false; | 
|  |  | 
|  | Value* base1 = address->child(0); | 
|  | Value* base2 = memory->lastChild(); | 
|  | if (base1 != base2 || !address->child(1)->hasIntPtr()) | 
|  | return false; | 
|  | intptr_t offset = address->child(1)->asIntPtr(); | 
|  | Value::OffsetType smallOffset = static_cast<Value::OffsetType>(offset); | 
|  | if (smallOffset != offset || !Arg::isValidIncrementIndexForm(smallOffset)) | 
|  | return false; | 
|  | if (m_locked.contains(address) || m_locked.contains(base1)) | 
|  | return false; | 
|  |  | 
|  | Arg incrementArg = Arg(); | 
|  | if (memory->offset()) { | 
|  | if (smallOffset == memory->offset()) | 
|  | incrementArg = Arg::preIndex(tmp(address), smallOffset); | 
|  | } else | 
|  | incrementArg = Arg::postIndex(tmp(address), smallOffset); | 
|  |  | 
|  | if (incrementArg) { | 
|  | append(relaxedMoveForType(address->type()), tmp(address->child(0)), tmp(address)); | 
|  | append(opcode, incrementArg, tmp(memory)); | 
|  | m_locked.add(address); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | }; | 
|  |  | 
|  | if (tryAppendIncrementAddress()) | 
|  | return; | 
|  |  | 
|  | append(trappingInst(m_value, kind, m_value, addr(m_value), tmp(m_value))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Load8S: { | 
|  | Air::Kind kind = Load8SignedExtendTo32; | 
|  | if (m_value->as<MemoryValue>()->hasFence()) { | 
|  | if (isX86()) | 
|  | kind.effects = true; | 
|  | else | 
|  | kind = LoadAcq8SignedExtendTo32; | 
|  | } | 
|  | append(trappingInst(m_value, kind, m_value, addr(m_value), tmp(m_value))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Load8Z: { | 
|  | Air::Kind kind = Load8; | 
|  | if (m_value->as<MemoryValue>()->hasFence()) { | 
|  | if (isX86()) | 
|  | kind.effects = true; | 
|  | else | 
|  | kind = LoadAcq8; | 
|  | } | 
|  | append(trappingInst(m_value, kind, m_value, addr(m_value), tmp(m_value))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Load16S: { | 
|  | Air::Kind kind = Load16SignedExtendTo32; | 
|  | if (m_value->as<MemoryValue>()->hasFence()) { | 
|  | if (isX86()) | 
|  | kind.effects = true; | 
|  | else | 
|  | kind = LoadAcq16SignedExtendTo32; | 
|  | } | 
|  | append(trappingInst(m_value, kind, m_value, addr(m_value), tmp(m_value))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Load16Z: { | 
|  | Air::Kind kind = Load16; | 
|  | if (m_value->as<MemoryValue>()->hasFence()) { | 
|  | if (isX86()) | 
|  | kind.effects = true; | 
|  | else | 
|  | kind = LoadAcq16; | 
|  | } | 
|  | append(trappingInst(m_value, kind, m_value, addr(m_value), tmp(m_value))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Add: { | 
|  | if (tryAppendLea()) | 
|  | return; | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | auto tryMultiplyAdd = [&] () -> bool { | 
|  | if (imm(right) && !m_valueToTmp[right]) | 
|  | return false; | 
|  |  | 
|  | // MADD: d = n * m + a | 
|  | auto tryAppendMultiplyAdd = [&] (Value* left, Value* right) -> bool { | 
|  | if (left->opcode() != Mul || !canBeInternal(left) || m_locked.contains(right)) | 
|  | return false; | 
|  | Value* multiplyLeft = left->child(0); | 
|  | Value* multiplyRight = left->child(1); | 
|  | Air::Opcode airOpcode = tryOpcodeForType(MultiplyAdd32, MultiplyAdd64, m_value->type()); | 
|  | auto tryNewAirOpcode = [&] () -> Air::Opcode { | 
|  | if (airOpcode != MultiplyAdd64) | 
|  | return Air::Oops; | 
|  | // SMADDL: d = SExt32(n) * SExt32(m) + a | 
|  | if (isMergeableValue(multiplyLeft, SExt32) && isMergeableValue(multiplyRight, SExt32)) | 
|  | return MultiplyAddSignExtend32; | 
|  | // UMADDL: d = ZExt32(n) * ZExt32(m) + a | 
|  | if (isMergeableValue(multiplyLeft, ZExt32) && isMergeableValue(multiplyRight, ZExt32)) | 
|  | return MultiplyAddZeroExtend32; | 
|  | return Air::Oops; | 
|  | }; | 
|  |  | 
|  | Air::Opcode newAirOpcode = tryNewAirOpcode(); | 
|  | if (isValidForm(newAirOpcode, Arg::Tmp, Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | append(newAirOpcode, tmp(multiplyLeft->child(0)), tmp(multiplyRight->child(0)), tmp(right), tmp(m_value)); | 
|  | commitInternal(left); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (!isValidForm(airOpcode, Arg::Tmp, Arg::Tmp, Arg::Tmp, Arg::Tmp)) | 
|  | return false; | 
|  | if (m_locked.contains(multiplyLeft) || m_locked.contains(multiplyRight)) | 
|  | return false; | 
|  | append(airOpcode, tmp(multiplyLeft), tmp(multiplyRight), tmp(right), tmp(m_value)); | 
|  | commitInternal(left); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | return tryAppendMultiplyAdd(left, right) || tryAppendMultiplyAdd(right, left); | 
|  | }; | 
|  |  | 
|  | if (tryMultiplyAdd()) | 
|  | return; | 
|  |  | 
|  | // add-with-shift Pattern: left + (right ShiftType amount) | 
|  | auto tryAppendAddWithShift = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeBasedOnShiftKind(right->opcode(), | 
|  | AddLeftShift32, AddLeftShift64, | 
|  | AddRightShift32, AddRightShift64, | 
|  | AddUnsignedRightShift32, AddUnsignedRightShift64); | 
|  | return tryAppendBitOpWithShift(left, right, opcode); | 
|  | }; | 
|  |  | 
|  | if (tryAppendAddWithShift(left, right) || tryAppendAddWithShift(right, left)) | 
|  | return; | 
|  |  | 
|  | appendBinOp<Add32, Add64, AddDouble, AddFloat, Commutative>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Sub: { | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | auto tryAppendMultiplySub = [&] () -> bool { | 
|  | if (imm(right) && !m_valueToTmp[right]) | 
|  | return false; | 
|  |  | 
|  | // MSUB: d = a - n * m | 
|  | if (m_locked.contains(left) || right->opcode() != Mul || !canBeInternal(right)) | 
|  | return false; | 
|  | Value* multiplyLeft = right->child(0); | 
|  | Value* multiplyRight = right->child(1); | 
|  | Air::Opcode airOpcode = tryOpcodeForType(MultiplySub32, MultiplySub64, m_value->type()); | 
|  | auto tryNewAirOpcode = [&] () -> Air::Opcode { | 
|  | if (airOpcode != MultiplySub64) | 
|  | return Air::Oops; | 
|  | // SMSUBL: d = a - SExt32(n) * SExt32(m) | 
|  | if (isMergeableValue(multiplyLeft, SExt32) && isMergeableValue(multiplyRight, SExt32)) | 
|  | return MultiplySubSignExtend32; | 
|  | // UMSUBL: d = a - ZExt32(n) * ZExt32(m) | 
|  | if (isMergeableValue(multiplyLeft, ZExt32) && isMergeableValue(multiplyRight, ZExt32)) | 
|  | return MultiplySubZeroExtend32; | 
|  | return Air::Oops; | 
|  | }; | 
|  |  | 
|  | Air::Opcode newAirOpcode = tryNewAirOpcode(); | 
|  | if (isValidForm(newAirOpcode, Arg::Tmp, Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | append(newAirOpcode, tmp(multiplyLeft->child(0)), tmp(multiplyRight->child(0)), tmp(left), tmp(m_value)); | 
|  | commitInternal(right); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (!isValidForm(airOpcode, Arg::Tmp, Arg::Tmp, Arg::Tmp, Arg::Tmp)) | 
|  | return false; | 
|  | if (m_locked.contains(multiplyLeft) || m_locked.contains(multiplyRight)) | 
|  | return false; | 
|  | append(airOpcode, tmp(multiplyLeft), tmp(multiplyRight), tmp(left), tmp(m_value)); | 
|  | commitInternal(right); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendMultiplySub()) | 
|  | return; | 
|  |  | 
|  | // sub-with-shift Pattern: left - (right ShiftType amount) | 
|  | Air::Opcode opcode = opcodeBasedOnShiftKind(right->opcode(), | 
|  | SubLeftShift32, SubLeftShift64, | 
|  | SubRightShift32, SubRightShift64, | 
|  | SubUnsignedRightShift32, SubUnsignedRightShift64); | 
|  | if (tryAppendBitOpWithShift(left, right, opcode)) | 
|  | return; | 
|  |  | 
|  | appendBinOp<Sub32, Sub64, SubDouble, SubFloat>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Neg: { | 
|  | auto tryAppendMultiplyNeg = [&] () -> bool { | 
|  | // MNEG : d = -(n * m) | 
|  | if (m_value->child(0)->opcode() != Mul || !canBeInternal(m_value->child(0))) | 
|  | return false; | 
|  | Value* multiplyLeft = m_value->child(0)->child(0); | 
|  | Value* multiplyRight = m_value->child(0)->child(1); | 
|  | Air::Opcode airOpcode = tryOpcodeForType(MultiplyNeg32, MultiplyNeg64, m_value->type()); | 
|  | auto tryNewAirOpcode = [&] () -> Air::Opcode { | 
|  | if (airOpcode != MultiplyNeg64) | 
|  | return Air::Oops; | 
|  | // SMNEGL: d = -(SExt32(n) * SExt32(m)) | 
|  | if (isMergeableValue(multiplyLeft, SExt32) && isMergeableValue(multiplyRight, SExt32)) | 
|  | return MultiplyNegSignExtend32; | 
|  | // UMNEGL: d = -(ZExt32(n) * ZExt32(m)) | 
|  | if (isMergeableValue(multiplyLeft, ZExt32) && isMergeableValue(multiplyRight, ZExt32)) | 
|  | return MultiplyNegZeroExtend32; | 
|  | return Air::Oops; | 
|  | }; | 
|  |  | 
|  | Air::Opcode newAirOpcode = tryNewAirOpcode(); | 
|  | if (isValidForm(newAirOpcode, Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | append(newAirOpcode, tmp(multiplyLeft->child(0)), tmp(multiplyRight->child(0)), tmp(m_value)); | 
|  | commitInternal(m_value->child(0)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (!isValidForm(airOpcode, Arg::Tmp, Arg::Tmp, Arg::Tmp)) | 
|  | return false; | 
|  | if (m_locked.contains(multiplyLeft) || m_locked.contains(multiplyRight)) | 
|  | return false; | 
|  | append(airOpcode, tmp(multiplyLeft), tmp(multiplyRight), tmp(m_value)); | 
|  | commitInternal(m_value->child(0)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendMultiplyNeg()) | 
|  | return; | 
|  |  | 
|  | appendUnOp<Neg32, Neg64, NegateDouble, NegateFloat>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Mul: { | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | auto tryAppendMultiplyWithExtend = [&] () -> bool { | 
|  | auto tryAirOpcode = [&] () -> Air::Opcode { | 
|  | if (m_value->type() != Int64) | 
|  | return Air::Oops; | 
|  | // SMULL: d = SExt32(n) * SExt32(m) | 
|  | if (isMergeableValue(left, SExt32) && isMergeableValue(right, SExt32)) | 
|  | return MultiplySignExtend32; | 
|  | // UMULL: d = ZExt32(n) * ZExt32(m) | 
|  | if (isMergeableValue(left, ZExt32) && isMergeableValue(right, ZExt32)) | 
|  | return MultiplyZeroExtend32; | 
|  | return Air::Oops; | 
|  | }; | 
|  |  | 
|  | Air::Opcode opcode = tryAirOpcode(); | 
|  | if (isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | append(opcode, tmp(left->child(0)), tmp(right->child(0)), tmp(m_value)); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | }; | 
|  |  | 
|  | if (tryAppendMultiplyWithExtend()) | 
|  | return; | 
|  |  | 
|  | appendBinOp<Mul32, Mul64, MulDouble, MulFloat, Commutative>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Div: { | 
|  | if (m_value->isChill()) | 
|  | RELEASE_ASSERT(isARM64()); | 
|  | if (m_value->type().isInt() && isX86()) { | 
|  | appendX86Div(Div); | 
|  | return; | 
|  | } | 
|  | ASSERT(!isX86() || m_value->type().isFloat()); | 
|  |  | 
|  | appendBinOp<Div32, Div64, DivDouble, DivFloat>(m_value->child(0), m_value->child(1)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case UDiv: { | 
|  | if (m_value->type().isInt() && isX86()) { | 
|  | appendX86UDiv(UDiv); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ASSERT(!isX86() && !m_value->type().isFloat()); | 
|  |  | 
|  | appendBinOp<UDiv32, UDiv64, Air::Oops, Air::Oops>(m_value->child(0), m_value->child(1)); | 
|  | return; | 
|  |  | 
|  | } | 
|  |  | 
|  | case Mod: { | 
|  | RELEASE_ASSERT(isX86()); | 
|  | RELEASE_ASSERT(!m_value->isChill()); | 
|  | appendX86Div(Mod); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case UMod: { | 
|  | RELEASE_ASSERT(isX86()); | 
|  | appendX86UDiv(UMod); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case BitAnd: { | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | if (right->isInt(0xff)) { | 
|  | appendUnOp<ZeroExtend8To32, ZeroExtend8To32>(left); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (right->isInt(0xffff)) { | 
|  | appendUnOp<ZeroExtend16To32, ZeroExtend16To32>(left); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (right->isInt64(0xffffffff) || right->isInt32(0xffffffff)) { | 
|  | appendUnOp<Move32, Move32>(left); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // UBFX Pattern: dest = (src >> lsb) & mask | 
|  | // Where: mask = (1 << width) - 1 | 
|  | auto tryAppendUBFX = [&] () -> bool { | 
|  | Air::Opcode opcode = opcodeForType(ExtractUnsignedBitfield32, ExtractUnsignedBitfield64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Imm, Arg::Tmp)) | 
|  | return false; | 
|  | if (left->opcode() != ZShr) | 
|  | return false; | 
|  |  | 
|  | Value* srcValue = left->child(0); | 
|  | Value* lsbValue = left->child(1); | 
|  | if (m_locked.contains(srcValue) || !imm(lsbValue) || lsbValue->asInt() < 0 || !right->hasInt()) | 
|  | return false; | 
|  |  | 
|  | uint64_t lsb = lsbValue->asInt(); | 
|  | uint64_t mask = right->asInt(); | 
|  | if (!mask || mask & (mask + 1)) | 
|  | return false; | 
|  | uint64_t width = WTF::bitCount(mask); | 
|  | uint64_t datasize = opcode == ExtractUnsignedBitfield32 ? 32 : 64; | 
|  | if (lsb + width > datasize) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(srcValue), imm(lsbValue), imm(width), tmp(m_value)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendUBFX()) | 
|  | return; | 
|  |  | 
|  | // BIC Pattern: d = n & (m ^ -1) | 
|  | auto tryAppendBIC = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeForType(ClearBitsWithMask32, ClearBitsWithMask64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Tmp)) | 
|  | return false; | 
|  | if (right->opcode() != BitXor) | 
|  | return false; | 
|  |  | 
|  | Value* nValue = left; | 
|  | Value* mValue = right->child(0); | 
|  | Value* minusOne = right->child(1); | 
|  | if (m_locked.contains(nValue) || m_locked.contains(mValue) || !minusOne->hasInt() || !minusOne->isInt(-1)) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(nValue), tmp(mValue), tmp(m_value)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendBIC(left, right) || tryAppendBIC(right, left)) | 
|  | return; | 
|  |  | 
|  | // and-with-shift Pattern: left & (right ShiftType amount) | 
|  | auto tryAppendAndWithShift = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeBasedOnShiftKind(right->opcode(), | 
|  | AndLeftShift32, AndLeftShift64, | 
|  | AndRightShift32, AndRightShift64, | 
|  | AndUnsignedRightShift32, AndUnsignedRightShift64); | 
|  | return tryAppendBitOpWithShift(left, right, opcode); | 
|  | }; | 
|  |  | 
|  | if (tryAppendAndWithShift(left, right) || tryAppendAndWithShift(right, left)) | 
|  | return; | 
|  |  | 
|  | appendBinOp<And32, And64, AndDouble, AndFloat, Commutative>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case BitOr: { | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | // EXTR Pattern: d = ((n & mask) << highWidth) | (m >> lowWidth) | 
|  | // Where: highWidth = datasize - lowWidth | 
|  | //        mask = (1 << lowWidth) - 1 | 
|  | auto tryAppendEXTR = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeForType(ExtractRegister32, ExtractRegister64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Imm, Arg::Tmp)) | 
|  | return false; | 
|  | if (left->opcode() != Shl || left->child(0)->opcode() != BitAnd || right->opcode() != ZShr) | 
|  | return false; | 
|  |  | 
|  | Value* nValue = left->child(0)->child(0); | 
|  | Value* maskValue = left->child(0)->child(1); | 
|  | Value* highWidthValue = left->child(1); | 
|  | Value* mValue = right->child(0); | 
|  | Value* lowWidthValue = right->child(1); | 
|  | if (m_locked.contains(nValue) || m_locked.contains(mValue) || !maskValue->hasInt()) | 
|  | return false; | 
|  | if (!imm(highWidthValue) || highWidthValue->asInt() < 0) | 
|  | return false; | 
|  | if (!imm(lowWidthValue) || lowWidthValue->asInt() < 0) | 
|  | return false; | 
|  |  | 
|  | uint64_t mask = maskValue->asInt(); | 
|  | if (!mask || mask & (mask + 1)) | 
|  | return false; | 
|  | uint64_t maskBitCount = WTF::bitCount(mask); | 
|  | uint64_t highWidth = highWidthValue->asInt(); | 
|  | uint64_t lowWidth = lowWidthValue->asInt(); | 
|  | uint64_t datasize = opcode == ExtractRegister32 ? 32 : 64; | 
|  | if (lowWidth + highWidth != datasize || maskBitCount != lowWidth) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(nValue), tmp(mValue), imm(lowWidthValue), tmp(m_value)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendEXTR(left, right) || tryAppendEXTR(right, left)) | 
|  | return; | 
|  |  | 
|  | // BFI Pattern: d = ((n & mask1) << lsb) | (d & mask2) | 
|  | // Where: mask1 = ((1 << width) - 1) | 
|  | //        mask2 = ~(mask1 << lsb) | 
|  | auto tryAppendBFI = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeForType(InsertBitField32, InsertBitField64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Imm, Arg::Tmp)) | 
|  | return false; | 
|  | if (left->opcode() != Shl || right->opcode() != BitAnd || left->child(0)->opcode() != BitAnd) | 
|  | return false; | 
|  |  | 
|  | Value* nValue = left->child(0)->child(0); | 
|  | Value* maskValue1 = left->child(0)->child(1); | 
|  | Value* lsbValue = left->child(1); | 
|  | Value* dValue = right->child(0); | 
|  | Value* maskValue2 = right->child(1); | 
|  | if (m_locked.contains(nValue) || m_locked.contains(dValue)) | 
|  | return false; | 
|  | if (!maskValue1->hasInt() || !imm(lsbValue) || lsbValue->asInt() < 0 || !maskValue2->hasInt()) | 
|  | return false; | 
|  |  | 
|  | uint64_t lsb = lsbValue->asInt(); | 
|  | uint64_t mask1 = maskValue1->asInt(); | 
|  | if (!mask1 || mask1 & (mask1 + 1)) | 
|  | return false; | 
|  | uint64_t datasize = opcode == InsertBitField32 ? 32 : 64; | 
|  | uint64_t width = WTF::bitCount(mask1); | 
|  | if (lsb + width > datasize) | 
|  | return false; | 
|  |  | 
|  | uint64_t mask2 = maskValue2->asInt(); | 
|  |  | 
|  | auto isValidMask2 = [&] () -> bool { | 
|  | uint64_t mask = ((mask1 << lsb) ^ mask2); | 
|  | uint64_t lowerSet = 0xffffffff; | 
|  | uint64_t fullSet = 0xffffffffffffffff; | 
|  | return datasize == 32 ? !((mask & lowerSet) ^ lowerSet) : !(mask ^ fullSet); | 
|  | }; | 
|  |  | 
|  | if (!isValidMask2()) | 
|  | return false; | 
|  |  | 
|  | Tmp result = tmp(m_value); | 
|  | append(relaxedMoveForType(m_value->type()), tmp(dValue), result); | 
|  | append(opcode, tmp(nValue), imm(lsbValue), imm(width), result); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendBFI(left, right) || tryAppendBFI(right, left)) | 
|  | return; | 
|  |  | 
|  | // BFXIL Pattern: d = ((n >> lsb) & mask1) | (d & mask2) | 
|  | // Where: mask1 = ((1 << width) - 1) | 
|  | //        mask2 = ~mask1 | 
|  | auto tryAppendBFXIL = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeForType(ExtractInsertBitfieldAtLowEnd32, ExtractInsertBitfieldAtLowEnd64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Imm, Arg::Tmp)) | 
|  | return false; | 
|  | if (left->opcode() != BitAnd || left->child(0)->opcode() != ZShr || right->opcode() != BitAnd) | 
|  | return false; | 
|  |  | 
|  | Value* nValue = left->child(0)->child(0); | 
|  | Value* maskValue1 = left->child(1); | 
|  | Value* lsbValue = left->child(0)->child(1); | 
|  | Value* dValue = right->child(0); | 
|  | Value* maskValue2 = right->child(1); | 
|  | if (m_locked.contains(nValue) || m_locked.contains(dValue)) | 
|  | return false; | 
|  | if (!maskValue1->hasInt() || !imm(lsbValue) || lsbValue->asInt() < 0 || !maskValue2->hasInt()) | 
|  | return false; | 
|  |  | 
|  | uint64_t lsb = lsbValue->asInt(); | 
|  | uint64_t mask1 = maskValue1->asInt(); | 
|  | if (!mask1 || mask1 & (mask1 + 1)) | 
|  | return false; | 
|  | uint64_t width = WTF::bitCount(mask1); | 
|  | uint64_t datasize = opcode == ExtractInsertBitfieldAtLowEnd32 ? 32 : 64; | 
|  | if (lsb + width > datasize) | 
|  | return false; | 
|  | uint64_t mask2 = maskValue2->asInt(); | 
|  |  | 
|  | auto isValidMask2 = [&] () -> bool { | 
|  | uint64_t mask = mask1 ^ mask2; | 
|  | uint64_t lowerSet = 0xffffffff; | 
|  | uint64_t fullSet = 0xffffffffffffffff; | 
|  | return datasize == 32 ? !((mask & lowerSet) ^ lowerSet) : !(mask ^ fullSet); | 
|  | }; | 
|  |  | 
|  | if (!isValidMask2()) | 
|  | return false; | 
|  |  | 
|  | Tmp result = tmp(m_value); | 
|  | append(relaxedMoveForType(m_value->type()), tmp(dValue), result); | 
|  | append(opcode, tmp(nValue), imm(lsbValue), imm(width), result); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendBFXIL(left, right) || tryAppendBFXIL(right, left)) | 
|  | return; | 
|  |  | 
|  | // ORN Pattern: d = n | (m ^ -1) | 
|  | auto tryAppendORN = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeForType(OrNot32, OrNot64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Tmp)) | 
|  | return false; | 
|  | if (right->opcode() != BitXor) | 
|  | return false; | 
|  |  | 
|  | Value* nValue = left; | 
|  | Value* mValue = right->child(0); | 
|  | Value* minusOne = right->child(1); | 
|  | if (m_locked.contains(nValue) || m_locked.contains(mValue) || !minusOne->hasInt() || !minusOne->isInt(-1)) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(nValue), tmp(mValue), tmp(m_value)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendORN(left, right) || tryAppendORN(right, left)) | 
|  | return; | 
|  |  | 
|  | // orr-with-shift Pattern: left | (right ShiftType amount) | 
|  | auto tryAppendOrWithShift = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeBasedOnShiftKind(right->opcode(), | 
|  | OrLeftShift32, OrLeftShift64, | 
|  | OrRightShift32, OrRightShift64, | 
|  | OrUnsignedRightShift32, OrUnsignedRightShift64); | 
|  | return tryAppendBitOpWithShift(left, right, opcode); | 
|  | }; | 
|  |  | 
|  | if (tryAppendOrWithShift(left, right) || tryAppendOrWithShift(right, left)) | 
|  | return; | 
|  |  | 
|  | appendBinOp<Or32, Or64, OrDouble, OrFloat, Commutative>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case BitXor: { | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | // FIXME: If canBeInternal(child), we should generate this using the comparison path. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=152367 | 
|  |  | 
|  | if (right->isInt(-1)) { | 
|  | appendUnOp<Not32, Not64>(left); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // This pattern is super useful on both x86 and ARM64, since the inversion of the CAS result | 
|  | // can be done with zero cost on x86 (just flip the set from E to NE) and it's a progression | 
|  | // on ARM64 (since STX returns 0 on success, so ordinarily we have to flip it). | 
|  | if (right->isInt(1) && left->opcode() == AtomicWeakCAS && canBeInternal(left)) { | 
|  | commitInternal(left); | 
|  | appendCAS(left, true); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // EON Pattern: d = n ^ (m ^ -1) | 
|  | auto tryAppendEON = [&] (Value* left, Value* right) -> bool { | 
|  | if (right->opcode() != BitXor) | 
|  | return false; | 
|  | Value* nValue = left; | 
|  | Value* minusOne = right->child(1); | 
|  | if (m_locked.contains(nValue) || !minusOne->hasInt() || !minusOne->isInt(-1)) | 
|  | return false; | 
|  |  | 
|  | // eon-with-shift Pattern: d = n ^ ((m ShiftType amount) ^ -1) | 
|  | auto tryAppendEONWithShift = [&] (Value* shiftValue) -> bool { | 
|  | Air::Opcode opcode = opcodeBasedOnShiftKind(shiftValue->opcode(), | 
|  | XorNotLeftShift32, XorNotLeftShift64, | 
|  | XorNotRightShift32, XorNotRightShift64, | 
|  | XorNotUnsignedRightShift32, XorNotUnsignedRightShift64); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Imm, Arg::Tmp) || !canBeInternal(right)) | 
|  | return false; | 
|  | Value* mValue = shiftValue->child(0); | 
|  | Value* amountValue = shiftValue->child(1); | 
|  | if (!canBeInternal(shiftValue) || m_locked.contains(mValue) || !imm(amountValue) || amountValue->asInt() < 0) | 
|  | return false; | 
|  | uint64_t amount = amountValue->asInt(); | 
|  | uint64_t datasize = m_value->type() == Int32 ? 32 : 64; | 
|  | if (amount >= datasize) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(nValue), tmp(mValue), imm(amountValue), tmp(m_value)); | 
|  | commitInternal(right); | 
|  | commitInternal(shiftValue); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendEONWithShift(right->child(0))) | 
|  | return true; | 
|  |  | 
|  | Value* mValue = right->child(0); | 
|  | Air::Opcode opcode = opcodeForType(XorNot32, XorNot64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Tmp, Arg::Tmp) || m_locked.contains(mValue)) | 
|  | return false; | 
|  | append(opcode, tmp(nValue), tmp(mValue), tmp(m_value)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendEON(left, right) || tryAppendEON(right, left)) | 
|  | return; | 
|  |  | 
|  | // eor-with-shift Pattern: left ^ (right ShiftType amount) | 
|  | auto tryAppendXorWithShift = [&] (Value* left, Value* right) -> bool { | 
|  | Air::Opcode opcode = opcodeBasedOnShiftKind(right->opcode(), | 
|  | XorLeftShift32, XorLeftShift64, | 
|  | XorRightShift32, XorRightShift64, | 
|  | XorUnsignedRightShift32, XorUnsignedRightShift64); | 
|  | return tryAppendBitOpWithShift(left, right, opcode); | 
|  | }; | 
|  |  | 
|  | if (tryAppendXorWithShift(left, right) || tryAppendXorWithShift(right, left)) | 
|  | return; | 
|  |  | 
|  | appendBinOp<Xor32, Xor64, XorDouble, XorFloat, Commutative>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Depend: { | 
|  | RELEASE_ASSERT(isARM64()); | 
|  | appendUnOp<Depend32, Depend64>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Shl: { | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | // UBFIZ Pattern: d = (n & mask) << lsb | 
|  | // Where: mask = (1 << width) - 1 | 
|  | auto tryAppendUBFIZ = [&] () -> bool { | 
|  | Air::Opcode opcode = opcodeForType(InsertUnsignedBitfieldInZero32, InsertUnsignedBitfieldInZero64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Imm, Arg::Tmp)) | 
|  | return false; | 
|  | if (left->opcode() != BitAnd) | 
|  | return false; | 
|  |  | 
|  | Value* nValue = left->child(0); | 
|  | Value* maskValue = left->child(1); | 
|  | if (m_locked.contains(nValue) || !maskValue->hasInt() || !imm(right) || right->asInt() < 0) | 
|  | return false; | 
|  |  | 
|  | uint64_t lsb = right->asInt(); | 
|  | uint64_t mask = maskValue->asInt(); | 
|  | if (!mask || mask & (mask + 1)) | 
|  | return false; | 
|  | uint64_t width = WTF::bitCount(mask); | 
|  | uint64_t datasize = opcode == InsertUnsignedBitfieldInZero32 ? 32 : 64; | 
|  | if (lsb + width > datasize) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(nValue), imm(right), imm(width), tmp(m_value)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendUBFIZ()) | 
|  | return; | 
|  |  | 
|  | // SBFIZ Pattern: d = ((src << amount) >> amount) << lsb | 
|  | // where: amount = datasize - width | 
|  | auto tryAppendSBFIZ = [&] () -> bool { | 
|  | Air::Opcode opcode = opcodeForType(InsertSignedBitfieldInZero32, InsertSignedBitfieldInZero64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Imm, Arg::Tmp)) | 
|  | return false; | 
|  | if (left->opcode() != SShr || left->child(0)->opcode() != Shl) | 
|  | return false; | 
|  |  | 
|  | Value* srcValue = left->child(0)->child(0); | 
|  | Value* amount1Value = left->child(0)->child(1); | 
|  | Value* amount2Value = left->child(1); | 
|  | Value* lsbValue = right; | 
|  | if (m_locked.contains(srcValue)) | 
|  | return false; | 
|  | if (!imm(amount1Value) || !imm(amount2Value) || !imm(lsbValue)) | 
|  | return false; | 
|  | if (amount1Value->asInt() < 0 || amount2Value->asInt() < 0 || lsbValue->asInt() < 0) | 
|  | return false; | 
|  |  | 
|  | uint64_t amount1 = amount1Value->asInt(); | 
|  | uint64_t amount2 = amount2Value->asInt(); | 
|  | uint64_t lsb = lsbValue->asInt(); | 
|  | uint64_t datasize = opcode == InsertSignedBitfieldInZero32 ? 32 : 64; | 
|  | uint64_t width = datasize - amount1; | 
|  | if (amount1 != amount2 || !width || lsb + width > datasize) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(srcValue), imm(lsbValue), imm(width), tmp(m_value)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendSBFIZ()) | 
|  | return; | 
|  |  | 
|  | if (right->isInt32(1)) { | 
|  | appendBinOp<Add32, Add64, AddDouble, AddFloat, Commutative>(left, left); | 
|  | return; | 
|  | } | 
|  |  | 
|  | appendShift<Lshift32, Lshift64>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case SShr: { | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | // SBFX Pattern: ((src >> lsb) << amount) >> amount | 
|  | // Where: amount = datasize - width | 
|  | auto tryAppendSBFX = [&] () -> bool { | 
|  | Air::Opcode opcode = opcodeForType(ExtractSignedBitfield32, ExtractSignedBitfield64, m_value->type()); | 
|  | if (!isValidForm(opcode, Arg::Tmp, Arg::Imm, Arg::Imm, Arg::Tmp)) | 
|  | return false; | 
|  | if (left->opcode() != Shl || (left->child(0)->opcode() != ZShr && left->child(0)->opcode() != SShr)) | 
|  | return false; | 
|  |  | 
|  | Value* srcValue = left->child(0)->child(0); | 
|  | Value* lsbValue = left->child(0)->child(1); | 
|  | Value* amount1Value = left->child(1); | 
|  | Value* amount2Value = right; | 
|  | if (m_locked.contains(srcValue)) | 
|  | return false; | 
|  | if (!imm(lsbValue) || !imm(amount1Value) || !imm(amount2Value)) | 
|  | return false; | 
|  | if (lsbValue->asInt() < 0 || amount1Value->asInt() < 0 || amount2Value->asInt() < 0) | 
|  | return false; | 
|  |  | 
|  | uint64_t amount1 = amount1Value->asInt(); | 
|  | uint64_t amount2 = amount2Value->asInt(); | 
|  | uint64_t lsb = lsbValue->asInt(); | 
|  | uint64_t datasize = opcode == ExtractSignedBitfield32 ? 32 : 64; | 
|  | uint64_t width = datasize - amount1; | 
|  | if (amount1 != amount2 || !width || lsb + width > datasize) | 
|  | return false; | 
|  |  | 
|  | append(opcode, tmp(srcValue), imm(lsbValue), imm(width), tmp(m_value)); | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if (tryAppendSBFX()) | 
|  | return; | 
|  |  | 
|  | appendShift<Rshift32, Rshift64>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case ZShr: { | 
|  | Value* left = m_value->child(0); | 
|  | Value* right = m_value->child(1); | 
|  |  | 
|  | appendShift<Urshift32, Urshift64>(left, right); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case RotR: { | 
|  | appendShift<RotateRight32, RotateRight64>(m_value->child(0), m_value->child(1)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case RotL: { | 
|  | appendShift<RotateLeft32, RotateLeft64>(m_value->child(0), m_value->child(1)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Clz: { | 
|  | appendUnOp<CountLeadingZeros32, CountLeadingZeros64>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Abs: { | 
|  | RELEASE_ASSERT_WITH_MESSAGE(!isX86(), "Abs is not supported natively on x86. It must be replaced before generation."); | 
|  | appendUnOp<Air::Oops, Air::Oops, AbsDouble, AbsFloat>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Ceil: { | 
|  | appendUnOp<Air::Oops, Air::Oops, CeilDouble, CeilFloat>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Floor: { | 
|  | appendUnOp<Air::Oops, Air::Oops, FloorDouble, FloorFloat>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Sqrt: { | 
|  | appendUnOp<Air::Oops, Air::Oops, SqrtDouble, SqrtFloat>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case BitwiseCast: { | 
|  | appendUnOp<Move32ToFloat, Move64ToDouble, MoveDoubleTo64, MoveFloatTo32>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Store: { | 
|  | Value* valueToStore = m_value->child(0); | 
|  | if (canBeInternal(valueToStore)) { | 
|  | bool matched = false; | 
|  | switch (valueToStore->opcode()) { | 
|  | case Add: | 
|  | matched = tryAppendStoreBinOp<Add32, Add64, Commutative>( | 
|  | valueToStore->child(0), valueToStore->child(1)); | 
|  | break; | 
|  | case Sub: | 
|  | if (valueToStore->child(0)->isInt(0)) { | 
|  | matched = tryAppendStoreUnOp<Neg32, Neg64>(valueToStore->child(1)); | 
|  | break; | 
|  | } | 
|  | matched = tryAppendStoreBinOp<Sub32, Sub64>( | 
|  | valueToStore->child(0), valueToStore->child(1)); | 
|  | break; | 
|  | case BitAnd: | 
|  | matched = tryAppendStoreBinOp<And32, And64, Commutative>( | 
|  | valueToStore->child(0), valueToStore->child(1)); | 
|  | break; | 
|  | case BitXor: | 
|  | if (valueToStore->child(1)->isInt(-1)) { | 
|  | matched = tryAppendStoreUnOp<Not32, Not64>(valueToStore->child(0)); | 
|  | break; | 
|  | } | 
|  | matched = tryAppendStoreBinOp<Xor32, Xor64, Commutative>( | 
|  | valueToStore->child(0), valueToStore->child(1)); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | if (matched) { | 
|  | commitInternal(valueToStore); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | appendStore(m_value, addr(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case B3::Store8: { | 
|  | Value* valueToStore = m_value->child(0); | 
|  | if (canBeInternal(valueToStore)) { | 
|  | bool matched = false; | 
|  | switch (valueToStore->opcode()) { | 
|  | case Add: | 
|  | matched = tryAppendStoreBinOp<Add8, Air::Oops, Commutative>( | 
|  | valueToStore->child(0), valueToStore->child(1)); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | if (matched) { | 
|  | commitInternal(valueToStore); | 
|  | return; | 
|  | } | 
|  | } | 
|  | appendStore(m_value, addr(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case B3::Store16: { | 
|  | Value* valueToStore = m_value->child(0); | 
|  | if (canBeInternal(valueToStore)) { | 
|  | bool matched = false; | 
|  | switch (valueToStore->opcode()) { | 
|  | case Add: | 
|  | matched = tryAppendStoreBinOp<Add16, Air::Oops, Commutative>( | 
|  | valueToStore->child(0), valueToStore->child(1)); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | if (matched) { | 
|  | commitInternal(valueToStore); | 
|  | return; | 
|  | } | 
|  | } | 
|  | appendStore(m_value, addr(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case WasmAddress: { | 
|  | WasmAddressValue* address = m_value->as<WasmAddressValue>(); | 
|  |  | 
|  | append(Add64, Arg(address->pinnedGPR()), tmp(m_value->child(0)), tmp(address)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Fence: { | 
|  | FenceValue* fence = m_value->as<FenceValue>(); | 
|  | if (!fence->write && !fence->read) | 
|  | return; | 
|  | if (!fence->write) { | 
|  | // A fence that reads but does not write is for protecting motion of stores. | 
|  | append(StoreFence); | 
|  | return; | 
|  | } | 
|  | if (!fence->read) { | 
|  | // A fence that writes but does not read is for protecting motion of loads. | 
|  | append(LoadFence); | 
|  | return; | 
|  | } | 
|  | append(MemoryFence); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Trunc: { | 
|  | ASSERT(tmp(m_value->child(0)) == tmp(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case SExt8: { | 
|  | appendUnOp<SignExtend8To32, Air::Oops>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case SExt16: { | 
|  | appendUnOp<SignExtend16To32, Air::Oops>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case ZExt32: { | 
|  | appendUnOp<Move32, Air::Oops>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case SExt32: { | 
|  | // FIXME: We should have support for movsbq/movswq | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=152232 | 
|  |  | 
|  | appendUnOp<SignExtend32ToPtr, Air::Oops>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case FloatToDouble: { | 
|  | appendUnOp<Air::Oops, Air::Oops, Air::Oops, ConvertFloatToDouble>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case DoubleToFloat: { | 
|  | appendUnOp<Air::Oops, Air::Oops, ConvertDoubleToFloat>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case ArgumentReg: { | 
|  | m_prologue.append(Inst( | 
|  | moveForType(m_value->type()), m_value, | 
|  | Tmp(m_value->as<ArgumentRegValue>()->argumentReg()), | 
|  | tmp(m_value))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Const32: | 
|  | case Const64: { | 
|  | if (imm(m_value)) | 
|  | append(Move, imm(m_value), tmp(m_value)); | 
|  | else | 
|  | append(Move, Arg::bigImm(m_value->asInt()), tmp(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case ConstDouble: | 
|  | case ConstFloat: { | 
|  | // We expect that the moveConstants() phase has run, and any doubles referenced from | 
|  | // stackmaps get fused. | 
|  | RELEASE_ASSERT(m_value->opcode() == ConstFloat || isIdentical(m_value->asDouble(), 0.0)); | 
|  | RELEASE_ASSERT(m_value->opcode() == ConstDouble || isIdentical(m_value->asFloat(), 0.0f)); | 
|  | append(MoveZeroToDouble, tmp(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case BottomTuple: { | 
|  | forEachImmOrTmp(m_value, [&] (Arg tmp, Type type, unsigned) { | 
|  | switch (type.kind()) { | 
|  | case Void: | 
|  | case Tuple: | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | break; | 
|  | case Int32: | 
|  | case Int64: | 
|  | ASSERT(Arg::isValidImmForm(0)); | 
|  | append(Move, imm(static_cast<int64_t>(0)), tmp.tmp()); | 
|  | break; | 
|  | case Float: | 
|  | case Double: | 
|  | append(MoveZeroToDouble, tmp.tmp()); | 
|  | break; | 
|  | } | 
|  | }); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case FramePointer: { | 
|  | ASSERT(tmp(m_value) == Tmp(GPRInfo::callFrameRegister)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case SlotBase: { | 
|  | append( | 
|  | pointerType() == Int64 ? Lea64 : Lea32, | 
|  | Arg::stack(m_value->as<SlotBaseValue>()->slot()), | 
|  | tmp(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Equal: | 
|  | case NotEqual: { | 
|  | // FIXME: Teach this to match patterns that arise from subwidth CAS. The CAS's result has to | 
|  | // be either zero- or sign-extended, and the value it's compared to should also be zero- or | 
|  | // sign-extended in a matching way. It's not super clear that this is very profitable. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=169250 | 
|  | if (m_value->child(0)->opcode() == AtomicStrongCAS | 
|  | && m_value->child(0)->as<AtomicValue>()->isCanonicalWidth() | 
|  | && m_value->child(0)->child(0) == m_value->child(1) | 
|  | && canBeInternal(m_value->child(0))) { | 
|  | ASSERT(!m_locked.contains(m_value->child(0)->child(1))); | 
|  | ASSERT(!m_locked.contains(m_value->child(1))); | 
|  |  | 
|  | commitInternal(m_value->child(0)); | 
|  | appendCAS(m_value->child(0), m_value->opcode() == NotEqual); | 
|  | return; | 
|  | } | 
|  |  | 
|  | m_insts.last().append(createCompare(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case LessThan: | 
|  | case GreaterThan: | 
|  | case LessEqual: | 
|  | case GreaterEqual: | 
|  | case Above: | 
|  | case Below: | 
|  | case AboveEqual: | 
|  | case BelowEqual: | 
|  | case EqualOrUnordered: { | 
|  | m_insts.last().append(createCompare(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Select: { | 
|  | MoveConditionallyConfig config; | 
|  | if (m_value->type().isInt()) { | 
|  | config.moveConditionally32 = MoveConditionally32; | 
|  | config.moveConditionally64 = MoveConditionally64; | 
|  | config.moveConditionallyTest32 = MoveConditionallyTest32; | 
|  | config.moveConditionallyTest64 = MoveConditionallyTest64; | 
|  | config.moveConditionallyDouble = MoveConditionallyDouble; | 
|  | config.moveConditionallyFloat = MoveConditionallyFloat; | 
|  | } else { | 
|  | // FIXME: it's not obvious that these are particularly efficient. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=169251 | 
|  | config.moveConditionally32 = MoveDoubleConditionally32; | 
|  | config.moveConditionally64 = MoveDoubleConditionally64; | 
|  | config.moveConditionallyTest32 = MoveDoubleConditionallyTest32; | 
|  | config.moveConditionallyTest64 = MoveDoubleConditionallyTest64; | 
|  | config.moveConditionallyDouble = MoveDoubleConditionallyDouble; | 
|  | config.moveConditionallyFloat = MoveDoubleConditionallyFloat; | 
|  | } | 
|  |  | 
|  | m_insts.last().append(createSelect(config)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case IToD: { | 
|  | appendUnOp<ConvertInt32ToDouble, ConvertInt64ToDouble>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case IToF: { | 
|  | appendUnOp<ConvertInt32ToFloat, ConvertInt64ToFloat>(m_value->child(0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case B3::CCall: { | 
|  | CCallValue* cCall = m_value->as<CCallValue>(); | 
|  |  | 
|  | Inst inst(m_isRare ? Air::ColdCCall : Air::CCall, cCall); | 
|  |  | 
|  | // We have a ton of flexibility regarding the callee argument, but currently, we don't | 
|  | // use it yet. It gets weird for reasons: | 
|  | // 1) We probably will never take advantage of this. We don't have C calls to locations | 
|  | //    loaded from addresses. We have JS calls like that, but those use Patchpoints. | 
|  | // 2) On X86_64 we still don't support call with BaseIndex. | 
|  | // 3) On non-X86, we don't natively support any kind of loading from address. | 
|  | // 4) We don't have an isValidForm() for the CCallSpecial so we have no smart way to | 
|  | //    decide. | 
|  | // FIXME: https://bugs.webkit.org/show_bug.cgi?id=151052 | 
|  | inst.args.append(tmp(cCall->child(0))); | 
|  |  | 
|  | if (cCall->type() != Void) | 
|  | inst.args.append(tmp(cCall)); | 
|  |  | 
|  | for (unsigned i = 1; i < cCall->numChildren(); ++i) | 
|  | inst.args.append(immOrTmp(cCall->child(i))); | 
|  |  | 
|  | m_insts.last().append(WTFMove(inst)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Patchpoint: { | 
|  | PatchpointValue* patchpointValue = m_value->as<PatchpointValue>(); | 
|  | ensureSpecial(m_patchpointSpecial); | 
|  |  | 
|  | Inst inst(Patch, patchpointValue, Arg::special(m_patchpointSpecial)); | 
|  |  | 
|  | Vector<Inst> after; | 
|  | auto generateResultOperand = [&] (Type type, ValueRep rep, Tmp tmp) { | 
|  | switch (rep.kind()) { | 
|  | case ValueRep::WarmAny: | 
|  | case ValueRep::ColdAny: | 
|  | case ValueRep::LateColdAny: | 
|  | case ValueRep::SomeRegister: | 
|  | case ValueRep::SomeEarlyRegister: | 
|  | case ValueRep::SomeLateRegister: | 
|  | inst.args.append(tmp); | 
|  | return; | 
|  | case ValueRep::Register: { | 
|  | Tmp reg = Tmp(rep.reg()); | 
|  | inst.args.append(reg); | 
|  | after.append(Inst(relaxedMoveForType(type), m_value, reg, tmp)); | 
|  | return; | 
|  | } | 
|  | case ValueRep::StackArgument: { | 
|  | Arg arg = Arg::callArg(rep.offsetFromSP()); | 
|  | inst.args.append(arg); | 
|  | after.append(Inst(moveForType(type), m_value, arg, tmp)); | 
|  | return; | 
|  | } | 
|  | default: | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | return; | 
|  | } | 
|  | }; | 
|  |  | 
|  | if (patchpointValue->type() != Void) { | 
|  | forEachImmOrTmp(patchpointValue, [&] (Arg arg, Type type, unsigned index) { | 
|  | generateResultOperand(type, patchpointValue->resultConstraints[index], arg.tmp()); | 
|  | }); | 
|  | } | 
|  |  | 
|  | fillStackmap(inst, patchpointValue, 0); | 
|  | for (auto& constraint : patchpointValue->resultConstraints) { | 
|  | if (constraint.isReg()) | 
|  | patchpointValue->lateClobbered().clear(constraint.reg()); | 
|  | } | 
|  |  | 
|  | for (unsigned i = patchpointValue->numGPScratchRegisters; i--;) | 
|  | inst.args.append(m_code.newTmp(GP)); | 
|  | for (unsigned i = patchpointValue->numFPScratchRegisters; i--;) | 
|  | inst.args.append(m_code.newTmp(FP)); | 
|  |  | 
|  | m_insts.last().append(WTFMove(inst)); | 
|  | m_insts.last().appendVector(after); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Extract: { | 
|  | Value* tupleValue = m_value->child(0); | 
|  | unsigned index = m_value->as<ExtractValue>()->index(); | 
|  |  | 
|  | const auto& tmps = tmpsForTuple(tupleValue); | 
|  | append(relaxedMoveForType(m_value->type()), tmps[index], tmp(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case CheckAdd: | 
|  | case CheckSub: | 
|  | case CheckMul: { | 
|  | CheckValue* checkValue = m_value->as<CheckValue>(); | 
|  |  | 
|  | Value* left = checkValue->child(0); | 
|  | Value* right = checkValue->child(1); | 
|  |  | 
|  | Tmp result = tmp(m_value); | 
|  |  | 
|  | // Handle checked negation. | 
|  | if (checkValue->opcode() == CheckSub && left->isInt(0)) { | 
|  | append(Move, tmp(right), result); | 
|  |  | 
|  | Air::Opcode opcode = | 
|  | opcodeForType(BranchNeg32, BranchNeg64, checkValue->type()); | 
|  | CheckSpecial* special = ensureCheckSpecial(opcode, 2); | 
|  |  | 
|  | Inst inst(Patch, checkValue, Arg::special(special)); | 
|  | inst.args.append(Arg::resCond(MacroAssembler::Overflow)); | 
|  | inst.args.append(result); | 
|  |  | 
|  | fillStackmap(inst, checkValue, 2); | 
|  |  | 
|  | m_insts.last().append(WTFMove(inst)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | Air::Opcode opcode = Air::Oops; | 
|  | Commutativity commutativity = NotCommutative; | 
|  | StackmapSpecial::RoleMode stackmapRole = StackmapSpecial::SameAsRep; | 
|  | switch (m_value->opcode()) { | 
|  | case CheckAdd: | 
|  | opcode = opcodeForType(BranchAdd32, BranchAdd64, m_value->type()); | 
|  | stackmapRole = StackmapSpecial::ForceLateUseUnlessRecoverable; | 
|  | commutativity = Commutative; | 
|  | break; | 
|  | case CheckSub: | 
|  | opcode = opcodeForType(BranchSub32, BranchSub64, m_value->type()); | 
|  | break; | 
|  | case CheckMul: | 
|  | opcode = opcodeForType(BranchMul32, BranchMul64, checkValue->type()); | 
|  | stackmapRole = StackmapSpecial::ForceLateUse; | 
|  | break; | 
|  | default: | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | // FIXME: It would be great to fuse Loads into these. We currently don't do it because the | 
|  | // rule for stackmaps is that all addresses are just stack addresses. Maybe we could relax | 
|  | // this rule here. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=151228 | 
|  |  | 
|  | Vector<Arg, 2> sources; | 
|  | if (imm(right) && isValidForm(opcode, Arg::ResCond, Arg::Tmp, Arg::Imm, Arg::Tmp)) { | 
|  | sources.append(tmp(left)); | 
|  | sources.append(imm(right)); | 
|  | } else if (imm(right) && isValidForm(opcode, Arg::ResCond, Arg::Imm, Arg::Tmp)) { | 
|  | sources.append(imm(right)); | 
|  | append(Move, tmp(left), result); | 
|  | } else if (isValidForm(opcode, Arg::ResCond, Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | sources.append(tmp(left)); | 
|  | sources.append(tmp(right)); | 
|  | }  else if (isValidForm(opcode, Arg::ResCond, Arg::Tmp, Arg::Tmp)) { | 
|  | if (commutativity == Commutative && preferRightForResult(left, right)) { | 
|  | sources.append(tmp(left)); | 
|  | append(Move, tmp(right), result); | 
|  | } else { | 
|  | sources.append(tmp(right)); | 
|  | append(Move, tmp(left), result); | 
|  | } | 
|  | } else if (isValidForm(opcode, Arg::ResCond, Arg::Tmp, Arg::Tmp, Arg::Tmp, Arg::Tmp, Arg::Tmp)) { | 
|  | sources.append(tmp(left)); | 
|  | sources.append(tmp(right)); | 
|  | sources.append(m_code.newTmp(m_value->resultBank())); | 
|  | sources.append(m_code.newTmp(m_value->resultBank())); | 
|  | } | 
|  |  | 
|  | // There is a really hilarious case that arises when we do BranchAdd32(%x, %x). We won't emit | 
|  | // such code, but the coalescing in our register allocator also does copy propagation, so | 
|  | // although we emit: | 
|  | // | 
|  | //     Move %tmp1, %tmp2 | 
|  | //     BranchAdd32 %tmp1, %tmp2 | 
|  | // | 
|  | // The register allocator may turn this into: | 
|  | // | 
|  | //     BranchAdd32 %rax, %rax | 
|  | // | 
|  | // Currently we handle this by ensuring that even this kind of addition can be undone. We can | 
|  | // undo it by using the carry flag. It's tempting to get rid of that code and just "fix" this | 
|  | // here by forcing LateUse on the stackmap. If we did that unconditionally, we'd lose a lot of | 
|  | // performance. So it's tempting to do it only if left == right. But that creates an awkward | 
|  | // constraint on Air: it means that Air would not be allowed to do any copy propagation. | 
|  | // Notice that the %rax,%rax situation happened after Air copy-propagated the Move we are | 
|  | // emitting. We know that copy-propagating over that Move causes add-to-self. But what if we | 
|  | // emit something like a Move - or even do other kinds of copy-propagation on tmp's - | 
|  | // somewhere else in this code. The add-to-self situation may only emerge after some other Air | 
|  | // optimizations remove other Move's or identity-like operations. That's why we don't use | 
|  | // LateUse here to take care of add-to-self. | 
|  |  | 
|  | CheckSpecial* special = ensureCheckSpecial(opcode, 2 + sources.size(), stackmapRole); | 
|  |  | 
|  | Inst inst(Patch, checkValue, Arg::special(special)); | 
|  |  | 
|  | inst.args.append(Arg::resCond(MacroAssembler::Overflow)); | 
|  |  | 
|  | inst.args.appendVector(sources); | 
|  | inst.args.append(result); | 
|  |  | 
|  | fillStackmap(inst, checkValue, 2); | 
|  |  | 
|  | m_insts.last().append(WTFMove(inst)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Check: { | 
|  | Inst branch = createBranch(m_value->child(0)); | 
|  |  | 
|  | CheckSpecial* special = ensureCheckSpecial(branch); | 
|  |  | 
|  | CheckValue* checkValue = m_value->as<CheckValue>(); | 
|  |  | 
|  | Inst inst(Patch, checkValue, Arg::special(special)); | 
|  | inst.args.appendVector(branch.args); | 
|  |  | 
|  | fillStackmap(inst, checkValue, 1); | 
|  |  | 
|  | m_insts.last().append(WTFMove(inst)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case B3::WasmBoundsCheck: { | 
|  | WasmBoundsCheckValue* value = m_value->as<WasmBoundsCheckValue>(); | 
|  |  | 
|  | Value* ptr = value->child(0); | 
|  | Tmp pointer = tmp(ptr); | 
|  |  | 
|  | Arg ptrPlusImm = m_code.newTmp(GP); | 
|  | append(Inst(Move32, value, pointer, ptrPlusImm)); | 
|  | if (value->offset()) { | 
|  | if (imm(value->offset())) | 
|  | append(Add64, imm(value->offset()), ptrPlusImm); | 
|  | else { | 
|  | Arg bigImm = m_code.newTmp(GP); | 
|  | append(Move, Arg::bigImm(value->offset()), bigImm); | 
|  | append(Add64, bigImm, ptrPlusImm); | 
|  | } | 
|  | } | 
|  |  | 
|  | Arg limit; | 
|  | switch (value->boundsType()) { | 
|  | case WasmBoundsCheckValue::Type::Pinned: | 
|  | limit = Arg(value->bounds().pinnedSize); | 
|  | break; | 
|  |  | 
|  | case WasmBoundsCheckValue::Type::Maximum: | 
|  | limit = m_code.newTmp(GP); | 
|  | if (imm(value->bounds().maximum)) | 
|  | append(Move, imm(value->bounds().maximum), limit); | 
|  | else | 
|  | append(Move, Arg::bigImm(value->bounds().maximum), limit); | 
|  | break; | 
|  | } | 
|  |  | 
|  | append(Inst(Air::WasmBoundsCheck, value, ptrPlusImm, limit)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Upsilon: { | 
|  | Value* value = m_value->child(0); | 
|  | Value* phi = m_value->as<UpsilonValue>()->phi(); | 
|  | if (value->type().isNumeric()) { | 
|  | append(relaxedMoveForType(value->type()), immOrTmp(value), m_phiToTmp[phi]); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const Vector<Type>& tuple = m_procedure.tupleForType(value->type()); | 
|  | const auto& valueTmps = tmpsForTuple(value); | 
|  | const auto& phiTmps = m_tuplePhiToTmps.find(phi)->value; | 
|  | ASSERT(valueTmps.size() == phiTmps.size()); | 
|  | for (unsigned i = 0; i < valueTmps.size(); ++i) | 
|  | append(relaxedMoveForType(tuple[i]), valueTmps[i], phiTmps[i]); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Phi: { | 
|  | // Snapshot the value of the Phi. It may change under us because you could do: | 
|  | // a = Phi() | 
|  | // Upsilon(@x, ^a) | 
|  | // @a => this should get the value of the Phi before the Upsilon, i.e. not @x. | 
|  |  | 
|  | if (m_value->type().isNumeric()) { | 
|  | append(relaxedMoveForType(m_value->type()), m_phiToTmp[m_value], tmp(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const Vector<Type>& tuple = m_procedure.tupleForType(m_value->type()); | 
|  | const auto& valueTmps = tmpsForTuple(m_value); | 
|  | const auto& phiTmps = m_tuplePhiToTmps.find(m_value)->value; | 
|  | ASSERT(valueTmps.size() == phiTmps.size()); | 
|  | for (unsigned i = 0; i < valueTmps.size(); ++i) | 
|  | append(relaxedMoveForType(tuple[i]), phiTmps[i], valueTmps[i]); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Set: { | 
|  | Value* value = m_value->child(0); | 
|  | const Vector<Tmp>& variableTmps = m_variableToTmps.get(m_value->as<VariableValue>()->variable()); | 
|  | forEachImmOrTmp(value, [&] (Arg immOrTmp, Type type, unsigned index) { | 
|  | append(relaxedMoveForType(type), immOrTmp, variableTmps[index]); | 
|  | }); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Get: { | 
|  | // Snapshot the value of the Get. It may change under us because you could do: | 
|  | // a = Get(var) | 
|  | // Set(@x, var) | 
|  | // @a => this should get the value of the Get before the Set, i.e. not @x. | 
|  |  | 
|  | const Vector<Tmp>& variableTmps = m_variableToTmps.get(m_value->as<VariableValue>()->variable()); | 
|  | forEachImmOrTmp(m_value, [&] (Arg tmp, Type type, unsigned index) { | 
|  | append(relaxedMoveForType(type), variableTmps[index], tmp.tmp()); | 
|  | }); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Branch: { | 
|  | if (canBeInternal(m_value->child(0))) { | 
|  | Value* branchChild = m_value->child(0); | 
|  |  | 
|  | switch (branchChild->opcode()) { | 
|  | case BitAnd: { | 
|  | Value* andValue = branchChild->child(0); | 
|  | Value* andMask = branchChild->child(1); | 
|  | Air::Opcode opcode = opcodeForType(BranchTestBit32, BranchTestBit64, andValue->type()); | 
|  |  | 
|  | Value* testValue = nullptr; | 
|  | Value* bitOffset = nullptr; | 
|  | Value* internalNode = nullptr; | 
|  | Value* negationNode = nullptr; | 
|  | bool inverted = false; | 
|  |  | 
|  | // if (~(val >> x)&1) | 
|  | if (andMask->isInt(1) | 
|  | && andValue->opcode() == BitXor && (andValue->child(1)->isInt32(-1) || andValue->child(1)->isInt64(-1l)) | 
|  | && (andValue->child(0)->opcode() == SShr || andValue->child(0)->opcode() == ZShr)) { | 
|  |  | 
|  | negationNode = andValue; | 
|  | testValue = andValue->child(0)->child(0); | 
|  | bitOffset = andValue->child(0)->child(1); | 
|  | internalNode = andValue->child(0); | 
|  | inverted = !inverted; | 
|  | } | 
|  |  | 
|  | // Turn if ((val >> x)&1) -> Bt val x | 
|  | if (andMask->isInt(1) && (andValue->opcode() == SShr || andValue->opcode() == ZShr)) { | 
|  | testValue = andValue->child(0); | 
|  | bitOffset = andValue->child(1); | 
|  | internalNode = andValue; | 
|  | } | 
|  |  | 
|  | // Turn if (val & (1<<x)) -> Bt val x | 
|  | if ((andMask->opcode() == Shl) && andMask->child(0)->isInt(1)) { | 
|  | testValue = andValue; | 
|  | bitOffset = andMask->child(1); | 
|  | internalNode = andMask; | 
|  | } | 
|  |  | 
|  | // if (~val & (1<<x)) or if ((~val >> x)&1) | 
|  | if (!negationNode && testValue && testValue->opcode() == BitXor && (testValue->child(1)->isInt32(-1) || testValue->child(1)->isInt64(-1l))) { | 
|  | negationNode = testValue; | 
|  | testValue = testValue->child(0); | 
|  | inverted = !inverted; | 
|  | } | 
|  |  | 
|  | if (testValue && bitOffset) { | 
|  | for (auto& basePromise : Vector<ArgPromise>::from(loadPromise(testValue), tmpPromise(testValue))) { | 
|  | bool hasLoad = basePromise.kind() != Arg::Tmp; | 
|  | bool canMakeInternal = (hasLoad ? canBeInternal(testValue) : !m_locked.contains(testValue)) | 
|  | && (!negationNode || canBeInternal(negationNode)) | 
|  | && (!internalNode || canBeInternal(internalNode)); | 
|  |  | 
|  | if (basePromise && canMakeInternal) { | 
|  | if (bitOffset->hasInt() && isValidForm(opcode, Arg::ResCond, basePromise.kind(), Arg::Imm)) { | 
|  | commitInternal(branchChild); | 
|  | commitInternal(internalNode); | 
|  | if (hasLoad) | 
|  | commitInternal(testValue); | 
|  | commitInternal(negationNode); | 
|  | append(basePromise.inst(opcode, m_value, Arg::resCond(MacroAssembler::NonZero).inverted(inverted), basePromise.consume(*this), Arg::imm(bitOffset->asInt()))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!m_locked.contains(bitOffset) && isValidForm(opcode, Arg::ResCond, basePromise.kind(), Arg::Tmp)) { | 
|  | commitInternal(branchChild); | 
|  | commitInternal(internalNode); | 
|  | if (hasLoad) | 
|  | commitInternal(testValue); | 
|  | commitInternal(negationNode); | 
|  | append(basePromise.inst(opcode, m_value, Arg::resCond(MacroAssembler::NonZero).inverted(inverted), basePromise.consume(*this), tmp(bitOffset))); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  | case AtomicWeakCAS: | 
|  | commitInternal(branchChild); | 
|  | appendCAS(branchChild, false); | 
|  | return; | 
|  |  | 
|  | case AtomicStrongCAS: | 
|  | // A branch is a comparison to zero. | 
|  | // FIXME: Teach this to match patterns that arise from subwidth CAS. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=169250 | 
|  | if (branchChild->child(0)->isInt(0) | 
|  | && branchChild->as<AtomicValue>()->isCanonicalWidth()) { | 
|  | commitInternal(branchChild); | 
|  | appendCAS(branchChild, true); | 
|  | return; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case Equal: | 
|  | case NotEqual: | 
|  | // FIXME: Teach this to match patterns that arise from subwidth CAS. | 
|  | // https://bugs.webkit.org/show_bug.cgi?id=169250 | 
|  | if (branchChild->child(0)->opcode() == AtomicStrongCAS | 
|  | && branchChild->child(0)->as<AtomicValue>()->isCanonicalWidth() | 
|  | && canBeInternal(branchChild->child(0)) | 
|  | && branchChild->child(0)->child(0) == branchChild->child(1)) { | 
|  | commitInternal(branchChild); | 
|  | commitInternal(branchChild->child(0)); | 
|  | appendCAS(branchChild->child(0), branchChild->opcode() == NotEqual); | 
|  | return; | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | m_insts.last().append(createBranch(m_value->child(0))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case B3::Jump: { | 
|  | append(Air::Jump); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Identity: | 
|  | case Opaque: { | 
|  | ASSERT(tmp(m_value->child(0)) == tmp(m_value)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case Return: { | 
|  | if (!m_value->numChildren()) { | 
|  | append(RetVoid); | 
|  | return; | 
|  | } | 
|  | Value* value = m_value->child(0); | 
|  | Tmp returnValueGPR = Tmp(GPRInfo::returnValueGPR); | 
|  | Tmp returnValueFPR = Tmp(FPRInfo::returnValueFPR); | 
|  | switch (value->type().kind()) { | 
|  | case Void: | 
|  | case Tuple: | 
|  | // It's impossible for a void value to be used as a child. We use RetVoid | 
|  | // for void returns. | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | break; | 
|  | case Int32: | 
|  | append(Move, immOrTmp(value), returnValueGPR); | 
|  | append(Ret32, returnValueGPR); | 
|  | break; | 
|  | case Int64: | 
|  | append(Move, immOrTmp(value), returnValueGPR); | 
|  | append(Ret64, returnValueGPR); | 
|  | break; | 
|  | case Float: | 
|  | append(MoveFloat, tmp(value), returnValueFPR); | 
|  | append(RetFloat, returnValueFPR); | 
|  | break; | 
|  | case Double: | 
|  | append(MoveDouble, tmp(value), returnValueFPR); | 
|  | append(RetDouble, returnValueFPR); | 
|  | break; | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | case B3::Oops: { | 
|  | append(Air::Oops); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case B3::EntrySwitch: { | 
|  | append(Air::EntrySwitch); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case AtomicWeakCAS: | 
|  | case AtomicStrongCAS: { | 
|  | appendCAS(m_value, false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case AtomicXchgAdd: { | 
|  | AtomicValue* atomic = m_value->as<AtomicValue>(); | 
|  | if (appendVoidAtomic(OPCODE_FOR_WIDTH(AtomicAdd, atomic->accessWidth()))) | 
|  | return; | 
|  |  | 
|  | Arg address = addr(atomic); | 
|  | Air::Opcode opcode = OPCODE_FOR_WIDTH(AtomicXchgAdd, atomic->accessWidth()); | 
|  | if (isValidForm(opcode, Arg::Tmp, address.kind(), Arg::Tmp)) { | 
|  | appendTrapping(opcode, tmp(atomic->child(0)), address, tmp(atomic)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (isValidForm(opcode, Arg::Tmp, address.kind())) { | 
|  | append(relaxedMoveForType(atomic->type()), tmp(atomic->child(0)), tmp(atomic)); | 
|  | appendTrapping(opcode, tmp(atomic), address); | 
|  | return; | 
|  | } | 
|  |  | 
|  | appendGeneralAtomic(OPCODE_FOR_CANONICAL_WIDTH(Add, atomic->accessWidth()), Commutative); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case AtomicXchgSub: { | 
|  | AtomicValue* atomic = m_value->as<AtomicValue>(); | 
|  | if (appendVoidAtomic(OPCODE_FOR_WIDTH(AtomicSub, atomic->accessWidth()))) | 
|  | return; | 
|  |  | 
|  | appendGeneralAtomic(OPCODE_FOR_CANONICAL_WIDTH(Sub, atomic->accessWidth())); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case AtomicXchgAnd: { | 
|  | AtomicValue* atomic = m_value->as<AtomicValue>(); | 
|  | if (appendVoidAtomic(OPCODE_FOR_WIDTH(AtomicAnd, atomic->accessWidth()))) | 
|  | return; | 
|  |  | 
|  | if (isARM64E()) { | 
|  | Arg address = addr(atomic); | 
|  | Air::Opcode opcode = OPCODE_FOR_WIDTH(AtomicXchgClear, atomic->accessWidth()); | 
|  | if (isValidForm(opcode, Arg::Tmp, address.kind(), Arg::Tmp)) { | 
|  | append(OPCODE_FOR_CANONICAL_WIDTH(Not, atomic->accessWidth()), tmp(atomic->child(0)), tmp(atomic)); | 
|  | appendTrapping(opcode, tmp(atomic), address, tmp(atomic)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | appendGeneralAtomic(OPCODE_FOR_CANONICAL_WIDTH(And, atomic->accessWidth()), Commutative); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case AtomicXchgOr: { | 
|  | AtomicValue* atomic = m_value->as<AtomicValue>(); | 
|  | if (appendVoidAtomic(OPCODE_FOR_WIDTH(AtomicOr, atomic->accessWidth()))) | 
|  | return; | 
|  |  | 
|  | Arg address = addr(atomic); | 
|  | Air::Opcode opcode = OPCODE_FOR_WIDTH(AtomicXchgOr, atomic->accessWidth()); | 
|  | if (isValidForm(opcode, Arg::Tmp, address.kind(), Arg::Tmp)) { | 
|  | appendTrapping(opcode, tmp(atomic->child(0)), address, tmp(atomic)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | appendGeneralAtomic(OPCODE_FOR_CANONICAL_WIDTH(Or, atomic->accessWidth()), Commutative); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case AtomicXchgXor: { | 
|  | AtomicValue* atomic = m_value->as<AtomicValue>(); | 
|  | if (appendVoidAtomic(OPCODE_FOR_WIDTH(AtomicXor, atomic->accessWidth()))) | 
|  | return; | 
|  |  | 
|  | Arg address = addr(atomic); | 
|  | Air::Opcode opcode = OPCODE_FOR_WIDTH(AtomicXchgXor, atomic->accessWidth()); | 
|  | if (isValidForm(opcode, Arg::Tmp, address.kind(), Arg::Tmp)) { | 
|  | appendTrapping(opcode, tmp(atomic->child(0)), address, tmp(atomic)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | appendGeneralAtomic(OPCODE_FOR_CANONICAL_WIDTH(Xor, atomic->accessWidth()), Commutative); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case AtomicXchg: { | 
|  | AtomicValue* atomic = m_value->as<AtomicValue>(); | 
|  |  | 
|  | Arg address = addr(atomic); | 
|  | Air::Opcode opcode = OPCODE_FOR_WIDTH(AtomicXchg, atomic->accessWidth()); | 
|  | if (isValidForm(opcode, Arg::Tmp, address.kind(), Arg::Tmp)) { | 
|  | appendTrapping(opcode, tmp(atomic->child(0)), address, tmp(atomic)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (isValidForm(opcode, Arg::Tmp, address.kind())) { | 
|  | append(relaxedMoveForType(atomic->type()), tmp(atomic->child(0)), tmp(atomic)); | 
|  | appendTrapping(opcode, tmp(atomic), address); | 
|  | return; | 
|  | } | 
|  |  | 
|  | appendGeneralAtomic(Air::Nop); | 
|  | return; | 
|  | } | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | dataLog("FATAL: could not lower ", deepDump(m_procedure, m_value), "\n"); | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | } | 
|  |  | 
|  | IndexSet<Value*> m_locked; // These are values that will have no Tmp in Air. | 
|  | IndexMap<Value*, Tmp> m_valueToTmp; // These are values that must have a Tmp in Air. We say that a Value* with a non-null Tmp is "pinned". | 
|  | IndexMap<Value*, Tmp> m_phiToTmp; // Each Phi gets its own Tmp. | 
|  | HashMap<Value*, Vector<Tmp>> m_tupleValueToTmps; // This is the same as m_valueToTmp for Values that are Tuples. | 
|  | HashMap<Value*, Vector<Tmp>> m_tuplePhiToTmps; // This is the same as m_phiToTmp for Phis that are Tuples. | 
|  | IndexMap<B3::BasicBlock*, Air::BasicBlock*> m_blockToBlock; | 
|  | HashMap<Variable*, Vector<Tmp>> m_variableToTmps; | 
|  |  | 
|  | UseCounts m_useCounts; | 
|  | PhiChildren m_phiChildren; | 
|  | BlockWorklist m_fastWorklist; | 
|  | Dominators& m_dominators; | 
|  |  | 
|  | Vector<Vector<Inst, 4>> m_insts; | 
|  | Vector<Inst> m_prologue; | 
|  |  | 
|  | B3::BasicBlock* m_block; | 
|  | bool m_isRare; | 
|  | unsigned m_index; | 
|  | Value* m_value; | 
|  |  | 
|  | PatchpointSpecial* m_patchpointSpecial { nullptr }; | 
|  | HashMap<CheckSpecial::Key, CheckSpecial*> m_checkSpecials; | 
|  |  | 
|  | Procedure& m_procedure; | 
|  | Code& m_code; | 
|  |  | 
|  | Air::BlockInsertionSet m_blockInsertionSet; | 
|  |  | 
|  | Tmp m_eax; | 
|  | Tmp m_ecx; | 
|  | Tmp m_edx; | 
|  | }; | 
|  |  | 
|  | } // anonymous namespace | 
|  |  | 
|  | void lowerToAir(Procedure& procedure) | 
|  | { | 
|  | PhaseScope phaseScope(procedure, "lowerToAir"); | 
|  | LowerToAir lowerToAir(procedure); | 
|  | lowerToAir.run(); | 
|  | } | 
|  |  | 
|  | } } // namespace JSC::B3 | 
|  |  | 
|  | #if !ASSERT_ENABLED | 
|  | IGNORE_RETURN_TYPE_WARNINGS_END | 
|  | #endif | 
|  |  | 
|  | #endif // ENABLE(B3_JIT) |