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..a34837665e3c041faa5066e9cf58780c15e56569 |
--- /dev/null |
+++ b/test/cctest/test-simulator-arm.cc |
@@ -0,0 +1,387 @@ |
+// 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 dest_reg, Register value_reg, |
+ Register addr_reg) { |
+ 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 AssembleLoadExcl(Assembler* assembler, MemoryAccess access, |
+ Register value_reg, Register addr_reg) { |
+ DCHECK(access.kind == MemoryAccess::Kind::LoadExcl); |
+ AssembleMemoryAccess(assembler, access, no_reg, value_reg, addr_reg); |
+} |
+ |
+static void AssembleStoreExcl(Assembler* assembler, MemoryAccess access, |
+ Register dest_reg, Register value_reg, |
+ Register addr_reg) { |
+ DCHECK(access.kind == MemoryAccess::Kind::StoreExcl); |
+ AssembleMemoryAccess(assembler, access, dest_reg, value_reg, addr_reg); |
+} |
+ |
+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); |
+ |
+ AssembleLoadExcl(&assm, access1, r1, r1); |
+ AssembleMemoryAccess(&assm, access2, r3, r2, r1); |
+ AssembleStoreExcl(&assm, access3, r0, r3, r1); |
+ |
+ __ 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, r0, r2, r1); |
+ __ bx(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()); |
+ |
+ 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 |