blob: 8e46f33b0123abf08b29f94a094f995b5107331e [file] [log] [blame]
// Copyright 2017 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-code-manager.h"
#include <iomanip>
#include "src/assembler-inl.h"
#include "src/base/atomic-utils.h"
#include "src/base/macros.h"
#include "src/base/platform/platform.h"
#include "src/code-stubs.h"
#include "src/codegen.h"
#include "src/disassembler.h"
#include "src/globals.h"
#include "src/macro-assembler.h"
#include "src/objects-inl.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-objects.h"
#define TRACE_HEAP(...) \
do { \
if (FLAG_wasm_trace_native_heap) PrintF(__VA_ARGS__); \
} while (false)
namespace v8 {
namespace internal {
namespace wasm {
namespace {
size_t native_module_ids = 0;
#if V8_TARGET_ARCH_X64
#define __ masm->
constexpr bool kModuleCanAllocateMoreMemory = false;
void GenerateJumpTrampoline(MacroAssembler* masm, Address target) {
__ movq(kScratchRegister, reinterpret_cast<uint64_t>(target));
__ jmp(kScratchRegister);
}
#undef __
#elif V8_TARGET_ARCH_S390X
#define __ masm->
constexpr bool kModuleCanAllocateMoreMemory = false;
void GenerateJumpTrampoline(MacroAssembler* masm, Address target) {
__ mov(ip, Operand(bit_cast<intptr_t, Address>(target)));
__ b(ip);
}
#undef __
#else
const bool kModuleCanAllocateMoreMemory = true;
#endif
void PatchTrampolineAndStubCalls(
const WasmCode* original_code, const WasmCode* new_code,
const std::unordered_map<Address, Address, AddressHasher>& reverse_lookup) {
RelocIterator orig_it(
original_code->instructions(), original_code->reloc_info(),
original_code->constant_pool(), RelocInfo::kCodeTargetMask);
for (RelocIterator it(new_code->instructions(), new_code->reloc_info(),
new_code->constant_pool(), RelocInfo::kCodeTargetMask);
!it.done(); it.next(), orig_it.next()) {
Address old_target = orig_it.rinfo()->target_address();
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_S390X
auto found = reverse_lookup.find(old_target);
DCHECK(found != reverse_lookup.end());
Address new_target = found->second;
#else
Address new_target = old_target;
#endif
it.rinfo()->set_target_address(nullptr, new_target, SKIP_WRITE_BARRIER,
SKIP_ICACHE_FLUSH);
}
}
} // namespace
DisjointAllocationPool::DisjointAllocationPool(Address start, Address end) {
ranges_.push_back({start, end});
}
void DisjointAllocationPool::Merge(DisjointAllocationPool&& other) {
auto dest_it = ranges_.begin();
auto dest_end = ranges_.end();
for (auto src_it = other.ranges_.begin(), src_end = other.ranges_.end();
src_it != src_end;) {
if (dest_it == dest_end) {
// everything else coming from src will be inserted
// at the back of ranges_ from now on.
ranges_.push_back(*src_it);
++src_it;
continue;
}
// Before or adjacent to dest. Insert or merge, and advance
// just src.
if (dest_it->first >= src_it->second) {
if (dest_it->first == src_it->second) {
dest_it->first = src_it->first;
} else {
ranges_.insert(dest_it, {src_it->first, src_it->second});
}
++src_it;
continue;
}
// Src is strictly after dest. Skip over this dest.
if (dest_it->second < src_it->first) {
++dest_it;
continue;
}
// Src is adjacent from above. Merge and advance
// just src, because the next src, if any, is bound to be
// strictly above the newly-formed range.
DCHECK_EQ(dest_it->second, src_it->first);
dest_it->second = src_it->second;
++src_it;
// Now that we merged, maybe this new range is adjacent to
// the next. Since we assume src to have come from the
// same original memory pool, it follows that the next src
// must be above or adjacent to the new bubble.
auto next_dest = dest_it;
++next_dest;
if (next_dest != dest_end && dest_it->second == next_dest->first) {
dest_it->second = next_dest->second;
ranges_.erase(next_dest);
}
// src_it points now at the next, if any, src
DCHECK_IMPLIES(src_it != src_end, src_it->first >= dest_it->second);
}
}
DisjointAllocationPool DisjointAllocationPool::Extract(size_t size,
ExtractionMode mode) {
DisjointAllocationPool ret;
for (auto it = ranges_.begin(), end = ranges_.end(); it != end;) {
auto current = it;
++it;
DCHECK_LT(current->first, current->second);
size_t current_size = reinterpret_cast<size_t>(current->second) -
reinterpret_cast<size_t>(current->first);
if (size == current_size) {
ret.ranges_.push_back(*current);
ranges_.erase(current);
return ret;
}
if (size < current_size) {
ret.ranges_.push_back({current->first, current->first + size});
current->first += size;
DCHECK(current->first < current->second);
return ret;
}
if (mode != kContiguous) {
size -= current_size;
ret.ranges_.push_back(*current);
ranges_.erase(current);
}
}
if (size > 0) {
Merge(std::move(ret));
return {};
}
return ret;
}
Address WasmCode::constant_pool() const {
if (FLAG_enable_embedded_constant_pool) {
if (constant_pool_offset_ < instructions().size()) {
return instructions().start() + constant_pool_offset_;
}
}
return nullptr;
}
size_t WasmCode::trap_handler_index() const {
CHECK(HasTrapHandlerIndex());
return static_cast<size_t>(trap_handler_index_);
}
void WasmCode::set_trap_handler_index(size_t value) {
trap_handler_index_ = value;
}
bool WasmCode::HasTrapHandlerIndex() const { return trap_handler_index_ >= 0; }
void WasmCode::ResetTrapHandlerIndex() { trap_handler_index_ = -1; }
void WasmCode::Print(Isolate* isolate) const {
OFStream os(stdout);
Disassemble(nullptr, isolate, os);
}
void WasmCode::Disassemble(const char* name, Isolate* isolate,
std::ostream& os) const {
if (name) os << "name: " << name << "\n";
if (index_.IsJust()) os << "index: " << index_.FromJust() << "\n";
os << "kind: " << GetWasmCodeKindAsString(kind_) << "\n";
os << "compiler: " << (is_liftoff() ? "Liftoff" : "TurboFan") << "\n";
size_t body_size = instructions().size();
os << "Body (size = " << body_size << ")\n";
#ifdef ENABLE_DISASSEMBLER
size_t instruction_size =
std::min(constant_pool_offset_, safepoint_table_offset_);
os << "Instructions (size = " << instruction_size << ")\n";
// TODO(mtrofin): rework the dependency on isolate and code in
// Disassembler::Decode.
Disassembler::Decode(isolate, &os, instructions().start(),
instructions().start() + instruction_size, nullptr);
os << "\n";
Object* source_positions_or_undef =
owner_->compiled_module()->source_positions()->get(index());
if (!source_positions_or_undef->IsUndefined(isolate)) {
os << "Source positions:\n pc offset position\n";
for (SourcePositionTableIterator it(
ByteArray::cast(source_positions_or_undef));
!it.done(); it.Advance()) {
os << std::setw(10) << std::hex << it.code_offset() << std::dec
<< std::setw(10) << it.source_position().ScriptOffset()
<< (it.is_statement() ? " statement" : "") << "\n";
}
os << "\n";
}
os << "RelocInfo (size = " << reloc_size_ << ")\n";
for (RelocIterator it(instructions(), reloc_info(), constant_pool());
!it.done(); it.next()) {
it.rinfo()->Print(isolate, os);
}
os << "\n";
#endif // ENABLE_DISASSEMBLER
}
const char* GetWasmCodeKindAsString(WasmCode::Kind kind) {
switch (kind) {
case WasmCode::kFunction:
return "wasm function";
case WasmCode::kWasmToWasmWrapper:
return "wasm-to-wasm";
case WasmCode::kWasmToJsWrapper:
return "wasm-to-js";
case WasmCode::kLazyStub:
return "lazy-compile";
case WasmCode::kInterpreterStub:
return "interpreter-entry";
case WasmCode::kCopiedStub:
return "copied stub";
case WasmCode::kTrampoline:
return "trampoline";
}
return "unknown kind";
}
WasmCode::~WasmCode() {
// Depending on finalizer order, the WasmCompiledModule finalizer may be
// called first, case in which we release here. If the InstanceFinalizer is
// called first, the handlers will be cleared in Reset, as-if the NativeModule
// may be later used again (which would be the case if the WasmCompiledModule
// were still held by a WasmModuleObject)
if (HasTrapHandlerIndex()) {
CHECK_LT(trap_handler_index(),
static_cast<size_t>(std::numeric_limits<int>::max()));
trap_handler::ReleaseHandlerData(static_cast<int>(trap_handler_index()));
}
}
NativeModule::NativeModule(uint32_t num_functions, uint32_t num_imports,
bool can_request_more, VirtualMemory* mem,
WasmCodeManager* code_manager)
: instance_id(native_module_ids++),
code_table_(num_functions),
num_imported_functions_(num_imports),
free_memory_(reinterpret_cast<Address>(mem->address()),
reinterpret_cast<Address>(mem->end())),
wasm_code_manager_(code_manager),
can_request_more_memory_(can_request_more) {
VirtualMemory my_mem;
owned_memory_.push_back(my_mem);
owned_memory_.back().TakeControl(mem);
owned_code_.reserve(num_functions);
}
void NativeModule::ResizeCodeTableForTest(size_t last_index) {
size_t new_size = last_index + 1;
if (new_size > FunctionCount()) {
Isolate* isolate = compiled_module()->GetIsolate();
code_table_.resize(new_size);
int grow_by = static_cast<int>(new_size) -
compiled_module()->source_positions()->length();
Handle<FixedArray> source_positions(compiled_module()->source_positions(),
isolate);
source_positions = isolate->factory()->CopyFixedArrayAndGrow(
source_positions, grow_by, TENURED);
compiled_module()->set_source_positions(*source_positions);
Handle<FixedArray> handler_table(compiled_module()->handler_table(),
isolate);
handler_table = isolate->factory()->CopyFixedArrayAndGrow(handler_table,
grow_by, TENURED);
compiled_module()->set_handler_table(*handler_table);
}
}
WasmCode* NativeModule::GetCode(uint32_t index) const {
return code_table_[index];
}
uint32_t NativeModule::FunctionCount() const {
DCHECK_LE(code_table_.size(), std::numeric_limits<uint32_t>::max());
return static_cast<uint32_t>(code_table_.size());
}
WasmCode* NativeModule::AddOwnedCode(
Vector<const byte> orig_instructions,
std::unique_ptr<const byte[]> reloc_info, size_t reloc_size,
Maybe<uint32_t> index, WasmCode::Kind kind, size_t constant_pool_offset,
uint32_t stack_slots, size_t safepoint_table_offset,
std::shared_ptr<ProtectedInstructions> protected_instructions,
bool is_liftoff) {
// both allocation and insertion in owned_code_ happen in the same critical
// section, thus ensuring owned_code_'s elements are rarely if ever moved.
base::LockGuard<base::Mutex> lock(&allocation_mutex_);
Address executable_buffer = AllocateForCode(orig_instructions.size());
if (executable_buffer == nullptr) return nullptr;
memcpy(executable_buffer, orig_instructions.start(),
orig_instructions.size());
std::unique_ptr<WasmCode> code(new WasmCode(
{executable_buffer, orig_instructions.size()}, std::move(reloc_info),
reloc_size, this, index, kind, constant_pool_offset, stack_slots,
safepoint_table_offset, std::move(protected_instructions), is_liftoff));
WasmCode* ret = code.get();
// TODO(mtrofin): We allocate in increasing address order, and
// even if we end up with segmented memory, we may end up only with a few
// large moves - if, for example, a new segment is below the current ones.
auto insert_before = std::upper_bound(owned_code_.begin(), owned_code_.end(),
code, owned_code_comparer_);
owned_code_.insert(insert_before, std::move(code));
wasm_code_manager_->FlushICache(ret->instructions().start(),
ret->instructions().size());
return ret;
}
WasmCode* NativeModule::AddCodeCopy(Handle<Code> code, WasmCode::Kind kind,
uint32_t index) {
WasmCode* ret = AddAnonymousCode(code, kind);
SetCodeTable(index, ret);
ret->index_ = Just(index);
compiled_module()->source_positions()->set(static_cast<int>(index),
code->source_position_table());
compiled_module()->handler_table()->set(static_cast<int>(index),
code->handler_table());
return ret;
}
WasmCode* NativeModule::AddInterpreterWrapper(Handle<Code> code,
uint32_t index) {
WasmCode* ret = AddAnonymousCode(code, WasmCode::kInterpreterStub);
ret->index_ = Just(index);
return ret;
}
WasmCode* NativeModule::SetLazyBuiltin(Handle<Code> code) {
DCHECK_NULL(lazy_builtin_);
lazy_builtin_ = AddAnonymousCode(code, WasmCode::kLazyStub);
for (uint32_t i = num_imported_functions(), e = FunctionCount(); i < e; ++i) {
SetCodeTable(i, lazy_builtin_);
}
return lazy_builtin_;
}
WasmCompiledModule* NativeModule::compiled_module() const {
return *compiled_module_;
}
void NativeModule::SetCompiledModule(
Handle<WasmCompiledModule> compiled_module) {
DCHECK(compiled_module_.is_null());
compiled_module_ = compiled_module;
}
WasmCode* NativeModule::AddAnonymousCode(Handle<Code> code,
WasmCode::Kind kind) {
std::unique_ptr<byte[]> reloc_info;
if (code->relocation_size() > 0) {
reloc_info.reset(new byte[code->relocation_size()]);
memcpy(reloc_info.get(), code->relocation_start(), code->relocation_size());
}
WasmCode* ret = AddOwnedCode(
{code->instruction_start(),
static_cast<size_t>(code->instruction_size())},
std::move(reloc_info), static_cast<size_t>(code->relocation_size()),
Nothing<uint32_t>(), kind, code->constant_pool_offset(),
(code->has_safepoint_info() ? code->stack_slots() : 0),
(code->has_safepoint_info() ? code->safepoint_table_offset() : 0), {});
if (ret == nullptr) return nullptr;
intptr_t delta = ret->instructions().start() - code->instruction_start();
int mask = RelocInfo::kApplyMask | RelocInfo::kCodeTargetMask |
RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT);
RelocIterator orig_it(*code, mask);
for (RelocIterator it(ret->instructions(), ret->reloc_info(),
ret->constant_pool(), mask);
!it.done(); it.next(), orig_it.next()) {
if (RelocInfo::IsCodeTarget(it.rinfo()->rmode())) {
Code* call_target =
Code::GetCodeFromTargetAddress(orig_it.rinfo()->target_address());
it.rinfo()->set_target_address(nullptr,
GetLocalAddressFor(handle(call_target)),
SKIP_WRITE_BARRIER, SKIP_ICACHE_FLUSH);
} else {
if (RelocInfo::IsEmbeddedObject(it.rinfo()->rmode())) {
DCHECK(Heap::IsImmovable(it.rinfo()->target_object()));
} else {
it.rinfo()->apply(delta);
}
}
}
return ret;
}
WasmCode* NativeModule::AddCode(
const CodeDesc& desc, uint32_t frame_slots, uint32_t index,
size_t safepoint_table_offset,
std::unique_ptr<ProtectedInstructions> protected_instructions,
bool is_liftoff) {
std::unique_ptr<byte[]> reloc_info;
if (desc.reloc_size) {
reloc_info.reset(new byte[desc.reloc_size]);
memcpy(reloc_info.get(), desc.buffer + desc.buffer_size - desc.reloc_size,
desc.reloc_size);
}
TurboAssembler* origin = reinterpret_cast<TurboAssembler*>(desc.origin);
WasmCode* ret = AddOwnedCode(
{desc.buffer, static_cast<size_t>(desc.instr_size)},
std::move(reloc_info), static_cast<size_t>(desc.reloc_size), Just(index),
WasmCode::kFunction, desc.instr_size - desc.constant_pool_size,
frame_slots, safepoint_table_offset, std::move(protected_instructions),
is_liftoff);
if (ret == nullptr) return nullptr;
SetCodeTable(index, ret);
// TODO(mtrofin): this is a copy and paste from Code::CopyFrom.
int mode_mask = RelocInfo::kCodeTargetMask |
RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT) |
RelocInfo::ModeMask(RelocInfo::RUNTIME_ENTRY) |
RelocInfo::kApplyMask;
// Needed to find target_object and runtime_entry on X64
AllowDeferredHandleDereference embedding_raw_address;
for (RelocIterator it(ret->instructions(), ret->reloc_info(),
ret->constant_pool(), mode_mask);
!it.done(); it.next()) {
RelocInfo::Mode mode = it.rinfo()->rmode();
if (mode == RelocInfo::EMBEDDED_OBJECT) {
Handle<HeapObject> p = it.rinfo()->target_object_handle(origin);
DCHECK_EQ(*p, p->GetIsolate()->heap()->undefined_value());
it.rinfo()->set_target_object(*p, SKIP_WRITE_BARRIER, SKIP_ICACHE_FLUSH);
} else if (RelocInfo::IsCodeTarget(mode)) {
// rewrite code handles to direct pointers to the first instruction in the
// code object
Handle<Object> p = it.rinfo()->target_object_handle(origin);
Code* code = Code::cast(*p);
it.rinfo()->set_target_address(nullptr, GetLocalAddressFor(handle(code)),
SKIP_WRITE_BARRIER, SKIP_ICACHE_FLUSH);
} else if (RelocInfo::IsRuntimeEntry(mode)) {
Address p = it.rinfo()->target_runtime_entry(origin);
it.rinfo()->set_target_runtime_entry(
origin->isolate(), p, SKIP_WRITE_BARRIER, SKIP_ICACHE_FLUSH);
} else {
intptr_t delta = ret->instructions().start() - desc.buffer;
it.rinfo()->apply(delta);
}
}
return ret;
}
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_S390X
Address NativeModule::CreateTrampolineTo(Handle<Code> code) {
MacroAssembler masm(code->GetIsolate(), nullptr, 0, CodeObjectRequired::kNo);
Address dest = code->instruction_start();
GenerateJumpTrampoline(&masm, dest);
CodeDesc code_desc;
masm.GetCode(nullptr, &code_desc);
WasmCode* wasm_code = AddOwnedCode(
{code_desc.buffer, static_cast<size_t>(code_desc.instr_size)}, nullptr, 0,
Nothing<uint32_t>(), WasmCode::kTrampoline, 0, 0, 0, {});
if (wasm_code == nullptr) return nullptr;
Address ret = wasm_code->instructions().start();
trampolines_.emplace(std::make_pair(dest, ret));
return ret;
}
#else
Address NativeModule::CreateTrampolineTo(Handle<Code> code) {
Address ret = code->instruction_start();
trampolines_.insert(std::make_pair(ret, ret));
return ret;
}
#endif
Address NativeModule::GetLocalAddressFor(Handle<Code> code) {
if (!Heap::IsImmovable(*code)) {
DCHECK(code->kind() == Code::STUB &&
CodeStub::MajorKeyFromKey(code->stub_key()) == CodeStub::DoubleToI);
uint32_t key = code->stub_key();
auto copy = stubs_.find(key);
if (copy == stubs_.end()) {
WasmCode* ret = AddAnonymousCode(code, WasmCode::kCopiedStub);
copy = stubs_.emplace(std::make_pair(key, ret)).first;
}
return copy->second->instructions().start();
} else {
Address index = code->instruction_start();
auto trampoline_iter = trampolines_.find(index);
if (trampoline_iter == trampolines_.end()) {
return CreateTrampolineTo(code);
} else {
return trampoline_iter->second;
}
}
}
WasmCode* NativeModule::GetExportedWrapper(uint32_t index) {
auto found = exported_wasm_to_wasm_wrappers_.find(index);
if (found != exported_wasm_to_wasm_wrappers_.end()) {
return found->second;
}
return nullptr;
}
WasmCode* NativeModule::AddExportedWrapper(Handle<Code> code, uint32_t index) {
WasmCode* ret = AddAnonymousCode(code, WasmCode::kWasmToWasmWrapper);
ret->index_ = Just(index);
exported_wasm_to_wasm_wrappers_.insert(std::make_pair(index, ret));
return ret;
}
void NativeModule::LinkAll() {
for (uint32_t index = 0; index < code_table_.size(); ++index) {
Link(index);
}
}
void NativeModule::Link(uint32_t index) {
WasmCode* code = code_table_[index];
// skip imports
if (!code) return;
int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_CALL);
for (RelocIterator it(code->instructions(), code->reloc_info(),
code->constant_pool(), mode_mask);
!it.done(); it.next()) {
uint32_t index = GetWasmCalleeTag(it.rinfo());
const WasmCode* target = GetCode(index);
if (target == nullptr) continue;
Address target_addr = target->instructions().start();
DCHECK_NOT_NULL(target);
it.rinfo()->set_wasm_call_address(nullptr, target_addr,
ICacheFlushMode::SKIP_ICACHE_FLUSH);
}
}
Address NativeModule::AllocateForCode(size_t size) {
// this happens under a lock assumed by the caller.
size = RoundUp(size, kCodeAlignment);
DisjointAllocationPool mem = free_memory_.Allocate(size);
if (mem.IsEmpty()) {
if (!can_request_more_memory_) return nullptr;
Address hint = owned_memory_.empty()
? nullptr
: reinterpret_cast<Address>(owned_memory_.back().end());
VirtualMemory empty_mem;
owned_memory_.push_back(empty_mem);
VirtualMemory& new_mem = owned_memory_.back();
wasm_code_manager_->TryAllocate(size, &new_mem, hint);
if (!new_mem.IsReserved()) return nullptr;
DisjointAllocationPool mem_pool(
reinterpret_cast<Address>(new_mem.address()),
reinterpret_cast<Address>(new_mem.end()));
wasm_code_manager_->AssignRanges(new_mem.address(), new_mem.end(), this);
free_memory_.Merge(std::move(mem_pool));
mem = free_memory_.Allocate(size);
if (mem.IsEmpty()) return nullptr;
}
Address ret = mem.ranges().front().first;
Address end = ret + size;
Address commit_start = RoundUp(ret, AllocatePageSize());
Address commit_end = RoundUp(end, AllocatePageSize());
// {commit_start} will be either ret or the start of the next page.
// {commit_end} will be the start of the page after the one in which
// the allocation ends.
// We start from an aligned start, and we know we allocated vmem in
// page multiples.
// We just need to commit what's not committed. The page in which we
// start is already committed (or we start at the beginning of a page).
// The end needs to be committed all through the end of the page.
if (commit_start < commit_end) {
#if V8_OS_WIN
// On Windows, we cannot commit a range that straddles different
// reservations of virtual memory. Because we bump-allocate, and because, if
// we need more memory, we append that memory at the end of the
// owned_memory_ list, we traverse that list in reverse order to find the
// reservation(s) that guide how to chunk the region to commit.
for (auto it = owned_memory_.crbegin(), rend = owned_memory_.crend();
it != rend && commit_start < commit_end; ++it) {
if (commit_end > it->end() || it->address() >= commit_end) continue;
Address start =
std::max(commit_start, reinterpret_cast<Address>(it->address()));
size_t commit_size = static_cast<size_t>(commit_end - start);
DCHECK(IsAligned(commit_size, AllocatePageSize()));
if (!wasm_code_manager_->Commit(start, commit_size)) {
return nullptr;
}
committed_memory_ += commit_size;
commit_end = start;
}
#else
size_t commit_size = static_cast<size_t>(commit_end - commit_start);
DCHECK(IsAligned(commit_size, AllocatePageSize()));
if (!wasm_code_manager_->Commit(commit_start, commit_size)) {
return nullptr;
}
committed_memory_ += commit_size;
#endif
}
DCHECK(IsAligned(reinterpret_cast<intptr_t>(ret), kCodeAlignment));
allocated_memory_.Merge(std::move(mem));
TRACE_HEAP("ID: %zu. Code alloc: %p,+%zu\n", instance_id,
reinterpret_cast<void*>(ret), size);
return ret;
}
WasmCode* NativeModule::Lookup(Address pc) {
if (owned_code_.empty()) return nullptr;
// Make a fake WasmCode temp, to look into owned_code_
std::unique_ptr<WasmCode> temp(new WasmCode(pc));
auto iter = std::upper_bound(owned_code_.begin(), owned_code_.end(), temp,
owned_code_comparer_);
if (iter == owned_code_.begin()) return nullptr;
--iter;
WasmCode* candidate = (*iter).get();
DCHECK_NOT_NULL(candidate);
if (candidate->instructions().start() <= pc &&
pc < candidate->instructions().start() +
candidate->instructions().size()) {
return candidate;
}
return nullptr;
}
WasmCode* NativeModule::CloneLazyBuiltinInto(uint32_t index) {
DCHECK_NOT_NULL(lazy_builtin());
WasmCode* ret = CloneCode(lazy_builtin());
SetCodeTable(index, ret);
ret->index_ = Just(index);
return ret;
}
bool NativeModule::CloneTrampolinesAndStubs(const NativeModule* other) {
for (auto& pair : other->trampolines_) {
Address key = pair.first;
Address local =
GetLocalAddressFor(handle(Code::GetCodeFromTargetAddress(key)));
if (local == nullptr) return false;
trampolines_.emplace(std::make_pair(key, local));
}
for (auto& pair : other->stubs_) {
uint32_t key = pair.first;
WasmCode* clone = CloneCode(pair.second);
if (!clone) return false;
stubs_.emplace(std::make_pair(key, clone));
}
return true;
}
WasmCode* NativeModule::CloneCode(const WasmCode* original_code) {
std::unique_ptr<byte[]> reloc_info;
if (original_code->reloc_info().size() > 0) {
reloc_info.reset(new byte[original_code->reloc_info().size()]);
memcpy(reloc_info.get(), original_code->reloc_info().start(),
original_code->reloc_info().size());
}
WasmCode* ret = AddOwnedCode(
original_code->instructions(), std::move(reloc_info),
original_code->reloc_info().size(), original_code->index_,
original_code->kind(), original_code->constant_pool_offset_,
original_code->stack_slots(), original_code->safepoint_table_offset_,
original_code->protected_instructions_);
if (ret == nullptr) return nullptr;
if (!ret->IsAnonymous()) {
SetCodeTable(ret->index(), ret);
}
intptr_t delta =
ret->instructions().start() - original_code->instructions().start();
for (RelocIterator it(ret->instructions(), ret->reloc_info(),
ret->constant_pool(), RelocInfo::kApplyMask);
!it.done(); it.next()) {
it.rinfo()->apply(delta);
}
return ret;
}
void NativeModule::SetCodeTable(uint32_t index, wasm::WasmCode* code) {
code_table_[index] = code;
}
NativeModule::~NativeModule() {
TRACE_HEAP("Deleting native module: %p\n", reinterpret_cast<void*>(this));
wasm_code_manager_->FreeNativeModuleMemories(this);
}
WasmCodeManager::WasmCodeManager(v8::Isolate* isolate, size_t max_committed)
: isolate_(isolate) {
DCHECK_LE(max_committed, kMaxWasmCodeMemory);
remaining_uncommitted_.SetValue(max_committed);
}
bool WasmCodeManager::Commit(Address start, size_t size) {
DCHECK(IsAligned(reinterpret_cast<size_t>(start), AllocatePageSize()));
DCHECK(IsAligned(size, AllocatePageSize()));
if (size > static_cast<size_t>(std::numeric_limits<intptr_t>::max())) {
return false;
}
// reserve the size.
intptr_t new_value = remaining_uncommitted_.Decrement(size);
if (new_value < 0) {
remaining_uncommitted_.Increment(size);
return false;
}
PageAllocator::Permission permission = FLAG_wasm_write_protect_code_memory
? PageAllocator::kReadWrite
: PageAllocator::kReadWriteExecute;
bool ret = SetPermissions(start, size, permission);
TRACE_HEAP("Setting rw permissions for %p:%p\n",
reinterpret_cast<void*>(start),
reinterpret_cast<void*>(start + size));
if (!ret) {
// Highly unlikely.
remaining_uncommitted_.Increment(size);
return false;
}
// This API assumes main thread
isolate_->AdjustAmountOfExternalAllocatedMemory(size);
if (WouldGCHelp()) {
// This API does not assume main thread, and would schedule
// a GC if called from a different thread, instead of synchronously
// doing one.
isolate_->MemoryPressureNotification(MemoryPressureLevel::kCritical);
}
return ret;
}
bool WasmCodeManager::WouldGCHelp() const {
// If all we have is one module, or none, no GC would help.
// GC would help if there's some remaining native modules that
// would be collected.
if (active_ <= 1) return false;
// We have an expectation on the largest size a native function
// may have.
constexpr size_t kMaxNativeFunction = 32 * MB;
intptr_t remaining = remaining_uncommitted_.Value();
DCHECK_GE(remaining, 0);
return static_cast<size_t>(remaining) < kMaxNativeFunction;
}
void WasmCodeManager::AssignRanges(void* start, void* end,
NativeModule* native_module) {
lookup_map_.insert(std::make_pair(
reinterpret_cast<Address>(start),
std::make_pair(reinterpret_cast<Address>(end), native_module)));
}
void WasmCodeManager::TryAllocate(size_t size, VirtualMemory* ret, void* hint) {
DCHECK_GT(size, 0);
size = RoundUp(size, AllocatePageSize());
if (hint == nullptr) hint = GetRandomMmapAddr();
if (!AlignedAllocVirtualMemory(size, static_cast<size_t>(AllocatePageSize()),
hint, ret)) {
DCHECK(!ret->IsReserved());
}
TRACE_HEAP("VMem alloc: %p:%p (%zu)\n", ret->address(), ret->end(),
ret->size());
}
size_t WasmCodeManager::GetAllocationChunk(const WasmModule& module) {
// TODO(mtrofin): this should pick up its 'maximal code range size'
// from something embedder-provided
if (kRequiresCodeRange) return kMaxWasmCodeMemory;
DCHECK(kModuleCanAllocateMoreMemory);
size_t ret = AllocatePageSize();
// a ballpark guesstimate on native inflation factor.
constexpr size_t kMultiplier = 4;
for (auto& function : module.functions) {
ret += kMultiplier * function.code.length();
}
return ret;
}
std::unique_ptr<NativeModule> WasmCodeManager::NewNativeModule(
const WasmModule& module) {
size_t code_size = GetAllocationChunk(module);
return NewNativeModule(
code_size, static_cast<uint32_t>(module.functions.size()),
module.num_imported_functions, kModuleCanAllocateMoreMemory);
}
std::unique_ptr<NativeModule> WasmCodeManager::NewNativeModule(
size_t size_estimate, uint32_t num_functions,
uint32_t num_imported_functions, bool can_request_more) {
VirtualMemory mem;
TryAllocate(size_estimate, &mem);
if (mem.IsReserved()) {
void* start = mem.address();
size_t size = mem.size();
void* end = mem.end();
std::unique_ptr<NativeModule> ret(new NativeModule(
num_functions, num_imported_functions, can_request_more, &mem, this));
TRACE_HEAP("New Module: ID:%zu. Mem: %p,+%zu\n", ret->instance_id, start,
size);
AssignRanges(start, end, ret.get());
++active_;
return ret;
}
V8::FatalProcessOutOfMemory("WasmCodeManager::NewNativeModule");
return nullptr;
}
bool NativeModule::SetExecutable(bool executable) {
if (is_executable_ == executable) return true;
TRACE_HEAP("Setting module %zu as executable: %d.\n", instance_id,
executable);
PageAllocator::Permission permission =
executable ? PageAllocator::kReadExecute : PageAllocator::kReadWrite;
if (FLAG_wasm_write_protect_code_memory) {
#if V8_OS_WIN
// On windows, we need to switch permissions per separate virtual memory
// reservation. This is really just a problem when the NativeModule is
// growable (meaning can_request_more_memory_). That's 32-bit in production,
// or unittests.
// For now, in that case, we commit at reserved memory granularity.
// Technically, that may be a waste, because we may reserve more than we
// use. On 32-bit though, the scarce resource is the address space -
// committed or not.
if (can_request_more_memory_) {
for (auto& vmem : owned_memory_) {
if (!SetPermissions(vmem.address(), vmem.size(), permission)) {
return false;
}
TRACE_HEAP("Set %p:%p to executable:%d\n", vmem.address(), vmem.end(),
executable);
}
is_executable_ = executable;
return true;
}
#endif
for (auto& range : allocated_memory_.ranges()) {
// allocated_memory_ is fine-grained, so we need to
// page-align it.
size_t range_size = RoundUp(
static_cast<size_t>(range.second - range.first), AllocatePageSize());
if (!SetPermissions(range.first, range_size, permission)) {
return false;
}
TRACE_HEAP("Set %p:%p to executable:%d\n",
reinterpret_cast<void*>(range.first),
reinterpret_cast<void*>(range.second), executable);
}
}
is_executable_ = executable;
return true;
}
std::unique_ptr<NativeModule> NativeModule::Clone() {
std::unique_ptr<NativeModule> ret = wasm_code_manager_->NewNativeModule(
owned_memory_.front().size(), FunctionCount(), num_imported_functions(),
can_request_more_memory_);
TRACE_HEAP("%zu cloned from %zu\n", ret->instance_id, instance_id);
if (!ret) return ret;
if (lazy_builtin() != nullptr) {
ret->lazy_builtin_ = ret->CloneCode(lazy_builtin());
}
if (!ret->CloneTrampolinesAndStubs(this)) return nullptr;
std::unordered_map<Address, Address, AddressHasher> reverse_lookup;
for (auto& pair : trampolines_) {
Address old_dest = pair.second;
auto local = ret->trampolines_.find(pair.first);
DCHECK(local != ret->trampolines_.end());
Address new_dest = local->second;
reverse_lookup.emplace(old_dest, new_dest);
}
for (auto& pair : stubs_) {
Address old_dest = pair.second->instructions().start();
auto local = ret->stubs_.find(pair.first);
DCHECK(local != ret->stubs_.end());
Address new_dest = local->second->instructions().start();
reverse_lookup.emplace(old_dest, new_dest);
}
for (auto& pair : ret->stubs_) {
WasmCode* new_stub = pair.second;
WasmCode* old_stub = stubs_.find(pair.first)->second;
PatchTrampolineAndStubCalls(old_stub, new_stub, reverse_lookup);
}
if (lazy_builtin_ != nullptr) {
PatchTrampolineAndStubCalls(lazy_builtin_, ret->lazy_builtin_,
reverse_lookup);
}
for (uint32_t i = num_imported_functions(), e = FunctionCount(); i < e; ++i) {
const WasmCode* original_code = GetCode(i);
switch (original_code->kind()) {
case WasmCode::kLazyStub: {
if (original_code->IsAnonymous()) {
ret->SetCodeTable(i, ret->lazy_builtin());
} else {
if (!ret->CloneLazyBuiltinInto(i)) return nullptr;
}
} break;
case WasmCode::kFunction: {
WasmCode* new_code = ret->CloneCode(original_code);
if (new_code == nullptr) return nullptr;
PatchTrampolineAndStubCalls(original_code, new_code, reverse_lookup);
} break;
default:
UNREACHABLE();
}
}
ret->specialization_data_ = specialization_data_;
return ret;
}
void WasmCodeManager::FreeNativeModuleMemories(NativeModule* native_module) {
DCHECK_GE(active_, 1);
--active_;
TRACE_HEAP("Freeing %zu\n", native_module->instance_id);
for (auto& vmem : native_module->owned_memory_) {
lookup_map_.erase(reinterpret_cast<Address>(vmem.address()));
Free(&vmem);
DCHECK(!vmem.IsReserved());
}
// No need to tell the GC anything if we're destroying the heap,
// which we currently indicate by having the isolate_ as null
if (isolate_ == nullptr) return;
size_t freed_mem = native_module->committed_memory_;
DCHECK(IsAligned(freed_mem, AllocatePageSize()));
remaining_uncommitted_.Increment(freed_mem);
isolate_->AdjustAmountOfExternalAllocatedMemory(
-static_cast<int64_t>(freed_mem));
}
// TODO(wasm): We can make this more efficient if needed. For
// example, we can preface the first instruction with a pointer to
// the WasmCode. In the meantime, we have a separate API so we can
// easily identify those places where we know we have the first
// instruction PC.
WasmCode* WasmCodeManager::GetCodeFromStartAddress(Address pc) const {
WasmCode* code = LookupCode(pc);
// This method can only be called for valid instruction start addresses.
DCHECK_NOT_NULL(code);
DCHECK_EQ(pc, code->instructions().start());
return code;
}
WasmCode* WasmCodeManager::LookupCode(Address pc) const {
if (lookup_map_.empty()) return nullptr;
auto iter = lookup_map_.upper_bound(pc);
if (iter == lookup_map_.begin()) return nullptr;
--iter;
Address range_start = iter->first;
Address range_end = iter->second.first;
NativeModule* candidate = iter->second.second;
DCHECK_NOT_NULL(candidate);
if (range_start <= pc && pc < range_end) {
return candidate->Lookup(pc);
}
return nullptr;
}
void WasmCodeManager::Free(VirtualMemory* mem) {
DCHECK(mem->IsReserved());
void* start = mem->address();
void* end = mem->end();
size_t size = mem->size();
mem->Free();
TRACE_HEAP("VMem Release: %p:%p (%zu)\n", start, end, size);
}
intptr_t WasmCodeManager::remaining_uncommitted() const {
return remaining_uncommitted_.Value();
}
void WasmCodeManager::FlushICache(Address start, size_t size) {
Assembler::FlushICache(reinterpret_cast<internal::Isolate*>(isolate_), start,
size);
}
NativeModuleModificationScope::NativeModuleModificationScope(
NativeModule* native_module)
: native_module_(native_module) {
if (native_module_) {
bool success = native_module_->SetExecutable(false);
CHECK(success);
}
}
NativeModuleModificationScope::~NativeModuleModificationScope() {
if (native_module_) {
bool success = native_module_->SetExecutable(true);
CHECK(success);
}
}
// On Intel, call sites are encoded as a displacement. For linking
// and for serialization/deserialization, we want to store/retrieve
// a tag (the function index). On Intel, that means accessing the
// raw displacement. Everywhere else, that simply means accessing
// the target address.
void SetWasmCalleeTag(RelocInfo* rinfo, uint32_t tag) {
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
*(reinterpret_cast<uint32_t*>(rinfo->target_address_address())) = tag;
#else
rinfo->set_target_address(nullptr, reinterpret_cast<Address>(tag),
SKIP_WRITE_BARRIER, SKIP_ICACHE_FLUSH);
#endif
}
uint32_t GetWasmCalleeTag(RelocInfo* rinfo) {
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
return *(reinterpret_cast<uint32_t*>(rinfo->target_address_address()));
#else
return static_cast<uint32_t>(
reinterpret_cast<size_t>(rinfo->target_address()));
#endif
}
} // namespace wasm
} // namespace internal
} // namespace v8
#undef TRACE_HEAP