Commit aadf812c authored by Clemens Backes's avatar Clemens Backes Committed by Commit Bot

[wasm] Improve code size estimates

UMA data shows that we currently still allocate up to ten code spaces
per module. This is because the code size estimates are vastly off,
especially if both Liftoff and TurboFan is being used.
Also, code sizes differ by platform.

This CL adds more logic to the {EstimateNativeModuleCodeSize} function
to distinguish Liftoff and TurboFan, and to use different constants per
platform. A largeish comment explains how the numbers were generated,
and that they are an extreme over-generalization. However, without
further information about the module, this is the best we can do.
After all, being off even by a factor of two does not hurt too much, as
explained in the comment.

R=jkummerow@chromium.org

Change-Id: Icd178f5f4d0c7c8fa29b11b6eff7d14e64a1af1c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1910102
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64913}
parent fb5c7c87
......@@ -1365,8 +1365,10 @@ std::shared_ptr<NativeModule> CompileToNativeModule(
OwnedVector<uint8_t>::Of(wire_bytes.module_bytes());
// Create a new {NativeModule} first.
const bool uses_liftoff = module->origin == kWasmOrigin && FLAG_liftoff;
size_t code_size_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get());
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get(),
uses_liftoff);
auto native_module = isolate->wasm_engine()->NewNativeModule(
isolate, enabled, std::move(module), code_size_estimate);
native_module->SetWireBytes(std::move(wire_bytes_copy));
......@@ -1823,8 +1825,10 @@ class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
} else {
// Decode passed.
std::shared_ptr<WasmModule> module = std::move(result).value();
const bool kUsesLiftoff = false;
size_t code_size_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get());
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get(),
kUsesLiftoff);
job->DoSync<PrepareAndStartCompile>(std::move(module), true,
code_size_estimate);
}
......@@ -2053,9 +2057,12 @@ bool AsyncStreamingProcessor::ProcessCodeSectionHeader(
// task.
int num_imported_functions =
static_cast<int>(decoder_.module()->num_imported_functions);
DCHECK_EQ(kWasmOrigin, decoder_.module()->origin);
const bool uses_liftoff = FLAG_liftoff;
size_t code_size_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
num_functions, num_imported_functions, code_section_length);
num_functions, num_imported_functions, code_section_length,
uses_liftoff);
job_->DoImmediately<AsyncCompileJob::PrepareAndStartCompile>(
decoder_.shared_module(), false, code_size_estimate);
auto* compilation_state = Impl(job_->native_module_->compilation_state());
......
......@@ -1540,7 +1540,8 @@ VirtualMemory WasmCodeManager::TryAllocate(size_t size, void* hint) {
}
// static
size_t WasmCodeManager::EstimateNativeModuleCodeSize(const WasmModule* module) {
size_t WasmCodeManager::EstimateNativeModuleCodeSize(const WasmModule* module,
bool include_liftoff) {
int num_functions = static_cast<int>(module->num_declared_functions);
int num_imported_functions = static_cast<int>(module->num_imported_functions);
int code_section_length = 0;
......@@ -1552,22 +1553,81 @@ size_t WasmCodeManager::EstimateNativeModuleCodeSize(const WasmModule* module) {
static_cast<int>(last_fn->code.end_offset() - first_fn->code.offset());
}
return EstimateNativeModuleCodeSize(num_functions, num_imported_functions,
code_section_length);
code_section_length, include_liftoff);
}
// static
size_t WasmCodeManager::EstimateNativeModuleCodeSize(int num_functions,
int num_imported_functions,
int code_section_length) {
constexpr size_t kCodeSizeMultiplier = 4;
constexpr size_t kCodeOverhead = 32; // for prologue, stack check, ...
constexpr size_t kStaticCodeSize = 512; // runtime stubs, ...
constexpr size_t kImportSize = 64 * kSystemPointerSize;
return kStaticCodeSize // static
+ kCodeOverhead * num_functions // per function
+ kCodeSizeMultiplier * code_section_length // per opcode (~ byte)
+ kImportSize * num_imported_functions; // per import
int code_section_length,
bool include_liftoff) {
// The numbers here are rough estimates, used to calculate the size of the
// initial code reservation and for estimating the amount of external memory
// reported to the GC.
// They do not need to be accurate. Choosing them too small will result in
// separate code spaces being allocated (compile time and runtime overhead),
// choosing them too large results in over-reservation (virtual address space
// only).
// The current numbers have been determined on 2019-11-11 by clemensb@, based
// on one small and one large module compiled from C++ by Emscripten. If in
// doubt, they where chosen slightly larger than required, as over-reservation
// is not a big issue currently.
// Numbers will change when Liftoff or TurboFan evolve, other toolchains are
// used to produce the wasm code, or characteristics of wasm modules on the
// web change. They might require occasional tuning.
// This patch might help to find reasonable numbers for any future adaptation:
// https://crrev.com/c/1910945
#if V8_TARGET_ARCH_X64
constexpr size_t kTurbofanFunctionOverhead = 20;
constexpr size_t kTurbofanCodeSizeMultiplier = 3;
constexpr size_t kLiftoffFunctionOverhead = 60;
constexpr size_t kLiftoffCodeSizeMultiplier = 4;
constexpr size_t kImportSize = 350;
#elif V8_TARGET_ARCH_IA32
constexpr size_t kTurbofanFunctionOverhead = 20;
constexpr size_t kTurbofanCodeSizeMultiplier = 4;
constexpr size_t kLiftoffFunctionOverhead = 60;
constexpr size_t kLiftoffCodeSizeMultiplier = 5;
constexpr size_t kImportSize = 480;
#elif V8_TARGET_ARCH_ARM
constexpr size_t kTurbofanFunctionOverhead = 40;
constexpr size_t kTurbofanCodeSizeMultiplier = 4;
constexpr size_t kLiftoffFunctionOverhead = 108;
constexpr size_t kLiftoffCodeSizeMultiplier = 7;
constexpr size_t kImportSize = 750;
#elif V8_TARGET_ARCH_ARM64
constexpr size_t kTurbofanFunctionOverhead = 60;
constexpr size_t kTurbofanCodeSizeMultiplier = 4;
constexpr size_t kLiftoffFunctionOverhead = 80;
constexpr size_t kLiftoffCodeSizeMultiplier = 7;
constexpr size_t kImportSize = 750;
#else
// Other platforms should add their own estimates if needed. Numbers below are
// the minimum of other architectures.
constexpr size_t kTurbofanFunctionOverhead = 20;
constexpr size_t kTurbofanCodeSizeMultiplier = 3;
constexpr size_t kLiftoffFunctionOverhead = 60;
constexpr size_t kLiftoffCodeSizeMultiplier = 4;
constexpr size_t kImportSize = 350;
#endif
const size_t overhead_per_function =
kTurbofanFunctionOverhead + kCodeAlignment / 2 +
(include_liftoff ? kLiftoffFunctionOverhead + kCodeAlignment / 2 : 0);
const size_t overhead_per_code_byte =
kTurbofanCodeSizeMultiplier +
(include_liftoff ? kLiftoffCodeSizeMultiplier : 0);
const size_t jump_table_size = RoundUp<kCodeAlignment>(
JumpTableAssembler::SizeForNumberOfSlots(num_functions));
const size_t far_jump_table_size =
RoundUp<kCodeAlignment>(JumpTableAssembler::SizeForNumberOfFarJumpSlots(
WasmCode::kRuntimeStubCount,
NumWasmFunctionsInFarJumpTable(num_functions)));
return jump_table_size // jump table
+ far_jump_table_size // far jump table
+ overhead_per_function * num_functions // per function
+ overhead_per_code_byte * code_section_length // per code byte
+ kImportSize * num_imported_functions; // per import
}
// static
......
......@@ -693,12 +693,14 @@ class V8_EXPORT_PRIVATE WasmCodeManager final {
}
// Estimate the needed code space from a completely decoded module.
static size_t EstimateNativeModuleCodeSize(const WasmModule* module);
static size_t EstimateNativeModuleCodeSize(const WasmModule* module,
bool include_liftoff);
// Estimate the needed code space from the number of functions and total code
// section length.
static size_t EstimateNativeModuleCodeSize(int num_functions,
int num_imported_functions,
int code_section_length);
int code_section_length,
bool include_liftoff);
// Estimate the size of meta data needed for the NativeModule, excluding
// generated code. This data still be stored on the C++ heap.
static size_t EstimateNativeModuleMetaDataSize(const WasmModule* module);
......
......@@ -192,8 +192,10 @@ Handle<WasmModuleObject> WasmModuleObject::New(
Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module,
Handle<Script> script, Handle<FixedArray> export_wrappers) {
const WasmModule* module = native_module->module();
const bool uses_liftoff =
FLAG_liftoff && native_module->module()->origin == wasm::kWasmOrigin;
size_t code_size_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module);
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module, uses_liftoff);
return New(isolate, std::move(native_module), script, export_wrappers,
code_size_estimate);
}
......@@ -2030,8 +2032,10 @@ Handle<AsmWasmData> AsmWasmData::New(
Handle<FixedArray> export_wrappers, Handle<ByteArray> asm_js_offset_table,
Handle<HeapNumber> uses_bitset) {
const WasmModule* module = native_module->module();
const bool kUsesLiftoff = false;
size_t memory_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module) +
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module,
kUsesLiftoff) +
wasm::WasmCodeManager::EstimateNativeModuleMetaDataSize(module);
Handle<Managed<wasm::NativeModule>> managed_native_module =
Managed<wasm::NativeModule>::FromSharedPtr(isolate, memory_estimate,
......
......@@ -619,8 +619,10 @@ MaybeHandle<WasmModuleObject> DeserializeNativeModule(
Handle<Script> script = CreateWasmScript(
isolate, wire_bytes, module->source_map_url, module->name);
const bool kIncludeLiftoff = false;
size_t code_size_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get());
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get(),
kIncludeLiftoff);
auto shared_native_module = isolate->wasm_engine()->NewNativeModule(
isolate, enabled_features, std::move(module), code_size_estimate);
shared_native_module->SetWireBytes(OwnedVector<uint8_t>::Of(wire_bytes_vec));
......
......@@ -318,8 +318,10 @@ Handle<WasmInstanceObject> TestingModuleBuilder::InitInstanceObject() {
isolate_->factory()->NewScript(isolate_->factory()->empty_string());
script->set_type(Script::TYPE_WASM);
const bool kUsesLiftoff = true;
size_t code_size_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(test_module_.get());
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(test_module_.get(),
kUsesLiftoff);
auto native_module = isolate_->wasm_engine()->NewNativeModule(
isolate_, enabled_features_, test_module_, code_size_estimate);
native_module->SetWireBytes(OwnedVector<const uint8_t>());
......
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