| /* |
| * Copyright (C) 2015-2020 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 "B3LowerMacros.h" |
| |
| #if ENABLE(B3_JIT) |
| |
| #include "AllowMacroScratchRegisterUsage.h" |
| #include "B3AtomicValue.h" |
| #include "B3BasicBlockInlines.h" |
| #include "B3BlockInsertionSet.h" |
| #include "B3CCallValue.h" |
| #include "B3CaseCollectionInlines.h" |
| #include "B3CheckValue.h" |
| #include "B3ConstPtrValue.h" |
| #include "B3FenceValue.h" |
| #include "B3InsertionSetInlines.h" |
| #include "B3MemoryValueInlines.h" |
| #include "B3PatchpointValue.h" |
| #include "B3PhaseScope.h" |
| #include "B3StackmapGenerationParams.h" |
| #include "B3SwitchValue.h" |
| #include "B3UpsilonValue.h" |
| #include "B3UseCounts.h" |
| #include "B3ValueInlines.h" |
| #include "B3WasmRefTypeCheckValue.h" |
| #include "B3WasmStructGetValue.h" |
| #include "B3WasmStructNewValue.h" |
| #include "B3WasmStructSetValue.h" |
| #include "CCallHelpers.h" |
| #include "GPRInfo.h" |
| #include "JSCJSValueInlines.h" |
| #include "JSCell.h" |
| #include "JSObject.h" |
| #include "JSWebAssemblyStruct.h" |
| #include "LinkBuffer.h" |
| #include "MarkedSpace.h" |
| #include "WasmExceptionType.h" |
| #include "WasmFaultSignalHandler.h" |
| #include "WasmOperations.h" |
| #include "WasmThunks.h" |
| #include "WasmTypeDefinition.h" |
| #include "WebAssemblyFunctionBase.h" |
| #include "WebAssemblyGCStructure.h" |
| #include <cmath> |
| #include <numeric> |
| #include <wtf/BitVector.h> |
| |
| WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN |
| |
| namespace JSC { namespace B3 { |
| |
| namespace { |
| |
| class LowerMacros { |
| public: |
| LowerMacros(Procedure& proc) |
| : m_proc(proc) |
| , m_blockInsertionSet(proc) |
| , m_insertionSet(proc) |
| , m_useCounts(proc) |
| { |
| } |
| |
| bool run() |
| { |
| RELEASE_ASSERT(!m_proc.hasQuirks()); |
| |
| for (BasicBlock* block : m_proc) { |
| m_block = block; |
| processCurrentBlock(); |
| } |
| m_changed |= m_blockInsertionSet.execute(); |
| if (m_changed) { |
| m_proc.resetReachability(); |
| m_proc.invalidateCFG(); |
| } |
| |
| // This indicates that we've |
| m_proc.setHasQuirks(true); |
| |
| return m_changed; |
| } |
| |
| private: |
| template <class Fn> |
| void replaceWithBinaryCall(Fn &&function) |
| { |
| Value* functionAddress = m_insertionSet.insert<ConstPtrValue>(m_index, m_origin, tagCFunction<OperationPtrTag>(function)); |
| Value* result = m_insertionSet.insert<CCallValue>(m_index, m_value->type(), m_origin, Effects::none(), functionAddress, m_value->child(0), m_value->child(1)); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| } |
| |
| void processCurrentBlock() |
| { |
| for (m_index = 0; m_index < m_block->size(); ++m_index) { |
| m_value = m_block->at(m_index); |
| m_origin = m_value->origin(); |
| switch (m_value->opcode()) { |
| case Mod: { |
| if (m_value->isChill()) { |
| if (isARM64()) { |
| BasicBlock* before = m_blockInsertionSet.splitForward(m_block, m_index, &m_insertionSet); |
| BasicBlock* zeroDenCase = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* normalModCase = m_blockInsertionSet.insertBefore(m_block); |
| |
| before->replaceLastWithNew<Value>(m_proc, Branch, m_origin, m_value->child(1)); |
| before->setSuccessors( |
| FrequentedBlock(normalModCase, FrequencyClass::Normal), |
| FrequentedBlock(zeroDenCase, FrequencyClass::Rare)); |
| |
| Value* divResult = normalModCase->appendNew<Value>(m_proc, chill(Div), m_origin, m_value->child(0), m_value->child(1)); |
| Value* multipliedBack = normalModCase->appendNew<Value>(m_proc, Mul, m_origin, divResult, m_value->child(1)); |
| Value* result = normalModCase->appendNew<Value>(m_proc, Sub, m_origin, m_value->child(0), multipliedBack); |
| UpsilonValue* normalResult = normalModCase->appendNew<UpsilonValue>(m_proc, m_origin, result); |
| normalModCase->appendNew<Value>(m_proc, Jump, m_origin); |
| normalModCase->setSuccessors(FrequentedBlock(m_block)); |
| |
| UpsilonValue* zeroResult = zeroDenCase->appendNew<UpsilonValue>( |
| m_proc, m_origin, |
| zeroDenCase->appendIntConstant(m_proc, m_value, 0)); |
| zeroDenCase->appendNew<Value>(m_proc, Jump, m_origin); |
| zeroDenCase->setSuccessors(FrequentedBlock(m_block)); |
| |
| Value* phi = m_insertionSet.insert<Value>(m_index, Phi, m_value->type(), m_origin); |
| normalResult->setPhi(phi); |
| zeroResult->setPhi(phi); |
| m_value->replaceWithIdentity(phi); |
| before->updatePredecessorsAfter(); |
| m_changed = true; |
| } else |
| makeDivisionChill(Mod); |
| break; |
| } |
| |
| if (m_value->type() == Double) { |
| Value* functionAddress = m_insertionSet.insert<ConstPtrValue>(m_index, m_origin, tagCFunction<OperationPtrTag>(Math::fmodDouble)); |
| Value* result = m_insertionSet.insert<CCallValue>(m_index, Double, m_origin, |
| Effects::none(), |
| functionAddress, |
| m_value->child(0), |
| m_value->child(1)); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| } else if (m_value->type() == Float) { |
| Value* numeratorAsDouble = m_insertionSet.insert<Value>(m_index, FloatToDouble, m_origin, m_value->child(0)); |
| Value* denominatorAsDouble = m_insertionSet.insert<Value>(m_index, FloatToDouble, m_origin, m_value->child(1)); |
| Value* functionAddress = m_insertionSet.insert<ConstPtrValue>(m_index, m_origin, tagCFunction<OperationPtrTag>(Math::fmodDouble)); |
| Value* doubleMod = m_insertionSet.insert<CCallValue>(m_index, Double, m_origin, |
| Effects::none(), |
| functionAddress, |
| numeratorAsDouble, |
| denominatorAsDouble); |
| Value* result = m_insertionSet.insert<Value>(m_index, DoubleToFloat, m_origin, doubleMod); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| } else if constexpr (isARM_THUMB2()) { |
| if (m_value->type() == Int64) |
| replaceWithBinaryCall(Math::i64_rem_s); |
| else |
| replaceWithBinaryCall(Math::i32_rem_s); |
| } else if (isARM64()) { |
| Value* divResult = m_insertionSet.insert<Value>(m_index, chill(Div), m_origin, m_value->child(0), m_value->child(1)); |
| Value* multipliedBack = m_insertionSet.insert<Value>(m_index, Mul, m_origin, divResult, m_value->child(1)); |
| Value* result = m_insertionSet.insert<Value>(m_index, Sub, m_origin, m_value->child(0), multipliedBack); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| } |
| break; |
| } |
| |
| case UMod: { |
| if constexpr (isARM_THUMB2()) { |
| if (m_value->child(0)->type() == Int64) |
| replaceWithBinaryCall(Math::i64_rem_u); |
| else |
| replaceWithBinaryCall(Math::i32_rem_u); |
| break; |
| } |
| if (isARM64()) { |
| Value* divResult = m_insertionSet.insert<Value>(m_index, UDiv, m_origin, m_value->child(0), m_value->child(1)); |
| Value* multipliedBack = m_insertionSet.insert<Value>(m_index, Mul, m_origin, divResult, m_value->child(1)); |
| Value* result = m_insertionSet.insert<Value>(m_index, Sub, m_origin, m_value->child(0), multipliedBack); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| } |
| break; |
| } |
| |
| case UDiv: { |
| if constexpr (!isARM_THUMB2()) |
| break; |
| if (m_value->type() == Int64) |
| replaceWithBinaryCall(Math::i64_div_u); |
| else |
| replaceWithBinaryCall(Math::i32_div_u); |
| break; |
| } |
| case FMax: |
| case FMin: { |
| if (isX86() || isARM_THUMB2()) { |
| bool isMax = m_value->opcode() == FMax; |
| |
| Value* a = m_value->child(0); |
| Value* b = m_value->child(1); |
| |
| Value* isEqualValue = m_insertionSet.insert<Value>( |
| m_index, Equal, m_origin, a, b); |
| |
| BasicBlock* before = m_blockInsertionSet.splitForward(m_block, m_index, &m_insertionSet); |
| |
| BasicBlock* isEqual = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* notEqual = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* isLessThan = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* notLessThan = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* isGreaterThan = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* isNaN = m_blockInsertionSet.insertBefore(m_block); |
| |
| before->replaceLastWithNew<Value>(m_proc, Branch, m_origin, isEqualValue); |
| before->setSuccessors(FrequentedBlock(isEqual), FrequentedBlock(notEqual)); |
| |
| Value* lessThanValue = notEqual->appendNew<Value>(m_proc, LessThan, m_origin, a, b); |
| notEqual->appendNew<Value>(m_proc, Branch, m_origin, lessThanValue); |
| notEqual->setSuccessors(FrequentedBlock(isLessThan), FrequentedBlock(notLessThan)); |
| |
| Value* greaterThanValue = notLessThan->appendNew<Value>(m_proc, GreaterThan, m_origin, a, b); |
| notLessThan->appendNew<Value>(m_proc, Branch, m_origin, greaterThanValue); |
| notLessThan->setSuccessors(FrequentedBlock(isGreaterThan), FrequentedBlock(isNaN)); |
| |
| UpsilonValue* isLessThanResult = isLessThan->appendNew<UpsilonValue>( |
| m_proc, m_origin, isMax ? b : a); |
| isLessThan->appendNew<Value>(m_proc, Jump, m_origin); |
| isLessThan->setSuccessors(FrequentedBlock(m_block)); |
| |
| UpsilonValue* isGreaterThanResult = isGreaterThan->appendNew<UpsilonValue>( |
| m_proc, m_origin, isMax ? a : b); |
| isGreaterThan->appendNew<Value>(m_proc, Jump, m_origin); |
| isGreaterThan->setSuccessors(FrequentedBlock(m_block)); |
| |
| UpsilonValue* isEqualResult = isEqual->appendNew<UpsilonValue>( |
| m_proc, m_origin, isEqual->appendNew<Value>(m_proc, isMax ? BitAnd : BitOr, m_origin, a, b)); |
| isEqual->appendNew<Value>(m_proc, Jump, m_origin); |
| isEqual->setSuccessors(FrequentedBlock(m_block)); |
| |
| UpsilonValue* isNaNResult = isNaN->appendNew<UpsilonValue>( |
| m_proc, m_origin, isNaN->appendNew<Value>(m_proc, Add, m_origin, a, b)); |
| isNaN->appendNew<Value>(m_proc, Jump, m_origin); |
| isNaN->setSuccessors(FrequentedBlock(m_block)); |
| |
| Value* phi = m_insertionSet.insert<Value>( |
| m_index, Phi, m_value->type(), m_origin); |
| isLessThanResult->setPhi(phi); |
| isGreaterThanResult->setPhi(phi); |
| isEqualResult->setPhi(phi); |
| isNaNResult->setPhi(phi); |
| |
| m_value->replaceWithIdentity(phi); |
| before->updatePredecessorsAfter(); |
| m_changed = true; |
| } |
| break; |
| } |
| |
| case Div: { |
| if (m_value->isChill()) |
| makeDivisionChill(Div); |
| else if (isARM_THUMB2() && (m_value->type() == Int64 || m_value->type() == Int32)) { |
| BasicBlock* before = m_blockInsertionSet.splitForward(m_block, m_index); |
| before->replaceLastWithNew<Value>(m_proc, Nop, m_origin); |
| Value* result = callDivModHelper(before, Div, m_value->child(0), m_value->child(1)); |
| before->appendNew<Value>(m_proc, Jump, m_origin); |
| before->setSuccessors(FrequentedBlock(m_block)); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| } |
| break; |
| } |
| |
| case Switch: { |
| SwitchValue* switchValue = m_value->as<SwitchValue>(); |
| Vector<SwitchCase> cases; |
| for (SwitchCase switchCase : switchValue->cases(m_block)) |
| cases.append(switchCase); |
| std::ranges::sort(cases, { }, &SwitchCase::caseValue); |
| FrequentedBlock fallThrough = m_block->fallThrough(); |
| m_block->values().removeLast(); |
| recursivelyBuildSwitch(cases, fallThrough, 0, false, cases.size(), m_block); |
| m_proc.deleteValue(switchValue); |
| m_block->updatePredecessorsAfter(); |
| m_changed = true; |
| break; |
| } |
| |
| case Depend: { |
| if (isX86()) { |
| // Create a load-load fence. This codegens to nothing on X86. We use it to tell the |
| // compiler not to block load motion. |
| FenceValue* fence = m_insertionSet.insert<FenceValue>(m_index, m_origin); |
| fence->read = HeapRange(); |
| fence->write = HeapRange::top(); |
| |
| // Kill the Depend, which should unlock a bunch of code simplification. |
| m_value->replaceWithBottom(m_insertionSet, m_index); |
| |
| m_changed = true; |
| } |
| break; |
| } |
| |
| case AtomicWeakCAS: |
| case AtomicStrongCAS: { |
| AtomicValue* atomic = m_value->as<AtomicValue>(); |
| Width width = atomic->accessWidth(); |
| |
| if (isCanonicalWidth(width)) |
| break; |
| |
| Value* expectedValue = atomic->child(0); |
| |
| if (!isX86()) { |
| // On ARM, the load part of the CAS does a load with zero extension. Therefore, we need |
| // to zero-extend the input. |
| Value* maskedExpectedValue = m_insertionSet.insert<Value>( |
| m_index, BitAnd, m_origin, expectedValue, |
| m_insertionSet.insertIntConstant(m_index, expectedValue, mask(width))); |
| |
| atomic->child(0) = maskedExpectedValue; |
| m_changed = true; |
| } |
| |
| if (atomic->opcode() == AtomicStrongCAS) { |
| Value* newValue = m_insertionSet.insert<Value>( |
| m_index, signExtendOpcode(width), m_origin, |
| m_insertionSet.insertClone(m_index, atomic)); |
| |
| atomic->replaceWithIdentity(newValue); |
| m_changed = true; |
| } |
| |
| break; |
| } |
| |
| case AtomicXchgAdd: |
| case AtomicXchgAnd: |
| case AtomicXchgOr: |
| case AtomicXchgSub: |
| case AtomicXchgXor: |
| case AtomicXchg: { |
| // On X86, these may actually return garbage in the high bits. On ARM64, these sorta |
| // zero-extend their high bits, except that the high bits might get polluted by high |
| // bits in the operand. So, either way, we need to throw a sign-extend on these |
| // things. |
| |
| if (isX86()) { |
| if (m_value->opcode() == AtomicXchgSub && m_useCounts.numUses(m_value)) { |
| // On x86, xchgadd is better than xchgsub if it has any users. |
| m_value->setOpcodeUnsafely(AtomicXchgAdd); |
| m_value->child(0) = m_insertionSet.insert<Value>( |
| m_index, Neg, m_origin, m_value->child(0)); |
| } |
| |
| bool exempt = false; |
| switch (m_value->opcode()) { |
| case AtomicXchgAnd: |
| case AtomicXchgOr: |
| case AtomicXchgSub: |
| case AtomicXchgXor: |
| exempt = true; |
| break; |
| default: |
| break; |
| } |
| if (exempt) |
| break; |
| } |
| |
| if (isARM64_LSE()) { |
| if (m_value->opcode() == AtomicXchgSub) { |
| m_value->setOpcodeUnsafely(AtomicXchgAdd); |
| m_value->child(0) = m_insertionSet.insert<Value>( |
| m_index, Neg, m_origin, m_value->child(0)); |
| } |
| } |
| |
| AtomicValue* atomic = m_value->as<AtomicValue>(); |
| Width width = atomic->accessWidth(); |
| |
| if (isCanonicalWidth(width)) |
| break; |
| |
| Value* newValue = m_insertionSet.insert<Value>( |
| m_index, signExtendOpcode(width), m_origin, |
| m_insertionSet.insertClone(m_index, atomic)); |
| |
| atomic->replaceWithIdentity(newValue); |
| m_changed = true; |
| break; |
| } |
| |
| case Load8Z: |
| case Load16Z: { |
| if (isX86()) |
| break; |
| |
| MemoryValue* memory = m_value->as<MemoryValue>(); |
| if (!memory->hasFence()) |
| break; |
| |
| // Sub-width load-acq on ARM64 always sign extends. |
| Value* newLoad = m_insertionSet.insertClone(m_index, memory); |
| newLoad->setOpcodeUnsafely(memory->opcode() == Load8Z ? Load8S : Load16S); |
| |
| Value* newValue = m_insertionSet.insert<Value>( |
| m_index, BitAnd, m_origin, newLoad, |
| m_insertionSet.insertIntConstant( |
| m_index, m_origin, Int32, mask(memory->accessWidth()))); |
| |
| m_value->replaceWithIdentity(newValue); |
| m_changed = true; |
| break; |
| } |
| |
| case VectorPopcnt: { |
| if (!isX86()) |
| break; |
| ASSERT(m_value->as<SIMDValue>()->simdLane() == SIMDLane::i8x16); |
| |
| // x86_64 does not natively support vector lanewise popcount, so we emulate it using multiple |
| // masks. |
| |
| v128_t bottomNibbleConst; |
| v128_t popcntConst; |
| bottomNibbleConst.u64x2[0] = 0x0f0f0f0f0f0f0f0f; |
| bottomNibbleConst.u64x2[1] = 0x0f0f0f0f0f0f0f0f; |
| popcntConst.u64x2[0] = 0x0302020102010100; |
| popcntConst.u64x2[1] = 0x0403030203020201; |
| Value* bottomNibbleMask = m_insertionSet.insert<Const128Value>(m_index, m_origin, bottomNibbleConst); |
| Value* popcntMask = m_insertionSet.insert<Const128Value>(m_index, m_origin, popcntConst); |
| |
| Value* four = m_insertionSet.insert<Const32Value>(m_index, m_origin, 4); |
| Value* v = m_value->child(0); |
| Value* upper = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorAndnot, B3::V128, SIMDLane::v128, SIMDSignMode::None, v, bottomNibbleMask); |
| Value* lower = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorAnd, B3::V128, SIMDLane::v128, SIMDSignMode::None, v, bottomNibbleMask); |
| upper = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorShr, B3::V128, SIMDLane::i16x8, SIMDSignMode::Unsigned, upper, four); |
| lower = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorSwizzle, B3::V128, SIMDLane::i8x16, SIMDSignMode::None, popcntMask, lower); |
| upper = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorSwizzle, B3::V128, SIMDLane::i8x16, SIMDSignMode::None, popcntMask, upper); |
| Value* result = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorAdd, B3::V128, SIMDLane::i8x16, SIMDSignMode::None, upper, lower); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| break; |
| } |
| |
| case VectorNot: { |
| if (!isX86()) |
| break; |
| // x86_64 has no vector bitwise NOT instruction, so we expand vxv.not v into vxv.xor -1, v |
| // here to give B3/Air a chance to optimize out repeated usage of the mask. |
| v128_t mask; |
| mask.u64x2[0] = 0xffffffffffffffff; |
| mask.u64x2[1] = 0xffffffffffffffff; |
| Value* ones = m_insertionSet.insert<Const128Value>(m_index, m_origin, mask); |
| Value* result = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorXor, B3::V128, SIMDLane::v128, SIMDSignMode::None, ones, m_value->child(0)); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| break; |
| } |
| |
| case VectorNeg: { |
| if (!isX86()) |
| break; |
| // x86_64 has no vector negate instruction. For integer vectors, we can replicate negation by |
| // subtracting from zero. For floating-point vectors, we need to toggle the sign using packed |
| // XOR. |
| SIMDValue* value = m_value->as<SIMDValue>(); |
| switch (value->simdLane()) { |
| case SIMDLane::i8x16: |
| case SIMDLane::i16x8: |
| case SIMDLane::i32x4: |
| case SIMDLane::i64x2: { |
| Value* zero = m_insertionSet.insert<Const128Value>(m_index, m_origin, v128_t()); |
| Value* result = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorSub, B3::V128, value->simdInfo(), zero, m_value->child(0)); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| break; |
| } |
| case SIMDLane::f32x4: { |
| Value* topBit = m_insertionSet.insert<Const32Value>(m_index, m_origin, 0x80000000u); |
| Value* floatMask = m_insertionSet.insert<Value>(m_index, BitwiseCast, m_origin, topBit); |
| Value* vectorMask = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorSplat, B3::V128, SIMDLane::f32x4, SIMDSignMode::None, floatMask); |
| Value* result = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorXor, B3::V128, SIMDLane::v128, SIMDSignMode::None, m_value->child(0), vectorMask); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| break; |
| } |
| case SIMDLane::f64x2: { |
| Value* topBit = m_insertionSet.insert<Const64Value>(m_index, m_origin, 0x8000000000000000ull); |
| Value* doubleMask = m_insertionSet.insert<Value>(m_index, BitwiseCast, m_origin, topBit); |
| Value* vectorMask = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorSplat, B3::V128, SIMDLane::f64x2, SIMDSignMode::None, doubleMask); |
| Value* result = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorXor, B3::V128, SIMDLane::v128, SIMDSignMode::None, m_value->child(0), vectorMask); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| break; |
| } |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| break; |
| } |
| |
| case VectorNotEqual: |
| if (isX86()) |
| invertedComparisonByXor(VectorEqual, m_value->child(0), m_value->child(1)); |
| break; |
| case VectorAbove: |
| if (isX86()) |
| invertedComparisonByXor(VectorBelowOrEqual, m_value->child(0), m_value->child(1)); |
| break; |
| case VectorBelow: |
| if (isX86()) |
| invertedComparisonByXor(VectorAboveOrEqual, m_value->child(0), m_value->child(1)); |
| break; |
| case VectorGreaterThanOrEqual: |
| if (isX86() && m_value->as<SIMDValue>()->simdLane() == SIMDLane::i64x2) { |
| // Note: rhs and lhs are reversed here, we are semantically negating LessThan. GreaterThan is |
| // just better supported on AVX. |
| invertedComparisonByXor(VectorGreaterThan, m_value->child(1), m_value->child(0)); |
| } |
| break; |
| case VectorLessThanOrEqual: |
| if (isX86() && m_value->as<SIMDValue>()->simdLane() == SIMDLane::i64x2) |
| invertedComparisonByXor(VectorGreaterThan, m_value->child(0), m_value->child(1)); |
| break; |
| case VectorShr: |
| case VectorShl: { |
| if constexpr (!isARM64()) |
| break; |
| SIMDValue* value = m_value->as<SIMDValue>(); |
| SIMDLane lane = value->simdLane(); |
| |
| int32_t mask = (elementByteSize(lane) * CHAR_BIT) - 1; |
| Value* shiftAmount = m_insertionSet.insert<Value>(m_index, BitAnd, m_origin, value->child(1), m_insertionSet.insertIntConstant(m_index, m_origin, Int32, mask)); |
| if (value->opcode() == VectorShr) { |
| // ARM64 doesn't have a version of this instruction for right shift. Instead, if the input to |
| // left shift is negative, it's a right shift by the absolute value of that amount. |
| shiftAmount = m_insertionSet.insert<Value>(m_index, Neg, m_origin, shiftAmount); |
| } |
| Value* shiftVector = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorSplat, B3::V128, SIMDLane::i8x16, SIMDSignMode::None, shiftAmount); |
| Value* result = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorShiftByVector, B3::V128, value->simdInfo(), value->child(0), shiftVector); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| break; |
| } |
| |
| case WasmStructGet: { |
| WasmStructGetValue* structGet = m_value->as<WasmStructGetValue>(); |
| Value* structPtr = structGet->child(0); |
| SUPPRESS_UNCOUNTED_LOCAL const Wasm::StructType* structType = structGet->structType(); |
| Wasm::StructFieldCount fieldIndex = structGet->fieldIndex(); |
| auto fieldType = structType->field(fieldIndex).type; |
| bool canTrap = structGet->kind().traps(); |
| HeapRange range = structGet->range(); |
| Mutability mutability = structGet->mutability(); |
| |
| int32_t fieldOffset = JSWebAssemblyStruct::offsetOfData() + structType->offsetOfFieldInPayload(fieldIndex); |
| |
| auto wrapTrapping = [&](auto input) -> B3::Kind { |
| if (canTrap) |
| return trapping(input); |
| return input; |
| }; |
| |
| Value* result; |
| if (fieldType.is<Wasm::PackedType>()) { |
| switch (fieldType.as<Wasm::PackedType>()) { |
| case Wasm::PackedType::I8: |
| result = m_insertionSet.insert<MemoryValue>(m_index, wrapTrapping(Load8Z), Int32, m_origin, structPtr, fieldOffset, range); |
| break; |
| case Wasm::PackedType::I16: |
| result = m_insertionSet.insert<MemoryValue>(m_index, wrapTrapping(Load16Z), Int32, m_origin, structPtr, fieldOffset, range); |
| break; |
| } |
| } else { |
| ASSERT(fieldType.is<Wasm::Type>()); |
| auto unpacked = fieldType.unpacked(); |
| Type b3Type; |
| switch (unpacked.kind) { |
| case Wasm::TypeKind::I32: |
| b3Type = Int32; |
| break; |
| case Wasm::TypeKind::I64: |
| b3Type = Int64; |
| break; |
| case Wasm::TypeKind::F32: |
| b3Type = Float; |
| break; |
| case Wasm::TypeKind::F64: |
| b3Type = Double; |
| break; |
| case Wasm::TypeKind::V128: |
| b3Type = V128; |
| break; |
| default: |
| // Reference types are stored as Int64 (pointer-sized) |
| b3Type = Int64; |
| break; |
| } |
| result = m_insertionSet.insert<MemoryValue>(m_index, wrapTrapping(Load), b3Type, m_origin, structPtr, fieldOffset, range); |
| } |
| |
| result->as<MemoryValue>()->setReadsMutability(mutability); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| break; |
| } |
| |
| case WasmStructSet: { |
| WasmStructSetValue* structSet = m_value->as<WasmStructSetValue>(); |
| Value* structPtr = structSet->child(0); |
| Value* value = structSet->child(1); |
| SUPPRESS_UNCOUNTED_LOCAL const Wasm::StructType* structType = structSet->structType(); |
| Wasm::StructFieldCount fieldIndex = structSet->fieldIndex(); |
| auto fieldType = structType->field(fieldIndex).type; |
| bool canTrap = structSet->kind().traps(); |
| HeapRange range = structSet->range(); |
| |
| int32_t fieldOffset = JSWebAssemblyStruct::offsetOfData() + structType->offsetOfFieldInPayload(fieldIndex); |
| |
| auto wrapTrapping = [&](auto input) -> B3::Kind { |
| if (canTrap) |
| return trapping(input); |
| return input; |
| }; |
| |
| if (fieldType.is<Wasm::PackedType>()) { |
| switch (fieldType.as<Wasm::PackedType>()) { |
| case Wasm::PackedType::I8: |
| m_insertionSet.insert<MemoryValue>(m_index, wrapTrapping(Store8), m_origin, value, structPtr, fieldOffset, range); |
| break; |
| case Wasm::PackedType::I16: |
| m_insertionSet.insert<MemoryValue>(m_index, wrapTrapping(Store16), m_origin, value, structPtr, fieldOffset, range); |
| break; |
| } |
| } else |
| m_insertionSet.insert<MemoryValue>(m_index, wrapTrapping(Store), m_origin, value, structPtr, fieldOffset, range); |
| |
| m_value->replaceWithNop(); |
| m_changed = true; |
| break; |
| } |
| |
| case WasmStructNew: { |
| WasmStructNewValue* structNew = m_value->as<WasmStructNewValue>(); |
| Value* instance = structNew->instance(); |
| Value* structureID = structNew->structureID(); |
| SUPPRESS_UNCOUNTED_LOCAL const Wasm::StructType* structType = structNew->structType(); |
| uint32_t typeIndex = structNew->typeIndex(); |
| auto rtt = structNew->rtt(); |
| int32_t allocatorsBaseOffset = structNew->allocatorsBaseOffset(); |
| |
| size_t allocationSize = JSWebAssemblyStruct::allocationSize(structType->instancePayloadSize()); |
| |
| static_assert(!(MarkedSpace::sizeStep & (MarkedSpace::sizeStep - 1)), "MarkedSpace::sizeStep must be a power of two."); |
| unsigned stepShift = getLSBSet(MarkedSpace::sizeStep); |
| size_t sizeClass = (allocationSize + MarkedSpace::sizeStep - 1) >> stepShift; |
| bool useFastPath = (sizeClass <= (MarkedSpace::largeCutoff >> stepShift)); |
| |
| BasicBlock* before = m_blockInsertionSet.splitForward(m_block, m_index, &m_insertionSet); |
| BasicBlock* slowPath = m_blockInsertionSet.insertBefore(m_block); |
| |
| UpsilonValue* fastUpsilon = nullptr; |
| if (useFastPath) { |
| BasicBlock* fastPathContinuation = m_blockInsertionSet.insertBefore(m_block); |
| |
| // Replace the Jump added by splitForward with Nop so we can add our own control flow |
| before->replaceLastWithNew<Value>(m_proc, Nop, m_origin); |
| |
| // The Instance constructor initializes all the allocators on creation, thus it is never nullptr. |
| int32_t allocatorOffset = allocatorsBaseOffset + static_cast<int32_t>(sizeClass * sizeof(Allocator)); |
| Value* allocator = before->appendNew<MemoryValue>(m_proc, Load, pointerType(), m_origin, instance, allocatorOffset); |
| |
| PatchpointValue* patchpoint = before->appendNew<PatchpointValue>(m_proc, pointerType(), m_origin); |
| if (isARM64()) { |
| // emitAllocateWithNonNullAllocator uses the scratch registers on ARM. |
| patchpoint->clobber(RegisterSetBuilder::macroClobberedGPRs()); |
| } |
| patchpoint->effects.terminal = true; |
| patchpoint->appendSomeRegisterWithClobber(allocator); |
| patchpoint->numGPScratchRegisters++; |
| patchpoint->resultConstraints = { ValueRep::SomeEarlyRegister }; |
| |
| patchpoint->setGenerator([=](CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| CCallHelpers::JumpList jumpToSlowPath; |
| |
| GPRReg allocatorGPR = params[1].gpr(); |
| |
| // We use a patchpoint to emit the allocation path because whenever we mess with |
| // allocation paths, we already reason about them at the machine code level. We know |
| // exactly what instruction sequence we want. We're confident that no compiler |
| // optimization could make this code better. So, it's best to have the code in |
| // AssemblyHelpers::emitAllocate(). That way, the same optimized path is shared by |
| // all of the compiler tiers. |
| jit.emitAllocateWithNonNullAllocator( |
| params[0].gpr(), JITAllocator::variableNonNull(), allocatorGPR, params.gpScratch(0), |
| jumpToSlowPath, CCallHelpers::SlowAllocationResult::UndefinedBehavior); |
| |
| CCallHelpers::Jump jumpToSuccess; |
| if (!params.fallsThroughToSuccessor(0)) |
| jumpToSuccess = jit.jump(); |
| |
| Vector<Box<CCallHelpers::Label>> labels = params.successorLabels(); |
| |
| params.addLatePath([=](CCallHelpers& jit) { |
| jumpToSlowPath.linkTo(*labels[1], &jit); |
| if (jumpToSuccess.isSet()) |
| jumpToSuccess.linkTo(*labels[0], &jit); |
| }); |
| }); |
| |
| before->setSuccessors({ fastPathContinuation, FrequencyClass::Normal }, { slowPath, FrequencyClass::Rare }); |
| |
| // Header initialization happens in fastPathContinuation, not in fastPath |
| Value* cell = patchpoint; |
| Value* typeInfo = fastPathContinuation->appendNew<Const32Value>(m_proc, m_origin, JSWebAssemblyStruct::typeInfoBlob().blob()); |
| fastPathContinuation->appendNew<MemoryValue>(m_proc, Store, m_origin, structureID, cell, static_cast<int32_t>(JSCell::structureIDOffset())); |
| fastPathContinuation->appendNew<MemoryValue>(m_proc, Store, m_origin, typeInfo, cell, static_cast<int32_t>(JSCell::indexingTypeAndMiscOffset())); |
| fastPathContinuation->appendNew<MemoryValue>(m_proc, Store, m_origin, fastPathContinuation->appendIntConstant(m_proc, m_origin, pointerType(), 0), cell, static_cast<int32_t>(JSObject::butterflyOffset())); |
| fastPathContinuation->appendNew<MemoryValue>(m_proc, Store, m_origin, fastPathContinuation->appendIntConstant(m_proc, m_origin, pointerType(), std::bit_cast<uintptr_t>(rtt.ptr())), cell, static_cast<int32_t>(WebAssemblyGCObjectBase::offsetOfRTT())); |
| |
| fastUpsilon = fastPathContinuation->appendNew<UpsilonValue>(m_proc, m_origin, cell); |
| fastPathContinuation->appendNew<Value>(m_proc, Jump, m_origin); |
| fastPathContinuation->setSuccessors(m_block); |
| } else { |
| // Just redirect the Jump added by splitForward to slowPath |
| before->setSuccessors(slowPath); |
| } |
| |
| Value* slowFunctionAddress = slowPath->appendNew<ConstPtrValue>(m_proc, m_origin, tagCFunction<OperationPtrTag>(Wasm::operationWasmStructNewEmpty)); |
| Value* typeIndexValue = slowPath->appendNew<Const32Value>(m_proc, m_origin, typeIndex); |
| Value* slowResult = slowPath->appendNew<CCallValue>(m_proc, Int64, m_origin, Effects::forCall(), slowFunctionAddress, instance, typeIndexValue); |
| |
| // Null check for slow path result |
| Value* isNull = slowPath->appendNew<Value>(m_proc, Equal, m_origin, slowResult, slowPath->appendNew<Const64Value>(m_proc, m_origin, JSValue::encode(jsNull()))); |
| CheckValue* check = slowPath->appendNew<CheckValue>(m_proc, Check, m_origin, isNull); |
| check->setGenerator([=](CCallHelpers& jit, const StackmapGenerationParams&) { |
| jit.move(CCallHelpers::TrustedImm32(static_cast<uint32_t>(Wasm::ExceptionType::BadStructNew)), GPRInfo::argumentGPR1); |
| jit.nearCallThunk(CodeLocationLabel<JITThunkPtrTag>(Wasm::Thunks::singleton().stub(Wasm::throwExceptionFromOMGThunkGenerator).code())); |
| }); |
| |
| UpsilonValue* slowUpsilon = slowPath->appendNew<UpsilonValue>(m_proc, m_origin, slowResult); |
| slowPath->appendNew<Value>(m_proc, Jump, m_origin); |
| slowPath->setSuccessors(m_block); |
| |
| Value* phi = m_insertionSet.insert<Value>(m_index, Phi, pointerType(), m_origin); |
| if (fastUpsilon) |
| fastUpsilon->setPhi(phi); |
| slowUpsilon->setPhi(phi); |
| |
| m_value->replaceWithIdentity(phi); |
| before->updatePredecessorsAfter(); |
| m_changed = true; |
| break; |
| } |
| |
| #if USE(JSVALUE64) |
| case WasmRefCast: |
| case WasmRefTest: { |
| WasmRefTypeCheckValue* typeCheck = m_value->as<WasmRefTypeCheckValue>(); |
| // FIXME: In most of cases, we do not need to have split. We could split only when necessary. |
| BasicBlock* before = m_blockInsertionSet.splitForward(m_block, m_index, &m_insertionSet); |
| BasicBlock* continuation = m_block; |
| m_value->replaceWithIdentity(emitRefTestOrCast(typeCheck, before, continuation)); |
| before->updatePredecessorsAfter(); |
| m_changed = true; |
| break; |
| } |
| #endif |
| |
| default: |
| break; |
| } |
| } |
| m_insertionSet.execute(m_block); |
| } |
| |
| void invertedComparisonByXor(Opcode opcodeToBeInverted, Value* lhs, Value* rhs) |
| { |
| v128_t allOnes; |
| allOnes.u64x2[0] = 0xffffffffffffffff; |
| allOnes.u64x2[1] = 0xffffffffffffffff; |
| Value* allOnesConst = m_insertionSet.insert<Const128Value>(m_index, m_origin, allOnes); |
| Value* compareResult = m_insertionSet.insert<SIMDValue>(m_index, m_origin, opcodeToBeInverted, B3::V128, m_value->as<SIMDValue>()->simdInfo(), lhs, rhs); |
| Value* result = m_insertionSet.insert<SIMDValue>(m_index, m_origin, VectorXor, B3::V128, SIMDLane::v128, SIMDSignMode::None, compareResult, allOnesConst); |
| m_value->replaceWithIdentity(result); |
| m_changed = true; |
| } |
| |
| #if USE(JSVALUE32_64) |
| Value* callDivModHelper(BasicBlock* block, Opcode nonChillOpcode, Value* num, Value* den) |
| { |
| Type type = num->type(); |
| Value* functionAddress; |
| if (nonChillOpcode == Div) { |
| if (m_value->type() == Int64) |
| functionAddress = block->appendNew<ConstPtrValue>(m_proc, m_origin, tagCFunction<OperationPtrTag>(Math::i64_div_s)); |
| else |
| functionAddress = block->appendNew<ConstPtrValue>(m_proc, m_origin, tagCFunction<OperationPtrTag>(Math::i32_div_s)); |
| } else { |
| if (m_value->type() == Int64) |
| functionAddress = block->appendNew<ConstPtrValue>(m_proc, m_origin, tagCFunction<OperationPtrTag>(Math::i64_rem_s)); |
| else |
| functionAddress = block->appendNew<ConstPtrValue>(m_proc, m_origin, tagCFunction<OperationPtrTag>(Math::i32_rem_s)); |
| } |
| return block->appendNew<CCallValue>(m_proc, type, m_origin, Effects::none(), functionAddress, num, den); |
| } |
| #else |
| Value* callDivModHelper(BasicBlock*, Opcode, Value*, Value*) |
| { |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| #endif |
| void makeDivisionChill(Opcode nonChillOpcode) |
| { |
| ASSERT(nonChillOpcode == Div || nonChillOpcode == Mod); |
| |
| // ARM supports this instruction natively. |
| if (isARM64()) |
| return; |
| |
| // We implement "res = Div<Chill>/Mod<Chill>(num, den)" as follows: |
| // |
| // if (den + 1 <=_unsigned 1) { |
| // if (!den) { |
| // res = 0; |
| // goto done; |
| // } |
| // if (num == -2147483648) { |
| // res = isDiv ? num : 0; |
| // goto done; |
| // } |
| // } |
| // res = num (/ or %) dev; |
| // done: |
| m_changed = true; |
| |
| Value* num = m_value->child(0); |
| Value* den = m_value->child(1); |
| |
| Value* one = m_insertionSet.insertIntConstant(m_index, m_value, 1); |
| Value* isDenOK = m_insertionSet.insert<Value>( |
| m_index, Above, m_origin, |
| m_insertionSet.insert<Value>(m_index, Add, m_origin, den, one), |
| one); |
| |
| BasicBlock* before = m_blockInsertionSet.splitForward(m_block, m_index, &m_insertionSet); |
| |
| BasicBlock* normalDivCase = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* shadyDenCase = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* zeroDenCase = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* neg1DenCase = m_blockInsertionSet.insertBefore(m_block); |
| BasicBlock* intMinCase = m_blockInsertionSet.insertBefore(m_block); |
| |
| before->replaceLastWithNew<Value>(m_proc, Branch, m_origin, isDenOK); |
| before->setSuccessors( |
| FrequentedBlock(normalDivCase, FrequencyClass::Normal), |
| FrequentedBlock(shadyDenCase, FrequencyClass::Rare)); |
| |
| Value* innerResult; |
| if (isARM_THUMB2() && (m_value->type() == Int64 || m_value->type() == Int32)) |
| innerResult = callDivModHelper(normalDivCase, nonChillOpcode, num, den); |
| else |
| innerResult = normalDivCase->appendNew<Value>(m_proc, nonChillOpcode, m_origin, num, den); |
| UpsilonValue* normalResult = normalDivCase->appendNew<UpsilonValue>( |
| m_proc, m_origin, |
| innerResult); |
| normalDivCase->appendNew<Value>(m_proc, Jump, m_origin); |
| normalDivCase->setSuccessors(FrequentedBlock(m_block)); |
| |
| shadyDenCase->appendNew<Value>(m_proc, Branch, m_origin, den); |
| shadyDenCase->setSuccessors( |
| FrequentedBlock(neg1DenCase, FrequencyClass::Normal), |
| FrequentedBlock(zeroDenCase, FrequencyClass::Rare)); |
| |
| UpsilonValue* zeroResult = zeroDenCase->appendNew<UpsilonValue>( |
| m_proc, m_origin, |
| zeroDenCase->appendIntConstant(m_proc, m_value, 0)); |
| zeroDenCase->appendNew<Value>(m_proc, Jump, m_origin); |
| zeroDenCase->setSuccessors(FrequentedBlock(m_block)); |
| |
| int64_t badNumeratorConst = 0; |
| switch (m_value->type().kind()) { |
| case Int32: |
| badNumeratorConst = std::numeric_limits<int32_t>::min(); |
| break; |
| case Int64: |
| badNumeratorConst = std::numeric_limits<int64_t>::min(); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| badNumeratorConst = 0; |
| } |
| |
| Value* badNumerator = |
| neg1DenCase->appendIntConstant(m_proc, m_value, badNumeratorConst); |
| |
| neg1DenCase->appendNew<Value>( |
| m_proc, Branch, m_origin, |
| neg1DenCase->appendNew<Value>( |
| m_proc, Equal, m_origin, num, badNumerator)); |
| neg1DenCase->setSuccessors( |
| FrequentedBlock(intMinCase, FrequencyClass::Rare), |
| FrequentedBlock(normalDivCase, FrequencyClass::Normal)); |
| |
| Value* intMinResult = nonChillOpcode == Div ? badNumerator : intMinCase->appendIntConstant(m_proc, m_value, 0); |
| UpsilonValue* intMinResultUpsilon = intMinCase->appendNew<UpsilonValue>( |
| m_proc, m_origin, intMinResult); |
| intMinCase->appendNew<Value>(m_proc, Jump, m_origin); |
| intMinCase->setSuccessors(FrequentedBlock(m_block)); |
| |
| Value* phi = m_insertionSet.insert<Value>( |
| m_index, Phi, m_value->type(), m_origin); |
| normalResult->setPhi(phi); |
| zeroResult->setPhi(phi); |
| intMinResultUpsilon->setPhi(phi); |
| |
| m_value->replaceWithIdentity(phi); |
| before->updatePredecessorsAfter(); |
| } |
| |
| void recursivelyBuildSwitch( |
| const Vector<SwitchCase>& cases, FrequentedBlock fallThrough, unsigned start, bool hardStart, |
| unsigned end, BasicBlock* before) |
| { |
| Value* child = m_value->child(0); |
| Type type = child->type(); |
| |
| // It's a good idea to use a table-based switch in some cases: the number of cases has to be |
| // large enough and they have to be dense enough. This could probably be improved a lot. For |
| // example, we could still use a jump table in cases where the inputs are sparse so long as we |
| // shift off the uninteresting bits. On the other hand, it's not clear that this would |
| // actually be any better than what we have done here and it's not clear that it would be |
| // better than a binary switch. |
| const unsigned minCasesForTable = 7; |
| const unsigned densityLimit = 4; |
| if (end - start >= minCasesForTable) { |
| int64_t firstValue = cases[start].caseValue(); |
| int64_t lastValue = cases[end - 1].caseValue(); |
| if ((lastValue - firstValue + 1) / (end - start) < densityLimit) { |
| size_t tableSize = lastValue - firstValue + 1 + 1; // + 1 for fallthrough |
| Value* index = before->appendNew<Value>(m_proc, Sub, m_origin, child, before->appendIntConstant(m_proc, m_origin, type, firstValue)); |
| Value* fallthroughIndex = before->appendIntConstant(m_proc, m_origin, type, tableSize - 1); |
| index = before->appendNew<B3::Value>(m_proc, Select, m_origin, before->appendNew<Value>(m_proc, AboveEqual, m_origin, index, fallthroughIndex), fallthroughIndex, index); |
| |
| if (index->type() != pointerType() && index->type() == Int32) |
| index = before->appendNew<Value>(m_proc, ZExt32, m_origin, index); |
| |
| using JumpTableCodePtr = CodePtr<JSSwitchPtrTag>; |
| JumpTableCodePtr* jumpTable = static_cast<JumpTableCodePtr*>(m_proc.addDataSection(sizeof(JumpTableCodePtr) * tableSize)); |
| auto* tableValue = before->appendIntConstant(m_proc, m_origin, pointerType(), std::bit_cast<uintptr_t>(jumpTable)); |
| auto* shifted = before->appendNew<Value>(m_proc, Shl, m_origin, index, before->appendIntConstant(m_proc, m_origin, Int32, getLSBSet(sizeof(JumpTableCodePtr)))); |
| auto* address = before->appendNew<Value>(m_proc, Add, pointerType(), m_origin, shifted, tableValue); |
| auto* load = before->appendNew<MemoryValue>(m_proc, Load, pointerType(), m_origin, address); |
| load->setControlDependent(false); |
| load->setReadsMutability(B3::Mutability::Immutable); |
| PatchpointValue* patchpoint = before->appendNew<PatchpointValue>(m_proc, Void, m_origin, cloningForbidden(Patchpoint)); |
| |
| patchpoint->effects = Effects(); |
| patchpoint->effects.terminal = true; |
| patchpoint->appendSomeRegister(load); |
| // Technically, we don't have to clobber macro registers on X86_64. This is probably OK though. |
| patchpoint->clobber(RegisterSetBuilder::macroClobberedGPRs()); |
| |
| before->clearSuccessors(); |
| BitVector handledIndices; |
| for (unsigned i = start; i < end; ++i) { |
| FrequentedBlock block = cases[i].target(); |
| int64_t value = cases[i].caseValue(); |
| before->appendSuccessor(block); |
| size_t index = value - firstValue; |
| ASSERT(!handledIndices.get(index)); |
| handledIndices.set(index); |
| } |
| before->appendSuccessor(fallThrough); |
| |
| patchpoint->setGenerator( |
| [=](CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| |
| GPRReg target = params[0].gpr(); |
| jit.farJump(target, JSSwitchPtrTag); |
| |
| // These labels are guaranteed to be populated before either late paths or |
| // link tasks run. |
| Vector<Box<CCallHelpers::Label>> labels = params.successorLabels(); |
| |
| jit.addLinkTask( |
| [=] (LinkBuffer& linkBuffer) { |
| JumpTableCodePtr fallThrough = linkBuffer.locationOf<JSSwitchPtrTag>(*labels.last()); |
| for (unsigned i = 0; i < tableSize; ++i) |
| jumpTable[i] = fallThrough; |
| unsigned labelIndex = 0; |
| for (unsigned tableIndex : handledIndices) |
| jumpTable[tableIndex] = linkBuffer.locationOf<JSSwitchPtrTag>(*labels[labelIndex++]); |
| }); |
| }); |
| return; |
| } |
| } |
| |
| // See comments in jit/BinarySwitch.cpp for a justification of this algorithm. The only |
| // thing we do differently is that we don't use randomness. |
| |
| const unsigned leafThreshold = 3; |
| |
| unsigned size = end - start; |
| |
| if (size <= leafThreshold) { |
| bool allConsecutive = false; |
| |
| if ((hardStart || (start && cases[start - 1].caseValue() == cases[start].caseValue() - 1)) |
| && end < cases.size() |
| && cases[end - 1].caseValue() == cases[end].caseValue() - 1) { |
| allConsecutive = true; |
| for (unsigned i = 0; i < size - 1; ++i) { |
| if (cases[start + i].caseValue() + 1 != cases[start + i + 1].caseValue()) { |
| allConsecutive = false; |
| break; |
| } |
| } |
| } |
| |
| unsigned limit = allConsecutive ? size - 1 : size; |
| |
| for (unsigned i = 0; i < limit; ++i) { |
| BasicBlock* nextCheck = m_blockInsertionSet.insertAfter(m_block); |
| before->appendNew<Value>( |
| m_proc, Branch, m_origin, |
| before->appendNew<Value>( |
| m_proc, Equal, m_origin, child, |
| before->appendIntConstant( |
| m_proc, m_origin, type, |
| cases[start + i].caseValue()))); |
| before->setSuccessors(cases[start + i].target(), FrequentedBlock(nextCheck)); |
| |
| before = nextCheck; |
| } |
| |
| before->appendNew<Value>(m_proc, Jump, m_origin); |
| if (allConsecutive) |
| before->setSuccessors(cases[end - 1].target()); |
| else |
| before->setSuccessors(fallThrough); |
| return; |
| } |
| |
| unsigned medianIndex = std::midpoint(start, end); |
| |
| BasicBlock* left = m_blockInsertionSet.insertAfter(m_block); |
| BasicBlock* right = m_blockInsertionSet.insertAfter(m_block); |
| |
| before->appendNew<Value>( |
| m_proc, Branch, m_origin, |
| before->appendNew<Value>( |
| m_proc, LessThan, m_origin, child, |
| before->appendIntConstant( |
| m_proc, m_origin, type, |
| cases[medianIndex].caseValue()))); |
| before->setSuccessors(FrequentedBlock(left), FrequentedBlock(right)); |
| |
| recursivelyBuildSwitch(cases, fallThrough, start, hardStart, medianIndex, left); |
| recursivelyBuildSwitch(cases, fallThrough, medianIndex, true, end, right); |
| } |
| |
| #if USE(JSVALUE64) |
| Value* emitRefTestOrCast(WasmRefTypeCheckValue* typeCheck, BasicBlock* before, BasicBlock* continuation) |
| { |
| enum class CastKind { Cast, Test }; |
| |
| // CastKind::Test, reference, allowNull, heapType, shouldNegate, result |
| Value* value = typeCheck->child(0); |
| int32_t toHeapType = typeCheck->targetHeapType(); |
| bool allowNull = typeCheck->allowNull(); |
| bool referenceIsNullable = typeCheck->referenceIsNullable(); |
| bool definitelyIsCellOrNull = typeCheck->definitelyIsCellOrNull(); |
| bool definitelyIsWasmGCObjectOrNull = typeCheck->definitelyIsWasmGCObjectOrNull(); |
| SUPPRESS_UNCOUNTED_LOCAL const Wasm::RTT* targetRTT = typeCheck->targetRTT(); |
| bool isCast = typeCheck->kind().opcode() == WasmRefCast; |
| CastKind castKind = isCast ? CastKind::Cast : CastKind::Test; |
| bool shouldNegate = typeCheck->shouldNegate(); |
| Value* result = nullptr; |
| |
| if (isCast) |
| result = value; |
| |
| BasicBlock* trueBlock = nullptr; |
| BasicBlock* falseBlock = nullptr; |
| if (!isCast) { |
| trueBlock = m_proc.addBlock(); |
| falseBlock = m_proc.addBlock(); |
| } |
| |
| auto castFailure = [=](CCallHelpers& jit, const StackmapGenerationParams&) { |
| jit.move(CCallHelpers::TrustedImm32(static_cast<uint32_t>(Wasm::ExceptionType::CastFailure)), GPRInfo::argumentGPR1); |
| jit.nearCallThunk(CodeLocationLabel<JITThunkPtrTag>(Wasm::Thunks::singleton().stub(Wasm::throwExceptionFromOMGThunkGenerator).code())); |
| }; |
| |
| auto castAccessOffset = [&] -> std::optional<ptrdiff_t> { |
| if (!isCast) |
| return std::nullopt; |
| |
| if (allowNull) |
| return std::nullopt; |
| |
| if (Wasm::typeIndexIsType(static_cast<Wasm::TypeIndex>(toHeapType))) |
| return std::nullopt; |
| |
| if (targetRTT->kind() == Wasm::RTTKind::Function) |
| return WebAssemblyFunctionBase::offsetOfRTT(); |
| |
| if (!definitelyIsCellOrNull) |
| return std::nullopt; |
| if (!definitelyIsWasmGCObjectOrNull) |
| return JSCell::typeInfoTypeOffset(); |
| return JSCell::structureIDOffset(); |
| }; |
| |
| bool canTrap = false; |
| auto wrapTrapping = [&](auto input) -> B3::Kind { |
| if (canTrap) { |
| canTrap = false; |
| return trapping(input); |
| } |
| return input; |
| }; |
| |
| // Ensure reference nullness agrees with heap type. |
| before->replaceLastWithNew<Value>(m_proc, Nop, m_origin); |
| before->clearSuccessors(); |
| auto* currentBlock = before; |
| |
| auto constant = [&](TypeKind type, uint64_t bits) -> Value* { |
| switch (type) { |
| case Int32: |
| return currentBlock->appendNew<Const32Value>(m_proc, m_origin, bits); |
| case Int64: |
| return currentBlock->appendNew<Const64Value>(m_proc, m_origin, bits); |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| }; |
| |
| auto emitCheckOrBranchForCast = [&](CastKind kind, Value* condition, const auto& generator, BasicBlock* falseBlock) { |
| if (kind == CastKind::Cast) { |
| CheckValue* check = currentBlock->appendNew<CheckValue>(m_proc, Check, m_origin, condition); |
| check->setGenerator(generator); |
| } else { |
| ASSERT(falseBlock); |
| BasicBlock* success = m_proc.addBlock(); |
| currentBlock->appendNewControlValue(m_proc, B3::Branch, m_origin, condition, FrequentedBlock(falseBlock), FrequentedBlock(success)); |
| falseBlock->addPredecessor(currentBlock); |
| success->addPredecessor(currentBlock); |
| currentBlock = success; |
| } |
| }; |
| |
| { |
| BasicBlock* nullCase = m_proc.addBlock(); |
| BasicBlock* nonNullCase = m_proc.addBlock(); |
| |
| Value* isNull = nullptr; |
| if (referenceIsNullable) { |
| if (auto offset = castAccessOffset(); offset && offset.value() <= Wasm::maxAcceptableOffsetForNullReference()) { |
| isNull = constant(Int32, 0); |
| canTrap = true; |
| } else |
| isNull = currentBlock->appendNew<Value>(m_proc, Equal, m_origin, value, constant(Int64, JSValue::encode(jsNull()))); |
| } else |
| isNull = constant(Int32, 0); |
| |
| currentBlock->appendNewControlValue(m_proc, B3::Branch, m_origin, isNull, FrequentedBlock(nullCase), FrequentedBlock(nonNullCase)); |
| nullCase->addPredecessor(currentBlock); |
| nonNullCase->addPredecessor(currentBlock); |
| |
| currentBlock = nullCase; |
| if (isCast) { |
| if (!allowNull) { |
| B3::PatchpointValue* throwException = currentBlock->appendNew<B3::PatchpointValue>(m_proc, B3::Void, m_origin); |
| throwException->setGenerator(castFailure); |
| } |
| currentBlock->appendNewControlValue(m_proc, Jump, m_origin, continuation); |
| continuation->addPredecessor(currentBlock); |
| } else { |
| BasicBlock* nextBlock; |
| if (!allowNull) |
| nextBlock = falseBlock; |
| else |
| nextBlock = trueBlock; |
| currentBlock->appendNewControlValue(m_proc, Jump, m_origin, nextBlock); |
| nextBlock->addPredecessor(currentBlock); |
| } |
| |
| currentBlock = nonNullCase; |
| } |
| |
| if (Wasm::typeIndexIsType(static_cast<Wasm::TypeIndex>(toHeapType))) { |
| switch (static_cast<Wasm::TypeKind>(toHeapType)) { |
| case Wasm::TypeKind::Funcref: |
| case Wasm::TypeKind::Externref: |
| case Wasm::TypeKind::Anyref: |
| case Wasm::TypeKind::Exnref: |
| // Casts to these types cannot fail as they are the top types of their respective hierarchies, and static type-checking does not allow cross-hierarchy casts. |
| break; |
| case Wasm::TypeKind::Noneref: |
| case Wasm::TypeKind::Nofuncref: |
| case Wasm::TypeKind::Noexternref: |
| case Wasm::TypeKind::Noexnref: |
| // Casts to any bottom type should always fail. |
| if (isCast) { |
| B3::PatchpointValue* throwException = currentBlock->appendNew<B3::PatchpointValue>(m_proc, B3::Void, m_origin); |
| throwException->setGenerator(castFailure); |
| } else { |
| currentBlock->appendNewControlValue(m_proc, Jump, m_origin, falseBlock); |
| falseBlock->addPredecessor(currentBlock); |
| currentBlock = m_proc.addBlock(); |
| } |
| break; |
| case Wasm::TypeKind::Eqref: { |
| auto nop = [] (CCallHelpers&, const B3::StackmapGenerationParams&) { }; |
| BasicBlock* endBlock = isCast ? continuation : trueBlock; |
| BasicBlock* checkObject = m_proc.addBlock(); |
| |
| // The eqref case chains together checks for i31, array, and struct with disjunctions so the control flow is more complicated, and requires some extra basic blocks to be created. |
| emitCheckOrBranchForCast(CastKind::Test, currentBlock->appendNew<Value>(m_proc, Below, m_origin, value, constant(Int64, JSValue::NumberTag)), nop, checkObject); |
| Value* untagged = currentBlock->appendNew<Value>(m_proc, Trunc, m_origin, value); |
| emitCheckOrBranchForCast(CastKind::Test, currentBlock->appendNew<Value>(m_proc, GreaterThan, m_origin, untagged, constant(Int32, Wasm::maxI31ref)), nop, checkObject); |
| emitCheckOrBranchForCast(CastKind::Test, currentBlock->appendNew<Value>(m_proc, LessThan, m_origin, untagged, constant(Int32, Wasm::minI31ref)), nop, checkObject); |
| currentBlock->appendNewControlValue(m_proc, Jump, m_origin, endBlock); |
| checkObject->addPredecessor(currentBlock); |
| endBlock->addPredecessor(currentBlock); |
| |
| currentBlock = checkObject; |
| if (!definitelyIsCellOrNull) |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, BitAnd, m_origin, value, constant(Int64, JSValue::NotCellMask)), castFailure, falseBlock); |
| if (!definitelyIsWasmGCObjectOrNull) { |
| auto* jsType = currentBlock->appendNew<MemoryValue>(m_proc, Load8Z, Int32, m_origin, value, safeCast<int32_t>(JSCell::typeInfoTypeOffset())); |
| |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, NotEqual, m_origin, jsType, constant(Int32, JSType::WebAssemblyGCObjectType)), castFailure, falseBlock); |
| } |
| break; |
| } |
| case Wasm::TypeKind::I31ref: { |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, Below, m_origin, value, constant(Int64, JSValue::NumberTag)), castFailure, falseBlock); |
| Value* untagged = currentBlock->appendNew<Value>(m_proc, Trunc, m_origin, value); |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, GreaterThan, m_origin, untagged, constant(Int32, Wasm::maxI31ref)), castFailure, falseBlock); |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, LessThan, m_origin, untagged, constant(Int32, Wasm::minI31ref)), castFailure, falseBlock); |
| break; |
| } |
| case Wasm::TypeKind::Arrayref: |
| case Wasm::TypeKind::Structref: { |
| if (!definitelyIsCellOrNull) |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, BitAnd, m_origin, value, constant(Int64, JSValue::NotCellMask)), castFailure, falseBlock); |
| if (!definitelyIsWasmGCObjectOrNull) { |
| auto* jsType = currentBlock->appendNew<MemoryValue>(m_proc, Load8Z, Int32, m_origin, value, safeCast<int32_t>(JSCell::typeInfoTypeOffset())); |
| |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, NotEqual, m_origin, jsType, constant(Int32, JSType::WebAssemblyGCObjectType)), castFailure, falseBlock); |
| } |
| Value* rtt = currentBlock->appendNew<MemoryValue>(m_proc, B3::Load, pointerType(), m_origin, value, safeCast<int32_t>(WebAssemblyGCObjectBase::offsetOfRTT())); |
| auto* kind = currentBlock->appendNew<MemoryValue>(m_proc, Load8Z, m_origin, rtt, safeCast<int32_t>(Wasm::RTT::offsetOfKind())); |
| kind->setControlDependent(false); |
| |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, NotEqual, m_origin, kind, constant(Int32, static_cast<uint8_t>(static_cast<Wasm::TypeKind>(toHeapType) == Wasm::TypeKind::Arrayref ? Wasm::RTTKind::Array : Wasm::RTTKind::Struct))), castFailure, falseBlock); |
| break; |
| } |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } else { |
| ([&] { |
| MemoryValue* rtt; |
| auto* targetRTTPointer = constant(Int64, std::bit_cast<uintptr_t>(targetRTT)); |
| if (targetRTT->kind() == Wasm::RTTKind::Function) |
| rtt = currentBlock->appendNew<MemoryValue>(m_proc, wrapTrapping(B3::Load), Int64, m_origin, value, safeCast<int32_t>(WebAssemblyFunctionBase::offsetOfRTT())); |
| else { |
| // The cell check is only needed for non-functions, as the typechecker does not allow non-Cell values for funcref casts. |
| if (!definitelyIsCellOrNull) |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, BitAnd, m_origin, value, constant(Int64, JSValue::NotCellMask)), castFailure, falseBlock); |
| |
| if (!definitelyIsWasmGCObjectOrNull) { |
| auto* jsType = currentBlock->appendNew<MemoryValue>(m_proc, wrapTrapping(Load8Z), Int32, m_origin, value, safeCast<int32_t>(JSCell::typeInfoTypeOffset())); |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, NotEqual, m_origin, jsType, constant(Int32, JSType::WebAssemblyGCObjectType)), castFailure, falseBlock); |
| } |
| |
| rtt = currentBlock->appendNew<MemoryValue>(m_proc, B3::Load, pointerType(), m_origin, value, safeCast<int32_t>(WebAssemblyGCObjectBase::offsetOfRTT())); |
| if (targetRTT->isFinalType()) { |
| // If signature is final type and pointer equality failed, this value must not be a subtype. |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, NotEqual, m_origin, rtt, targetRTTPointer), castFailure, falseBlock); |
| return; |
| } |
| |
| if (targetRTT->displaySizeExcludingThis() < Wasm::RTT::inlinedDisplaySize) { |
| auto* pointer = currentBlock->appendNew<MemoryValue>(m_proc, B3::Load, Int64, m_origin, rtt, safeCast<int32_t>(Wasm::RTT::offsetOfData() + targetRTT->displaySizeExcludingThis() * sizeof(RefPtr<const Wasm::RTT>))); |
| pointer->setReadsMutability(B3::Mutability::Immutable); |
| pointer->setControlDependent(false); |
| |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, NotEqual, m_origin, pointer, targetRTTPointer), castFailure, falseBlock); |
| return; |
| } |
| } |
| |
| BasicBlock* equalBlock; |
| if (isCast) |
| equalBlock = continuation; |
| else |
| equalBlock = trueBlock; |
| BasicBlock* slowPath = m_proc.addBlock(); |
| currentBlock->appendNewControlValue(m_proc, B3::Branch, m_origin, currentBlock->appendNew<Value>(m_proc, Equal, m_origin, rtt, targetRTTPointer), FrequentedBlock(equalBlock), FrequentedBlock(slowPath)); |
| equalBlock->addPredecessor(currentBlock); |
| slowPath->addPredecessor(currentBlock); |
| |
| currentBlock = slowPath; |
| if (targetRTT->isFinalType()) { |
| // If signature is final type and pointer equality failed, this value must not be a subtype. |
| emitCheckOrBranchForCast(castKind, constant(Int32, 1), castFailure, falseBlock); |
| } else { |
| auto* displaySizeExcludingThis = currentBlock->appendNew<MemoryValue>(m_proc, B3::Load, Int32, m_origin, rtt, safeCast<int32_t>(Wasm::RTT::offsetOfDisplaySizeExcludingThis())); |
| displaySizeExcludingThis->setControlDependent(false); |
| |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, BelowEqual, m_origin, displaySizeExcludingThis, constant(Int32, targetRTT->displaySizeExcludingThis())), castFailure, falseBlock); |
| |
| auto* pointer = currentBlock->appendNew<MemoryValue>(m_proc, B3::Load, Int64, m_origin, rtt, safeCast<int32_t>(Wasm::RTT::offsetOfData() + targetRTT->displaySizeExcludingThis() * sizeof(RefPtr<const Wasm::RTT>))); |
| pointer->setReadsMutability(B3::Mutability::Immutable); |
| pointer->setControlDependent(false); |
| |
| emitCheckOrBranchForCast(castKind, currentBlock->appendNew<Value>(m_proc, NotEqual, m_origin, pointer, targetRTTPointer), castFailure, falseBlock); |
| } |
| }()); |
| } |
| |
| if (isCast) { |
| currentBlock->appendNewControlValue(m_proc, Jump, m_origin, continuation); |
| continuation->addPredecessor(currentBlock); |
| currentBlock = continuation; |
| } else { |
| currentBlock->appendNewControlValue(m_proc, Jump, m_origin, trueBlock); |
| trueBlock->addPredecessor(currentBlock); |
| currentBlock = trueBlock; |
| UpsilonValue* trueUpsilon = currentBlock->appendNew<UpsilonValue>(m_proc, m_origin, constant(B3::Int32, shouldNegate ? 0 : 1)); |
| currentBlock->appendNewControlValue(m_proc, Jump, m_origin, continuation); |
| continuation->addPredecessor(currentBlock); |
| |
| currentBlock = falseBlock; |
| UpsilonValue* falseUpsilon = currentBlock->appendNew<UpsilonValue>(m_proc, m_origin, constant(B3::Int32, shouldNegate ? 1 : 0)); |
| currentBlock->appendNewControlValue(m_proc, Jump, m_origin, continuation); |
| continuation->addPredecessor(currentBlock); |
| |
| currentBlock = continuation; |
| Value* phi = m_insertionSet.insert<Value>(m_index, Phi, m_value->type(), m_origin); |
| trueUpsilon->setPhi(phi); |
| falseUpsilon->setPhi(phi); |
| result = phi; |
| } |
| |
| return result; |
| } |
| #endif |
| |
| Procedure& m_proc; |
| BlockInsertionSet m_blockInsertionSet; |
| InsertionSet m_insertionSet; |
| UseCounts m_useCounts; |
| BasicBlock* m_block; |
| unsigned m_index; |
| Value* m_value; |
| Origin m_origin; |
| bool m_changed { false }; |
| }; |
| |
| } // anonymous namespace |
| |
| bool lowerMacros(Procedure& proc) |
| { |
| PhaseScope phaseScope(proc, "B3::lowerMacros"_s); |
| LowerMacros lowerMacros(proc); |
| return lowerMacros.run(); |
| } |
| |
| } } // namespace JSC::B3 |
| |
| WTF_ALLOW_UNSAFE_BUFFER_USAGE_END |
| |
| #endif // ENABLE(B3_JIT) |