[wasm] Fix Wasm Lazy Compilation

Fix recognition of lazy functions when {--wasm-lazy-compilation} is
used.

Bug: chromium:956771
Change-Id: I3f9bb25ccf3920a6c3d266876faace8841dcdc61
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1585843
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Clemens Hammacher <clemensh@chromium.org>
Commit-Queue: Frederik Gossen <frgossen@google.com>
Cr-Commit-Position: refs/heads/master@{#61114}
This commit is contained in:
Frederik Gossen 2019-04-30 13:23:27 +02:00 committed by Commit Bot
parent 0a0d70eb8c
commit 197b1d979c
6 changed files with 145 additions and 84 deletions

View File

@ -323,7 +323,7 @@ class CompilationStateImpl {
// Initialize compilation progress. Set compilation tiers to expect for
// baseline and top tier compilation. Must be set before {AddCompilationUnits}
// is invoked which triggers background compilation.
void InitializeCompilationProgress();
void InitializeCompilationProgress(bool lazy_module);
// Add the callback function to be called on compilation events. Needs to be
// set before {AddCompilationUnits} is run to ensure that it receives all
@ -538,7 +538,8 @@ const WasmCompilationHint* GetCompilationHint(const WasmModule* module,
CompileStrategy GetCompileStrategy(const WasmModule* module,
const WasmFeatures& enabled_features,
uint32_t func_index) {
uint32_t func_index, bool lazy_module) {
if (lazy_module) return CompileStrategy::kLazy;
if (!enabled_features.compilation_hints) return CompileStrategy::kDefault;
auto* hint = GetCompilationHint(module, func_index);
if (hint == nullptr) return CompileStrategy::kDefault;
@ -554,14 +555,6 @@ CompileStrategy GetCompileStrategy(const WasmModule* module,
}
}
CompileStrategy GetCompileStrategy(const WasmModule* module,
const NativeModule* native_module,
const WasmFeatures& enabled_features,
uint32_t func_index) {
if (native_module->lazy_compilation()) return CompileStrategy::kLazy;
return GetCompileStrategy(module, enabled_features, func_index);
}
struct ExecutionTierPair {
ExecutionTier baseline_tier;
ExecutionTier top_tier;
@ -635,10 +628,14 @@ class CompilationUnitBuilder {
native_module_->enabled_features(), func_index);
// In this case, the baseline is lazily compiled, if at all. The compilation
// unit is added even if the baseline tier is the same.
DCHECK_EQ(
CompileStrategy::kLazyBaselineEagerTopTier,
GetCompileStrategy(native_module_->module(),
native_module_->enabled_features(), func_index));
#ifdef DEBUG
auto* module = native_module_->module();
DCHECK_EQ(kWasmOrigin, module->origin);
const bool lazy_module = false;
DCHECK_EQ(CompileStrategy::kLazyBaselineEagerTopTier,
GetCompileStrategy(module, native_module_->enabled_features(),
func_index, lazy_module));
#endif
tiering_units_.emplace_back(CreateUnit(func_index, tiers.top_tier));
}
@ -713,7 +710,7 @@ enum OnlyLazyFunctions : bool {
void ValidateSequentially(
const WasmModule* module, NativeModule* native_module, Counters* counters,
AccountingAllocator* allocator, ErrorThrower* thrower,
AccountingAllocator* allocator, ErrorThrower* thrower, bool lazy_module,
OnlyLazyFunctions only_lazy_functions = kAllFunctions) {
DCHECK(!thrower->error());
uint32_t start = module->num_imported_functions;
@ -722,8 +719,8 @@ void ValidateSequentially(
for (uint32_t func_index = start; func_index < end; func_index++) {
// Skip non-lazy functions if requested.
if (only_lazy_functions) {
CompileStrategy strategy = GetCompileStrategy(
module, native_module, enabled_features, func_index);
CompileStrategy strategy =
GetCompileStrategy(module, enabled_features, func_index, lazy_module);
if (strategy != CompileStrategy::kLazy &&
strategy != CompileStrategy::kLazyBaselineEagerTopTier) {
continue;
@ -741,6 +738,11 @@ void ValidateSequentially(
}
}
bool IsLazyModule(const WasmModule* module) {
return FLAG_wasm_lazy_compilation ||
(FLAG_asm_wasm_lazy_compilation && module->origin == kAsmJsOrigin);
}
} // namespace
bool CompileLazy(Isolate* isolate, NativeModule* native_module,
@ -803,7 +805,8 @@ bool CompileLazy(Isolate* isolate, NativeModule* native_module,
int throughput_sample = static_cast<int>(func_kb / compilation_seconds);
counters->wasm_lazy_compilation_throughput()->AddSample(throughput_sample);
if (GetCompileStrategy(module, enabled_features, func_index) ==
const bool lazy_module = IsLazyModule(module);
if (GetCompileStrategy(module, enabled_features, func_index, lazy_module) ==
CompileStrategy::kLazy &&
tiers.baseline_tier < tiers.top_tier) {
auto tiering_unit =
@ -942,7 +945,8 @@ bool ExecuteCompilationUnits(
void InitializeCompilationUnits(NativeModule* native_module) {
CompilationStateImpl* compilation_state =
Impl(native_module->compilation_state());
compilation_state->InitializeCompilationProgress();
const bool lazy_module = IsLazyModule(native_module->module());
compilation_state->InitializeCompilationProgress(lazy_module);
ModuleWireBytes wire_bytes(native_module->wire_bytes());
CompilationUnitBuilder builder(native_module);
@ -951,7 +955,7 @@ void InitializeCompilationUnits(NativeModule* native_module) {
uint32_t end = start + module->num_declared_functions;
for (uint32_t func_index = start; func_index < end; func_index++) {
CompileStrategy strategy = GetCompileStrategy(
module, native_module, native_module->enabled_features(), func_index);
module, native_module->enabled_features(), func_index, lazy_module);
if (strategy == CompileStrategy::kLazy) {
native_module->UseLazyStub(func_index);
} else if (strategy == CompileStrategy::kLazyBaselineEagerTopTier) {
@ -969,32 +973,48 @@ bool NeedsDeterministicCompile() {
return FLAG_trace_wasm_decoder || FLAG_wasm_num_compilation_tasks <= 1;
}
bool MayCompriseLazyFunctions(const WasmModule* module,
const WasmFeatures& enabled_features,
bool lazy_module) {
if (lazy_module || enabled_features.compilation_hints) return true;
#ifdef ENABLE_SLOW_DCHECKS
int start = module->num_imported_functions;
int end = start + module->num_declared_functions;
for (int func_index = start; func_index < end; func_index++) {
SLOW_DCHECK(GetCompileStrategy(module, enabled_features, func_index,
lazy_module) != CompileStrategy::kLazy);
}
#endif
return false;
}
void CompileNativeModule(Isolate* isolate, ErrorThrower* thrower,
const WasmModule* wasm_module,
NativeModule* native_module) {
ModuleWireBytes wire_bytes(native_module->wire_bytes());
auto* compilation_state = Impl(native_module->compilation_state());
if (FLAG_wasm_lazy_compilation ||
(FLAG_asm_wasm_lazy_compilation && wasm_module->origin == kAsmJsOrigin)) {
const bool lazy_module = IsLazyModule(wasm_module);
if (lazy_module) {
if (wasm_module->origin == kWasmOrigin && !FLAG_wasm_lazy_validation) {
// Validate wasm modules for lazy compilation if requested. Never validate
// asm.js modules as these are valid by construction (otherwise a CHECK
// will fail during lazy compilation).
ValidateSequentially(wasm_module, native_module, isolate->counters(),
isolate->allocator(), thrower);
isolate->allocator(), thrower, lazy_module);
// On error: Return and leave the module in an unexecutable state.
if (thrower->error()) return;
}
native_module->set_lazy_compilation(true);
compilation_state->InitializeCompilationProgress();
compilation_state->InitializeCompilationProgress(lazy_module);
native_module->UseLazyStubs();
return;
}
if (native_module->enabled_features().compilation_hints &&
!FLAG_wasm_lazy_validation) {
if (!FLAG_wasm_lazy_validation &&
MayCompriseLazyFunctions(wasm_module, native_module->enabled_features(),
lazy_module)) {
ValidateSequentially(wasm_module, native_module, isolate->counters(),
isolate->allocator(), thrower, kOnlyLazyFunctions);
isolate->allocator(), thrower, lazy_module,
kOnlyLazyFunctions);
// On error: Return and leave the module in an unexecutable state.
if (thrower->error()) return;
}
@ -1041,8 +1061,9 @@ void CompileNativeModule(Isolate* isolate, ErrorThrower* thrower,
compilation_state->PublishDetectedFeatures(isolate);
if (compilation_state->failed()) {
DCHECK_IMPLIES(lazy_module, !FLAG_wasm_lazy_validation);
ValidateSequentially(wasm_module, native_module, isolate->counters(),
isolate->allocator(), thrower);
isolate->allocator(), thrower, lazy_module);
CHECK(thrower->error());
}
}
@ -1123,6 +1144,7 @@ AsyncCompileJob::AsyncCompileJob(
std::shared_ptr<CompilationResultResolver> resolver)
: isolate_(isolate),
enabled_features_(enabled),
wasm_lazy_compilation_(FLAG_wasm_lazy_compilation),
bytes_copy_(std::move(bytes_copy)),
wire_bytes_(bytes_copy_.get(), bytes_copy_.get() + length),
resolver_(std::move(resolver)) {
@ -1297,8 +1319,11 @@ void AsyncCompileJob::DecodeFailed(const WasmError& error) {
void AsyncCompileJob::AsyncCompileFailed() {
ErrorThrower thrower(isolate_, "WebAssembly.compile()");
DCHECK_EQ(native_module_->module()->origin, kWasmOrigin);
const bool lazy_module = wasm_lazy_compilation_;
ValidateSequentially(native_module_->module(), native_module_.get(),
isolate_->counters(), isolate_->allocator(), &thrower);
isolate_->counters(), isolate_->allocator(), &thrower,
lazy_module);
DCHECK(thrower.error());
// {job} keeps the {this} pointer alive.
std::shared_ptr<AsyncCompileJob> job =
@ -1489,36 +1514,40 @@ class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
TRACE_COMPILE("(1) Decoding module...\n");
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"),
"AsyncCompileJob::DecodeModule");
result = DecodeWasmModule(
job->enabled_features_, job->wire_bytes_.start(),
job->wire_bytes_.end(), false, kWasmOrigin, counters_,
job->isolate()->wasm_engine()->allocator());
auto enabled_features = job->enabled_features_;
result = DecodeWasmModule(enabled_features, job->wire_bytes_.start(),
job->wire_bytes_.end(), false, kWasmOrigin,
counters_,
job->isolate()->wasm_engine()->allocator());
// Validate lazy functions here if requested.
auto enabled_features = job->enabled_features_;
if (enabled_features.compilation_hints && !FLAG_wasm_lazy_validation &&
result.ok()) {
if (!FLAG_wasm_lazy_validation && result.ok()) {
const WasmModule* module = result.value().get();
auto allocator = job->isolate()->wasm_engine()->allocator();
int start = module->num_imported_functions;
int end = start + module->num_declared_functions;
DCHECK_EQ(module->origin, kWasmOrigin);
const bool lazy_module = job->wasm_lazy_compilation_;
if (MayCompriseLazyFunctions(module, enabled_features, lazy_module)) {
auto allocator = job->isolate()->wasm_engine()->allocator();
int start = module->num_imported_functions;
int end = start + module->num_declared_functions;
for (int func_index = start; func_index < end; func_index++) {
const WasmFunction* func = &module->functions[func_index];
Vector<const uint8_t> code = job->wire_bytes_.GetFunctionBytes(func);
for (int func_index = start; func_index < end; func_index++) {
const WasmFunction* func = &module->functions[func_index];
Vector<const uint8_t> code =
job->wire_bytes_.GetFunctionBytes(func);
CompileStrategy strategy =
GetCompileStrategy(module, enabled_features, func_index);
bool validate_lazily_compiled_function =
strategy == CompileStrategy::kLazy ||
strategy == CompileStrategy::kLazyBaselineEagerTopTier;
if (validate_lazily_compiled_function) {
DecodeResult function_result =
ValidateSingleFunction(module, func_index, code, counters_,
allocator, enabled_features);
if (function_result.failed()) {
result = ModuleResult(function_result.error());
break;
CompileStrategy strategy = GetCompileStrategy(
module, enabled_features, func_index, lazy_module);
bool validate_lazily_compiled_function =
strategy == CompileStrategy::kLazy ||
strategy == CompileStrategy::kLazyBaselineEagerTopTier;
if (validate_lazily_compiled_function) {
DecodeResult function_result =
ValidateSingleFunction(module, func_index, code, counters_,
allocator, enabled_features);
if (function_result.failed()) {
result = ModuleResult(function_result.error());
break;
}
}
}
}
@ -1754,7 +1783,9 @@ bool AsyncStreamingProcessor::ProcessCodeSectionHeader(
decoder_.shared_module(), false);
auto* compilation_state = Impl(job_->native_module_->compilation_state());
compilation_state->SetWireBytesStorage(std::move(wire_bytes_storage));
compilation_state->InitializeCompilationProgress();
DCHECK_EQ(job_->native_module_->module()->origin, kWasmOrigin);
const bool lazy_module = job_->wasm_lazy_compilation_;
compilation_state->InitializeCompilationProgress(lazy_module);
// Set outstanding_finishers_ to 2, because both the AsyncCompileJob and the
// AsyncStreamingProcessor have to finish.
@ -1774,12 +1805,14 @@ bool AsyncStreamingProcessor::ProcessFunctionBody(Vector<const uint8_t> bytes,
NativeModule* native_module = job_->native_module_.get();
const WasmModule* module = native_module->module();
auto enabled_features = native_module->enabled_features();
auto enabled_features = job_->enabled_features_;
uint32_t func_index =
num_functions_ + decoder_.module()->num_imported_functions;
DCHECK_EQ(module->origin, kWasmOrigin);
const bool lazy_module = job_->wasm_lazy_compilation_;
CompileStrategy strategy =
GetCompileStrategy(module, enabled_features, func_index);
GetCompileStrategy(module, enabled_features, func_index, lazy_module);
bool validate_lazily_compiled_function =
!FLAG_wasm_lazy_validation &&
(strategy == CompileStrategy::kLazy ||
@ -1926,7 +1959,7 @@ void CompilationStateImpl::AbortCompilation() {
callbacks_.clear();
}
void CompilationStateImpl::InitializeCompilationProgress() {
void CompilationStateImpl::InitializeCompilationProgress(bool lazy_module) {
DCHECK(!failed());
auto enabled_features = native_module_->enabled_features();
auto* module = native_module_->module();
@ -1941,8 +1974,8 @@ void CompilationStateImpl::InitializeCompilationProgress() {
for (int func_index = start; func_index < end; func_index++) {
ExecutionTierPair requested_tiers = GetRequestedExecutionTiers(
module, compile_mode(), enabled_features, func_index);
CompileStrategy strategy = GetCompileStrategy(module, native_module_,
enabled_features, func_index);
CompileStrategy strategy =
GetCompileStrategy(module, enabled_features, func_index, lazy_module);
bool required_for_baseline = strategy == CompileStrategy::kEager;
bool required_for_top_tier = strategy != CompileStrategy::kLazy;
@ -1967,24 +2000,9 @@ void CompilationStateImpl::InitializeCompilationProgress() {
RequiredTopTierField::update(function_progress, required_top_tier);
compilation_progress_.push_back(function_progress);
}
DCHECK_IMPLIES(
!enabled_features.compilation_hints && !FLAG_wasm_lazy_compilation &&
!(FLAG_asm_wasm_lazy_compilation && module->origin == kAsmJsOrigin),
outstanding_baseline_functions_ ==
static_cast<int>(module->num_declared_functions));
DCHECK_IMPLIES(
!enabled_features.compilation_hints && !FLAG_wasm_lazy_compilation &&
!(FLAG_asm_wasm_lazy_compilation && module->origin == kAsmJsOrigin),
outstanding_top_tier_functions_ ==
static_cast<int>(module->num_declared_functions));
DCHECK_IMPLIES(
FLAG_wasm_lazy_compilation ||
(FLAG_asm_wasm_lazy_compilation && module->origin == kAsmJsOrigin),
outstanding_baseline_functions_ == 0);
DCHECK_IMPLIES(
FLAG_wasm_lazy_compilation ||
(FLAG_asm_wasm_lazy_compilation && module->origin == kAsmJsOrigin),
outstanding_top_tier_functions_ == 0);
DCHECK_IMPLIES(lazy_module, outstanding_baseline_functions_ == 0);
DCHECK_IMPLIES(lazy_module, outstanding_top_tier_functions_ == 0);
DCHECK_LE(0, outstanding_baseline_functions_);
DCHECK_LE(outstanding_baseline_functions_, outstanding_top_tier_functions_);
// Trigger callbacks if module needs no baseline or top tier compilation. This

View File

@ -149,6 +149,7 @@ class AsyncCompileJob {
Isolate* const isolate_;
const WasmFeatures enabled_features_;
const bool wasm_lazy_compilation_;
// Copy of the module wire bytes, moved into the {native_module_} on its
// creation.
std::unique_ptr<byte[]> bytes_copy_;

View File

@ -376,8 +376,6 @@ class V8_EXPORT_PRIVATE NativeModule final {
UseTrapHandler use_trap_handler() const { return use_trap_handler_; }
void set_lazy_compile_frozen(bool frozen) { lazy_compile_frozen_ = frozen; }
bool lazy_compile_frozen() const { return lazy_compile_frozen_; }
void set_lazy_compilation(bool lazy) { lazy_compilation_ = lazy; }
bool lazy_compilation() const { return lazy_compilation_; }
Vector<const uint8_t> wire_bytes() const { return wire_bytes_->as_vector(); }
const WasmModule* module() const { return module_.get(); }
std::shared_ptr<const WasmModule> shared_module() const { return module_; }
@ -535,7 +533,6 @@ class V8_EXPORT_PRIVATE NativeModule final {
UseTrapHandler use_trap_handler_ = kNoTrapHandler;
bool is_executable_ = false;
bool lazy_compile_frozen_ = false;
bool lazy_compilation_ = false;
DISALLOW_COPY_AND_ASSIGN(NativeModule);
};

View File

@ -633,8 +633,6 @@ MaybeHandle<WasmModuleObject> DeserializeNativeModule(
std::move(wire_bytes_copy), script, Handle<ByteArray>::null());
NativeModule* native_module = module_object->native_module();
native_module->set_lazy_compilation(FLAG_wasm_lazy_compilation);
NativeModuleDeserializer deserializer(native_module);
WasmCodeRefScope wasm_code_ref_scope;

View File

@ -0,0 +1,28 @@
// 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.
// Flags: --wasm-lazy-compilation
load('test/mjsunit/wasm/wasm-module-builder.js');
(function testLazyModuleAsyncCompilation() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction("some", kSig_i_ii)
assertPromiseResult(WebAssembly.compile(builder.toBuffer())
.then(assertUnreachable,
error => assertEquals("WebAssembly.compile(): function body must " +
"end with \"end\" opcode @+26",
error.message)));
})();
(function testLazyModuleSyncCompilation() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction("some", kSig_i_ii)
assertThrows(() => builder.toModule(),
WebAssembly.CompileError,
"WebAssembly.Module(): Compiling function #0:\"some\" failed: " +
"function body must end with \"end\" opcode @+26");
})();

View File

@ -0,0 +1,19 @@
// 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.
// Flags: --wasm-lazy-compilation --wasm-test-streaming
load('test/mjsunit/wasm/wasm-module-builder.js');
(function testLazyModuleStreamingCompilation() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction("some", kSig_i_ii);
let bytes = builder.toBuffer();
assertPromiseResult(WebAssembly.compileStreaming(Promise.resolve(bytes))
.then(assertUnreachable,
error => assertEquals("WebAssembly.compile(): function body must " +
"end with \"end\" opcode @+26",
error.message)));
})();