Commit 66ae5f7d authored by binji's avatar binji Committed by Commit bot

Implement ldrex/strex instructions in ARM simulator

This CL implements ldrex, ldrexb, ldrexh, strex, strexb, and strexh in the
Simulator. These instructions provide "exclusive" access, which provides mutual
exclusion for concurrent threads of execution.

The ARM specification gives some leeway to implementors, but essentially
describes each processor as having Local Monitor and Global Monitor. The Local
Monitor is used to check the exclusivity state without having to synchronize
with other processors. The Global Monitor is shared between processors. We
model both to make it easier to match behavior with the spec.

When running with multiple OS threads, each thread has its own isolate, and
each isolate has its own Simulator. The Local Monitor is stored directly on the
Simulator, and the Global Monitor is stored as a lazy singleton. The Global
Monitor maintains a linked-list of all Simulators.

All loads/stores (even non-exclusive) are guarded by the Global Monitor's mutex.

BUG=v8:4614

Review-Url: https://codereview.chromium.org/2006183004
Cr-Commit-Position: refs/heads/master@{#42481}
parent 9662547c
......@@ -22,6 +22,10 @@
namespace v8 {
namespace internal {
// static
base::LazyInstance<Simulator::GlobalMonitor>::type Simulator::global_monitor_ =
LAZY_INSTANCE_INITIALIZER;
// This macro provides a platform independent use of sscanf. The reason for
// SScanF not being implemented in a platform independent way through
// ::v8::internal::OS in the same way as SNPrintF is that the
......@@ -710,9 +714,10 @@ Simulator::Simulator(Isolate* isolate) : isolate_(isolate) {
last_debugger_input_ = NULL;
}
Simulator::~Simulator() { free(stack_); }
Simulator::~Simulator() {
global_monitor_.Pointer()->RemoveProcessor(&global_monitor_processor_);
free(stack_);
}
// When the generated code calls an external reference we need to catch that in
// the simulator. The external reference will be a function compiled for the
......@@ -1040,78 +1045,166 @@ void Simulator::TrashCallerSaveRegisters() {
int Simulator::ReadW(int32_t addr, Instruction* instr) {
// All supported ARM targets allow unaligned accesses, so we don't need to
// check the alignment here.
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoad(addr);
intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
return *ptr;
}
int Simulator::ReadExW(int32_t addr, Instruction* instr) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoadExcl(addr, TransactionSize::Word);
global_monitor_.Pointer()->NotifyLoadExcl_Locked(addr,
&global_monitor_processor_);
intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
return *ptr;
}
void Simulator::WriteW(int32_t addr, int value, Instruction* instr) {
// All supported ARM targets allow unaligned accesses, so we don't need to
// check the alignment here.
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyStore(addr);
global_monitor_.Pointer()->NotifyStore_Locked(addr,
&global_monitor_processor_);
intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
*ptr = value;
}
int Simulator::WriteExW(int32_t addr, int value, Instruction* instr) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
if (local_monitor_.NotifyStoreExcl(addr, TransactionSize::Word) &&
global_monitor_.Pointer()->NotifyStoreExcl_Locked(
addr, &global_monitor_processor_)) {
intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
*ptr = value;
return 0;
} else {
return 1;
}
}
uint16_t Simulator::ReadHU(int32_t addr, Instruction* instr) {
// All supported ARM targets allow unaligned accesses, so we don't need to
// check the alignment here.
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoad(addr);
uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
return *ptr;
}
int16_t Simulator::ReadH(int32_t addr, Instruction* instr) {
// All supported ARM targets allow unaligned accesses, so we don't need to
// check the alignment here.
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoad(addr);
int16_t* ptr = reinterpret_cast<int16_t*>(addr);
return *ptr;
}
uint16_t Simulator::ReadExHU(int32_t addr, Instruction* instr) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoadExcl(addr, TransactionSize::HalfWord);
global_monitor_.Pointer()->NotifyLoadExcl_Locked(addr,
&global_monitor_processor_);
uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
return *ptr;
}
void Simulator::WriteH(int32_t addr, uint16_t value, Instruction* instr) {
// All supported ARM targets allow unaligned accesses, so we don't need to
// check the alignment here.
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyStore(addr);
global_monitor_.Pointer()->NotifyStore_Locked(addr,
&global_monitor_processor_);
uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
*ptr = value;
}
void Simulator::WriteH(int32_t addr, int16_t value, Instruction* instr) {
// All supported ARM targets allow unaligned accesses, so we don't need to
// check the alignment here.
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyStore(addr);
global_monitor_.Pointer()->NotifyStore_Locked(addr,
&global_monitor_processor_);
int16_t* ptr = reinterpret_cast<int16_t*>(addr);
*ptr = value;
}
int Simulator::WriteExH(int32_t addr, uint16_t value, Instruction* instr) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
if (local_monitor_.NotifyStoreExcl(addr, TransactionSize::HalfWord) &&
global_monitor_.Pointer()->NotifyStoreExcl_Locked(
addr, &global_monitor_processor_)) {
uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
*ptr = value;
return 0;
} else {
return 1;
}
}
uint8_t Simulator::ReadBU(int32_t addr) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoad(addr);
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
return *ptr;
}
int8_t Simulator::ReadB(int32_t addr) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoad(addr);
int8_t* ptr = reinterpret_cast<int8_t*>(addr);
return *ptr;
}
uint8_t Simulator::ReadExBU(int32_t addr) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoadExcl(addr, TransactionSize::Byte);
global_monitor_.Pointer()->NotifyLoadExcl_Locked(addr,
&global_monitor_processor_);
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
return *ptr;
}
void Simulator::WriteB(int32_t addr, uint8_t value) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyStore(addr);
global_monitor_.Pointer()->NotifyStore_Locked(addr,
&global_monitor_processor_);
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
*ptr = value;
}
void Simulator::WriteB(int32_t addr, int8_t value) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyStore(addr);
global_monitor_.Pointer()->NotifyStore_Locked(addr,
&global_monitor_processor_);
int8_t* ptr = reinterpret_cast<int8_t*>(addr);
*ptr = value;
}
int Simulator::WriteExB(int32_t addr, uint8_t value) {
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
if (local_monitor_.NotifyStoreExcl(addr, TransactionSize::Byte) &&
global_monitor_.Pointer()->NotifyStoreExcl_Locked(
addr, &global_monitor_processor_)) {
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
*ptr = value;
return 0;
} else {
return 1;
}
}
int32_t* Simulator::ReadDW(int32_t addr) {
// All supported ARM targets allow unaligned accesses, so we don't need to
// check the alignment here.
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyLoad(addr);
int32_t* ptr = reinterpret_cast<int32_t*>(addr);
return ptr;
}
......@@ -1120,6 +1213,10 @@ int32_t* Simulator::ReadDW(int32_t addr) {
void Simulator::WriteDW(int32_t addr, int32_t value1, int32_t value2) {
// All supported ARM targets allow unaligned accesses, so we don't need to
// check the alignment here.
base::LockGuard<base::Mutex> lock_guard(&global_monitor_.Pointer()->mutex);
local_monitor_.NotifyStore(addr);
global_monitor_.Pointer()->NotifyStore_Locked(addr,
&global_monitor_processor_);
int32_t* ptr = reinterpret_cast<int32_t*>(addr);
*ptr++ = value1;
*ptr = value2;
......@@ -2073,7 +2170,72 @@ void Simulator::DecodeType01(Instruction* instr) {
}
}
} else {
UNIMPLEMENTED(); // Not used by V8.
if (instr->Bits(24, 23) == 3) {
if (instr->Bit(20) == 1) {
// ldrex
int rt = instr->RtValue();
int rn = instr->RnValue();
int32_t addr = get_register(rn);
switch (instr->Bits(22, 21)) {
case 0: {
// Format(instr, "ldrex'cond 'rt, ['rn]");
int value = ReadExW(addr, instr);
set_register(rt, value);
break;
}
case 2: {
// Format(instr, "ldrexb'cond 'rt, ['rn]");
uint8_t value = ReadExBU(addr);
set_register(rt, value);
break;
}
case 3: {
// Format(instr, "ldrexh'cond 'rt, ['rn]");
uint16_t value = ReadExHU(addr, instr);
set_register(rt, value);
break;
}
default:
UNREACHABLE();
break;
}
} else {
// The instruction is documented as strex rd, rt, [rn], but the
// "rt" register is using the rm bits.
int rd = instr->RdValue();
int rt = instr->RmValue();
int rn = instr->RnValue();
int32_t addr = get_register(rn);
switch (instr->Bits(22, 21)) {
case 0: {
// Format(instr, "strex'cond 'rd, 'rm, ['rn]");
int value = get_register(rt);
int status = WriteExW(addr, value, instr);
set_register(rd, status);
break;
}
case 2: {
// Format(instr, "strexb'cond 'rd, 'rm, ['rn]");
uint8_t value = get_register(rt);
int status = WriteExB(addr, value);
set_register(rd, status);
break;
}
case 3: {
// Format(instr, "strexh'cond 'rd, 'rm, ['rn]");
uint16_t value = get_register(rt);
int status = WriteExH(addr, value, instr);
set_register(rd, status);
break;
}
default:
UNREACHABLE();
break;
}
}
} else {
UNIMPLEMENTED(); // Not used by V8.
}
}
} else {
// extra load/store instructions
......@@ -5345,6 +5507,207 @@ uintptr_t Simulator::PopAddress() {
return address;
}
Simulator::LocalMonitor::LocalMonitor()
: access_state_(MonitorAccess::Open),
tagged_addr_(0),
size_(TransactionSize::None) {}
void Simulator::LocalMonitor::Clear() {
access_state_ = MonitorAccess::Open;
tagged_addr_ = 0;
size_ = TransactionSize::None;
}
void Simulator::LocalMonitor::NotifyLoad(int32_t addr) {
if (access_state_ == MonitorAccess::Exclusive) {
// A load could cause a cache eviction which will affect the monitor. As a
// result, it's most strict to unconditionally clear the local monitor on
// load.
Clear();
}
}
void Simulator::LocalMonitor::NotifyLoadExcl(int32_t addr,
TransactionSize size) {
access_state_ = MonitorAccess::Exclusive;
tagged_addr_ = addr;
size_ = size;
}
void Simulator::LocalMonitor::NotifyStore(int32_t addr) {
if (access_state_ == MonitorAccess::Exclusive) {
// It is implementation-defined whether a non-exclusive store to an address
// covered by the local monitor during exclusive access transitions to open
// or exclusive access. See ARM DDI 0406C.b, A3.4.1.
//
// However, a store could cause a cache eviction which will affect the
// monitor. As a result, it's most strict to unconditionally clear the
// local monitor on store.
Clear();
}
}
bool Simulator::LocalMonitor::NotifyStoreExcl(int32_t addr,
TransactionSize size) {
if (access_state_ == MonitorAccess::Exclusive) {
// It is allowed for a processor to require that the address matches
// exactly (A3.4.5), so this comparison does not mask addr.
if (addr == tagged_addr_ && size_ == size) {
Clear();
return true;
} else {
// It is implementation-defined whether an exclusive store to a
// non-tagged address will update memory. Behavior is unpredictable if
// the transaction size of the exclusive store differs from that of the
// exclusive load. See ARM DDI 0406C.b, A3.4.5.
Clear();
return false;
}
} else {
DCHECK(access_state_ == MonitorAccess::Open);
return false;
}
}
Simulator::GlobalMonitor::Processor::Processor()
: access_state_(MonitorAccess::Open),
tagged_addr_(0),
next_(nullptr),
prev_(nullptr),
failure_counter_(0) {}
void Simulator::GlobalMonitor::Processor::Clear_Locked() {
access_state_ = MonitorAccess::Open;
tagged_addr_ = 0;
}
void Simulator::GlobalMonitor::Processor::NotifyLoadExcl_Locked(int32_t addr) {
access_state_ = MonitorAccess::Exclusive;
tagged_addr_ = addr;
}
void Simulator::GlobalMonitor::Processor::NotifyStore_Locked(
int32_t addr, bool is_requesting_processor) {
if (access_state_ == MonitorAccess::Exclusive) {
// It is implementation-defined whether a non-exclusive store by the
// requesting processor to an address covered by the global monitor
// during exclusive access transitions to open or exclusive access.
//
// For any other processor, the access state always transitions to open
// access.
//
// See ARM DDI 0406C.b, A3.4.2.
//
// However, similar to the local monitor, it is possible that a store
// caused a cache eviction, which can affect the montior, so
// conservatively, we always clear the monitor.
Clear_Locked();
}
}
bool Simulator::GlobalMonitor::Processor::NotifyStoreExcl_Locked(
int32_t addr, bool is_requesting_processor) {
if (access_state_ == MonitorAccess::Exclusive) {
if (is_requesting_processor) {
// It is allowed for a processor to require that the address matches
// exactly (A3.4.5), so this comparison does not mask addr.
if (addr == tagged_addr_) {
// The access state for the requesting processor after a successful
// exclusive store is implementation-defined, but according to the ARM
// DDI, this has no effect on the subsequent operation of the global
// monitor.
Clear_Locked();
// Introduce occasional strex failures. This is to simulate the
// behavior of hardware, which can randomly fail due to background
// cache evictions.
if (failure_counter_++ >= kMaxFailureCounter) {
failure_counter_ = 0;
return false;
} else {
return true;
}
}
} else if ((addr & kExclusiveTaggedAddrMask) ==
(tagged_addr_ & kExclusiveTaggedAddrMask)) {
// Check the masked addresses when responding to a successful lock by
// another processor so the implementation is more conservative (i.e. the
// granularity of locking is as large as possible.)
Clear_Locked();
return false;
}
}
return false;
}
Simulator::GlobalMonitor::GlobalMonitor() : head_(nullptr) {}
void Simulator::GlobalMonitor::NotifyLoadExcl_Locked(int32_t addr,
Processor* processor) {
processor->NotifyLoadExcl_Locked(addr);
PrependProcessor_Locked(processor);
}
void Simulator::GlobalMonitor::NotifyStore_Locked(int32_t addr,
Processor* processor) {
// Notify each processor of the store operation.
for (Processor* iter = head_; iter; iter = iter->next_) {
bool is_requesting_processor = iter == processor;
iter->NotifyStore_Locked(addr, is_requesting_processor);
}
}
bool Simulator::GlobalMonitor::NotifyStoreExcl_Locked(int32_t addr,
Processor* processor) {
DCHECK(IsProcessorInLinkedList_Locked(processor));
if (processor->NotifyStoreExcl_Locked(addr, true)) {
// Notify the other processors that this StoreExcl succeeded.
for (Processor* iter = head_; iter; iter = iter->next_) {
if (iter != processor) {
iter->NotifyStoreExcl_Locked(addr, false);
}
}
return true;
} else {
return false;
}
}
bool Simulator::GlobalMonitor::IsProcessorInLinkedList_Locked(
Processor* processor) const {
return head_ == processor || processor->next_ || processor->prev_;
}
void Simulator::GlobalMonitor::PrependProcessor_Locked(Processor* processor) {
if (IsProcessorInLinkedList_Locked(processor)) {
return;
}
if (head_) {
head_->prev_ = processor;
}
processor->prev_ = nullptr;
processor->next_ = head_;
head_ = processor;
}
void Simulator::GlobalMonitor::RemoveProcessor(Processor* processor) {
base::LockGuard<base::Mutex> lock_guard(&mutex);
if (!IsProcessorInLinkedList_Locked(processor)) {
return;
}
if (processor->prev_) {
processor->prev_->next_ = processor->next_;
} else {
head_ = processor->next_;
}
if (processor->next_) {
processor->next_->prev_ = processor->prev_;
}
processor->prev_ = nullptr;
processor->next_ = nullptr;
}
} // namespace internal
} // namespace v8
......
......@@ -14,6 +14,8 @@
#define V8_ARM_SIMULATOR_ARM_H_
#include "src/allocation.h"
#include "src/base/lazy-instance.h"
#include "src/base/platform/mutex.h"
#if !defined(USE_SIMULATOR)
// Running without a simulator on a native arm platform.
......@@ -302,19 +304,27 @@ class Simulator {
void PrintStopInfo(uint32_t code);
// Read and write memory.
// The *Ex functions are exclusive access. The writes return the strex status:
// 0 if the write succeeds, and 1 if the write fails.
inline uint8_t ReadBU(int32_t addr);
inline int8_t ReadB(int32_t addr);
uint8_t ReadExBU(int32_t addr);
inline void WriteB(int32_t addr, uint8_t value);
inline void WriteB(int32_t addr, int8_t value);
int WriteExB(int32_t addr, uint8_t value);
inline uint16_t ReadHU(int32_t addr, Instruction* instr);
inline int16_t ReadH(int32_t addr, Instruction* instr);
uint16_t ReadExHU(int32_t addr, Instruction* instr);
// Note: Overloaded on the sign of the value.
inline void WriteH(int32_t addr, uint16_t value, Instruction* instr);
inline void WriteH(int32_t addr, int16_t value, Instruction* instr);
int WriteExH(int32_t addr, uint16_t value, Instruction* instr);
inline int ReadW(int32_t addr, Instruction* instr);
int ReadExW(int32_t addr, Instruction* instr);
inline void WriteW(int32_t addr, int value, Instruction* instr);
int WriteExW(int32_t addr, int value, Instruction* instr);
int32_t* ReadDW(int32_t addr);
void WriteDW(int32_t addr, int32_t value1, int32_t value2);
......@@ -437,6 +447,94 @@ class Simulator {
char* desc;
};
StopCountAndDesc watched_stops_[kNumOfWatchedStops];
// Syncronization primitives. See ARM DDI 0406C.b, A2.9.
enum class MonitorAccess {
Open,
Exclusive,
};
enum class TransactionSize {
None = 0,
Byte = 1,
HalfWord = 2,
Word = 4,
};
// The least-significant bits of the address are ignored. The number of bits
// is implementation-defined, between 3 and 11. See ARM DDI 0406C.b, A3.4.3.
static const int32_t kExclusiveTaggedAddrMask = ~((1 << 11) - 1);
class LocalMonitor {
public:
LocalMonitor();
// These functions manage the state machine for the local monitor, but do
// not actually perform loads and stores. NotifyStoreExcl only returns
// true if the exclusive store is allowed; the global monitor will still
// have to be checked to see whether the memory should be updated.
void NotifyLoad(int32_t addr);
void NotifyLoadExcl(int32_t addr, TransactionSize size);
void NotifyStore(int32_t addr);
bool NotifyStoreExcl(int32_t addr, TransactionSize size);
private:
void Clear();
MonitorAccess access_state_;
int32_t tagged_addr_;
TransactionSize size_;
};
class GlobalMonitor {
public:
GlobalMonitor();
class Processor {
public:
Processor();
private:
friend class GlobalMonitor;
// These functions manage the state machine for the global monitor, but do
// not actually perform loads and stores.
void Clear_Locked();
void NotifyLoadExcl_Locked(int32_t addr);
void NotifyStore_Locked(int32_t addr, bool is_requesting_processor);
bool NotifyStoreExcl_Locked(int32_t addr, bool is_requesting_processor);
MonitorAccess access_state_;
int32_t tagged_addr_;
Processor* next_;
Processor* prev_;
// A strex can fail due to background cache evictions. Rather than
// simulating this, we'll just occasionally introduce cases where an
// exclusive store fails. This will happen once after every
// kMaxFailureCounter exclusive stores.
static const int kMaxFailureCounter = 5;
int failure_counter_;
};
// Exposed so it can be accessed by Simulator::{Read,Write}Ex*.
base::Mutex mutex;
void NotifyLoadExcl_Locked(int32_t addr, Processor* processor);
void NotifyStore_Locked(int32_t addr, Processor* processor);
bool NotifyStoreExcl_Locked(int32_t addr, Processor* processor);
// Called when the simulator is destroyed.
void RemoveProcessor(Processor* processor);
private:
bool IsProcessorInLinkedList_Locked(Processor* processor) const;
void PrependProcessor_Locked(Processor* processor);
Processor* head_;
};
LocalMonitor local_monitor_;
GlobalMonitor::Processor global_monitor_processor_;
static base::LazyInstance<GlobalMonitor>::type global_monitor_;
};
......
......@@ -207,6 +207,7 @@ v8_executable("cctest") {
"test-disasm-arm.cc",
"test-macro-assembler-arm.cc",
"test-run-wasm-relocation-arm.cc",
"test-simulator-arm.cc",
"wasm/test-run-wasm-simd.cc",
]
} else if (v8_current_cpu == "arm64") {
......
......@@ -248,6 +248,7 @@
'test-disasm-arm.cc',
'test-macro-assembler-arm.cc',
'test-run-wasm-relocation-arm.cc',
'test-simulator-arm.cc',
'wasm/test-run-wasm-simd-lowering.cc'
],
'cctest_sources_arm64': [ ### gcmole(arch:arm64) ###
......
// 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment