Commit b22f0e6d authored by Andreas Haas's avatar Andreas Haas Committed by V8 LUCI CQ

[wasm] Add check that lazy compilation does not trigger a GC

For lazy compilation there is one generic lazy compilation builtin that
spills all registers on the stack and then triggers the compilation of
the called function. Some of these registers may contain references.
If a GC was triggered during lazy compilation, the GC would have to
know which spill slots on the stack contain references.

This CL adds a check to guarantee that no GC can be triggered during
lazy compilation. Thereby it is not necessary for the GC to know which
spill slots contain references.

If successful, lazy compilation indeed does not allocate on the heap
and therefore cannot trigger a GC. However, when compilation fails, an
error objects needs to be allocated and thrown. This allocation may
trigger a GC, but that's not a problem, because the reference
parameters which may get corrupted by the GC will not be used anyways,
because the called function will never get executed after the failed
compilation.

R=clemensb@chromium.org

Fixes: v8:11366
Change-Id: Ic526d169d4e80ba83f517970ff234e669f854331
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3599474Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80187}
parent 5cd1a4cf
......@@ -208,6 +208,11 @@ RUNTIME_FUNCTION(Runtime_WasmStackGuard) {
}
RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
// The parameters of the called function we are going to compile have been
// spilled on the stack. Some of these parameters may be references. As we
// don't know which parameters are references, we have to make sure that no GC
// is triggered during the compilation of the function.
base::Optional<DisallowGarbageCollection> no_gc(base::in_place);
ClearThreadInWasmScope wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
......@@ -223,6 +228,14 @@ RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
isolate->set_context(instance->native_context());
bool success = wasm::CompileLazy(isolate, instance, func_index);
if (!success) {
{
// Compilation of function failed. We have to allocate the exception
// object. This allocation may trigger a GC, but that's okay, because the
// parameters on the stack will not be used anymore anyways.
no_gc.reset();
wasm::ThrowLazyCompilationError(
isolate, instance->module_object().native_module(), func_index);
}
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots(isolate).exception();
}
......
......@@ -1087,7 +1087,6 @@ void SetCompileError(ErrorThrower* thrower, ModuleWireBytes wire_bytes,
DecodeResult ValidateSingleFunction(const WasmModule* module, int func_index,
base::Vector<const uint8_t> code,
Counters* counters,
AccountingAllocator* allocator,
WasmFeatures enabled_features) {
const WasmFunction* func = &module->functions[func_index];
......@@ -1125,8 +1124,8 @@ void ValidateSequentially(
ModuleWireBytes wire_bytes{native_module->wire_bytes()};
const WasmFunction* func = &module->functions[func_index];
base::Vector<const uint8_t> code = wire_bytes.GetFunctionBytes(func);
DecodeResult result = ValidateSingleFunction(
module, func_index, code, counters, allocator, enabled_features);
DecodeResult result = ValidateSingleFunction(module, func_index, code,
allocator, enabled_features);
if (result.failed()) {
SetCompileError(thrower, wire_bytes, func, module, result.error());
}
......@@ -1174,7 +1173,6 @@ bool CompileLazy(Isolate* isolate, Handle<WasmInstanceObject> instance,
WasmCompilationUnit baseline_unit{func_index, tiers.baseline_tier,
kNoDebugging};
CompilationEnv env = native_module->CreateCompilationEnv();
WasmEngine* engine = GetWasmEngine();
// TODO(wasm): Use an assembler buffer cache for lazy compilation.
AssemblerBufferCache* assembler_buffer_cache = nullptr;
WasmFeatures detected_features;
......@@ -1192,17 +1190,7 @@ bool CompileLazy(Isolate* isolate, Handle<WasmInstanceObject> instance,
// {--wasm-lazy-validation} is enabled. Otherwise, the module was fully
// verified before starting its execution.
CHECK_IMPLIES(result.failed(), FLAG_wasm_lazy_validation);
const WasmFunction* func = &module->functions[func_index];
if (result.failed()) {
ErrorThrower thrower(isolate, nullptr);
base::Vector<const uint8_t> code =
compilation_state->GetWireBytesStorage()->GetCode(func->code);
DecodeResult decode_result =
ValidateSingleFunction(module, func_index, code, counters,
engine->allocator(), enabled_features);
CHECK(decode_result.failed());
SetCompileError(&thrower, ModuleWireBytes(native_module->wire_bytes()),
func, module, decode_result.error());
return false;
}
......@@ -1246,6 +1234,28 @@ bool CompileLazy(Isolate* isolate, Handle<WasmInstanceObject> instance,
return true;
}
void ThrowLazyCompilationError(Isolate* isolate,
const NativeModule* native_module,
int func_index) {
const WasmModule* module = native_module->module();
CompilationStateImpl* compilation_state =
Impl(native_module->compilation_state());
const WasmFunction* func = &module->functions[func_index];
base::Vector<const uint8_t> code =
compilation_state->GetWireBytesStorage()->GetCode(func->code);
WasmEngine* engine = GetWasmEngine();
auto enabled_features = native_module->enabled_features();
DecodeResult decode_result = ValidateSingleFunction(
module, func_index, code, engine->allocator(), enabled_features);
CHECK(decode_result.failed());
wasm::ErrorThrower thrower(isolate, nullptr);
SetCompileError(&thrower, ModuleWireBytes(native_module->wire_bytes()), func,
module, decode_result.error());
}
class TransitiveTypeFeedbackProcessor {
public:
TransitiveTypeFeedbackProcessor(const WasmModule* module,
......@@ -2578,9 +2588,8 @@ class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
strategy == CompileStrategy::kLazy ||
strategy == CompileStrategy::kLazyBaselineEagerTopTier;
if (validate_lazily_compiled_function) {
DecodeResult function_result =
ValidateSingleFunction(module, func_index, code, counters_,
allocator, enabled_features);
DecodeResult function_result = ValidateSingleFunction(
module, func_index, code, allocator, enabled_features);
if (function_result.failed()) {
result = ModuleResult(function_result.error());
break;
......@@ -2939,9 +2948,8 @@ bool AsyncStreamingProcessor::ProcessFunctionBody(
if (validate_lazily_compiled_function) {
// 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, async_counters_.get(),
allocator_, enabled_features);
DecodeResult result = ValidateSingleFunction(module, func_index, bytes,
allocator_, enabled_features);
if (result.failed()) {
FinishAsyncCompileJobWithError(result.error());
......
......@@ -78,6 +78,11 @@ WasmCode* CompileImportWrapper(
// also lazy.
bool CompileLazy(Isolate*, Handle<WasmInstanceObject>, int func_index);
// Throws the compilation error after failed lazy compilation.
void ThrowLazyCompilationError(Isolate* isolate,
const NativeModule* native_module,
int func_index);
V8_EXPORT_PRIVATE void TriggerTierUp(Isolate*, NativeModule*, int func_index,
Handle<WasmInstanceObject> 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