[wasm] Add size estimates for managed objects

This CL estimates the sizes of the important managed objects in WASM:
the decoded module {WasmModule}, the native module that contains code
{NativeModule}, and the natively-allocated indirect and import tables
{WasmInstanceNativeAllocations}.

Since Managed<T> updates the isolate's external allocated memory,
it is no longer necessary to do so upon committing or releasing a
native module's memory.

R=mstarzinger@chromium.org
CC=ulan@chromium.org

Bug: v8:7424
Change-Id: Iff4e07d0d328383a925febd654ccbfc95f0930e9
Reviewed-on: https://chromium-review.googlesource.com/1079067
Commit-Queue: Ben Titzer <titzer@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53675}
This commit is contained in:
Ben L. Titzer 2018-06-12 17:51:30 +02:00 committed by Commit Bot
parent dffc2a5a68
commit 26d0d95eb8
8 changed files with 64 additions and 33 deletions

View File

@ -1370,7 +1370,7 @@ MaybeHandle<WasmModuleObject> CompileToModuleObjectInternal(
// The {managed_module} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object.
size_t module_size = 0; // TODO(titzer): estimate size of decoded module.
size_t module_size = EstimateWasmModuleSize(module.get());
Handle<Managed<WasmModule>> managed_module =
Managed<WasmModule>::FromUniquePtr(isolate, module_size,
std::move(module));
@ -2901,7 +2901,7 @@ void AsyncCompileJob::FinishCompile() {
// The {managed_module} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object.
size_t module_size = 0; // TODO(titzer): estimate size of decoded module.
size_t module_size = EstimateWasmModuleSize(module_.get());
Handle<Managed<WasmModule>> managed_module =
Managed<WasmModule>::FromUniquePtr(isolate_, module_size,
std::move(module_));

View File

@ -885,8 +885,6 @@ bool WasmCodeManager::Commit(Address start, size_t size) {
remaining_uncommitted_code_space_.fetch_add(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
@ -927,34 +925,39 @@ void WasmCodeManager::TryAllocate(size_t size, VirtualMemory* ret, void* hint) {
reinterpret_cast<void*>(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;
size_t WasmCodeManager::EstimateNativeModuleSize(const WasmModule* module) {
constexpr size_t kCodeSizeMultiplier = 4;
constexpr size_t kImportSize = 32 * kPointerSize;
for (auto& function : module.functions) {
ret += kMultiplier * function.code.length();
size_t estimate =
AllocatePageSize() /* TODO(titzer): 1 page spot bonus */ +
sizeof(NativeModule) +
(sizeof(WasmCode*) * module->functions.size() /* code table size */) +
(sizeof(WasmCode) * module->functions.size() /* code object size */) +
(kImportSize * module->num_imported_functions /* import size */);
for (auto& function : module->functions) {
estimate += kCodeSizeMultiplier * function.code.length();
}
return ret;
return estimate;
}
std::unique_ptr<NativeModule> WasmCodeManager::NewNativeModule(
const WasmModule& module, ModuleEnv& env) {
size_t code_size = GetAllocationChunk(module);
size_t memory_estimate = EstimateNativeModuleSize(&module);
return NewNativeModule(
code_size, static_cast<uint32_t>(module.functions.size()),
memory_estimate, static_cast<uint32_t>(module.functions.size()),
module.num_imported_functions, kModuleCanAllocateMoreMemory, env);
}
std::unique_ptr<NativeModule> WasmCodeManager::NewNativeModule(
size_t size_estimate, uint32_t num_functions,
size_t memory_estimate, uint32_t num_functions,
uint32_t num_imported_functions, bool can_request_more, ModuleEnv& env) {
VirtualMemory mem;
TryAllocate(size_estimate, &mem);
// If the code must be contiguous, reserve enough address space up front.
size_t vmem_size = kRequiresCodeRange ? kMaxWasmCodeMemory : memory_estimate;
TryAllocate(vmem_size, &mem);
if (mem.IsReserved()) {
Address start = mem.address();
size_t size = mem.size();
@ -1041,8 +1044,6 @@ void WasmCodeManager::FreeNativeModule(NativeModule* native_module) {
// which we currently indicate by having the isolate_ as null
if (isolate_ == nullptr) return;
remaining_uncommitted_code_space_.fetch_add(code_size);
isolate_->AdjustAmountOfExternalAllocatedMemory(
-static_cast<int64_t>(code_size));
}
// TODO(wasm): We can make this more efficient if needed. For

View File

@ -439,6 +439,7 @@ class V8_EXPORT_PRIVATE WasmCodeManager final {
void SetModuleCodeSizeHistogram(Histogram* histogram) {
module_code_size_mb_ = histogram;
}
static size_t EstimateNativeModuleSize(const WasmModule* module);
private:
friend class NativeModule;
@ -452,7 +453,6 @@ class V8_EXPORT_PRIVATE WasmCodeManager final {
void FreeNativeModule(NativeModule*);
void Free(VirtualMemory* mem);
void AssignRanges(Address start, Address end, NativeModule*);
size_t GetAllocationChunk(const WasmModule& module);
bool WouldGCHelp() const;
std::map<Address, std::pair<Address, NativeModule*>> lookup_map_;

View File

@ -341,6 +341,24 @@ Handle<FixedArray> DecodeLocalNames(Isolate* isolate,
}
return locals_names;
}
namespace {
template <typename T>
inline size_t VectorSize(const std::vector<T>& vector) {
return sizeof(T) * vector.size();
}
} // namespace
size_t EstimateWasmModuleSize(const WasmModule* module) {
size_t estimate =
sizeof(WasmModule) + VectorSize(module->signatures) +
VectorSize(module->signature_ids) + VectorSize(module->functions) +
VectorSize(module->data_segments) + VectorSize(module->function_tables) +
VectorSize(module->import_table) + VectorSize(module->export_table) +
VectorSize(module->exceptions) + VectorSize(module->table_inits);
// TODO(wasm): include names table and wire bytes in size estimate
return estimate;
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -180,6 +180,8 @@ struct V8_EXPORT_PRIVATE WasmModule {
void AddNameForTesting(int function_index, WireBytesRef name);
};
size_t EstimateWasmModuleSize(const WasmModule* module);
// Interface to the storage (wire bytes) of a wasm module.
// It is illegal for anyone receiving a ModuleWireBytes to store pointers based
// on module_bytes, as this storage is only guaranteed to be alive as long as

View File

@ -127,6 +127,16 @@ class WasmInstanceNativeAllocations {
#undef SET
};
size_t EstimateNativeAllocationsSize(const WasmModule* module) {
size_t estimate = sizeof(WasmInstanceNativeAllocations) +
(1 * kPointerSize * module->num_imported_mutable_globals) +
(2 * kPointerSize * module->num_imported_functions);
for (auto& table : module->function_tables) {
estimate += 3 * kPointerSize * table.initial_size;
}
return estimate;
}
WasmInstanceNativeAllocations* GetNativeAllocations(
WasmInstanceObject* instance) {
return reinterpret_cast<Managed<WasmInstanceNativeAllocations>*>(
@ -811,11 +821,10 @@ Handle<WasmInstanceObject> WasmInstanceObject::New(
reinterpret_cast<WasmInstanceObject*>(*instance_object), isolate);
// Initialize the imported function arrays.
auto num_imported_functions =
module_object->shared()->module()->num_imported_functions;
auto num_imported_mutable_globals =
module_object->shared()->module()->num_imported_mutable_globals;
size_t native_allocations_size = 0; // TODO(titzer): estimate properly.
auto module = module_object->shared()->module();
auto num_imported_functions = module->num_imported_functions;
auto num_imported_mutable_globals = module->num_imported_mutable_globals;
size_t native_allocations_size = EstimateNativeAllocationsSize(module);
auto native_allocations = Managed<WasmInstanceNativeAllocations>::Allocate(
isolate, native_allocations_size, instance, num_imported_functions,
num_imported_mutable_globals);
@ -1388,12 +1397,13 @@ Handle<WasmCompiledModule> WasmCompiledModule::New(Isolate* isolate,
isolate->factory()->NewStruct(WASM_COMPILED_MODULE_TYPE, TENURED));
compiled_module->set_weak_owning_instance(isolate->heap()->empty_weak_cell());
{
size_t memory_estimate =
isolate->wasm_engine()->code_manager()->EstimateNativeModuleSize(
module);
auto native_module =
isolate->wasm_engine()->code_manager()->NewNativeModule(*module, env);
size_t native_module_size =
0; // TODO(titzer): estimate native module size.
Handle<Foreign> native_module_wrapper =
Managed<wasm::NativeModule>::FromUniquePtr(isolate, native_module_size,
Managed<wasm::NativeModule>::FromUniquePtr(isolate, memory_estimate,
std::move(native_module));
compiled_module->set_native_module(*native_module_wrapper);
}

View File

@ -591,8 +591,8 @@ MaybeHandle<WasmModuleObject> DeserializeNativeModule(
.ToHandleChecked();
DCHECK(module_bytes->IsSeqOneByteString());
// The {managed_module} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object.
size_t module_size = 0; // TODO(titzer): estimate size properly.
// and it will be destroyed when the GC reclaims it.
size_t module_size = EstimateWasmModuleSize(decode_result.val.get());
Handle<Managed<WasmModule>> managed_module =
Managed<WasmModule>::FromUniquePtr(isolate, module_size,
std::move(decode_result.val));

View File

@ -255,7 +255,7 @@ TEST_P(WasmCodeManagerTest, GrowingVsFixedModule) {
WasmCodeManager manager(v8_isolate(), 3 * page());
NativeModulePtr nm = AllocModule(&manager, 1 * page(), GetParam());
if (GetParam() == Fixed) {
ASSERT_DEATH_IF_SUPPORTED(AddCode(nm.get(), 0, 1 * page() + kCodeAlignment),
ASSERT_DEATH_IF_SUPPORTED(AddCode(nm.get(), 0, kMaxWasmCodeMemory + 1),
"OOM in NativeModule::AddOwnedCode");
} else {
CHECK_NOT_NULL(AddCode(nm.get(), 0, 1 * page() + kCodeAlignment));