summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Orion Hodson <oth@google.com> 2017-07-21 11:42:10 +0100
committer Orion Hodson <oth@google.com> 2017-07-24 17:29:40 +0100
commit56fe32eecd4f25237e66811fd766355a07908d22 (patch)
tree7c2c75d54edf0865598c106cb013f8c3794bd767
parent84b65e977302e1cf16d188636c22c164c7793554 (diff)
Jit Code Cache instruction pipeline flushing
Restores instruction pipeline flushing on all cores following crashes on ARMv7 with dual JIT code page mappings. We were inadvertantly toggling permission on a non-executable page rather than executable. Removes the data cache flush for roots data and replaces it with a sequentially consistent barrier. Fix MemMap::RemapAtEnd() when all pages are given out. To meet invariants checked in the destructor, the base pointer needs to be assigned as nullptr when this happens. Bug: 63833411 Bug: 62332932 Test: art/test.py --target Change-Id: I705cf5a3c80e78c4e912ea3d2c3c4aa89dee26bb
-rw-r--r--compiler/optimizing/optimizing_compiler.cc17
-rw-r--r--runtime/jit/jit_code_cache.cc88
-rw-r--r--runtime/jit/jit_code_cache.h6
-rw-r--r--runtime/mem_map.cc8
4 files changed, 79 insertions, 40 deletions
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index b76a0df861..f4b67b292c 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -1210,14 +1210,14 @@ bool OptimizingCompiler::JitCompile(Thread* self,
uint8_t* stack_map_data = nullptr;
uint8_t* method_info_data = nullptr;
uint8_t* roots_data = nullptr;
- uint32_t data_size = code_cache->ReserveData(self,
- stack_map_size,
- method_info_size,
- number_of_roots,
- method,
- &stack_map_data,
- &method_info_data,
- &roots_data);
+ code_cache->ReserveData(self,
+ stack_map_size,
+ method_info_size,
+ number_of_roots,
+ method,
+ &stack_map_data,
+ &method_info_data,
+ &roots_data);
if (stack_map_data == nullptr || roots_data == nullptr) {
return false;
}
@@ -1238,7 +1238,6 @@ bool OptimizingCompiler::JitCompile(Thread* self,
codegen->GetFpuSpillMask(),
code_allocator.GetMemory().data(),
code_allocator.GetSize(),
- data_size,
osr,
roots,
codegen->GetGraph()->HasShouldDeoptimizeFlag(),
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 27501b929c..653fb87ca9 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -121,6 +121,10 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
return nullptr;
}
+ // Align both capacities to page size, as that's the unit mspaces use.
+ initial_capacity = RoundDown(initial_capacity, 2 * kPageSize);
+ max_capacity = RoundDown(max_capacity, 2 * kPageSize);
+
std::string error_str;
// Map name specific for android_os_Debug.cpp accounting.
// Map in low 4gb to simplify accessing root tables for x86_64.
@@ -142,22 +146,21 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
return nullptr;
}
- // Align both capacities to page size, as that's the unit mspaces use.
- initial_capacity = RoundDown(initial_capacity, 2 * kPageSize);
- max_capacity = RoundDown(max_capacity, 2 * kPageSize);
-
// Create a region for JIT data and executable code. This will be
// laid out as:
//
// +----------------+ --------------------
- // : : ^ ^
+ // | code_sync_map_ | ^ code_sync_size ^
+ // | | v |
+ // +----------------+ -- |
+ // : : ^ |
// : post_code_map : | post_code_size |
// : [padding] : v |
// +----------------+ - |
// | | ^ |
- // | code_map | | code_size |
+ // | code_map | | code_size | total_mapping_size
// | [JIT Code] | v |
- // +----------------+ - | total_mapping_size
+ // +----------------+ - |
// : : ^ |
// : pre_code_map : | pre_code_size |
// : [padding] : v |
@@ -167,17 +170,23 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
// | [Jit Data] | v v
// +----------------+ --------------------
//
+ // The code_sync_map_ contains a page that we use flush CPU instruction
+ // pipelines (see FlushInstructionPipelines()).
+ //
// The padding regions - pre_code_map and post_code_map - exist to
// put some random distance between the writable JIT code mapping
// and the executable mapping. The padding is discarded at the end
// of this function.
- size_t total_mapping_size = kMaxMapSpacingPages * kPageSize;
- size_t data_size = RoundUp((max_capacity - total_mapping_size) / 2, kPageSize);
+ //
+ size_t data_size = (max_capacity - kMaxMapSpacingPages * kPageSize) / 2;
size_t pre_code_size =
- GetRandomNumber(kMinMapSpacingPages, kMaxMapSpacingPages) * kPageSize;
- size_t code_size = max_capacity - total_mapping_size - data_size;
- size_t post_code_size = total_mapping_size - pre_code_size;
- DCHECK_EQ(code_size + data_size + total_mapping_size, max_capacity);
+ GetRandomNumber(kMinMapSpacingPages, kMaxMapSpacingPages - 1) * kPageSize;
+ size_t code_size = max_capacity - data_size - kMaxMapSpacingPages * kPageSize;
+ size_t code_sync_size = kPageSize;
+ size_t post_code_size = kMaxMapSpacingPages * kPageSize - pre_code_size - code_sync_size;
+ DCHECK_EQ(data_size, code_size);
+ DCHECK_EQ(pre_code_size + post_code_size + code_sync_size, kMaxMapSpacingPages * kPageSize);
+ DCHECK_EQ(data_size + pre_code_size + code_size + post_code_size + code_sync_size, max_capacity);
// Create pre-code padding region after data region, discarded after
// code and data regions are set-up.
@@ -191,7 +200,7 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
return nullptr;
}
DCHECK_EQ(data_map->Size(), data_size);
- DCHECK_EQ(pre_code_map->Size(), pre_code_size + code_size + post_code_size);
+ DCHECK_EQ(pre_code_map->Size(), pre_code_size + code_size + post_code_size + code_sync_size);
// Create code region.
unique_fd writable_code_fd;
@@ -206,7 +215,7 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
return nullptr;
}
DCHECK_EQ(pre_code_map->Size(), pre_code_size);
- DCHECK_EQ(code_map->Size(), code_size + post_code_size);
+ DCHECK_EQ(code_map->Size(), code_size + post_code_size + code_sync_size);
// Padding after code region, discarded after code and data regions
// are set-up.
@@ -220,7 +229,19 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
return nullptr;
}
DCHECK_EQ(code_map->Size(), code_size);
+ DCHECK_EQ(post_code_map->Size(), post_code_size + code_sync_size);
+
+ std::unique_ptr<MemMap> code_sync_map(SplitMemMap(post_code_map.get(),
+ "jit-code-sync",
+ post_code_size,
+ kProtCode,
+ error_msg,
+ use_ashmem));
+ if (code_sync_map == nullptr) {
+ return nullptr;
+ }
DCHECK_EQ(post_code_map->Size(), post_code_size);
+ DCHECK_EQ(code_sync_map->Size(), code_sync_size);
std::unique_ptr<MemMap> writable_code_map;
if (use_two_mappings) {
@@ -246,6 +267,7 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
return new JitCodeCache(writable_code_map.release(),
code_map.release(),
data_map.release(),
+ code_sync_map.release(),
code_size,
data_size,
max_capacity,
@@ -255,6 +277,7 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
JitCodeCache::JitCodeCache(MemMap* writable_code_map,
MemMap* executable_code_map,
MemMap* data_map,
+ MemMap* code_sync_map,
size_t initial_code_capacity,
size_t initial_data_capacity,
size_t max_capacity,
@@ -265,6 +288,7 @@ JitCodeCache::JitCodeCache(MemMap* writable_code_map,
data_map_(data_map),
executable_code_map_(executable_code_map),
writable_code_map_(writable_code_map),
+ code_sync_map_(code_sync_map),
max_capacity_(max_capacity),
current_capacity_(initial_code_capacity + initial_data_capacity),
code_end_(initial_code_capacity),
@@ -382,7 +406,7 @@ void* JitCodeCache::ToWritableAddress(const void* executable_address) const {
class ScopedCodeCacheWrite : ScopedTrace {
public:
- explicit ScopedCodeCacheWrite(JitCodeCache* code_cache, bool only_for_tlb_shootdown = false)
+ explicit ScopedCodeCacheWrite(JitCodeCache* code_cache)
: ScopedTrace("ScopedCodeCacheWrite") {
ScopedTrace trace("mprotect all");
int prot_to_start_writing = kProtAll;
@@ -398,7 +422,7 @@ class ScopedCodeCacheWrite : ScopedTrace {
writable_map_ = code_cache->GetWritableMemMap();
// If we're using ScopedCacheWrite only for TLB shootdown, we limit the scope of mprotect to
// one page.
- size_ = only_for_tlb_shootdown ? kPageSize : writable_map_->Size();
+ size_ = writable_map_->Size();
CHECKED_MPROTECT(writable_map_->Begin(), size_, prot_to_start_writing);
}
~ScopedCodeCacheWrite() {
@@ -424,7 +448,6 @@ uint8_t* JitCodeCache::CommitCode(Thread* self,
size_t fp_spill_mask,
const uint8_t* code,
size_t code_size,
- size_t data_size,
bool osr,
Handle<mirror::ObjectArray<mirror::Object>> roots,
bool has_should_deoptimize_flag,
@@ -439,7 +462,6 @@ uint8_t* JitCodeCache::CommitCode(Thread* self,
fp_spill_mask,
code,
code_size,
- data_size,
osr,
roots,
has_should_deoptimize_flag,
@@ -457,7 +479,6 @@ uint8_t* JitCodeCache::CommitCode(Thread* self,
fp_spill_mask,
code,
code_size,
- data_size,
osr,
roots,
has_should_deoptimize_flag,
@@ -744,6 +765,20 @@ static void ClearMethodCounter(ArtMethod* method, bool was_warm) {
method->SetCounter(std::min(jit_warmup_threshold - 1, 1));
}
+static void FlushInstructionPiplines(uint8_t* sync_page) {
+ // After updating the JIT code cache we need to force all CPUs to
+ // flush their instruction pipelines. In the absence of system call
+ // to do this explicitly, we can achieve this indirectly by toggling
+ // permissions on an executable page. This should send an IPI to
+ // each core to update the TLB entry with the interrupt raised on
+ // each core causing the instruction pipeline to be flushed.
+ CHECKED_MPROTECT(sync_page, kPageSize, kProtAll);
+ // Ensure the sync_page is present otherwise a TLB update may not be
+ // necessary.
+ sync_page[0] = 0;
+ CHECKED_MPROTECT(sync_page, kPageSize, kProtCode);
+}
+
#ifdef __aarch64__
static void FlushJitCodeCacheRange(uint8_t* code_ptr,
@@ -835,7 +870,6 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self,
size_t fp_spill_mask,
const uint8_t* code,
size_t code_size,
- size_t data_size,
bool osr,
Handle<mirror::ObjectArray<mirror::Object>> roots,
bool has_should_deoptimize_flag,
@@ -878,6 +912,7 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self,
code_size);
FlushJitCodeCacheRange(code_ptr, writable_ptr, code_size);
+ FlushInstructionPiplines(code_sync_map_->Begin());
DCHECK(!Runtime::Current()->IsAotCompiler());
if (has_should_deoptimize_flag) {
@@ -927,13 +962,10 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self,
DCHECK_EQ(FromStackMapToRoots(stack_map), roots_data);
DCHECK_LE(roots_data, stack_map);
FillRootTable(roots_data, roots);
- {
- // Flush data cache, as compiled code references literals in it.
- // We also need a TLB shootdown to act as memory barrier across cores.
- ScopedCodeCacheWrite ccw(this, /* only_for_tlb_shootdown */ true);
- FlushDataCache(reinterpret_cast<char*>(roots_data),
- reinterpret_cast<char*>(roots_data + data_size));
- }
+
+ // Ensure the updates to the root table are visible with a store fence.
+ QuasiAtomic::ThreadFenceSequentiallyConsistent();
+
method_code_map_.Put(code_ptr, method);
if (osr) {
number_of_osr_compilations_++;
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index a062ce4ac2..175501f915 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -113,7 +113,6 @@ class JitCodeCache {
size_t fp_spill_mask,
const uint8_t* code,
size_t code_size,
- size_t data_size,
bool osr,
Handle<mirror::ObjectArray<mirror::Object>> roots,
bool has_should_deoptimize_flag,
@@ -255,6 +254,7 @@ class JitCodeCache {
JitCodeCache(MemMap* code_map,
MemMap* data_map,
MemMap* writable_code_map,
+ MemMap* code_sync_map,
size_t initial_code_capacity,
size_t initial_data_capacity,
size_t max_capacity,
@@ -272,7 +272,6 @@ class JitCodeCache {
size_t fp_spill_mask,
const uint8_t* code,
size_t code_size,
- size_t data_size,
bool osr,
Handle<mirror::ObjectArray<mirror::Object>> roots,
bool has_should_deoptimize_flag,
@@ -383,6 +382,9 @@ class JitCodeCache {
std::unique_ptr<MemMap> executable_code_map_;
// Mem map which holds a non-executable view of code for JIT.
std::unique_ptr<MemMap> writable_code_map_;
+ // Mem map which holds one executable page that we use for flushing instruction
+ // fetch buffers. The code on this page is never executed.
+ std::unique_ptr<MemMap> code_sync_map_;
// The opaque mspace for allocating code.
void* code_mspace_ GUARDED_BY(lock_);
// The opaque mspace for allocating data.
diff --git a/runtime/mem_map.cc b/runtime/mem_map.cc
index 17035dda8f..1ef72ba916 100644
--- a/runtime/mem_map.cc
+++ b/runtime/mem_map.cc
@@ -497,7 +497,7 @@ MemMap::~MemMap() {
MEMORY_TOOL_MAKE_UNDEFINED(base_begin_, base_size_);
int result = munmap(base_begin_, base_size_);
if (result == -1) {
- PLOG(FATAL) << "munmap failed";
+ PLOG(FATAL) << "munmap failed: " << BaseBegin() << "..." << BaseEnd();
}
}
@@ -561,6 +561,12 @@ MemMap* MemMap::RemapAtEnd(uint8_t* new_end,
size_ = new_end - reinterpret_cast<uint8_t*>(begin_);
base_size_ = new_base_end - reinterpret_cast<uint8_t*>(base_begin_);
DCHECK_LE(begin_ + size_, reinterpret_cast<uint8_t*>(base_begin_) + base_size_);
+ if (base_size_ == 0u) {
+ // All pages in this MemMap have been handed out. Invalidate base
+ // pointer to prevent the destructor calling munmap() on
+ // zero-length region (which can't succeed).
+ base_begin_ = nullptr;
+ }
size_t tail_size = old_end - new_end;
uint8_t* tail_base_begin = new_base_end;
size_t tail_base_size = old_base_end - new_base_end;