blob: 447195d1a61683921a87cfa5ecf7b1f7bb05d9a0 [file] [log] [blame]
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/wasm-import-wrapper-cache.h"
#include <vector>
#include "src/codegen/assembler-inl.h"
#include "src/codegen/flush-instruction-cache.h"
#include "src/common/code-memory-access-inl.h"
#include "src/compiler/wasm-compiler.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/std-object-sizes.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-code-pointer-table-inl.h"
#include "src/wasm/wasm-engine.h"
namespace v8::internal::wasm {
WasmCode* WasmImportWrapperCache::ModificationScope::operator[](
const CacheKey& key) {
return cache_->entry_map_[key];
}
// The wrapper cache is shared per-process; but it is initialized on demand, and
// this action is triggered by some isolate; so we use this isolate for error
// reporting and running GCs if required.
void WasmImportWrapperCache::LazyInitialize(Isolate* triggering_isolate) {
base::MutexGuard lock(&mutex_);
if (code_allocator_.get() != nullptr) return; // Already initialized.
// Most wrappers are small (200-300 bytes), most modules don't need many.
// 32K is enough for ~100 wrappers.
static constexpr size_t kInitialReservationSize = 1 << 15;
// See {NewNativeModule} for reasoning.
static constexpr int kAllocationRetries = 2;
VirtualMemory code_space;
for (int retries = 0;; ++retries) {
code_space = GetWasmCodeManager()->TryAllocate(kInitialReservationSize);
if (code_space.IsReserved()) break;
if (retries == kAllocationRetries) {
V8::FatalProcessOutOfMemory(
triggering_isolate,
"Failed to allocate code space for import wrappers");
UNREACHABLE();
}
triggering_isolate->heap()->MemoryPressureNotification(
MemoryPressureLevel::kCritical, true);
}
code_allocator_.reset(
new WasmCodeAllocator(triggering_isolate->async_counters()));
base::AddressRegion initial_region = code_space.region();
code_allocator_->Init(std::move(code_space));
code_allocator_->InitializeCodeRange(nullptr, initial_region);
}
WasmCode* WasmImportWrapperCache::ModificationScope::AddWrapper(
const CacheKey& key, WasmCompilationResult result, WasmCode::Kind kind,
uint64_t signature_hash) {
cache_->mutex_.AssertHeld();
// Equivalent of NativeModule::AddCode().
const CodeDesc& desc = result.code_desc;
base::Vector<uint8_t> code_space =
cache_->code_allocator_->AllocateForWrapper(desc.instr_size);
// Equivalent of NativeModule::AddCodeWithCodeSpace().
base::Vector<uint8_t> reloc_info{
desc.buffer + desc.buffer_size - desc.reloc_size,
static_cast<size_t>(desc.reloc_size)};
// Contrary to the NativeModule, we don't track code size here, because we
// have no source to attribute it to.
const int safepoint_table_offset =
desc.safepoint_table_size == 0 ? 0 : desc.safepoint_table_offset;
const int handler_table_offset = desc.handler_table_offset;
const int constant_pool_offset = desc.constant_pool_offset;
const int code_comments_offset = desc.code_comments_offset;
const int instr_size = desc.instr_size;
{
WritableJitAllocation jit_allocation =
ThreadIsolation::RegisterJitAllocation(
reinterpret_cast<Address>(code_space.begin()), code_space.size(),
ThreadIsolation::JitAllocationType::kWasmCode, true);
jit_allocation.CopyCode(0, desc.buffer, desc.instr_size);
intptr_t delta = code_space.begin() - desc.buffer;
Address code_start = reinterpret_cast<Address>(code_space.begin());
Address constant_pool_start = code_start + constant_pool_offset;
for (WritableRelocIterator it(jit_allocation, code_space, reloc_info,
constant_pool_start, RelocInfo::kApplyMask);
!it.done(); it.next()) {
// Wrappers should contain no direct calls to Wasm functions.
DCHECK(!RelocInfo::IsWasmCall(it.rinfo()->rmode()));
// Wrappers should not call builtins via a Wasm jump table.
DCHECK(!RelocInfo::IsWasmStubCall(it.rinfo()->rmode()));
it.rinfo()->apply(delta);
}
}
FlushInstructionCache(code_space.begin(), code_space.size());
const int frame_slot_count = result.frame_slot_count;
const int ool_spill_count = result.ool_spill_count;
constexpr bool frame_has_feedback_slot = false;
WasmCode* code = new WasmCode{nullptr /* no NativeModule */,
kAnonymousFuncIndex,
code_space,
frame_slot_count,
ool_spill_count,
result.tagged_parameter_slots,
safepoint_table_offset,
handler_table_offset,
constant_pool_offset,
code_comments_offset,
instr_size,
result.protected_instructions_data.as_vector(),
reloc_info,
result.source_positions.as_vector(),
result.inlining_positions.as_vector(),
result.deopt_data.as_vector(),
kind,
ExecutionTier::kNone,
wasm::kNotForDebugging,
signature_hash,
frame_has_feedback_slot};
// The refcount of a WasmCode is initialized to 1. For wrappers, we track
// all refcounts explicitly, i.e. there will be a call to {IncRef()} that
// doesn't distinguish between newly compiled and older cached wrappers.
// So at this point, we lower the refcount to zero (reflecting the fact that
// there are no references yet), while using a WasmCodeRefScope to make sure
// that this doesn't cause the WasmCode to be freed immediately.
WasmCodeRefScope::AddRef(code);
code->DecRefOnLiveCode();
code->Validate();
cache_->entry_map_[key] = code;
// As an optimization, we assume that wrappers are allocated in increasing
// memory locations.
std::map<Address, WasmCode*>& codes = cache_->codes_;
codes.emplace_hint(codes.end(), code->instruction_start(), code);
return code;
}
WasmCode* WasmImportWrapperCache::FindWrapper(WasmCodePointer call_target) {
if (call_target == kInvalidWasmCodePointer) return nullptr;
base::MutexGuard lock(&mutex_);
auto iter = codes_.find(
GetProcessWideWasmCodePointerTable()->GetEntrypointWithoutSignatureCheck(
call_target));
if (iter == codes_.end()) return nullptr;
WasmCodeRefScope::AddRef(iter->second);
if (iter->second->is_dying()) return nullptr;
return iter->second;
}
WasmCode* WasmImportWrapperCache::CompileWasmImportCallWrapper(
Isolate* isolate, ImportCallKind kind, const CanonicalSig* sig,
CanonicalTypeIndex sig_index, bool source_positions, int expected_arity,
Suspend suspend) {
WasmCompilationResult result = compiler::CompileWasmImportCallWrapper(
kind, sig, source_positions, expected_arity, suspend);
WasmCode* wasm_code;
{
ModificationScope cache_scope(this);
CacheKey key(kind, sig_index, expected_arity, suspend);
// Now that we have the lock (in the form of the cache_scope), check
// again whether another thread has just created the wrapper.
wasm_code = cache_scope[key];
if (wasm_code) {
WasmCodeRefScope::AddRef(wasm_code);
if (!wasm_code->is_dying()) return wasm_code;
}
wasm_code = cache_scope.AddWrapper(key, std::move(result),
WasmCode::Kind::kWasmToJsWrapper,
sig->signature_hash());
}
// To avoid lock order inversion, code printing must happen after the
// end of the {cache_scope}.
wasm_code->MaybePrint();
isolate->counters()->wasm_generated_code_size()->Increment(
wasm_code->instructions().length());
isolate->counters()->wasm_reloc_size()->Increment(
wasm_code->reloc_info().length());
if (GetWasmEngine()->LogWrapperCode(wasm_code)) {
// Log the code immediately in the current isolate.
GetWasmEngine()->LogOutstandingCodesForIsolate(isolate);
}
return wasm_code;
}
void WasmImportWrapperCache::LogForIsolate(Isolate* isolate) {
base::MutexGuard lock(&mutex_);
for (const auto& entry : codes_) {
entry.second->LogCode(isolate, "", -1); // No source URL, no ScriptId.
}
}
void WasmImportWrapperCache::Free(std::vector<WasmCode*>& wrappers) {
base::MutexGuard lock(&mutex_);
if (codes_.empty() || wrappers.empty()) return;
// {WasmCodeAllocator::FreeCode()} wants code objects to be sorted.
std::sort(wrappers.begin(), wrappers.end(), [](WasmCode* a, WasmCode* b) {
return a->instruction_start() < b->instruction_start();
});
// Possible future optimization: if the size of {wrappers} is very small,
// don't allocate the set, use linear scan instead.
std::unordered_set<WasmCode*> fastset;
for (WasmCode* wrapper : wrappers) {
fastset.insert(wrapper);
codes_.erase(wrapper->instruction_start());
}
for (auto it = entry_map_.begin(); it != entry_map_.end();) {
if (fastset.contains(it->second)) {
it = entry_map_.erase(it);
} else {
it++;
}
}
code_allocator_->FreeCode(base::VectorOf(wrappers));
for (WasmCode* wrapper : wrappers) {
delete wrapper;
}
// Make sure nobody tries to access stale pointers.
wrappers.clear();
}
WasmCode* WasmImportWrapperCache::MaybeGet(ImportCallKind kind,
CanonicalTypeIndex type_index,
int expected_arity,
Suspend suspend) const {
base::MutexGuard lock(&mutex_);
auto it = entry_map_.find({kind, type_index, expected_arity, suspend});
if (it == entry_map_.end()) return nullptr;
WasmCodeRefScope::AddRef(it->second);
if (it->second->is_dying()) return nullptr;
return it->second;
}
WasmCode* WasmImportWrapperCache::Lookup(Address pc) const {
// This can be called from the disassembler via `code->MaybePrint()` in
// `AddWrapper()` above, so we need a recursive mutex.
base::MutexGuard lock(&mutex_);
auto iter = codes_.upper_bound(pc);
if (iter == codes_.begin()) return nullptr;
--iter;
WasmCode* candidate = iter->second;
DCHECK_EQ(candidate->instruction_start(), iter->first);
if (!candidate->contains(pc)) return nullptr;
WasmCodeRefScope::AddRef(candidate);
// Note: this function is used for iterating the stack, where dying
// code objects can still have their last few activations, so we
// must return {candidate} even if {candidate->is_dying()}.
return candidate;
}
size_t WasmImportWrapperCache::EstimateCurrentMemoryConsumption() const {
UPDATE_WHEN_CLASS_CHANGES(WasmImportWrapperCache, 88);
base::MutexGuard lock(&mutex_);
return sizeof(WasmImportWrapperCache) + ContentSize(entry_map_) +
ContentSize(codes_);
}
} // namespace v8::internal::wasm