Commit 28705dfb authored by Frederik Gossen's avatar Frederik Gossen Committed by Commit Bot

[wasm-hints] Lazy Validation Flag

Add lazy validation for lazily compiled functions. The code is validated
only on first use. This applies to functions that are lazily compiled by
compilation hint as well as to entirely lazy modules.

Bug: v8:9003
Change-Id: If6a640db4bf4b846ac5e3805c138b8ac0a493cf9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1569427
Commit-Queue: Frederik Gossen <frgossen@google.com>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60921}
parent 47a69050
......@@ -670,12 +670,12 @@ DEFINE_BOOL(wasm_trap_handler, true,
"use signal handlers to catch out of bounds memory access in wasm"
" (currently Linux x86_64 only)")
DEFINE_BOOL(wasm_fuzzer_gen_test, false,
"Generate a test case when running a wasm fuzzer")
"generate a test case when running a wasm fuzzer")
DEFINE_IMPLICATION(wasm_fuzzer_gen_test, single_threaded)
DEFINE_BOOL(print_wasm_code, false, "Print WebAssembly code")
DEFINE_BOOL(print_wasm_stub_code, false, "Print WebAssembly stub code")
DEFINE_BOOL(wasm_interpret_all, false,
"Execute all wasm code in the wasm interpreter")
"execute all wasm code in the wasm interpreter")
DEFINE_BOOL(asm_wasm_lazy_compilation, false,
"enable lazy compilation for asm-wasm modules")
DEFINE_IMPLICATION(validate_asm, asm_wasm_lazy_compilation)
......@@ -684,7 +684,9 @@ DEFINE_BOOL(wasm_lazy_compilation, false,
DEFINE_DEBUG_BOOL(trace_wasm_lazy_compilation, false,
"trace lazy compilation of wasm functions")
DEFINE_BOOL(wasm_grow_shared_memory, false,
"Allow growing shared WebAssembly memory objects")
"allow growing shared WebAssembly memory objects")
DEFINE_BOOL(wasm_lazy_validation, false,
"enable lazy validation for lazily compiled wasm functions")
// wasm-interpret-all resets {asm-,}wasm-lazy-compilation.
DEFINE_NEG_IMPLICATION(wasm_interpret_all, asm_wasm_lazy_compilation)
DEFINE_NEG_IMPLICATION(wasm_interpret_all, wasm_lazy_compilation)
......
......@@ -1698,6 +1698,16 @@ Object Isolate::UnwindAndFindHandler() {
wasm_code->constant_pool(), return_sp, frame->fp());
}
case StackFrame::WASM_COMPILE_LAZY: {
// Can only fail directly on invocation. This happens if an invalid
// function was validated lazily.
DCHECK_IMPLIES(trap_handler::IsTrapHandlerEnabled(),
trap_handler::IsThreadInWasm());
DCHECK(FLAG_wasm_lazy_validation);
trap_handler::ClearThreadInWasm();
break;
}
case StackFrame::OPTIMIZED: {
// For optimized frames we perform a lookup in the handler table.
if (!catchable_by_js) break;
......
......@@ -310,7 +310,8 @@ RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
CONVERT_ARG_HANDLE_CHECKED(WasmInstanceObject, instance, 0);
CONVERT_SMI_ARG_CHECKED(func_index, 1);
ClearThreadInWasmScope wasm_flag;
// This runtime function is always called from wasm code.
ClearThreadInWasmScope flag_scope;
#ifdef DEBUG
StackFrameIterator it(isolate, isolate->thread_local_top());
......@@ -322,10 +323,17 @@ RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
DCHECK_EQ(*instance, WasmCompileLazyFrame::cast(it.frame())->wasm_instance());
#endif
DCHECK(isolate->context().is_null());
isolate->set_context(instance->native_context());
auto* native_module = instance->module_object()->native_module();
wasm::CompileLazy(isolate, native_module, func_index);
bool success = wasm::CompileLazy(isolate, native_module, func_index);
if (!success) {
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots(isolate).exception();
}
Address entrypoint = native_module->GetCallTargetForFunction(func_index);
return Object(entrypoint);
}
......
......@@ -619,60 +619,120 @@ class CompilationUnitBuilder {
std::vector<std::unique_ptr<WasmCompilationUnit>> tiering_units_;
};
void SetCompileError(ErrorThrower* thrower, ModuleWireBytes wire_bytes,
const WasmFunction* func, const WasmModule* module,
WasmError error) {
WasmName name = wire_bytes.GetNameOrNull(func, module);
if (name.start() == nullptr) {
thrower->CompileError("Compiling function #%d failed: %s @+%u",
func->func_index, error.message().c_str(),
error.offset());
} else {
TruncatedUserString<> truncated_name(name);
thrower->CompileError("Compiling function #%d:\"%.*s\" failed: %s @+%u",
func->func_index, truncated_name.length(),
truncated_name.start(), error.message().c_str(),
error.offset());
}
}
DecodeResult ValidateSingleFunction(const WasmModule* module, int func_index,
Vector<const uint8_t> code,
Counters* counters,
AccountingAllocator* allocator,
WasmFeatures enabled_features) {
const WasmFunction* func = &module->functions[func_index];
FunctionBody body{func->sig, func->code.offset(), code.start(), code.end()};
DecodeResult result;
auto time_counter =
SELECT_WASM_COUNTER(counters, module->origin, wasm_decode, function_time);
TimedHistogramScope wasm_decode_function_time_scope(time_counter);
WasmFeatures detected;
result = VerifyWasmCode(allocator, enabled_features, module, &detected, body);
return result;
}
enum class OnlyLazyFunctions : bool { kNo = false, kYes = true };
void ValidateSequentially(
const WasmModule* module, NativeModule* native_module, Counters* counters,
AccountingAllocator* allocator, ErrorThrower* thrower,
OnlyLazyFunctions only_lazy_functions = OnlyLazyFunctions::kNo) {
DCHECK(!thrower->error());
uint32_t start = module->num_imported_functions;
uint32_t end = start + module->num_declared_functions;
auto enabled_features = native_module->enabled_features();
for (uint32_t func_index = start; func_index < end; func_index++) {
// Skip non-lazy functions if requested.
if (only_lazy_functions == OnlyLazyFunctions::kYes &&
!IsLazyCompilation(module, native_module, enabled_features,
func_index)) {
continue;
}
ModuleWireBytes wire_bytes{native_module->wire_bytes()};
const WasmFunction* func = &module->functions[func_index];
Vector<const uint8_t> code = wire_bytes.GetFunctionBytes(func);
DecodeResult result = ValidateSingleFunction(
module, func_index, code, counters, allocator, enabled_features);
if (result.failed()) {
SetCompileError(thrower, wire_bytes, func, module, result.error());
}
}
}
} // namespace
void CompileLazy(Isolate* isolate, NativeModule* native_module,
bool CompileLazy(Isolate* isolate, NativeModule* native_module,
uint32_t func_index) {
const WasmModule* module = native_module->module();
auto enabled_features = native_module->enabled_features();
Counters* counters = isolate->counters();
HistogramTimerScope lazy_time_scope(counters->wasm_lazy_compilation_time());
DCHECK(!native_module->lazy_compile_frozen());
HistogramTimerScope lazy_time_scope(counters->wasm_lazy_compilation_time());
NativeModuleModificationScope native_module_modification_scope(native_module);
base::ElapsedTimer compilation_timer;
NativeModuleModificationScope native_module_modification_scope(native_module);
compilation_timer.Start();
DCHECK(!native_module->HasCode(static_cast<uint32_t>(func_index)));
compilation_timer.Start();
TRACE_LAZY("Compiling wasm-function#%d.\n", func_index);
const uint8_t* module_start = native_module->wire_bytes().start();
const WasmFunction* func = &native_module->module()->functions[func_index];
FunctionBody func_body{func->sig, func->code.offset(),
module_start + func->code.offset(),
module_start + func->code.end_offset()};
CompilationStateImpl* compilation_state =
Impl(native_module->compilation_state());
ExecutionTierPair tiers = GetRequestedExecutionTiers(
native_module->module(), compilation_state->compile_mode(),
native_module->enabled_features(), func_index);
module, compilation_state->compile_mode(), enabled_features, func_index);
WasmCompilationUnit baseline_unit(func_index, tiers.baseline_tier);
CompilationEnv env = native_module->CreateCompilationEnv();
WasmCompilationResult result = baseline_unit.ExecuteCompilation(
isolate->wasm_engine(), &env, compilation_state->GetWireBytesStorage(),
isolate->counters(), compilation_state->detected_features());
WasmCodeRefScope code_ref_scope;
WasmCode* code = native_module->AddCompiledCode(std::move(result));
counters, compilation_state->detected_features());
if (tiers.baseline_tier < tiers.top_tier) {
auto tiering_unit =
base::make_unique<WasmCompilationUnit>(func_index, tiers.top_tier);
compilation_state->AddTopTierCompilationUnit(std::move(tiering_unit));
// During lazy compilation, we can only get compilation errors when
// {--wasm-lazy-validation} is enabled. Otherwise, the module was fully
// verified before starting its execution.
DCHECK_IMPLIES(result.failed(), FLAG_wasm_lazy_validation);
const WasmFunction* func = &module->functions[func_index];
if (result.failed()) {
ErrorThrower thrower(isolate, nullptr);
Vector<const uint8_t> code =
compilation_state->GetWireBytesStorage()->GetCode(func->code);
DecodeResult decode_result = ValidateSingleFunction(
module, func_index, code, counters, isolate->wasm_engine()->allocator(),
enabled_features);
CHECK(decode_result.failed());
SetCompileError(&thrower, ModuleWireBytes(native_module->wire_bytes()),
func, module, decode_result.error());
return false;
}
// During lazy compilation, we should never get compilation errors. The module
// was verified before starting execution with lazy compilation.
// This might be OOM, but then we cannot continue execution anyway.
// TODO(clemensh): According to the spec, we can actually skip validation at
// module creation time, and return a function that always traps here.
CHECK(!compilation_state->failed());
// The code we just produced should be the one that was requested.
WasmCodeRefScope code_ref_scope;
WasmCode* code = native_module->AddCompiledCode(std::move(result));
DCHECK_EQ(func_index, code->index());
if (WasmCode::ShouldBeLogged(isolate)) code->LogCode(isolate);
......@@ -684,6 +744,14 @@ void 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 (tiers.baseline_tier < tiers.top_tier) {
auto tiering_unit =
base::make_unique<WasmCompilationUnit>(func_index, tiers.top_tier);
compilation_state->AddTopTierCompilationUnit(std::move(tiering_unit));
}
return true;
}
namespace {
......@@ -811,65 +879,6 @@ bool ExecuteCompilationUnits(
return true;
}
DecodeResult ValidateSingleFunction(const WasmModule* module, int func_index,
Vector<const uint8_t> code,
Counters* counters,
AccountingAllocator* allocator,
WasmFeatures enabled_features) {
const WasmFunction* func = &module->functions[func_index];
FunctionBody body{func->sig, func->code.offset(), code.start(), code.end()};
DecodeResult result;
{
auto time_counter = SELECT_WASM_COUNTER(counters, module->origin,
wasm_decode, function_time);
TimedHistogramScope wasm_decode_function_time_scope(time_counter);
WasmFeatures detected;
result =
VerifyWasmCode(allocator, enabled_features, module, &detected, body);
}
return result;
}
enum class OnlyLazyFunctions : bool { kNo = false, kYes = true };
void ValidateSequentially(
const WasmModule* module, NativeModule* native_module, Counters* counters,
AccountingAllocator* allocator, ErrorThrower* thrower,
OnlyLazyFunctions only_lazy_functions = OnlyLazyFunctions ::kNo) {
DCHECK(!thrower->error());
uint32_t start = module->num_imported_functions;
uint32_t end = start + module->num_declared_functions;
auto enabled_features = native_module->enabled_features();
for (uint32_t func_index = start; func_index < end; func_index++) {
// Skip non-lazy functions if requested.
if (only_lazy_functions == OnlyLazyFunctions::kYes &&
!IsLazyCompilation(module, native_module, enabled_features,
func_index)) {
continue;
}
ModuleWireBytes wire_bytes{native_module->wire_bytes()};
const WasmFunction* func = &module->functions[func_index];
Vector<const uint8_t> code = wire_bytes.GetFunctionBytes(func);
DecodeResult result = ValidateSingleFunction(
module, func_index, code, counters, allocator, enabled_features);
if (result.failed()) {
WasmName name = wire_bytes.GetNameOrNull(func, module);
if (name.start() == nullptr) {
thrower->CompileError(
"Compiling function #%d failed: %s @+%u", func->func_index,
result.error().message().c_str(), result.error().offset());
} else {
TruncatedUserString<> name(wire_bytes.GetNameOrNull(func, module));
thrower->CompileError("Compiling function #%d:\"%.*s\" failed: %s @+%u",
func->func_index, name.length(), name.start(),
result.error().message().c_str(),
result.error().offset());
}
}
}
}
void InitializeCompilationUnits(NativeModule* native_module) {
// Set number of functions that must be compiled to consider the module fully
// compiled.
......@@ -910,13 +919,10 @@ void CompileNativeModule(Isolate* isolate, ErrorThrower* thrower,
if (FLAG_wasm_lazy_compilation ||
(FLAG_asm_wasm_lazy_compilation && wasm_module->origin == kAsmJsOrigin)) {
if (wasm_module->origin == kWasmOrigin) {
// Validate wasm modules for lazy compilation. Don't validate asm.js
// modules, they are valid by construction (otherwise a CHECK will fail
// during lazy compilation).
// TODO(clemensh): According to the spec, we can actually skip validation
// at module creation time, and return a function that always traps at
// (lazy) compilation time.
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);
// On error: Return and leave the module in an unexecutable state.
......@@ -927,7 +933,8 @@ void CompileNativeModule(Isolate* isolate, ErrorThrower* thrower,
return;
}
if (native_module->enabled_features().compilation_hints) {
if (native_module->enabled_features().compilation_hints &&
!FLAG_wasm_lazy_validation) {
ValidateSequentially(wasm_module, native_module, isolate->counters(),
isolate->allocator(), thrower,
OnlyLazyFunctions::kYes);
......@@ -1431,9 +1438,10 @@ class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
job->wire_bytes_.end(), false, kWasmOrigin, counters_,
job->isolate()->wasm_engine()->allocator());
// Validate lazy functions here.
// Validate lazy functions here if requested.
auto enabled_features = job->enabled_features_;
if (enabled_features.compilation_hints && result.ok()) {
if (enabled_features.compilation_hints && !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;
......@@ -1719,17 +1727,19 @@ bool AsyncStreamingProcessor::ProcessFunctionBody(Vector<const uint8_t> bytes,
num_functions_ + decoder_.module()->num_imported_functions;
if (IsLazyCompilation(module, native_module, enabled_features, func_index)) {
Counters* counters = Impl(native_module->compilation_state())->counters();
AccountingAllocator* allocator = native_module->engine()->allocator();
// The native module does not own the wire bytes until {SetWireBytes} is
// called in {OnFinishedStream}. Validation must use {bytes} parameter.
DecodeResult result = ValidateSingleFunction(
module, func_index, bytes, counters, allocator, enabled_features);
if (result.failed()) {
FinishAsyncCompileJobWithError(result.error());
return false;
if (!FLAG_wasm_lazy_validation) {
Counters* counters = Impl(native_module->compilation_state())->counters();
AccountingAllocator* allocator = native_module->engine()->allocator();
// The native module does not own the wire bytes until {SetWireBytes} is
// called in {OnFinishedStream}. Validation must use {bytes} parameter.
DecodeResult result = ValidateSingleFunction(
module, func_index, bytes, counters, allocator, enabled_features);
if (result.failed()) {
FinishAsyncCompileJobWithError(result.error());
return false;
}
}
native_module->UseLazyStub(func_index);
......
......@@ -50,8 +50,10 @@ V8_EXPORT_PRIVATE Handle<Script> CreateWasmScript(
Isolate* isolate, const ModuleWireBytes& wire_bytes,
const std::string& source_map_url);
// Triggered by the WasmCompileLazy builtin.
void CompileLazy(Isolate*, NativeModule*, uint32_t func_index);
// Triggered by the WasmCompileLazy builtin. The return value indicates whether
// compilation was successful. Lazy compilation can fail only if validation is
// also lazy.
bool CompileLazy(Isolate*, NativeModule*, uint32_t func_index);
// Encapsulates all the state and steps of an asynchronous compilation.
// An asynchronous compile job consists of a number of tasks that are executed
......
// 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: --experimental-wasm-compilation-hints --wasm-lazy-validation
load('test/mjsunit/wasm/wasm-module-builder.js');
(function testInstantiateLazyValidation() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('id', kSig_i_i)
.addBody([kExprGetLocal, 0,
kExprI64Const, 1,
kExprI32Mul])
.setCompilationHint(kCompilationHintStrategyLazy,
kCompilationHintTierBaseline,
kCompilationHintTierBaseline)
.exportFunc();
let expected_error_msg = "Compiling function #0:\"id\" failed: i32.mul[1] " +
"expected type i32, found i64.const of type i64 " +
"@+56";
let assertCompileErrorOnInvocation = function(instance) {
assertThrows(() => instance.exports.id(3),
WebAssembly.CompileError,
expected_error_msg)
};
// Synchronous case.
let instance = builder.instantiate();
assertCompileErrorOnInvocation(instance);
// Asynchronous case.
let bytes = builder.toBuffer();
assertPromiseResult(WebAssembly.instantiate(bytes)
.then(p => assertCompileErrorOnInvocation(p.instance)));
})();
// 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: --experimental-wasm-compilation-hints --wasm-test-streaming --wasm-lazy-validation
load('test/mjsunit/wasm/wasm-module-builder.js');
(function testInstantiateStreamingLazyValidation() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('id', kSig_i_i)
.addBody([kExprGetLocal, 0,
kExprI64Const, 1,
kExprI32Mul])
.setCompilationHint(kCompilationHintStrategyLazy,
kCompilationHintTierDefault,
kCompilationHintTierDefault)
.exportFunc();
let expected_error_msg = "Compiling function #0:\"id\" failed: i32.mul[1] " +
"expected type i32, found i64.const of type i64 " +
"@+56";
let assertCompileErrorOnInvocation = function(instance) {
assertThrows(() => instance.exports.id(3),
WebAssembly.CompileError,
expected_error_msg)
};
let bytes = builder.toBuffer();
assertPromiseResult(WebAssembly.instantiateStreaming(Promise.resolve(bytes))
.then(({module, instance}) => assertCompileErrorOnInvocation(instance)));
})();
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