| /* |
| * Copyright (C) 2015-2021 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 "testb3.h" |
| |
| #if ENABLE(B3_JIT) |
| |
| void testPinRegisters() |
| { |
| auto go = [&] (bool pin) { |
| Procedure proc; |
| RegisterSet csrs; |
| csrs.merge(RegisterSet::calleeSaveRegisters()); |
| csrs.exclude(RegisterSet::stackRegisters()); |
| if (pin) { |
| csrs.forEach( |
| [&] (Reg reg) { |
| proc.pinRegister(reg); |
| }); |
| } |
| BasicBlock* root = proc.addBlock(); |
| Value* a = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| Value* b = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1); |
| Value* c = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR2); |
| Value* d = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::regCS0); |
| root->appendNew<CCallValue>( |
| proc, Void, Origin(), |
| root->appendNew<ConstPtrValue>(proc, Origin(), static_cast<intptr_t>(0x1234))); |
| root->appendNew<CCallValue>( |
| proc, Void, Origin(), |
| root->appendNew<ConstPtrValue>(proc, Origin(), static_cast<intptr_t>(0x1235)), |
| a, b, c); |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, Void, Origin()); |
| patchpoint->appendSomeRegister(d); |
| unsigned optLevel = proc.optLevel(); |
| patchpoint->setGenerator( |
| [&] (CCallHelpers&, const StackmapGenerationParams& params) { |
| if (optLevel > 1) |
| CHECK_EQ(params[0].gpr(), GPRInfo::regCS0); |
| }); |
| root->appendNew<Value>(proc, Return, Origin()); |
| auto code = compileProc(proc); |
| bool usesCSRs = false; |
| for (Air::BasicBlock* block : proc.code()) { |
| for (Air::Inst& inst : *block) { |
| if (inst.kind.opcode == Air::Patch && inst.origin == patchpoint) |
| continue; |
| inst.forEachTmpFast( |
| [&] (Air::Tmp tmp) { |
| if (tmp.isReg()) |
| usesCSRs |= csrs.get(tmp.reg()); |
| }); |
| } |
| } |
| if (proc.optLevel() < 2) { |
| // Our less good register allocators may use the |
| // pinned CSRs in a move. |
| usesCSRs = false; |
| } |
| for (const RegisterAtOffset& regAtOffset : proc.calleeSaveRegisterAtOffsetList()) |
| usesCSRs |= csrs.get(regAtOffset.reg()); |
| CHECK_EQ(usesCSRs, !pin); |
| }; |
| |
| go(true); |
| go(false); |
| } |
| |
| void testX86LeaAddAddShlLeft() |
| { |
| // Add(Add(Shl(@x, $c), @y), $d) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 2)), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)), |
| root->appendNew<ConstPtrValue>(proc, Origin(), 100)); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| if (proc.optLevel() > 1) |
| checkUsesInstruction(*code, "lea 0x64(%rdi,%rsi,4), %rax"); |
| else |
| checkUsesInstruction(*code, "lea"); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), (1 + (2 << 2)) + 100); |
| } |
| |
| void testX86LeaAddAddShlRight() |
| { |
| // Add(Add(@x, Shl(@y, $c)), $d) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 2))), |
| root->appendNew<ConstPtrValue>(proc, Origin(), 100)); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| if (proc.optLevel() > 1) |
| checkUsesInstruction(*code, "lea 0x64(%rdi,%rsi,4), %rax"); |
| else |
| checkUsesInstruction(*code, "lea"); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), (1 + (2 << 2)) + 100); |
| } |
| |
| void testX86LeaAddAdd() |
| { |
| // Add(Add(@x, @y), $c) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)), |
| root->appendNew<ConstPtrValue>(proc, Origin(), 100)); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), (1 + 2) + 100); |
| if (proc.optLevel() > 1) { |
| checkDisassembly( |
| *code, |
| [&] (const char* disassembly) -> bool { |
| return strstr(disassembly, "lea 0x64(%rdi,%rsi,1), %rax") |
| || strstr(disassembly, "lea 0x64(%rsi,%rdi,1), %rax"); |
| }, |
| "Expected to find something like lea 0x64(%rdi,%rsi,1), %rax but didn't!"); |
| } |
| } |
| |
| void testX86LeaAddShlRight() |
| { |
| // Add(Shl(@x, $c), @y) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 2))); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| if (proc.optLevel() > 1) |
| checkUsesInstruction(*code, "lea (%rdi,%rsi,4), %rax"); |
| else |
| checkUsesInstruction(*code, "lea"); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), 1 + (2 << 2)); |
| } |
| |
| void testX86LeaAddShlLeftScale1() |
| { |
| // Add(Shl(@x, $c), @y) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 0))); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), 1 + 2); |
| if (proc.optLevel() > 1) { |
| checkDisassembly( |
| *code, |
| [&] (const char* disassembly) -> bool { |
| return strstr(disassembly, "lea (%rdi,%rsi,1), %rax") |
| || strstr(disassembly, "lea (%rsi,%rdi,1), %rax"); |
| }, |
| "Expected to find something like lea (%rdi,%rsi,1), %rax but didn't!"); |
| } |
| } |
| |
| void testX86LeaAddShlLeftScale2() |
| { |
| // Add(Shl(@x, $c), @y) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 1))); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| if (proc.optLevel() > 1) |
| checkUsesInstruction(*code, "lea (%rdi,%rsi,2), %rax"); |
| else |
| checkUsesInstruction(*code, "lea"); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), 1 + (2 << 1)); |
| } |
| |
| void testX86LeaAddShlLeftScale4() |
| { |
| // Add(Shl(@x, $c), @y) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 2)), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| if (proc.optLevel() > 1) |
| checkUsesInstruction(*code, "lea (%rdi,%rsi,4), %rax"); |
| else |
| checkUsesInstruction(*code, "lea"); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), 1 + (2 << 2)); |
| } |
| |
| void testX86LeaAddShlLeftScale8() |
| { |
| // Add(Shl(@x, $c), @y) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 3))); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| if (proc.optLevel() > 1) |
| checkUsesInstruction(*code, "lea (%rdi,%rsi,8), %rax"); |
| else |
| checkUsesInstruction(*code, "lea"); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), 1 + (2 << 3)); |
| } |
| |
| void testAddShl32() |
| { |
| // Add(Shl(@x, $c), @y) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 32))); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| CHECK_EQ(invoke<int64_t>(*code, 1, 2), 1 + (static_cast<int64_t>(2) << static_cast<int64_t>(32))); |
| } |
| |
| void testAddShl64() |
| { |
| // Add(Shl(@x, $c), @y) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 64))); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), 1 + 2); |
| } |
| |
| void testAddShl65() |
| { |
| // Add(Shl(@x, $c), @y) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* result = root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 65))); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| CHECK_EQ(invoke<intptr_t>(*code, 1, 2), 1 + (2 << 1)); |
| } |
| |
| void testReduceStrengthReassociation(bool flip) |
| { |
| // Add(Add(@x, $c), @y) -> Add(Add(@x, @y), $c) |
| // and |
| // Add(@y, Add(@x, $c)) -> Add(Add(@x, @y), $c) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* arg1 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| Value* arg2 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1); |
| |
| Value* innerAdd = root->appendNew<Value>( |
| proc, Add, Origin(), arg1, |
| root->appendNew<ConstPtrValue>(proc, Origin(), 42)); |
| |
| Value* outerAdd; |
| if (flip) |
| outerAdd = root->appendNew<Value>(proc, Add, Origin(), arg2, innerAdd); |
| else |
| outerAdd = root->appendNew<Value>(proc, Add, Origin(), innerAdd, arg2); |
| |
| root->appendNew<Value>(proc, Return, Origin(), outerAdd); |
| |
| proc.resetReachability(); |
| |
| if (shouldBeVerbose(proc)) { |
| dataLog("IR before reduceStrength:\n"); |
| dataLog(proc); |
| } |
| |
| reduceStrength(proc); |
| |
| if (shouldBeVerbose(proc)) { |
| dataLog("IR after reduceStrength:\n"); |
| dataLog(proc); |
| } |
| |
| CHECK_EQ(root->last()->opcode(), Return); |
| CHECK_EQ(root->last()->child(0)->opcode(), Add); |
| CHECK(root->last()->child(0)->child(1)->isIntPtr(42)); |
| CHECK_EQ(root->last()->child(0)->child(0)->opcode(), Add); |
| CHECK( |
| (root->last()->child(0)->child(0)->child(0) == arg1 && root->last()->child(0)->child(0)->child(1) == arg2) || |
| (root->last()->child(0)->child(0)->child(0) == arg2 && root->last()->child(0)->child(0)->child(1) == arg1)); |
| } |
| |
| void testLoadBaseIndexShift2() |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<MemoryValue>( |
| proc, Load, Int32, Origin(), |
| root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 2))))); |
| auto code = compileProc(proc); |
| if (isX86() && proc.optLevel() > 1) |
| checkUsesInstruction(*code, "(%rdi,%rsi,4)"); |
| int32_t value = 12341234; |
| char* ptr = bitwise_cast<char*>(&value); |
| for (unsigned i = 0; i < 10; ++i) |
| CHECK_EQ(invoke<int32_t>(*code, ptr - (static_cast<intptr_t>(1) << static_cast<intptr_t>(2)) * i, i), 12341234); |
| } |
| |
| void testLoadBaseIndexShift32() |
| { |
| #if CPU(ADDRESS64) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<MemoryValue>( |
| proc, Load, Int32, Origin(), |
| root->appendNew<Value>( |
| proc, Add, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| root->appendNew<Value>( |
| proc, Shl, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1), |
| root->appendNew<Const32Value>(proc, Origin(), 32))))); |
| auto code = compileProc(proc); |
| int32_t value = 12341234; |
| char* ptr = bitwise_cast<char*>(&value); |
| for (unsigned i = 0; i < 10; ++i) |
| CHECK_EQ(invoke<int32_t>(*code, ptr - (static_cast<intptr_t>(1) << static_cast<intptr_t>(32)) * i, i), 12341234); |
| #endif |
| } |
| |
| void testOptimizeMaterialization() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<CCallValue>( |
| proc, Void, Origin(), |
| root->appendNew<ConstPtrValue>(proc, Origin(), 0x123423453456llu), |
| root->appendNew<ConstPtrValue>(proc, Origin(), 0x123423453456llu + 35)); |
| root->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| bool found = false; |
| for (Air::BasicBlock* block : proc.code()) { |
| for (Air::Inst& inst : *block) { |
| if (inst.kind.opcode != Air::Add64) |
| continue; |
| if (inst.args[0] != Air::Arg::imm(35)) |
| continue; |
| found = true; |
| } |
| } |
| CHECK(found); |
| } |
| |
| template<typename Func> |
| void generateLoop(Procedure& proc, const Func& func) |
| { |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* loop = proc.addBlock(); |
| BasicBlock* end = proc.addBlock(); |
| |
| UpsilonValue* initialIndex = root->appendNew<UpsilonValue>( |
| proc, Origin(), root->appendNew<Const32Value>(proc, Origin(), 0)); |
| root->appendNew<Value>(proc, Jump, Origin()); |
| root->setSuccessors(loop); |
| |
| Value* index = loop->appendNew<Value>(proc, Phi, Int32, Origin()); |
| initialIndex->setPhi(index); |
| |
| Value* one = func(loop, index); |
| |
| Value* nextIndex = loop->appendNew<Value>(proc, Add, Origin(), index, one); |
| UpsilonValue* loopIndex = loop->appendNew<UpsilonValue>(proc, Origin(), nextIndex); |
| loopIndex->setPhi(index); |
| loop->appendNew<Value>( |
| proc, Branch, Origin(), |
| loop->appendNew<Value>( |
| proc, LessThan, Origin(), nextIndex, |
| loop->appendNew<Const32Value>(proc, Origin(), 100))); |
| loop->setSuccessors(loop, end); |
| |
| end->appendNew<Value>(proc, Return, Origin()); |
| } |
| |
| static std::array<int, 100> makeArrayForLoops() |
| { |
| std::array<int, 100> result; |
| for (unsigned i = 0; i < result.size(); ++i) |
| result[i] = i & 1; |
| return result; |
| } |
| |
| template<typename Func> |
| void generateLoopNotBackwardsDominant(Procedure& proc, std::array<int, 100>& array, const Func& func) |
| { |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* loopHeader = proc.addBlock(); |
| BasicBlock* loopCall = proc.addBlock(); |
| BasicBlock* loopFooter = proc.addBlock(); |
| BasicBlock* end = proc.addBlock(); |
| |
| UpsilonValue* initialIndex = root->appendNew<UpsilonValue>( |
| proc, Origin(), root->appendNew<Const32Value>(proc, Origin(), 0)); |
| // If you look carefully, you'll notice that this is an extremely sneaky use of Upsilon that demonstrates |
| // the extent to which our SSA is different from normal-person SSA. |
| UpsilonValue* defaultOne = root->appendNew<UpsilonValue>( |
| proc, Origin(), root->appendNew<Const32Value>(proc, Origin(), 1)); |
| root->appendNew<Value>(proc, Jump, Origin()); |
| root->setSuccessors(loopHeader); |
| |
| Value* index = loopHeader->appendNew<Value>(proc, Phi, Int32, Origin()); |
| initialIndex->setPhi(index); |
| |
| // if (array[index]) |
| loopHeader->appendNew<Value>( |
| proc, Branch, Origin(), |
| loopHeader->appendNew<MemoryValue>( |
| proc, Load, Int32, Origin(), |
| loopHeader->appendNew<Value>( |
| proc, Add, Origin(), |
| loopHeader->appendNew<ConstPtrValue>(proc, Origin(), &array), |
| loopHeader->appendNew<Value>( |
| proc, Mul, Origin(), |
| loopHeader->appendNew<Value>(proc, ZExt32, Origin(), index), |
| loopHeader->appendNew<ConstPtrValue>(proc, Origin(), sizeof(int)))))); |
| loopHeader->setSuccessors(loopCall, loopFooter); |
| |
| Value* functionCall = func(loopCall, index); |
| UpsilonValue* oneFromFunction = loopCall->appendNew<UpsilonValue>(proc, Origin(), functionCall); |
| loopCall->appendNew<Value>(proc, Jump, Origin()); |
| loopCall->setSuccessors(loopFooter); |
| |
| Value* one = loopFooter->appendNew<Value>(proc, Phi, Int32, Origin()); |
| defaultOne->setPhi(one); |
| oneFromFunction->setPhi(one); |
| Value* nextIndex = loopFooter->appendNew<Value>(proc, Add, Origin(), index, one); |
| UpsilonValue* loopIndex = loopFooter->appendNew<UpsilonValue>(proc, Origin(), nextIndex); |
| loopIndex->setPhi(index); |
| loopFooter->appendNew<Value>( |
| proc, Branch, Origin(), |
| loopFooter->appendNew<Value>( |
| proc, LessThan, Origin(), nextIndex, |
| loopFooter->appendNew<Const32Value>(proc, Origin(), 100))); |
| loopFooter->setSuccessors(loopHeader, end); |
| |
| end->appendNew<Value>(proc, Return, Origin()); |
| } |
| |
| extern "C" { |
| static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(oneFunction, int, (int* callCount)); |
| } |
| JSC_DEFINE_JIT_OPERATION(oneFunction, int, (int* callCount)) |
| { |
| (*callCount)++; |
| return 1; |
| } |
| |
| extern "C" { |
| static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(noOpFunction, void, ()); |
| } |
| JSC_DEFINE_JIT_OPERATION(noOpFunction, void, ()) |
| { |
| } |
| |
| void testLICMPure() |
| { |
| Procedure proc; |
| |
| if (proc.optLevel() < 2) |
| return; |
| |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), Effects::none(), |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMPureSideExits() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.exitsSideways = true; |
| loop->appendNew<CCallValue>( |
| proc, Void, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(noOpFunction))); |
| |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), Effects::none(), |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMPureWritesPinned() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.writesPinned = true; |
| loop->appendNew<CCallValue>( |
| proc, Void, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(noOpFunction))); |
| |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), Effects::none(), |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMPureWrites() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.writes = HeapRange(63479); |
| loop->appendNew<CCallValue>( |
| proc, Void, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(noOpFunction))); |
| |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), Effects::none(), |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMReadsLocalState() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.readsLocalState = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); // We'll fail to hoist because the loop has Upsilons. |
| } |
| |
| void testLICMReadsPinned() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.readsPinned = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMReads() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.reads = HeapRange::top(); |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMPureNotBackwardsDominant() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| auto array = makeArrayForLoops(); |
| generateLoopNotBackwardsDominant( |
| proc, array, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), Effects::none(), |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMPureFoiledByChild() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value* index) -> Value* { |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), Effects::none(), |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| index); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMPureNotBackwardsDominantFoiledByChild() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| auto array = makeArrayForLoops(); |
| generateLoopNotBackwardsDominant( |
| proc, array, |
| [&] (BasicBlock* loop, Value* index) -> Value* { |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), Effects::none(), |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| index); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 50u); |
| } |
| |
| void testLICMExitsSideways() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.exitsSideways = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMWritesLocalState() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.writesLocalState = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMWrites() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.writes = HeapRange(666); |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMFence() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.fence = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMWritesPinned() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.writesPinned = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMControlDependent() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.controlDependent = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMControlDependentNotBackwardsDominant() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| auto array = makeArrayForLoops(); |
| generateLoopNotBackwardsDominant( |
| proc, array, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.controlDependent = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 50u); |
| } |
| |
| void testLICMControlDependentSideExits() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.exitsSideways = true; |
| loop->appendNew<CCallValue>( |
| proc, Void, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(noOpFunction))); |
| |
| effects = Effects::none(); |
| effects.controlDependent = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMReadsPinnedWritesPinned() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.writesPinned = true; |
| loop->appendNew<CCallValue>( |
| proc, Void, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(noOpFunction))); |
| |
| effects = Effects::none(); |
| effects.readsPinned = true; |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMReadsWritesDifferentHeaps() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.writes = HeapRange(6436); |
| loop->appendNew<CCallValue>( |
| proc, Void, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(noOpFunction))); |
| |
| effects = Effects::none(); |
| effects.reads = HeapRange(4886); |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 1u); |
| } |
| |
| void testLICMReadsWritesOverlappingHeaps() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| Effects effects = Effects::none(); |
| effects.writes = HeapRange(6436, 74458); |
| loop->appendNew<CCallValue>( |
| proc, Void, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(noOpFunction))); |
| |
| effects = Effects::none(); |
| effects.reads = HeapRange(48864, 78239); |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), effects, |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testLICMDefaultCall() |
| { |
| Procedure proc; |
| generateLoop( |
| proc, |
| [&] (BasicBlock* loop, Value*) -> Value* { |
| return loop->appendNew<CCallValue>( |
| proc, Int32, Origin(), |
| loop->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(oneFunction)), |
| loop->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| }); |
| |
| unsigned callCount = 0; |
| compileAndRun<void>(proc, &callCount); |
| CHECK_EQ(callCount, 100u); |
| } |
| |
| void testDepend32() |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| Value* first = root->appendNew<MemoryValue>(proc, Load, Int32, Origin(), ptr, 0); |
| Value* second = root->appendNew<MemoryValue>( |
| proc, Load, Int32, Origin(), |
| root->appendNew<Value>( |
| proc, Add, Origin(), ptr, |
| root->appendNew<Value>( |
| proc, ZExt32, Origin(), |
| root->appendNew<Value>(proc, Depend, Origin(), first))), |
| 4); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<Value>(proc, Add, Origin(), first, second)); |
| |
| int32_t values[2]; |
| values[0] = 42; |
| values[1] = 0xbeef; |
| |
| auto code = compileProc(proc); |
| if (isARM64()) |
| checkUsesInstruction(*code, "eor"); |
| else if (isX86()) { |
| checkDoesNotUseInstruction(*code, "mfence"); |
| checkDoesNotUseInstruction(*code, "lock"); |
| } |
| CHECK_EQ(invoke<int32_t>(*code, values), 42 + 0xbeef); |
| } |
| |
| void testDepend64() |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| Value* first = root->appendNew<MemoryValue>(proc, Load, Int64, Origin(), ptr, 0); |
| Value* second = root->appendNew<MemoryValue>( |
| proc, Load, Int64, Origin(), |
| root->appendNew<Value>( |
| proc, Add, Origin(), ptr, |
| root->appendNew<Value>(proc, Depend, Origin(), first)), |
| 8); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<Value>(proc, Add, Origin(), first, second)); |
| |
| int64_t values[2]; |
| values[0] = 42; |
| values[1] = 0xbeef; |
| |
| auto code = compileProc(proc); |
| if (isARM64()) |
| checkUsesInstruction(*code, "eor"); |
| else if (isX86()) { |
| checkDoesNotUseInstruction(*code, "mfence"); |
| checkDoesNotUseInstruction(*code, "lock"); |
| } |
| CHECK_EQ(invoke<int64_t>(*code, values), 42 + 0xbeef); |
| } |
| |
| void testWasmBoundsCheck(unsigned offset) |
| { |
| Procedure proc; |
| if (proc.optLevel() < 1) |
| return; |
| GPRReg pinned = GPRInfo::argumentGPR1; |
| proc.pinRegister(pinned); |
| |
| proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR) { |
| CHECK_EQ(pinnedGPR, pinned); |
| |
| // This should always work because a function this simple should never have callee |
| // saves. |
| jit.move(CCallHelpers::TrustedImm32(42), GPRInfo::returnValueGPR); |
| jit.emitFunctionEpilogue(); |
| jit.ret(); |
| }); |
| |
| BasicBlock* root = proc.addBlock(); |
| Value* left = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| if (pointerType() != Int32) |
| left = root->appendNew<Value>(proc, Trunc, Origin(), left); |
| root->appendNew<WasmBoundsCheckValue>(proc, Origin(), pinned, left, offset); |
| Value* result = root->appendNew<Const32Value>(proc, Origin(), 0x42); |
| root->appendNewControlValue(proc, Return, Origin(), result); |
| |
| auto code = compileProc(proc); |
| uint32_t bound = 2 + offset; |
| auto computeResult = [&] (uint32_t input) { |
| return input + offset < bound ? 0x42 : 42; |
| }; |
| |
| CHECK_EQ(invoke<int32_t>(*code, 1, bound), computeResult(1)); |
| CHECK_EQ(invoke<int32_t>(*code, 3, bound), computeResult(3)); |
| CHECK_EQ(invoke<int32_t>(*code, 2, bound), computeResult(2)); |
| } |
| |
| void testWasmAddress() |
| { |
| Procedure proc; |
| GPRReg pinnedGPR = GPRInfo::argumentGPR2; |
| proc.pinRegister(pinnedGPR); |
| |
| unsigned loopCount = 100; |
| Vector<unsigned> values(loopCount); |
| unsigned numToStore = 42; |
| |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* header = proc.addBlock(); |
| BasicBlock* body = proc.addBlock(); |
| BasicBlock* continuation = proc.addBlock(); |
| |
| // Root |
| Value* loopCountValue = root->appendNew<Value>(proc, Trunc, Origin(), root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| Value* valueToStore = root->appendNew<Value>(proc, Trunc, Origin(), root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1)); |
| UpsilonValue* beginUpsilon = root->appendNew<UpsilonValue>(proc, Origin(), root->appendNew<Const32Value>(proc, Origin(), 0)); |
| root->appendNewControlValue(proc, Jump, Origin(), header); |
| |
| // Header |
| Value* indexPhi = header->appendNew<Value>(proc, Phi, Int32, Origin()); |
| header->appendNewControlValue(proc, Branch, Origin(), |
| header->appendNew<Value>(proc, Below, Origin(), indexPhi, loopCountValue), |
| body, continuation); |
| |
| // Body |
| Value* pointer = body->appendNew<Value>(proc, Mul, Origin(), indexPhi, |
| body->appendNew<Const32Value>(proc, Origin(), sizeof(unsigned))); |
| pointer = body->appendNew<Value>(proc, ZExt32, Origin(), pointer); |
| body->appendNew<MemoryValue>(proc, Store, Origin(), valueToStore, |
| body->appendNew<WasmAddressValue>(proc, Origin(), pointer, pinnedGPR), 0); |
| UpsilonValue* incUpsilon = body->appendNew<UpsilonValue>(proc, Origin(), |
| body->appendNew<Value>(proc, Add, Origin(), indexPhi, |
| body->appendNew<Const32Value>(proc, Origin(), 1))); |
| body->appendNewControlValue(proc, Jump, Origin(), header); |
| |
| // Continuation |
| continuation->appendNewControlValue(proc, Return, Origin()); |
| |
| beginUpsilon->setPhi(indexPhi); |
| incUpsilon->setPhi(indexPhi); |
| |
| |
| auto code = compileProc(proc); |
| invoke<void>(*code, loopCount, numToStore, values.data()); |
| for (unsigned value : values) |
| CHECK_EQ(numToStore, value); |
| } |
| |
| void testFastTLSLoad() |
| { |
| #if ENABLE(FAST_TLS_JIT) |
| _pthread_setspecific_direct(WTF_TESTING_KEY, bitwise_cast<void*>(static_cast<uintptr_t>(0xbeef))); |
| |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, pointerType(), Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->setGenerator( |
| [&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.loadFromTLSPtr(fastTLSOffsetForKey(WTF_TESTING_KEY), params[0].gpr()); |
| }); |
| |
| root->appendNew<Value>(proc, Return, Origin(), patchpoint); |
| |
| CHECK_EQ(compileAndRun<uintptr_t>(proc), static_cast<uintptr_t>(0xbeef)); |
| #endif |
| } |
| |
| void testFastTLSStore() |
| { |
| #if ENABLE(FAST_TLS_JIT) |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, Void, Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->numGPScratchRegisters = 1; |
| patchpoint->setGenerator( |
| [&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| GPRReg scratch = params.gpScratch(0); |
| jit.move(CCallHelpers::TrustedImm32(0xdead), scratch); |
| jit.storeToTLSPtr(scratch, fastTLSOffsetForKey(WTF_TESTING_KEY)); |
| }); |
| |
| root->appendNewControlValue(proc, Return, Origin()); |
| |
| compileAndRun<void>(proc); |
| CHECK_EQ(bitwise_cast<uintptr_t>(_pthread_getspecific_direct(WTF_TESTING_KEY)), static_cast<uintptr_t>(0xdead)); |
| #endif |
| } |
| |
| static NEVER_INLINE bool doubleEq(double a, double b) { return a == b; } |
| static NEVER_INLINE bool doubleNeq(double a, double b) { return a != b; } |
| static NEVER_INLINE bool doubleGt(double a, double b) { return a > b; } |
| static NEVER_INLINE bool doubleGte(double a, double b) { return a >= b; } |
| static NEVER_INLINE bool doubleLt(double a, double b) { return a < b; } |
| static NEVER_INLINE bool doubleLte(double a, double b) { return a <= b; } |
| |
| void testDoubleLiteralComparison(double a, double b) |
| { |
| using Test = std::tuple<B3::Opcode, bool (*)(double, double)>; |
| StdList<Test> tests = { |
| Test { NotEqual, doubleNeq }, |
| Test { Equal, doubleEq }, |
| Test { EqualOrUnordered, doubleEq }, |
| Test { GreaterThan, doubleGt }, |
| Test { GreaterEqual, doubleGte }, |
| Test { LessThan, doubleLt }, |
| Test { LessEqual, doubleLte }, |
| }; |
| |
| for (const Test& test : tests) { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* valueA = root->appendNew<ConstDoubleValue>(proc, Origin(), a); |
| Value* valueB = root->appendNew<ConstDoubleValue>(proc, Origin(), b); |
| |
| // This is here just to make reduceDoubleToFloat do things. |
| Value* valueC = root->appendNew<ConstDoubleValue>(proc, Origin(), 0.0); |
| Value* valueAsFloat = root->appendNew<Value>(proc, DoubleToFloat, Origin(), valueC); |
| |
| root->appendNewControlValue( |
| proc, Return, Origin(), |
| root->appendNew<Value>(proc, BitAnd, Origin(), |
| root->appendNew<Value>(proc, std::get<0>(test), Origin(), valueA, valueB), |
| root->appendNew<Value>(proc, Equal, Origin(), valueAsFloat, valueAsFloat))); |
| |
| CHECK(!!compileAndRun<int32_t>(proc) == std::get<1>(test)(a, b)); |
| } |
| } |
| |
| void testFloatEqualOrUnorderedFolding() |
| { |
| for (auto& first : floatingPointOperands<float>()) { |
| for (auto& second : floatingPointOperands<float>()) { |
| float a = first.value; |
| float b = second.value; |
| bool expectedResult = (a == b) || std::isunordered(a, b); |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* constA = root->appendNew<ConstFloatValue>(proc, Origin(), a); |
| Value* constB = root->appendNew<ConstFloatValue>(proc, Origin(), b); |
| |
| root->appendNewControlValue(proc, Return, Origin(), |
| root->appendNew<Value>( |
| proc, EqualOrUnordered, Origin(), |
| constA, |
| constB)); |
| CHECK(!!compileAndRun<int32_t>(proc) == expectedResult); |
| } |
| } |
| } |
| |
| void testFloatEqualOrUnorderedFoldingNaN() |
| { |
| StdList<float> nans = { |
| bitwise_cast<float>(0xfffffffd), |
| bitwise_cast<float>(0xfffffffe), |
| bitwise_cast<float>(0xfffffff0), |
| static_cast<float>(PNaN), |
| }; |
| |
| unsigned i = 0; |
| for (float nan : nans) { |
| RELEASE_ASSERT(std::isnan(nan)); |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* a = root->appendNew<ConstFloatValue>(proc, Origin(), nan); |
| Value* b = root->appendNew<Value>(proc, DoubleToFloat, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), FPRInfo::argumentFPR0)); |
| |
| if (i % 2) |
| std::swap(a, b); |
| ++i; |
| root->appendNewControlValue(proc, Return, Origin(), |
| root->appendNew<Value>(proc, EqualOrUnordered, Origin(), a, b)); |
| CHECK(!!compileAndRun<int32_t>(proc, static_cast<double>(1.0))); |
| } |
| } |
| |
| void testFloatEqualOrUnorderedDontFold() |
| { |
| for (auto& first : floatingPointOperands<float>()) { |
| float a = first.value; |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* constA = root->appendNew<ConstFloatValue>(proc, Origin(), a); |
| Value* b = root->appendNew<Value>(proc, DoubleToFloat, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), FPRInfo::argumentFPR0)); |
| root->appendNewControlValue(proc, Return, Origin(), |
| root->appendNew<Value>( |
| proc, EqualOrUnordered, Origin(), constA, b)); |
| |
| auto code = compileProc(proc); |
| |
| for (auto& second : floatingPointOperands<float>()) { |
| float b = second.value; |
| bool expectedResult = (a == b) || std::isunordered(a, b); |
| CHECK(!!invoke<int32_t>(*code, static_cast<double>(b)) == expectedResult); |
| } |
| } |
| } |
| |
| extern "C" { |
| static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(functionNineArgs, void, (int32_t, void*, void*, void*, void*, void*, void*, void*, void*)); |
| } |
| JSC_DEFINE_JIT_OPERATION(functionNineArgs, void, (int32_t, void*, void*, void*, void*, void*, void*, void*, void*)) |
| { |
| } |
| |
| void testShuffleDoesntTrashCalleeSaves() |
| { |
| Procedure proc; |
| |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* likely = proc.addBlock(); |
| BasicBlock* unlikely = proc.addBlock(); |
| |
| RegisterSet regs = RegisterSet::allGPRs(); |
| regs.exclude(RegisterSet::stackRegisters()); |
| regs.exclude(RegisterSet::reservedHardwareRegisters()); |
| regs.exclude(RegisterSet::calleeSaveRegisters()); |
| regs.exclude(RegisterSet::argumentGPRS()); |
| |
| unsigned i = 0; |
| Vector<Value*> patches; |
| for (Reg reg : regs) { |
| ++i; |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, Int32, Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| RELEASE_ASSERT(reg.isGPR()); |
| patchpoint->resultConstraints = { ValueRep::reg(reg.gpr()) }; |
| patchpoint->setGenerator( |
| [=] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.move(CCallHelpers::TrustedImm32(i), params[0].gpr()); |
| }); |
| patches.append(patchpoint); |
| } |
| |
| Value* arg1 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::toArgumentRegister(0 % GPRInfo::numberOfArgumentRegisters)); |
| Value* arg2 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::toArgumentRegister(1 % GPRInfo::numberOfArgumentRegisters)); |
| Value* arg3 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::toArgumentRegister(2 % GPRInfo::numberOfArgumentRegisters)); |
| Value* arg4 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::toArgumentRegister(3 % GPRInfo::numberOfArgumentRegisters)); |
| Value* arg5 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::toArgumentRegister(4 % GPRInfo::numberOfArgumentRegisters)); |
| Value* arg6 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::toArgumentRegister(5 % GPRInfo::numberOfArgumentRegisters)); |
| Value* arg7 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::toArgumentRegister(6 % GPRInfo::numberOfArgumentRegisters)); |
| Value* arg8 = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::toArgumentRegister(7 % GPRInfo::numberOfArgumentRegisters)); |
| |
| PatchpointValue* ptr = root->appendNew<PatchpointValue>(proc, Int64, Origin()); |
| ptr->clobber(RegisterSet::macroScratchRegisters()); |
| ptr->resultConstraints = { ValueRep::reg(GPRInfo::regCS0) }; |
| ptr->appendSomeRegister(arg1); |
| ptr->setGenerator( |
| [=] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.move(params[1].gpr(), params[0].gpr()); |
| }); |
| |
| Value* condition = root->appendNew<Value>( |
| proc, Equal, Origin(), |
| ptr, |
| root->appendNew<Const64Value>(proc, Origin(), 0)); |
| |
| root->appendNewControlValue( |
| proc, Branch, Origin(), |
| condition, |
| FrequentedBlock(likely, FrequencyClass::Normal), FrequentedBlock(unlikely, FrequencyClass::Rare)); |
| |
| // Never executes. |
| Value* const42 = likely->appendNew<Const32Value>(proc, Origin(), 42); |
| likely->appendNewControlValue(proc, Return, Origin(), const42); |
| |
| // Always executes. |
| Value* constNumber = unlikely->appendNew<Const32Value>(proc, Origin(), 0x1); |
| |
| unlikely->appendNew<CCallValue>( |
| proc, Void, Origin(), |
| unlikely->appendNew<ConstPtrValue>(proc, Origin(), tagCFunction<OperationPtrTag>(functionNineArgs)), |
| constNumber, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); |
| |
| PatchpointValue* voidPatch = unlikely->appendNew<PatchpointValue>(proc, Void, Origin()); |
| voidPatch->clobber(RegisterSet::macroScratchRegisters()); |
| for (Value* v : patches) |
| voidPatch->appendSomeRegister(v); |
| voidPatch->appendSomeRegister(arg1); |
| voidPatch->appendSomeRegister(arg2); |
| voidPatch->appendSomeRegister(arg3); |
| voidPatch->appendSomeRegister(arg4); |
| voidPatch->appendSomeRegister(arg5); |
| voidPatch->appendSomeRegister(arg6); |
| voidPatch->setGenerator([=] (CCallHelpers&, const StackmapGenerationParams&) { }); |
| |
| unlikely->appendNewControlValue(proc, Return, Origin(), |
| unlikely->appendNew<MemoryValue>(proc, Load, Int32, Origin(), ptr)); |
| |
| int32_t* inputPtr = static_cast<int32_t*>(fastMalloc(sizeof(int32_t))); |
| *inputPtr = 48; |
| CHECK(compileAndRun<int32_t>(proc, inputPtr) == 48); |
| fastFree(inputPtr); |
| } |
| |
| void testDemotePatchpointTerminal() |
| { |
| Procedure proc; |
| |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* done = proc.addBlock(); |
| |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, Int32, Origin()); |
| patchpoint->effects.terminal = true; |
| root->setSuccessors(done); |
| |
| done->appendNew<Value>(proc, Return, Origin(), patchpoint); |
| |
| proc.resetReachability(); |
| breakCriticalEdges(proc); |
| IndexSet<Value*> valuesToDemote; |
| valuesToDemote.add(patchpoint); |
| demoteValues(proc, valuesToDemote); |
| validate(proc); |
| } |
| |
| void testReportUsedRegistersLateUseFollowedByEarlyDefDoesNotMarkUseAsDead() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| BasicBlock* root = proc.addBlock(); |
| |
| RegisterSet allRegs = RegisterSet::allGPRs(); |
| allRegs.exclude(RegisterSet::stackRegisters()); |
| allRegs.exclude(RegisterSet::reservedHardwareRegisters()); |
| |
| { |
| // Make every reg 42 (just needs to be a value other than 10). |
| Value* const42 = root->appendNew<Const32Value>(proc, Origin(), 42); |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, Void, Origin()); |
| for (Reg reg : allRegs) |
| patchpoint->append(const42, ValueRep::reg(reg)); |
| patchpoint->setGenerator([&] (CCallHelpers&, const StackmapGenerationParams&) { }); |
| } |
| |
| { |
| Value* const10 = root->appendNew<Const32Value>(proc, Origin(), 10); |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, Void, Origin()); |
| for (Reg reg : allRegs) |
| patchpoint->append(const10, ValueRep::lateReg(reg)); |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams&) { |
| for (Reg reg : allRegs) { |
| auto done = jit.branch32(CCallHelpers::Equal, reg.gpr(), CCallHelpers::TrustedImm32(10)); |
| jit.breakpoint(); |
| done.link(&jit); |
| } |
| }); |
| } |
| |
| { |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, Int32, Origin()); |
| patchpoint->resultConstraints = { ValueRep::SomeEarlyRegister }; |
| patchpoint->setGenerator([&] (CCallHelpers&, const StackmapGenerationParams& params) { |
| RELEASE_ASSERT(allRegs.contains(params[0].gpr())); |
| }); |
| } |
| |
| root->appendNewControlValue(proc, Return, Origin()); |
| |
| compileAndRun<void>(proc); |
| } |
| |
| void testInfiniteLoopDoesntCauseBadHoisting() |
| { |
| Procedure proc; |
| if (proc.optLevel() < 2) |
| return; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* header = proc.addBlock(); |
| BasicBlock* loadBlock = proc.addBlock(); |
| BasicBlock* postLoadBlock = proc.addBlock(); |
| |
| Value* arg = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| root->appendNewControlValue(proc, Jump, Origin(), header); |
| |
| header->appendNewControlValue( |
| proc, Branch, Origin(), |
| header->appendNew<Value>(proc, Equal, Origin(), |
| arg, |
| header->appendNew<Const64Value>(proc, Origin(), 10)), header, loadBlock); |
| |
| PatchpointValue* patchpoint = loadBlock->appendNew<PatchpointValue>(proc, Void, Origin()); |
| patchpoint->effects = Effects::none(); |
| patchpoint->effects.writesLocalState = true; // Don't DCE this. |
| patchpoint->setGenerator( |
| [&] (CCallHelpers& jit, const StackmapGenerationParams&) { |
| // This works because we don't have callee saves. |
| jit.emitFunctionEpilogue(); |
| jit.ret(); |
| }); |
| |
| Value* badLoad = loadBlock->appendNew<MemoryValue>(proc, Load, Int64, Origin(), arg, 0); |
| |
| loadBlock->appendNewControlValue( |
| proc, Branch, Origin(), |
| loadBlock->appendNew<Value>(proc, Equal, Origin(), |
| badLoad, |
| loadBlock->appendNew<Const64Value>(proc, Origin(), 45)), header, postLoadBlock); |
| |
| postLoadBlock->appendNewControlValue(proc, Return, Origin(), badLoad); |
| |
| // The patchpoint early ret() works because we don't have callee saves. |
| auto code = compileProc(proc); |
| RELEASE_ASSERT(!proc.calleeSaveRegisterAtOffsetList().size()); |
| invoke<void>(*code, static_cast<uint64_t>(55)); // Shouldn't crash dereferncing 55. |
| } |
| |
| static void testSimpleTuplePair(unsigned first, int64_t second) |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, proc.addTuple({ Int32, Int64 }), Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->resultConstraints = { ValueRep::SomeRegister, ValueRep::SomeRegister }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.move(CCallHelpers::TrustedImm32(first), params[0].gpr()); |
| jit.move(CCallHelpers::TrustedImm64(second), params[1].gpr()); |
| }); |
| Value* i32 = root->appendNew<Value>(proc, ZExt32, Origin(), |
| root->appendNew<ExtractValue>(proc, Origin(), Int32, patchpoint, 0)); |
| Value* i64 = root->appendNew<ExtractValue>(proc, Origin(), Int64, patchpoint, 1); |
| root->appendNew<Value>(proc, Return, Origin(), root->appendNew<Value>(proc, Add, Origin(), i32, i64)); |
| |
| CHECK_EQ(compileAndRun<int64_t>(proc), first + second); |
| } |
| |
| static void testSimpleTuplePairUnused(unsigned first, int64_t second) |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, proc.addTuple({ Int32, Int64, Double }), Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->resultConstraints = { ValueRep::SomeRegister, ValueRep::SomeRegister, ValueRep::SomeRegister }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.move(CCallHelpers::TrustedImm32(first), params[0].gpr()); |
| jit.move(CCallHelpers::TrustedImm64(second), params[1].gpr()); |
| jit.moveDouble(CCallHelpers::Imm64(bitwise_cast<uint64_t>(0.0)), params[2].fpr()); |
| }); |
| Value* i32 = root->appendNew<Value>(proc, ZExt32, Origin(), |
| root->appendNew<ExtractValue>(proc, Origin(), Int32, patchpoint, 0)); |
| Value* i64 = root->appendNew<ExtractValue>(proc, Origin(), Int64, patchpoint, 1); |
| root->appendNew<Value>(proc, Return, Origin(), root->appendNew<Value>(proc, Add, Origin(), i32, i64)); |
| |
| CHECK_EQ(compileAndRun<int64_t>(proc), first + second); |
| } |
| |
| static void testSimpleTuplePairStack(unsigned first, int64_t second) |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, proc.addTuple({ Int32, Int64 }), Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->resultConstraints = { ValueRep::SomeRegister, ValueRep::stackArgument(0) }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.move(CCallHelpers::TrustedImm32(first), params[0].gpr()); |
| jit.store64(CCallHelpers::TrustedImm64(second), CCallHelpers::Address(CCallHelpers::framePointerRegister, params[1].offsetFromFP())); |
| }); |
| Value* i32 = root->appendNew<Value>(proc, ZExt32, Origin(), |
| root->appendNew<ExtractValue>(proc, Origin(), Int32, patchpoint, 0)); |
| Value* i64 = root->appendNew<ExtractValue>(proc, Origin(), Int64, patchpoint, 1); |
| root->appendNew<Value>(proc, Return, Origin(), root->appendNew<Value>(proc, Add, Origin(), i32, i64)); |
| |
| CHECK_EQ(compileAndRun<int64_t>(proc), first + second); |
| } |
| |
| template<B3::TypeKind kind, typename ResultType> |
| static void testBottomTupleValue() |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| auto* tuple = root->appendNew<BottomTupleValue>(proc, Origin(), proc.addTuple({ |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| })); |
| Value* result = root->appendNew<ExtractValue>(proc, Origin(), kind, tuple, 0); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| CHECK_EQ(compileAndRun<ResultType>(proc), 0); |
| } |
| |
| template<B3::TypeKind kind, typename ResultType> |
| static void testTouchAllBottomTupleValue() |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| Type tupleType = proc.addTuple({ |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| kind, kind, kind, kind, kind, |
| }); |
| auto* tuple = root->appendNew<BottomTupleValue>(proc, Origin(), tupleType); |
| Value* result = root->appendNew<ExtractValue>(proc, Origin(), kind, tuple, 0); |
| for (unsigned index = 1; index < proc.resultCount(tupleType); ++index) |
| result = root->appendNew<Value>(proc, Add, Origin(), result, root->appendNew<ExtractValue>(proc, Origin(), kind, tuple, index)); |
| root->appendNew<Value>(proc, Return, Origin(), result); |
| CHECK_EQ(compileAndRun<ResultType>(proc), 0); |
| } |
| |
| template<bool shouldFixSSA> |
| static void tailDupedTuplePair(unsigned first, double second) |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* truthy = proc.addBlock(); |
| BasicBlock* falsey = proc.addBlock(); |
| |
| Type tupleType = proc.addTuple({ Int32, Double }); |
| Variable* var = proc.addVariable(tupleType); |
| |
| Value* test = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, tupleType, Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->resultConstraints = { ValueRep::SomeRegister, ValueRep::stackArgument(0) }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.move(CCallHelpers::TrustedImm32(first), params[0].gpr()); |
| jit.store64(CCallHelpers::TrustedImm64(bitwise_cast<uint64_t>(second)), CCallHelpers::Address(CCallHelpers::framePointerRegister, params[1].offsetFromFP())); |
| }); |
| root->appendNew<VariableValue>(proc, Set, Origin(), var, patchpoint); |
| root->appendNewControlValue(proc, Branch, Origin(), test, FrequentedBlock(truthy), FrequentedBlock(falsey)); |
| |
| auto addDup = [&] (BasicBlock* block) { |
| Value* tuple = block->appendNew<VariableValue>(proc, B3::Get, Origin(), var); |
| Value* i32 = block->appendNew<Value>(proc, ZExt32, Origin(), |
| block->appendNew<ExtractValue>(proc, Origin(), Int32, tuple, 0)); |
| i32 = block->appendNew<Value>(proc, IToD, Origin(), i32); |
| Value* f64 = block->appendNew<ExtractValue>(proc, Origin(), Double, tuple, 1); |
| block->appendNew<Value>(proc, Return, Origin(), block->appendNew<Value>(proc, Add, Origin(), i32, f64)); |
| }; |
| |
| addDup(truthy); |
| addDup(falsey); |
| |
| proc.resetReachability(); |
| if (shouldFixSSA) |
| fixSSA(proc); |
| CHECK_EQ(compileAndRun<double>(proc, first), first + second); |
| } |
| |
| template<bool shouldFixSSA> |
| static void tuplePairVariableLoop(unsigned first, uint64_t second) |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* body = proc.addBlock(); |
| BasicBlock* exit = proc.addBlock(); |
| |
| Type tupleType = proc.addTuple({ Int32, Int64 }); |
| Variable* var = proc.addVariable(tupleType); |
| |
| { |
| Value* first = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| Value* second = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1); |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, tupleType, Origin()); |
| patchpoint->append({ first, ValueRep::SomeRegister }); |
| patchpoint->append({ second, ValueRep::SomeRegister }); |
| patchpoint->resultConstraints = { ValueRep::SomeEarlyRegister, ValueRep::SomeEarlyRegister }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| jit.move(params[2].gpr(), params[0].gpr()); |
| jit.move(params[3].gpr(), params[1].gpr()); |
| }); |
| root->appendNew<VariableValue>(proc, Set, Origin(), var, patchpoint); |
| root->appendNewControlValue(proc, Jump, Origin(), body); |
| } |
| |
| { |
| Value* tuple = body->appendNew<VariableValue>(proc, B3::Get, Origin(), var); |
| Value* first = body->appendNew<ExtractValue>(proc, Origin(), Int32, tuple, 0); |
| Value* second = body->appendNew<ExtractValue>(proc, Origin(), Int64, tuple, 1); |
| PatchpointValue* patchpoint = body->appendNew<PatchpointValue>(proc, tupleType, Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->append({ first, ValueRep::SomeRegister }); |
| patchpoint->append({ second, ValueRep::SomeRegister }); |
| patchpoint->resultConstraints = { ValueRep::SomeEarlyRegister, ValueRep::stackArgument(0) }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| CHECK(params[3].gpr() != params[0].gpr()); |
| CHECK(params[2].gpr() != params[0].gpr()); |
| jit.add64(CCallHelpers::TrustedImm32(1), params[3].gpr(), params[0].gpr()); |
| jit.store64(params[0].gpr(), CCallHelpers::Address(CCallHelpers::framePointerRegister, params[1].offsetFromFP())); |
| |
| jit.move(params[2].gpr(), params[0].gpr()); |
| jit.urshift32(CCallHelpers::TrustedImm32(1), params[0].gpr()); |
| }); |
| body->appendNew<VariableValue>(proc, Set, Origin(), var, patchpoint); |
| Value* condition = body->appendNew<ExtractValue>(proc, Origin(), Int32, patchpoint, 0); |
| body->appendNewControlValue(proc, Branch, Origin(), condition, FrequentedBlock(body), FrequentedBlock(exit)); |
| } |
| |
| { |
| Value* tuple = exit->appendNew<VariableValue>(proc, B3::Get, Origin(), var); |
| Value* second = exit->appendNew<ExtractValue>(proc, Origin(), Int64, tuple, 1); |
| exit->appendNew<Value>(proc, Return, Origin(), second); |
| } |
| |
| proc.resetReachability(); |
| validate(proc); |
| if (shouldFixSSA) |
| fixSSA(proc); |
| CHECK_EQ(compileAndRun<uint64_t>(proc, first, second), second + (first ? getMSBSet(first) : first) + 1); |
| } |
| |
| template<bool shouldFixSSA> |
| static void tupleNestedLoop(int32_t first, double second) |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* outerLoop = proc.addBlock(); |
| BasicBlock* innerLoop = proc.addBlock(); |
| BasicBlock* outerContinuation = proc.addBlock(); |
| |
| Type tupleType = proc.addTuple({ Int32, Double, Int32 }); |
| Variable* varOuter = proc.addVariable(tupleType); |
| Variable* varInner = proc.addVariable(tupleType); |
| Variable* tookInner = proc.addVariable(Int32); |
| |
| { |
| Value* first = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| Value* second = root->appendNew<ArgumentRegValue>(proc, Origin(), FPRInfo::argumentFPR0); |
| PatchpointValue* patchpoint = root->appendNew<PatchpointValue>(proc, tupleType, Origin()); |
| patchpoint->append({ first, ValueRep::SomeRegisterWithClobber }); |
| patchpoint->append({ second, ValueRep::SomeRegisterWithClobber }); |
| patchpoint->resultConstraints = { ValueRep::SomeRegister, ValueRep::SomeRegister, ValueRep::SomeEarlyRegister }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| jit.move(params[3].gpr(), params[0].gpr()); |
| jit.move(params[0].gpr(), params[2].gpr()); |
| jit.move(params[4].fpr(), params[1].fpr()); |
| }); |
| root->appendNew<VariableValue>(proc, Set, Origin(), varOuter, patchpoint); |
| root->appendNew<VariableValue>(proc, Set, Origin(), tookInner, root->appendIntConstant(proc, Origin(), Int32, 0)); |
| root->appendNewControlValue(proc, Jump, Origin(), outerLoop); |
| } |
| |
| { |
| Value* tuple = outerLoop->appendNew<VariableValue>(proc, B3::Get, Origin(), varOuter); |
| Value* first = outerLoop->appendNew<ExtractValue>(proc, Origin(), Int32, tuple, 0); |
| Value* second = outerLoop->appendNew<ExtractValue>(proc, Origin(), Double, tuple, 1); |
| Value* third = outerLoop->appendNew<VariableValue>(proc, B3::Get, Origin(), tookInner); |
| PatchpointValue* patchpoint = outerLoop->appendNew<PatchpointValue>(proc, tupleType, Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->append({ first, ValueRep::SomeRegisterWithClobber }); |
| patchpoint->append({ second, ValueRep::SomeRegisterWithClobber }); |
| patchpoint->append({ third, ValueRep::SomeRegisterWithClobber }); |
| patchpoint->resultConstraints = { ValueRep::SomeRegister, ValueRep::SomeRegister, ValueRep::SomeRegister }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.move(params[3].gpr(), params[0].gpr()); |
| jit.moveConditionally32(CCallHelpers::Equal, params[5].gpr(), CCallHelpers::TrustedImm32(0), params[0].gpr(), params[5].gpr(), params[2].gpr()); |
| jit.move(params[4].fpr(), params[1].fpr()); |
| }); |
| outerLoop->appendNew<VariableValue>(proc, Set, Origin(), varOuter, patchpoint); |
| outerLoop->appendNew<VariableValue>(proc, Set, Origin(), varInner, patchpoint); |
| Value* condition = outerLoop->appendNew<ExtractValue>(proc, Origin(), Int32, patchpoint, 2); |
| outerLoop->appendNewControlValue(proc, Branch, Origin(), condition, FrequentedBlock(outerContinuation), FrequentedBlock(innerLoop)); |
| } |
| |
| { |
| Value* tuple = innerLoop->appendNew<VariableValue>(proc, B3::Get, Origin(), varInner); |
| Value* first = innerLoop->appendNew<ExtractValue>(proc, Origin(), Int32, tuple, 0); |
| Value* second = innerLoop->appendNew<ExtractValue>(proc, Origin(), Double, tuple, 1); |
| PatchpointValue* patchpoint = innerLoop->appendNew<PatchpointValue>(proc, tupleType, Origin()); |
| patchpoint->clobber(RegisterSet::macroScratchRegisters()); |
| patchpoint->append({ first, ValueRep::SomeRegisterWithClobber }); |
| patchpoint->append({ second, ValueRep::SomeRegisterWithClobber }); |
| patchpoint->resultConstraints = { ValueRep::SomeRegister, ValueRep::SomeRegister, ValueRep::SomeEarlyRegister }; |
| patchpoint->setGenerator([&] (CCallHelpers& jit, const StackmapGenerationParams& params) { |
| AllowMacroScratchRegisterUsage allowScratch(jit); |
| jit.move(params[3].gpr(), params[0].gpr()); |
| jit.move(CCallHelpers::TrustedImm32(0), params[2].gpr()); |
| jit.move(params[4].fpr(), params[1].fpr()); |
| }); |
| innerLoop->appendNew<VariableValue>(proc, Set, Origin(), varOuter, patchpoint); |
| innerLoop->appendNew<VariableValue>(proc, Set, Origin(), varInner, patchpoint); |
| Value* condition = innerLoop->appendNew<ExtractValue>(proc, Origin(), Int32, patchpoint, 2); |
| innerLoop->appendNew<VariableValue>(proc, Set, Origin(), tookInner, innerLoop->appendIntConstant(proc, Origin(), Int32, 1)); |
| innerLoop->appendNewControlValue(proc, Branch, Origin(), condition, FrequentedBlock(innerLoop), FrequentedBlock(outerLoop)); |
| } |
| |
| { |
| Value* tuple = outerContinuation->appendNew<VariableValue>(proc, B3::Get, Origin(), varInner); |
| Value* first = outerContinuation->appendNew<ExtractValue>(proc, Origin(), Int32, tuple, 0); |
| Value* second = outerContinuation->appendNew<ExtractValue>(proc, Origin(), Double, tuple, 1); |
| Value* result = outerContinuation->appendNew<Value>(proc, Add, Origin(), second, outerContinuation->appendNew<Value>(proc, IToD, Origin(), first)); |
| outerContinuation->appendNewControlValue(proc, Return, Origin(), result); |
| } |
| |
| proc.resetReachability(); |
| validate(proc); |
| if (shouldFixSSA) |
| fixSSA(proc); |
| CHECK_EQ(compileAndRun<double>(proc, first, second), first + second); |
| } |
| |
| void addTupleTests(const char* filter, Deque<RefPtr<SharedTask<void()>>>& tasks) |
| { |
| RUN_BINARY(testSimpleTuplePair, int32Operands(), int64Operands()); |
| RUN_BINARY(testSimpleTuplePairUnused, int32Operands(), int64Operands()); |
| RUN_BINARY(testSimpleTuplePairStack, int32Operands(), int64Operands()); |
| RUN((testBottomTupleValue<Int32, int32_t>)()); |
| RUN((testBottomTupleValue<Int64, int64_t>)()); |
| RUN((testBottomTupleValue<Float, float>)()); |
| RUN((testBottomTupleValue<Double, double>)()); |
| RUN((testTouchAllBottomTupleValue<Int32, int32_t>)()); |
| RUN((testTouchAllBottomTupleValue<Int64, int64_t>)()); |
| RUN((testTouchAllBottomTupleValue<Float, float>)()); |
| RUN((testTouchAllBottomTupleValue<Double, double>)()); |
| // use int64 as second argument because checking for NaN is annoying and doesn't really matter for this test. |
| RUN_BINARY(tailDupedTuplePair<true>, int32Operands(), int64Operands()); |
| RUN_BINARY(tailDupedTuplePair<false>, int32Operands(), int64Operands()); |
| RUN_BINARY(tuplePairVariableLoop<true>, int32Operands(), int64Operands()); |
| RUN_BINARY(tuplePairVariableLoop<false>, int32Operands(), int64Operands()); |
| RUN_BINARY(tupleNestedLoop<true>, int32Operands(), int64Operands()); |
| RUN_BINARY(tupleNestedLoop<false>, int32Operands(), int64Operands()); |
| } |
| |
| template <typename FloatType> |
| static void testFMaxMin() |
| { |
| auto checkResult = [&] (FloatType result, FloatType expected) { |
| CHECK_EQ(std::isnan(result), std::isnan(expected)); |
| if (!std::isnan(expected)) { |
| CHECK_EQ(result, expected); |
| CHECK_EQ(std::signbit(result), std::signbit(expected)); |
| } |
| }; |
| |
| auto runArgTest = [&] (bool max, FloatType arg1, FloatType arg2) { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* a = root->appendNew<ArgumentRegValue>(proc, Origin(), FPRInfo::argumentFPR0); |
| Value* b = root->appendNew<ArgumentRegValue>(proc, Origin(), FPRInfo::argumentFPR1); |
| if (std::is_same_v<FloatType, float>) { |
| a = root->appendNew<Value>(proc, Trunc, Origin(), a); |
| b = root->appendNew<Value>(proc, Trunc, Origin(), b); |
| } |
| Value* result = root->appendNew<Value>(proc, max ? FMax : FMin, Origin(), a, b); |
| root->appendNewControlValue(proc, Return, Origin(), result); |
| auto code = compileProc(proc); |
| return invoke<FloatType>(*code, arg1, arg2); |
| }; |
| |
| auto runConstTest = [&] (bool max, FloatType arg1, FloatType arg2) { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| Value* a; |
| Value* b; |
| if (std::is_same_v<FloatType, float>) { |
| a = root->appendNew<ConstFloatValue>(proc, Origin(), arg1); |
| b = root->appendNew<ConstFloatValue>(proc, Origin(), arg2); |
| } else { |
| a = root->appendNew<ConstDoubleValue>(proc, Origin(), arg1); |
| b = root->appendNew<ConstDoubleValue>(proc, Origin(), arg2); |
| } |
| Value* result = root->appendNew<Value>(proc, max ? FMax : FMin, Origin(), a, b); |
| root->appendNewControlValue(proc, Return, Origin(), result); |
| auto code = compileProc(proc); |
| return invoke<FloatType>(*code, arg1, arg2); |
| }; |
| |
| auto runMinTest = [&] (FloatType a, FloatType b, FloatType expected) { |
| checkResult(runArgTest(false, a, b), expected); |
| checkResult(runArgTest(false, b, a), expected); |
| checkResult(runConstTest(false, a, b), expected); |
| checkResult(runConstTest(false, b, a), expected); |
| }; |
| |
| auto runMaxTest = [&] (FloatType a, FloatType b, FloatType expected) { |
| checkResult(runArgTest(true, a, b), expected); |
| checkResult(runConstTest(true, a, b), expected); |
| checkResult(runArgTest(true, b, a), expected); |
| checkResult(runConstTest(true, b, a), expected); |
| }; |
| |
| auto inf = std::numeric_limits<FloatType>::infinity(); |
| |
| runMinTest(10.0, 0.0, 0.0); |
| runMinTest(-10.0, 4.0, -10.0); |
| runMinTest(4.1, 4.2, 4.1); |
| runMinTest(-4.1, -4.2, -4.2); |
| runMinTest(0.0, -0.0, -0.0); |
| runMinTest(-0.0, -0.0, -0.0); |
| runMinTest(0.0, 0.0, 0.0); |
| runMinTest(-inf, 0, -inf); |
| runMinTest(-inf, inf, -inf); |
| runMinTest(inf, 42.0, 42.0); |
| if constexpr (std::is_same_v<FloatType, float>) { |
| runMinTest(0.0, std::nanf(""), std::nanf("")); |
| runMinTest(std::nanf(""), 42.0, std::nanf("")); |
| } else if constexpr (std::is_same_v<FloatType, double>) { |
| runMinTest(0.0, std::nan(""), std::nan("")); |
| runMinTest(std::nan(""), 42.0, std::nan("")); |
| } |
| |
| |
| runMaxTest(0.0, 10.0, 10.0); |
| runMaxTest(-10.0, 4.0, 4.0); |
| runMaxTest(4.1, 4.2, 4.2); |
| runMaxTest(-4.1, -4.2, -4.1); |
| runMaxTest(0.0, -0.0, 0.0); |
| runMaxTest(-0.0, -0.0, -0.0); |
| runMaxTest(0.0, 0.0, 0.0); |
| runMaxTest(-inf, 0, 0); |
| runMaxTest(-inf, inf, inf); |
| runMaxTest(inf, 42.0, inf); |
| if constexpr (std::is_same_v<FloatType, float>) { |
| runMaxTest(0.0, std::nanf(""), std::nanf("")); |
| runMaxTest(std::nanf(""), 42.0, std::nanf("")); |
| } else if constexpr (std::is_same_v<FloatType, double>) { |
| runMaxTest(0.0, std::nan(""), std::nan("")); |
| runMaxTest(std::nan(""), 42.0, std::nan("")); |
| } |
| } |
| |
| void testFloatMaxMin() |
| { |
| testFMaxMin<float>(); |
| } |
| |
| void testDoubleMaxMin() |
| { |
| testFMaxMin<double>(); |
| } |
| |
| #endif // ENABLE(B3_JIT) |