// 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 = Kind::None; Size size = Size::Byte; size_t offset = 0; int value = 0; }; 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(isolate, &desc); Handle code = isolate->factory()->NewCode( desc, Code::ComputeFlags(Code::STUB), Handle()); F3 f = FUNCTION_CAST(code->entry()); TestData t = initial_data; int res = reinterpret_cast(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(isolate, &desc); Handle code = isolate->factory()->NewCode( desc, Code::ComputeFlags(Code::STUB), Handle()); F3 f = FUNCTION_CAST(code->entry()); return reinterpret_cast( 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); v8::Isolate::Scope scope(isolate); v8::base::LockGuard 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 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 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