Chromium Code Reviews| Index: test/cctest/test-simulator-arm.cc |
| diff --git a/test/cctest/test-simulator-arm.cc b/test/cctest/test-simulator-arm.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..16de87e3479183b5db722d6c0c087e944855871f |
| --- /dev/null |
| +++ b/test/cctest/test-simulator-arm.cc |
| @@ -0,0 +1,377 @@ |
| +// Copyright 2016 the V8 project authors. All rights reserved. |
| +// Redistribution and use in source and binary forms, with or without |
| +// modification, are permitted provided that the following conditions are |
| +// met: |
| +// |
| +// * Redistributions of source code must retain the above copyright |
| +// notice, this list of conditions and the following disclaimer. |
| +// * 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. |
| +// * Neither the name of Google Inc. nor the names of its |
| +// contributors may be used to endorse or promote products derived |
| +// from this software without specific prior written permission. |
| +// |
| +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| +// "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 THE COPYRIGHT |
| +// OWNER 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 "src/v8.h" |
| +#include "test/cctest/cctest.h" |
| + |
| +#include "src/arm/simulator-arm.h" |
| +#include "src/disassembler.h" |
| +#include "src/factory.h" |
| +#include "src/macro-assembler.h" |
| + |
| +#if defined(USE_SIMULATOR) |
| + |
| +#ifndef V8_TARGET_LITTLE_ENDIAN |
| +#error Expected ARM to be little-endian |
| +#endif |
| + |
| +using namespace v8::base; |
| +using namespace v8::internal; |
| + |
| +// Define these function prototypes to match JSEntryFunction in execution.cc. |
| +typedef Object* (*F1)(int x, int p1, int p2, int p3, int p4); |
| +typedef Object* (*F3)(void* p0, int p1, int p2, int p3, int p4); |
| + |
| +#define __ assm. |
| + |
| +struct MemoryAccess { |
| + enum class Kind { |
| + None, |
| + Load, |
| + LoadExcl, |
| + Store, |
| + StoreExcl, |
| + }; |
| + |
| + enum class Size { |
| + Byte, |
| + HalfWord, |
| + Word, |
| + }; |
| + |
| + MemoryAccess() : kind(Kind::None) {} |
| + MemoryAccess(Kind kind, Size size, size_t offset, int value = 0) |
| + : kind(kind), size(size), offset(offset), value(value) {} |
| + |
| + Kind kind; |
| + Size size; |
| + size_t offset; |
| + int value; |
| +}; |
| + |
| +struct TestData { |
| + explicit TestData(int w) : w(w) {} |
| + |
| + union { |
| + int32_t w; |
| + int16_t h; |
| + int8_t b; |
| + }; |
| + int dummy; |
| +}; |
| + |
| +static void AssembleMemoryAccess(Assembler* assembler, MemoryAccess access, |
| + Register addr_reg, Register value_reg, |
|
jbramley
2017/01/04 17:45:41
I think it would be more natural to order them lik
binji
2017/01/04 20:35:53
Done.
|
| + Register dest_reg = r0) { |
|
jbramley
2017/01/04 17:45:41
Shouldn't the default be no_reg?
binji
2017/01/04 20:35:53
I ended up just removing the default and using an
|
| + Assembler& assm = *assembler; |
| + __ add(addr_reg, r0, Operand(access.offset)); |
| + |
| + switch (access.kind) { |
| + case MemoryAccess::Kind::None: |
| + break; |
| + |
| + case MemoryAccess::Kind::Load: |
| + switch (access.size) { |
| + case MemoryAccess::Size::Byte: |
| + __ ldrb(value_reg, MemOperand(addr_reg)); |
| + break; |
| + |
| + case MemoryAccess::Size::HalfWord: |
| + __ ldrh(value_reg, MemOperand(addr_reg)); |
| + break; |
| + |
| + case MemoryAccess::Size::Word: |
| + __ ldr(value_reg, MemOperand(addr_reg)); |
| + break; |
| + } |
| + break; |
| + |
| + case MemoryAccess::Kind::LoadExcl: |
| + switch (access.size) { |
| + case MemoryAccess::Size::Byte: |
| + __ ldrexb(value_reg, addr_reg); |
| + break; |
| + |
| + case MemoryAccess::Size::HalfWord: |
| + __ ldrexh(value_reg, addr_reg); |
| + break; |
| + |
| + case MemoryAccess::Size::Word: |
| + __ ldrex(value_reg, addr_reg); |
| + break; |
| + } |
| + break; |
| + |
| + case MemoryAccess::Kind::Store: |
| + switch (access.size) { |
| + case MemoryAccess::Size::Byte: |
| + __ mov(value_reg, Operand(access.value)); |
| + __ strb(value_reg, MemOperand(addr_reg)); |
| + break; |
| + |
| + case MemoryAccess::Size::HalfWord: |
| + __ mov(value_reg, Operand(access.value)); |
| + __ strh(value_reg, MemOperand(addr_reg)); |
| + break; |
| + |
| + case MemoryAccess::Size::Word: |
| + __ mov(value_reg, Operand(access.value)); |
| + __ str(value_reg, MemOperand(addr_reg)); |
| + break; |
| + } |
| + break; |
| + |
| + case MemoryAccess::Kind::StoreExcl: |
| + switch (access.size) { |
| + case MemoryAccess::Size::Byte: |
| + __ mov(value_reg, Operand(access.value)); |
| + __ strexb(dest_reg, value_reg, addr_reg); |
| + break; |
| + |
| + case MemoryAccess::Size::HalfWord: |
| + __ mov(value_reg, Operand(access.value)); |
| + __ strexh(dest_reg, value_reg, addr_reg); |
| + break; |
| + |
| + case MemoryAccess::Size::Word: |
| + __ mov(value_reg, Operand(access.value)); |
| + __ strex(dest_reg, value_reg, addr_reg); |
| + break; |
| + } |
| + break; |
| + } |
| +} |
| + |
| +static void TestInvalidateExclusiveAccess( |
| + TestData initial_data, MemoryAccess access1, MemoryAccess access2, |
| + MemoryAccess access3, int expected_res, TestData expected_data) { |
| + Isolate* isolate = CcTest::i_isolate(); |
| + HandleScope scope(isolate); |
| + |
| + Assembler assm(isolate, NULL, 0); |
| + |
| + DCHECK(access1.kind == MemoryAccess::Kind::LoadExcl); |
| + DCHECK(access3.kind == MemoryAccess::Kind::StoreExcl); |
| + |
| + AssembleMemoryAccess(&assm, access1, r1, r1); |
| + AssembleMemoryAccess(&assm, access2, r1, r2, r3); |
| + AssembleMemoryAccess(&assm, access3, r1, r3, r0); |
| + |
| + __ mov(pc, Operand(lr)); |
| + |
| + CodeDesc desc; |
| + assm.GetCode(&desc); |
| + Handle<Code> code = isolate->factory()->NewCode( |
| + desc, Code::ComputeFlags(Code::STUB), Handle<Code>()); |
| + F3 f = FUNCTION_CAST<F3>(code->entry()); |
| + TestData t = initial_data; |
| + |
| + int res = |
| + reinterpret_cast<int>(CALL_GENERATED_CODE(isolate, f, &t, 0, 0, 0, 0)); |
| + CHECK_EQ(expected_res, res); |
| + switch (access3.size) { |
| + case MemoryAccess::Size::Byte: |
| + CHECK_EQ(expected_data.b, t.b); |
| + break; |
| + |
| + case MemoryAccess::Size::HalfWord: |
| + CHECK_EQ(expected_data.h, t.h); |
| + break; |
| + |
| + case MemoryAccess::Size::Word: |
| + CHECK_EQ(expected_data.w, t.w); |
| + break; |
| + } |
| +} |
| + |
| +TEST(simulator_invalidate_exclusive_access) { |
| + using Kind = MemoryAccess::Kind; |
| + using Size = MemoryAccess::Size; |
| + |
| + MemoryAccess ldrex_w(Kind::LoadExcl, Size::Word, offsetof(TestData, w)); |
| + MemoryAccess strex_w(Kind::StoreExcl, Size::Word, offsetof(TestData, w), 7); |
| + |
| + // Address mismatch. |
| + TestInvalidateExclusiveAccess( |
| + TestData(1), ldrex_w, |
| + MemoryAccess(Kind::LoadExcl, Size::Word, offsetof(TestData, dummy)), |
| + strex_w, 1, TestData(1)); |
| + |
| + // Size mismatch. |
| + TestInvalidateExclusiveAccess( |
| + TestData(1), ldrex_w, MemoryAccess(), |
| + MemoryAccess(Kind::StoreExcl, Size::HalfWord, offsetof(TestData, w), 7), |
| + 1, TestData(1)); |
| + |
| + // Load between ldrex/strex. |
| + TestInvalidateExclusiveAccess( |
| + TestData(1), ldrex_w, |
| + MemoryAccess(Kind::Load, Size::Word, offsetof(TestData, dummy)), strex_w, |
| + 1, TestData(1)); |
| + |
| + // Store between ldrex/strex. |
| + TestInvalidateExclusiveAccess( |
| + TestData(1), ldrex_w, |
| + MemoryAccess(Kind::Store, Size::Word, offsetof(TestData, dummy)), strex_w, |
| + 1, TestData(1)); |
| + |
| + // Match |
| + TestInvalidateExclusiveAccess(TestData(1), ldrex_w, MemoryAccess(), strex_w, |
| + 0, TestData(7)); |
| +} |
| + |
| +static int ExecuteMemoryAccess(Isolate* isolate, TestData* test_data, |
| + MemoryAccess access) { |
| + HandleScope scope(isolate); |
| + Assembler assm(isolate, NULL, 0); |
| + AssembleMemoryAccess(&assm, access, r1, r2, r0); |
| + __ mov(pc, Operand(lr)); |
|
jbramley
2017/01/04 17:45:41
"bx(lr)" would be better.
binji
2017/01/04 20:35:53
Done.
|
| + |
| + CodeDesc desc; |
| + assm.GetCode(&desc); |
| + Handle<Code> code = isolate->factory()->NewCode( |
| + desc, Code::ComputeFlags(Code::STUB), Handle<Code>()); |
| + F3 f = FUNCTION_CAST<F3>(code->entry()); |
| + |
| + return reinterpret_cast<int>( |
| + CALL_GENERATED_CODE(isolate, f, test_data, 0, 0, 0, 0)); |
| +} |
| + |
| +class MemoryAccessThread : public v8::base::Thread { |
| + public: |
| + MemoryAccessThread() |
| + : Thread(Options("MemoryAccessThread")), |
| + test_data_(NULL), |
| + is_finished_(false), |
| + has_request_(false), |
| + did_request_(false) {} |
| + |
| + virtual void Run() { |
| + v8::Isolate::CreateParams create_params; |
| + create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| + v8::Isolate* isolate = v8::Isolate::New(create_params); |
| + Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate); |
| + v8::Isolate::Scope scope(isolate); |
| + |
| + v8::base::LockGuard<v8::base::Mutex> lock_guard(&mutex_); |
| + while (!is_finished_) { |
| + while (!(has_request_ || is_finished_)) { |
| + has_request_cv_.Wait(&mutex_); |
| + } |
| + |
| + if (is_finished_) { |
| + break; |
| + } |
| + |
| + ExecuteMemoryAccess(i_isolate, test_data_, access_); |
| + has_request_ = false; |
| + did_request_ = true; |
| + did_request_cv_.NotifyOne(); |
| + } |
| + } |
| + |
| + void NextAndWait(TestData* test_data, MemoryAccess access) { |
| + DCHECK(!has_request_); |
| + v8::base::LockGuard<v8::base::Mutex> lock_guard(&mutex_); |
| + test_data_ = test_data; |
| + access_ = access; |
| + has_request_ = true; |
| + has_request_cv_.NotifyOne(); |
| + while (!did_request_) { |
| + did_request_cv_.Wait(&mutex_); |
| + } |
| + did_request_ = false; |
| + } |
| + |
| + void Finish() { |
| + v8::base::LockGuard<v8::base::Mutex> lock_guard(&mutex_); |
| + is_finished_ = true; |
| + has_request_cv_.NotifyOne(); |
| + } |
| + |
| + private: |
| + TestData* test_data_; |
| + MemoryAccess access_; |
| + bool is_finished_; |
| + bool has_request_; |
| + bool did_request_; |
| + v8::base::Mutex mutex_; |
| + v8::base::ConditionVariable has_request_cv_; |
| + v8::base::ConditionVariable did_request_cv_; |
| +}; |
| + |
| +TEST(simulator_invalidate_exclusive_access_threaded) { |
| + using Kind = MemoryAccess::Kind; |
| + using Size = MemoryAccess::Size; |
| + |
| + Isolate* isolate = CcTest::i_isolate(); |
| + HandleScope scope(isolate); |
| + |
| + TestData test_data(1); |
| + |
| + MemoryAccessThread thread; |
| + thread.Start(); |
| + |
| + MemoryAccess ldrex_w(Kind::LoadExcl, Size::Word, offsetof(TestData, w)); |
| + MemoryAccess strex_w(Kind::StoreExcl, Size::Word, offsetof(TestData, w), 7); |
| + |
| + // Exclusive store completed by another thread first. |
| + test_data = TestData(1); |
| + thread.NextAndWait(&test_data, MemoryAccess(Kind::LoadExcl, Size::Word, |
| + offsetof(TestData, w))); |
| + ExecuteMemoryAccess(isolate, &test_data, ldrex_w); |
| + thread.NextAndWait(&test_data, MemoryAccess(Kind::StoreExcl, Size::Word, |
| + offsetof(TestData, w), 5)); |
| + CHECK_EQ(1, ExecuteMemoryAccess(isolate, &test_data, strex_w)); |
| + CHECK_EQ(5, test_data.w); |
| + |
| + // Exclusive store completed by another thread; different address, but masked |
| + // to same |
| + test_data = TestData(1); |
| + ExecuteMemoryAccess(isolate, &test_data, ldrex_w); |
| + thread.NextAndWait(&test_data, MemoryAccess(Kind::LoadExcl, Size::Word, |
| + offsetof(TestData, dummy))); |
| + thread.NextAndWait(&test_data, MemoryAccess(Kind::StoreExcl, Size::Word, |
| + offsetof(TestData, dummy), 5)); |
| + CHECK_EQ(1, ExecuteMemoryAccess(isolate, &test_data, strex_w)); |
| + CHECK_EQ(1, test_data.w); |
| + |
| + // Test failure when store between ldrex/strex. |
| + test_data = TestData(1); |
| + ExecuteMemoryAccess(isolate, &test_data, ldrex_w); |
| + thread.NextAndWait(&test_data, MemoryAccess(Kind::Store, Size::Word, |
| + offsetof(TestData, dummy))); |
| + CHECK_EQ(1, ExecuteMemoryAccess(isolate, &test_data, strex_w)); |
| + CHECK_EQ(1, test_data.w); |
| + |
| + thread.Finish(); |
| + thread.Join(); |
| +} |
| + |
| +#undef __ |
| + |
| +#endif // USE_SIMULATOR |