[wasm][capi] Optimize all functions before serialization

The existing implementation of `serialize` in the C-API is to produce
a snapshot of the current state of the `NativeModule`. However, so far
all users of `serialize` did not care about the runtime of `serialize`,
but cared about `deserialize` starting up fast.

With this CL all functions of a module get tiered up to TurboFan before
serializing the module.

R=clemensb@chromium.org

Change-Id: Icaef846e33509d90b38559c0b689f798d35a98db
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4129495
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85052}
This commit is contained in:
Andreas Haas 2023-01-02 12:36:31 +01:00 committed by V8 LUCI CQ
parent 58ae6e4a81
commit 84e470845a
8 changed files with 65 additions and 14 deletions

View File

@ -1179,13 +1179,13 @@ auto Module::exports() const -> ownvec<ExportType> {
return ExportsImpl(impl(this)->v8_object());
}
// We serialize the state of the module when calling this method; an arbitrary
// number of functions can be tiered up to TurboFan, and only those will be
// serialized.
// The caller is responsible for "warming up" the module before serializing.
// We tier up all functions to TurboFan, and then serialize all TurboFan code.
// If no TurboFan code existed before calling this function, then the call to
// {serialize} may take a long time.
auto Module::serialize() const -> vec<byte_t> {
i::wasm::NativeModule* native_module =
impl(this)->v8_object()->native_module();
native_module->compilation_state()->TierUpAllFunctions();
v8::base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
size_t binary_size = wire_bytes.size();
i::wasm::WasmSerializer serializer(native_module);
@ -1200,8 +1200,10 @@ auto Module::serialize() const -> vec<byte_t> {
ptr += binary_size;
if (!serializer.SerializeNativeModule(
{reinterpret_cast<uint8_t*>(ptr), serial_size})) {
// Serialization failed, because no TurboFan code is present yet. In this
// case, the serialized module just contains the wire bytes.
// Serialization fails if no TurboFan code is present. This may happen
// because the module does not have any functions, or because another thread
// modifies the {NativeModule} concurrently. In this case, the serialized
// module just contains the wire bytes.
buffer = vec<byte_t>::make_uninitialized(size_size + binary_size);
byte_t* ptr = buffer.get();
i::wasm::LEBHelper::write_u64v(reinterpret_cast<uint8_t**>(&ptr),

View File

@ -172,6 +172,8 @@ class V8_EXPORT_PRIVATE CompilationState {
// Set a higher priority for the compilation job.
void SetHighPriority();
void TierUpAllFunctions();
bool failed() const;
bool baseline_compilation_finished() const;

View File

@ -158,7 +158,7 @@ WasmCompilationResult WasmCompilationUnit::ExecuteFunctionCompilation(
}
// static
void WasmCompilationUnit::CompileWasmFunction(Isolate* isolate,
void WasmCompilationUnit::CompileWasmFunction(Counters* counters,
NativeModule* native_module,
WasmFeatures* detected,
const WasmFunction* function,
@ -174,7 +174,7 @@ void WasmCompilationUnit::CompileWasmFunction(Isolate* isolate,
CompilationEnv env = native_module->CreateCompilationEnv();
WasmCompilationResult result = unit.ExecuteCompilation(
&env, native_module->compilation_state()->GetWireBytesStorage().get(),
isolate->counters(), nullptr, detected);
counters, nullptr, detected);
if (result.succeeded()) {
WasmCodeRefScope code_ref_scope;
native_module->PublishCode(

View File

@ -76,7 +76,7 @@ class V8_EXPORT_PRIVATE WasmCompilationUnit final {
ForDebugging for_debugging() const { return for_debugging_; }
int func_index() const { return func_index_; }
static void CompileWasmFunction(Isolate*, NativeModule*,
static void CompileWasmFunction(Counters*, NativeModule*,
WasmFeatures* detected, const WasmFunction*,
ExecutionTier);

View File

@ -631,6 +631,8 @@ class CompilationStateImpl {
compile_job_->UpdatePriority(TaskPriority::kUserBlocking);
}
void TierUpAllFunctions();
bool failed() const {
return compile_failed_.load(std::memory_order_relaxed);
}
@ -836,6 +838,10 @@ void CompilationState::AddCallback(
void CompilationState::SetHighPriority() { Impl(this)->SetHighPriority(); }
void CompilationState::TierUpAllFunctions() {
Impl(this)->TierUpAllFunctions();
}
void CompilationState::InitializeAfterDeserialization(
base::Vector<const int> lazy_functions,
base::Vector<const int> eager_functions) {
@ -1410,7 +1416,8 @@ void TierUpNowForTesting(Isolate* isolate, WasmInstanceObject instance,
TransitiveTypeFeedbackProcessor::Process(instance, func_index);
}
auto* native_module = instance.module_object().native_module();
wasm::GetWasmEngine()->CompileFunction(isolate, native_module, func_index,
wasm::GetWasmEngine()->CompileFunction(isolate->counters(), native_module,
func_index,
wasm::ExecutionTier::kTurbofan);
CHECK(!native_module->compilation_state()->failed());
}
@ -3698,6 +3705,45 @@ void CompilationStateImpl::WaitForCompilationEvent(
semaphore->Wait();
}
void CompilationStateImpl::TierUpAllFunctions() {
const WasmModule* module = native_module_->module();
uint32_t num_wasm_functions = module->num_declared_functions;
WasmCodeRefScope code_ref_scope;
CompilationUnitBuilder builder(native_module_);
for (uint32_t i = 0; i < num_wasm_functions; ++i) {
int func_index = module->num_imported_functions + i;
WasmCode* code = native_module_->GetCode(func_index);
if (!code || !code->is_turbofan()) {
builder.AddTopTierUnit(func_index, ExecutionTier::kTurbofan);
}
}
builder.Commit();
// Join the compilation, until no compilation units are left anymore.
class DummyDelegate final : public JobDelegate {
bool ShouldYield() override { return false; }
bool IsJoiningThread() const override { return true; }
void NotifyConcurrencyIncrease() override { UNIMPLEMENTED(); }
uint8_t GetTaskId() override { return kMainTaskId; }
};
DummyDelegate delegate;
ExecuteCompilationUnits(native_module_weak_, async_counters_.get(), &delegate,
kBaselineOrTopTier);
// We cannot wait for other compilation threads to finish, so we explicitly
// compile all functions which are not yet available as TurboFan code.
for (uint32_t i = 0; i < num_wasm_functions; ++i) {
uint32_t func_index = module->num_imported_functions + i;
WasmCode* code = native_module_->GetCode(func_index);
if (!code || !code->is_turbofan()) {
wasm::GetWasmEngine()->CompileFunction(async_counters_.get(),
native_module_, func_index,
wasm::ExecutionTier::kTurbofan);
}
}
}
namespace {
using JSToWasmWrapperQueue = WrapperQueue<JSToWasmWrapperKey, std::nullptr_t,
base::hash<JSToWasmWrapperKey>>;

View File

@ -705,12 +705,13 @@ std::shared_ptr<StreamingDecoder> WasmEngine::StartStreamingCompilation(
isolate, enabled, context, api_method_name, std::move(resolver));
}
void WasmEngine::CompileFunction(Isolate* isolate, NativeModule* native_module,
void WasmEngine::CompileFunction(Counters* counters,
NativeModule* native_module,
uint32_t function_index, ExecutionTier tier) {
// Note we assume that "one-off" compilations can discard detected features.
WasmFeatures detected = WasmFeatures::None();
WasmCompilationUnit::CompileWasmFunction(
isolate, native_module, &detected,
counters, native_module, &detected,
&native_module->module()->functions[function_index], tier);
}

View File

@ -202,7 +202,7 @@ class V8_EXPORT_PRIVATE WasmEngine {
// Compiles the function with the given index at a specific compilation tier.
// Errors are stored internally in the CompilationState.
// This is mostly used for testing to force a function into a specific tier.
void CompileFunction(Isolate* isolate, NativeModule* native_module,
void CompileFunction(Counters* counters, NativeModule* native_module,
uint32_t function_index, ExecutionTier tier);
void EnterDebuggingForIsolate(Isolate* isolate);

View File

@ -301,7 +301,7 @@ TEST(SharedEngineRunThreadedTierUp) {
Handle<WasmInstanceObject> instance = isolate->ImportInstance(module);
WasmFeatures detected = WasmFeatures::None();
WasmCompilationUnit::CompileWasmFunction(
isolate->isolate(), module.get(), &detected,
isolate->isolate()->counters(), module.get(), &detected,
&module->module()->functions[0], ExecutionTier::kTurbofan);
CHECK_EQ(23, isolate->Run(instance));
});