| /* |
| * Copyright (C) 2015-2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "testb3.h" |
| |
| #include <wtf/UniqueArray.h> |
| |
| #if ENABLE(B3_JIT) |
| |
| template<typename T> |
| void testAtomicWeakCAS() |
| { |
| constexpr Type type = NativeTraits<T>::type; |
| constexpr Width width = NativeTraits<T>::width; |
| |
| auto checkMyDisassembly = [&] (Compilation& compilation, bool fenced) { |
| if (isX86()) { |
| checkUsesInstruction(compilation, "lock"); |
| checkUsesInstruction(compilation, "cmpxchg"); |
| } else { |
| if (fenced) { |
| checkUsesInstruction(compilation, "ldax"); |
| checkUsesInstruction(compilation, "stlx"); |
| } else { |
| checkUsesInstruction(compilation, "ldx"); |
| checkUsesInstruction(compilation, "stx"); |
| } |
| } |
| }; |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* reloop = proc.addBlock(); |
| BasicBlock* done = proc.addBlock(); |
| |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| root->appendNew<Value>(proc, Jump, Origin()); |
| root->setSuccessors(reloop); |
| |
| reloop->appendNew<Value>( |
| proc, Branch, Origin(), |
| reloop->appendNew<AtomicValue>( |
| proc, AtomicWeakCAS, Origin(), width, |
| reloop->appendIntConstant(proc, Origin(), type, 42), |
| reloop->appendIntConstant(proc, Origin(), type, 0xbeef), |
| ptr)); |
| reloop->setSuccessors(done, reloop); |
| |
| done->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* reloop = proc.addBlock(); |
| BasicBlock* done = proc.addBlock(); |
| |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| root->appendNew<Value>(proc, Jump, Origin()); |
| root->setSuccessors(reloop); |
| |
| reloop->appendNew<Value>( |
| proc, Branch, Origin(), |
| reloop->appendNew<AtomicValue>( |
| proc, AtomicWeakCAS, Origin(), width, |
| reloop->appendIntConstant(proc, Origin(), type, 42), |
| reloop->appendIntConstant(proc, Origin(), type, 0xbeef), |
| ptr, 0, HeapRange(42), HeapRange())); |
| reloop->setSuccessors(done, reloop); |
| |
| done->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, false); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* succ = proc.addBlock(); |
| BasicBlock* fail = proc.addBlock(); |
| |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| root->appendNew<Value>( |
| proc, Branch, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicWeakCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| ptr)); |
| root->setSuccessors(succ, fail); |
| |
| succ->appendNew<MemoryValue>( |
| proc, storeOpcode(GP, width), Origin(), |
| succ->appendIntConstant(proc, Origin(), type, 100), |
| ptr); |
| succ->appendNew<Value>(proc, Return, Origin()); |
| |
| fail->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| while (value[0] == 42) |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(100)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* succ = proc.addBlock(); |
| BasicBlock* fail = proc.addBlock(); |
| |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| root->appendNew<Value>( |
| proc, Branch, Origin(), |
| root->appendNew<Value>( |
| proc, Equal, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicWeakCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| ptr), |
| root->appendIntConstant(proc, Origin(), Int32, 0))); |
| root->setSuccessors(fail, succ); |
| |
| succ->appendNew<MemoryValue>( |
| proc, storeOpcode(GP, width), Origin(), |
| succ->appendIntConstant(proc, Origin(), type, 100), |
| ptr); |
| succ->appendNew<Value>(proc, Return, Origin()); |
| |
| fail->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| while (value[0] == 42) |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(100)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicWeakCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0))); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| while (!invoke<bool>(*code, value)) { } |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| |
| value[0] = static_cast<T>(300); |
| CHECK(!invoke<bool>(*code, value)); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<Value>( |
| proc, Equal, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicWeakCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)), |
| root->appendNew<Const32Value>(proc, Origin(), 0))); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| while (invoke<bool>(*code, value)) { } |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| |
| value[0] = static_cast<T>(300); |
| CHECK(invoke<bool>(*code, value)); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicWeakCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| 42)); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| while (!invoke<bool>(*code, bitwise_cast<intptr_t>(value) - 42)) { } |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| |
| value[0] = static_cast<T>(300); |
| CHECK(!invoke<bool>(*code, bitwise_cast<intptr_t>(value) - 42)); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| } |
| |
| template<typename T> |
| void testAtomicStrongCAS() |
| { |
| constexpr Type type = NativeTraits<T>::type; |
| constexpr Width width = NativeTraits<T>::width; |
| |
| auto checkMyDisassembly = [&] (Compilation& compilation, bool fenced) { |
| if (isX86()) { |
| checkUsesInstruction(compilation, "lock"); |
| checkUsesInstruction(compilation, "cmpxchg"); |
| } else { |
| if (fenced) { |
| checkUsesInstruction(compilation, "ldax"); |
| checkUsesInstruction(compilation, "stlx"); |
| } else { |
| checkUsesInstruction(compilation, "ldx"); |
| checkUsesInstruction(compilation, "stx"); |
| } |
| } |
| }; |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* succ = proc.addBlock(); |
| BasicBlock* fail = proc.addBlock(); |
| |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| root->appendNew<Value>( |
| proc, Branch, Origin(), |
| root->appendNew<Value>( |
| proc, Equal, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicStrongCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| ptr), |
| root->appendIntConstant(proc, Origin(), type, 42))); |
| root->setSuccessors(succ, fail); |
| |
| succ->appendNew<MemoryValue>( |
| proc, storeOpcode(GP, width), Origin(), |
| succ->appendIntConstant(proc, Origin(), type, 100), |
| ptr); |
| succ->appendNew<Value>(proc, Return, Origin()); |
| |
| fail->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(100)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* succ = proc.addBlock(); |
| BasicBlock* fail = proc.addBlock(); |
| |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| root->appendNew<Value>( |
| proc, Branch, Origin(), |
| root->appendNew<Value>( |
| proc, Equal, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicStrongCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| ptr, 0, HeapRange(42), HeapRange()), |
| root->appendIntConstant(proc, Origin(), type, 42))); |
| root->setSuccessors(succ, fail); |
| |
| succ->appendNew<MemoryValue>( |
| proc, storeOpcode(GP, width), Origin(), |
| succ->appendIntConstant(proc, Origin(), type, 100), |
| ptr); |
| succ->appendNew<Value>(proc, Return, Origin()); |
| |
| fail->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(100)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, false); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* succ = proc.addBlock(); |
| BasicBlock* fail = proc.addBlock(); |
| |
| Value* ptr = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| root->appendNew<Value>( |
| proc, Branch, Origin(), |
| root->appendNew<Value>( |
| proc, NotEqual, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicStrongCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| ptr), |
| root->appendIntConstant(proc, Origin(), type, 42))); |
| root->setSuccessors(fail, succ); |
| |
| succ->appendNew<MemoryValue>( |
| proc, storeOpcode(GP, width), Origin(), |
| succ->appendIntConstant(proc, Origin(), type, 100), |
| ptr); |
| succ->appendNew<Value>(proc, Return, Origin()); |
| |
| fail->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(100)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| invoke<void>(*code, value); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicStrongCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0))); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| CHECK_EQ(invoke<typename NativeTraits<T>::CanonicalType>(*code, value), 42); |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| CHECK_EQ(invoke<typename NativeTraits<T>::CanonicalType>(*code, value), static_cast<typename NativeTraits<T>::CanonicalType>(static_cast<T>(300))); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(-1); |
| CHECK_EQ(invoke<typename NativeTraits<T>::CanonicalType>(*code, value), static_cast<typename NativeTraits<T>::CanonicalType>(static_cast<T>(-1))); |
| CHECK_EQ(value[0], static_cast<T>(-1)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| // Test for https://bugs.webkit.org/show_bug.cgi?id=169867. |
| |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<Value>( |
| proc, BitXor, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicStrongCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)), |
| root->appendIntConstant(proc, Origin(), type, 1))); |
| |
| typename NativeTraits<T>::CanonicalType one = 1; |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| CHECK_EQ(invoke<typename NativeTraits<T>::CanonicalType>(*code, value), 42 ^ one); |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| CHECK_EQ(invoke<typename NativeTraits<T>::CanonicalType>(*code, value), static_cast<typename NativeTraits<T>::CanonicalType>(static_cast<T>(300)) ^ one); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(-1); |
| CHECK_EQ(invoke<typename NativeTraits<T>::CanonicalType>(*code, value), static_cast<typename NativeTraits<T>::CanonicalType>(static_cast<T>(-1)) ^ one); |
| CHECK_EQ(value[0], static_cast<T>(-1)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<Value>( |
| proc, Equal, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicStrongCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)), |
| root->appendIntConstant(proc, Origin(), type, 42))); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| CHECK(invoke<bool>(*code, value)); |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| CHECK(!invoke<bool>(*code, value)); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<Value>( |
| proc, Equal, Origin(), |
| root->appendNew<Value>( |
| proc, NotEqual, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, AtomicStrongCAS, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendIntConstant(proc, Origin(), type, 0xbeef), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)), |
| root->appendIntConstant(proc, Origin(), type, 42)), |
| root->appendNew<Const32Value>(proc, Origin(), 0))); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 42; |
| value[1] = 13; |
| CHECK(invoke<bool>(*code, value)); |
| CHECK_EQ(value[0], static_cast<T>(0xbeef)); |
| CHECK_EQ(value[1], 13); |
| value[0] = static_cast<T>(300); |
| CHECK(!invoke<bool>(*code, &value)); |
| CHECK_EQ(value[0], static_cast<T>(300)); |
| CHECK_EQ(value[1], 13); |
| checkMyDisassembly(*code, true); |
| } |
| } |
| |
| template<typename T> |
| void testAtomicXchg(B3::Opcode opcode) |
| { |
| constexpr Type type = NativeTraits<T>::type; |
| constexpr Width width = NativeTraits<T>::width; |
| |
| auto doTheMath = [&] (T& memory, T operand) -> T { |
| T oldValue = memory; |
| switch (opcode) { |
| case AtomicXchgAdd: |
| memory += operand; |
| break; |
| case AtomicXchgAnd: |
| memory &= operand; |
| break; |
| case AtomicXchgOr: |
| memory |= operand; |
| break; |
| case AtomicXchgSub: |
| memory -= operand; |
| break; |
| case AtomicXchgXor: |
| memory ^= operand; |
| break; |
| case AtomicXchg: |
| memory = operand; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| return oldValue; |
| }; |
| |
| auto oldValue = [&] (T memory, T operand) -> T { |
| return doTheMath(memory, operand); |
| }; |
| |
| auto newValue = [&] (T memory, T operand) -> T { |
| doTheMath(memory, operand); |
| return memory; |
| }; |
| |
| auto checkMyDisassembly = [&] (Compilation& compilation, bool fenced) { |
| if (isX86()) |
| checkUsesInstruction(compilation, "lock"); |
| else { |
| if (fenced) { |
| checkUsesInstruction(compilation, "ldax"); |
| checkUsesInstruction(compilation, "stlx"); |
| } else { |
| checkUsesInstruction(compilation, "ldx"); |
| checkUsesInstruction(compilation, "stx"); |
| } |
| } |
| }; |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, opcode, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 1), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0))); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 5; |
| value[1] = 100; |
| CHECK_EQ(invoke<T>(*code, value), oldValue(5, 1)); |
| CHECK_EQ(value[0], newValue(5, 1)); |
| CHECK_EQ(value[1], 100); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<Value>( |
| proc, Return, Origin(), |
| root->appendNew<AtomicValue>( |
| proc, opcode, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0))); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 5; |
| value[1] = 100; |
| CHECK_EQ(invoke<T>(*code, value), oldValue(5, 42)); |
| CHECK_EQ(value[0], newValue(5, 42)); |
| CHECK_EQ(value[1], 100); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<AtomicValue>( |
| proc, opcode, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)); |
| root->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 5; |
| value[1] = 100; |
| invoke<T>(*code, value); |
| CHECK_EQ(value[0], newValue(5, 42)); |
| CHECK_EQ(value[1], 100); |
| checkMyDisassembly(*code, true); |
| } |
| |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| root->appendNew<AtomicValue>( |
| proc, opcode, Origin(), width, |
| root->appendIntConstant(proc, Origin(), type, 42), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| 0, HeapRange(42), HeapRange()); |
| root->appendNew<Value>(proc, Return, Origin()); |
| |
| auto code = compileProc(proc); |
| T value[2]; |
| value[0] = 5; |
| value[1] = 100; |
| invoke<T>(*code, value); |
| CHECK_EQ(value[0], newValue(5, 42)); |
| CHECK_EQ(value[1], 100); |
| checkMyDisassembly(*code, false); |
| } |
| } |
| |
| void addAtomicTests(const char* filter, Deque<RefPtr<SharedTask<void()>>>& tasks) |
| { |
| RUN(testAtomicWeakCAS<int8_t>()); |
| RUN(testAtomicWeakCAS<int16_t>()); |
| RUN(testAtomicWeakCAS<int32_t>()); |
| RUN(testAtomicWeakCAS<int64_t>()); |
| RUN(testAtomicStrongCAS<int8_t>()); |
| RUN(testAtomicStrongCAS<int16_t>()); |
| RUN(testAtomicStrongCAS<int32_t>()); |
| RUN(testAtomicStrongCAS<int64_t>()); |
| RUN(testAtomicXchg<int8_t>(AtomicXchgAdd)); |
| RUN(testAtomicXchg<int16_t>(AtomicXchgAdd)); |
| RUN(testAtomicXchg<int32_t>(AtomicXchgAdd)); |
| RUN(testAtomicXchg<int64_t>(AtomicXchgAdd)); |
| RUN(testAtomicXchg<int8_t>(AtomicXchgAnd)); |
| RUN(testAtomicXchg<int16_t>(AtomicXchgAnd)); |
| RUN(testAtomicXchg<int32_t>(AtomicXchgAnd)); |
| RUN(testAtomicXchg<int64_t>(AtomicXchgAnd)); |
| RUN(testAtomicXchg<int8_t>(AtomicXchgOr)); |
| RUN(testAtomicXchg<int16_t>(AtomicXchgOr)); |
| RUN(testAtomicXchg<int32_t>(AtomicXchgOr)); |
| RUN(testAtomicXchg<int64_t>(AtomicXchgOr)); |
| RUN(testAtomicXchg<int8_t>(AtomicXchgSub)); |
| RUN(testAtomicXchg<int16_t>(AtomicXchgSub)); |
| RUN(testAtomicXchg<int32_t>(AtomicXchgSub)); |
| RUN(testAtomicXchg<int64_t>(AtomicXchgSub)); |
| RUN(testAtomicXchg<int8_t>(AtomicXchgXor)); |
| RUN(testAtomicXchg<int16_t>(AtomicXchgXor)); |
| RUN(testAtomicXchg<int32_t>(AtomicXchgXor)); |
| RUN(testAtomicXchg<int64_t>(AtomicXchgXor)); |
| RUN(testAtomicXchg<int8_t>(AtomicXchg)); |
| RUN(testAtomicXchg<int16_t>(AtomicXchg)); |
| RUN(testAtomicXchg<int32_t>(AtomicXchg)); |
| RUN(testAtomicXchg<int64_t>(AtomicXchg)); |
| } |
| |
| template<typename CType, typename InputType> |
| void testLoad(B3::Type type, B3::Opcode opcode, InputType value) |
| { |
| // Simple load from an absolute address. |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| root->appendNewControlValue( |
| proc, Return, Origin(), |
| root->appendNew<MemoryValue>( |
| proc, opcode, type, Origin(), |
| root->appendNew<ConstPtrValue>(proc, Origin(), &value))); |
| |
| CHECK(isIdentical(compileAndRun<CType>(proc), modelLoad<CType>(value))); |
| } |
| |
| // Simple load from an address in a register. |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| root->appendNewControlValue( |
| proc, Return, Origin(), |
| root->appendNew<MemoryValue>( |
| proc, opcode, type, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0))); |
| |
| CHECK(isIdentical(compileAndRun<CType>(proc, &value), modelLoad<CType>(value))); |
| } |
| |
| // Simple load from an address in a register, at an offset. |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| root->appendNewControlValue( |
| proc, Return, Origin(), |
| root->appendNew<MemoryValue>( |
| proc, opcode, type, Origin(), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0), |
| static_cast<int32_t>(sizeof(InputType)))); |
| |
| CHECK(isIdentical(compileAndRun<CType>(proc, &value - 1), modelLoad<CType>(value))); |
| } |
| |
| // Load from a simple base-index with various scales. |
| for (unsigned logScale = 0; logScale <= 3; ++logScale) { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| root->appendNewControlValue( |
| proc, Return, Origin(), |
| root->appendNew<MemoryValue>( |
| proc, opcode, type, 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(), logScale))))); |
| |
| CHECK(isIdentical(compileAndRun<CType>(proc, &value - 2, (sizeof(InputType) * 2) >> logScale), modelLoad<CType>(value))); |
| } |
| |
| // Load from a simple base-index with various scales, but commuted. |
| for (unsigned logScale = 0; logScale <= 3; ++logScale) { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| |
| root->appendNewControlValue( |
| proc, Return, Origin(), |
| root->appendNew<MemoryValue>( |
| proc, opcode, type, 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(), logScale)), |
| root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0)))); |
| |
| CHECK(isIdentical(compileAndRun<CType>(proc, &value - 2, (sizeof(InputType) * 2) >> logScale), modelLoad<CType>(value))); |
| } |
| } |
| |
| template<typename T> |
| void testLoad(B3::Opcode opcode, int32_t value) |
| { |
| return testLoad<T>(B3::Int32, opcode, value); |
| } |
| |
| template<typename T> |
| void testLoad(B3::Type type, T value) |
| { |
| return testLoad<T>(type, Load, value); |
| } |
| |
| void addLoadTests(const char* filter, Deque<RefPtr<SharedTask<void()>>>& tasks) |
| { |
| RUN(testLoad(Int32, 60)); |
| RUN(testLoad(Int32, -60)); |
| RUN(testLoad(Int32, 1000)); |
| RUN(testLoad(Int32, -1000)); |
| RUN(testLoad(Int32, 1000000)); |
| RUN(testLoad(Int32, -1000000)); |
| RUN(testLoad(Int32, 1000000000)); |
| RUN(testLoad(Int32, -1000000000)); |
| RUN_BINARY(testLoad, { MAKE_OPERAND(Int64) }, int64Operands()); |
| RUN_BINARY(testLoad, { MAKE_OPERAND(Float) }, floatingPointOperands<float>()); |
| RUN_BINARY(testLoad, { MAKE_OPERAND(Double) }, floatingPointOperands<double>()); |
| |
| RUN(testLoad<int8_t>(Load8S, 60)); |
| RUN(testLoad<int8_t>(Load8S, -60)); |
| RUN(testLoad<int8_t>(Load8S, 1000)); |
| RUN(testLoad<int8_t>(Load8S, -1000)); |
| RUN(testLoad<int8_t>(Load8S, 1000000)); |
| RUN(testLoad<int8_t>(Load8S, -1000000)); |
| RUN(testLoad<int8_t>(Load8S, 1000000000)); |
| RUN(testLoad<int8_t>(Load8S, -1000000000)); |
| |
| RUN(testLoad<uint8_t>(Load8Z, 60)); |
| RUN(testLoad<uint8_t>(Load8Z, -60)); |
| RUN(testLoad<uint8_t>(Load8Z, 1000)); |
| RUN(testLoad<uint8_t>(Load8Z, -1000)); |
| RUN(testLoad<uint8_t>(Load8Z, 1000000)); |
| RUN(testLoad<uint8_t>(Load8Z, -1000000)); |
| RUN(testLoad<uint8_t>(Load8Z, 1000000000)); |
| RUN(testLoad<uint8_t>(Load8Z, -1000000000)); |
| |
| RUN(testLoad<int16_t>(Load16S, 60)); |
| RUN(testLoad<int16_t>(Load16S, -60)); |
| RUN(testLoad<int16_t>(Load16S, 1000)); |
| RUN(testLoad<int16_t>(Load16S, -1000)); |
| RUN(testLoad<int16_t>(Load16S, 1000000)); |
| RUN(testLoad<int16_t>(Load16S, -1000000)); |
| RUN(testLoad<int16_t>(Load16S, 1000000000)); |
| RUN(testLoad<int16_t>(Load16S, -1000000000)); |
| |
| RUN(testLoad<uint16_t>(Load16Z, 60)); |
| RUN(testLoad<uint16_t>(Load16Z, -60)); |
| RUN(testLoad<uint16_t>(Load16Z, 1000)); |
| RUN(testLoad<uint16_t>(Load16Z, -1000)); |
| RUN(testLoad<uint16_t>(Load16Z, 1000000)); |
| RUN(testLoad<uint16_t>(Load16Z, -1000000)); |
| RUN(testLoad<uint16_t>(Load16Z, 1000000000)); |
| RUN(testLoad<uint16_t>(Load16Z, -1000000000)); |
| } |
| |
| void testFastForwardCopy32() |
| { |
| #if CPU(X86_64) |
| for (const bool aligned : { true, false }) { |
| for (const bool overlap : { false, true }) { |
| for (size_t arrsize : { 1, 4, 5, 6, 8, 10, 12, 16, 20, 40, 100, 1000}) { |
| size_t overlapAmount = 5; |
| |
| UniqueArray<uint32_t> array1, array2; |
| uint32_t* arr1, *arr2; |
| |
| if (overlap) { |
| array1 = makeUniqueArray<uint32_t>(arrsize * 2); |
| arr1 = &array1[0]; |
| arr2 = arr1 + (arrsize - overlapAmount); |
| } else { |
| array1 = makeUniqueArray<uint32_t>(arrsize); |
| array2 = makeUniqueArray<uint32_t>(arrsize); |
| arr1 = &array1[0]; |
| arr2 = &array2[0]; |
| } |
| |
| if (!aligned && arrsize < 3) |
| continue; |
| if (overlap && arrsize <= overlapAmount + 3) |
| continue; |
| |
| if (!aligned) { |
| ++arr1; |
| ++arr2; |
| arrsize -= 1; |
| overlapAmount -= 1; |
| } |
| |
| for (size_t i = 0; i < arrsize; ++i) |
| arr1[i] = i; |
| |
| fastForwardCopy32(arr2, arr1, arrsize); |
| |
| if (overlap) { |
| for (size_t i = 0; i < arrsize - overlapAmount; ++i) |
| CHECK(arr2[i] == i); |
| for (size_t i = arrsize - overlapAmount; i < arrsize; ++i) |
| CHECK(arr2[i] == i - (arrsize - overlapAmount)); |
| } else { |
| for (size_t i = 0; i < arrsize; ++i) |
| CHECK(arr2[i] == i); |
| } |
| |
| if (!aligned) { |
| --arr1; |
| --arr2; |
| } |
| } |
| } |
| } |
| #endif |
| } |
| |
| void testByteCopyLoop() |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* head = proc.addBlock(); |
| BasicBlock* update = proc.addBlock(); |
| BasicBlock* continuation = proc.addBlock(); |
| |
| auto* arraySrc = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| auto* arrayDst = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1); |
| auto* arraySize = root->appendNew<Value>(proc, Trunc, Origin(), root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR2)); |
| auto* one = root->appendNew<Const32Value>(proc, Origin(), 1); |
| auto* two = root->appendNew<Const32Value>(proc, Origin(), 2); |
| UpsilonValue* startingIndex = root->appendNew<UpsilonValue>(proc, Origin(), root->appendNew<Const32Value>(proc, Origin(), 0)); |
| root->appendNew<Value>(proc, Jump, Origin()); |
| root->setSuccessors(FrequentedBlock(head)); |
| |
| auto* index = head->appendNew<Value>(proc, Phi, Int32, Origin()); |
| startingIndex->setPhi(index); |
| auto* loadIndex = head->appendNew<Value>(proc, Add, Origin(), arraySrc, |
| head->appendNew<Value>(proc, ZExt32, Origin(), head->appendNew<Value>(proc, Shl, Origin(), index, two))); |
| auto* storeIndex = head->appendNew<Value>(proc, Add, Origin(), arrayDst, |
| head->appendNew<Value>(proc, ZExt32, Origin(), head->appendNew<Value>(proc, Shl, Origin(), index, two))); |
| head->appendNew<MemoryValue>(proc, Store, Origin(), head->appendNew<MemoryValue>(proc, Load, Int32, Origin(), loadIndex), storeIndex); |
| auto* newIndex = head->appendNew<Value>(proc, Add, Origin(), index, one); |
| auto* cmpValue = head->appendNew<Value>(proc, GreaterThan, Origin(), newIndex, arraySize); |
| head->appendNew<Value>(proc, Branch, Origin(), cmpValue); |
| head->setSuccessors(FrequentedBlock(continuation), FrequentedBlock(update)); |
| |
| UpsilonValue* updateIndex = update->appendNew<UpsilonValue>(proc, Origin(), newIndex); |
| updateIndex->setPhi(index); |
| update->appendNew<Value>(proc, Jump, Origin()); |
| update->setSuccessors(FrequentedBlock(head)); |
| |
| continuation->appendNewControlValue(proc, Return, Origin()); |
| |
| int* arr1 = new int[3]; |
| int* arr2 = new int[3]; |
| |
| arr1[0] = 0; |
| arr1[1] = 0; |
| arr1[2] = 0; |
| arr2[0] = 1; |
| arr2[1] = 2; |
| arr2[2] = 3; |
| |
| compileAndRun<void>(proc, arr2, arr1, 3); |
| |
| CHECK_EQ(arr1[0], 1); |
| CHECK_EQ(arr1[1], 2); |
| CHECK_EQ(arr1[2], 3); |
| |
| delete[] arr1; |
| delete [] arr2; |
| } |
| |
| void testByteCopyLoopStartIsLoopDependent() |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* head = proc.addBlock(); |
| BasicBlock* update = proc.addBlock(); |
| BasicBlock* continuation = proc.addBlock(); |
| |
| auto* arraySrc = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| auto* arrayDst = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1); |
| auto* arraySize = root->appendNew<Value>(proc, Trunc, Origin(), root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR2)); |
| auto* one = root->appendNew<Const32Value>(proc, Origin(), 1); |
| auto* two = root->appendNew<Const32Value>(proc, Origin(), 2); |
| root->appendNew<Value>(proc, Jump, Origin()); |
| root->setSuccessors(FrequentedBlock(head)); |
| |
| UpsilonValue* startingIndex = head->appendNew<UpsilonValue>(proc, Origin(), head->appendNew<Const32Value>(proc, Origin(), 0)); |
| auto* index = head->appendNew<Value>(proc, Phi, Int32, Origin()); |
| startingIndex->setPhi(index); |
| auto* loadIndex = head->appendNew<Value>(proc, Add, Origin(), arraySrc, |
| head->appendNew<Value>(proc, ZExt32, Origin(), head->appendNew<Value>(proc, Shl, Origin(), index, two))); |
| auto* storeIndex = head->appendNew<Value>(proc, Add, Origin(), arrayDst, |
| head->appendNew<Value>(proc, ZExt32, Origin(), head->appendNew<Value>(proc, Shl, Origin(), index, two))); |
| head->appendNew<MemoryValue>(proc, Store, Origin(), head->appendNew<MemoryValue>(proc, Load, Int32, Origin(), loadIndex), storeIndex); |
| auto* newIndex = head->appendNew<Value>(proc, Add, Origin(), index, one); |
| auto* cmpValue = head->appendNew<Value>(proc, GreaterThan, Origin(), newIndex, arraySize); |
| head->appendNew<Value>(proc, Branch, Origin(), cmpValue); |
| head->setSuccessors(FrequentedBlock(continuation), FrequentedBlock(update)); |
| |
| UpsilonValue* updateIndex = update->appendNew<UpsilonValue>(proc, Origin(), newIndex); |
| updateIndex->setPhi(index); |
| update->appendNew<Value>(proc, Jump, Origin()); |
| update->setSuccessors(FrequentedBlock(head)); |
| |
| continuation->appendNewControlValue(proc, Return, Origin()); |
| |
| int* arr1 = new int[3]; |
| int* arr2 = new int[3]; |
| |
| arr1[0] = 0; |
| arr1[1] = 0; |
| arr1[2] = 0; |
| arr2[0] = 1; |
| arr2[1] = 2; |
| arr2[2] = 3; |
| |
| compileAndRun<void>(proc, arr2, arr1, 0); |
| |
| CHECK_EQ(arr1[0], 1); |
| CHECK_EQ(arr1[1], 0); |
| CHECK_EQ(arr1[2], 0); |
| |
| delete[] arr1; |
| delete [] arr2; |
| } |
| |
| void testByteCopyLoopBoundIsLoopDependent() |
| { |
| Procedure proc; |
| BasicBlock* root = proc.addBlock(); |
| BasicBlock* head = proc.addBlock(); |
| BasicBlock* update = proc.addBlock(); |
| BasicBlock* continuation = proc.addBlock(); |
| |
| auto* arraySrc = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0); |
| auto* arrayDst = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR1); |
| auto* one = root->appendNew<Const32Value>(proc, Origin(), 1); |
| auto* two = root->appendNew<Const32Value>(proc, Origin(), 2); |
| UpsilonValue* startingIndex = root->appendNew<UpsilonValue>(proc, Origin(), root->appendNew<Const32Value>(proc, Origin(), 0)); |
| root->appendNew<Value>(proc, Jump, Origin()); |
| root->setSuccessors(FrequentedBlock(head)); |
| |
| auto* index = head->appendNew<Value>(proc, Phi, Int32, Origin()); |
| startingIndex->setPhi(index); |
| auto* loadIndex = head->appendNew<Value>(proc, Add, Origin(), arraySrc, |
| head->appendNew<Value>(proc, ZExt32, Origin(), head->appendNew<Value>(proc, Shl, Origin(), index, two))); |
| auto* storeIndex = head->appendNew<Value>(proc, Add, Origin(), arrayDst, |
| head->appendNew<Value>(proc, ZExt32, Origin(), head->appendNew<Value>(proc, Shl, Origin(), index, two))); |
| head->appendNew<MemoryValue>(proc, Store, Origin(), head->appendNew<MemoryValue>(proc, Load, Int32, Origin(), loadIndex), storeIndex); |
| auto* newIndex = head->appendNew<Value>(proc, Add, Origin(), index, one); |
| auto* cmpValue = head->appendNew<Value>(proc, GreaterThan, Origin(), newIndex, index); |
| head->appendNew<Value>(proc, Branch, Origin(), cmpValue); |
| head->setSuccessors(FrequentedBlock(continuation), FrequentedBlock(update)); |
| |
| UpsilonValue* updateIndex = update->appendNew<UpsilonValue>(proc, Origin(), newIndex); |
| updateIndex->setPhi(index); |
| update->appendNew<Value>(proc, Jump, Origin()); |
| update->setSuccessors(FrequentedBlock(head)); |
| |
| continuation->appendNewControlValue(proc, Return, Origin()); |
| |
| int* arr1 = new int[3]; |
| int* arr2 = new int[3]; |
| |
| arr1[0] = 0; |
| arr1[1] = 0; |
| arr1[2] = 0; |
| arr2[0] = 1; |
| arr2[1] = 2; |
| arr2[2] = 3; |
| |
| compileAndRun<void>(proc, arr2, arr1, 3); |
| |
| CHECK_EQ(arr1[0], 1); |
| CHECK_EQ(arr1[1], 0); |
| CHECK_EQ(arr1[2], 0); |
| |
| delete[] arr1; |
| delete [] arr2; |
| } |
| |
| void addCopyTests(const char* filter, Deque<RefPtr<SharedTask<void()>>>& tasks) |
| { |
| RUN(testFastForwardCopy32()); |
| RUN(testByteCopyLoop()); |
| RUN(testByteCopyLoopStartIsLoopDependent()); |
| RUN(testByteCopyLoopBoundIsLoopDependent()); |
| } |
| |
| #endif // ENABLE(B3_JIT) |