Commit 529c0664 authored by Frederik Gossen's avatar Frederik Gossen Committed by Commit Bot

[wasm-hints] Lazy Baseline Compilation

Allow for a third compilation strategy that compiles baseline code
lazily but initiates top tier compilation immediately. The strategy aims
at reducing startup time.

Bug: v8:9003
Change-Id: Ifd2060b25386c5221a45f6038c3849afeb956e69
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1571620Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Commit-Queue: Frederik Gossen <frgossen@google.com>
Cr-Commit-Position: refs/heads/master@{#61077}
parent b5da9fcb
......@@ -68,6 +68,13 @@ enum class CompileStrategy : uint8_t {
// Compiles baseline ahead of execution and starts top tier compilation in
// background (if applicable).
kEager,
// Triggers baseline compilation on first use (just like {kLazy}) with the
// difference that top tier compilation is started eagerly.
// This strategy can help to reduce startup time at the risk of blocking
// execution, but only in its early phase (until top tier compilation
// finishes).
kLazyBaselineEagerTopTier,
// Marker for default strategy.
kDefault = kEager,
};
......@@ -549,6 +556,8 @@ CompileStrategy GetCompileStrategy(const WasmModule* module,
return CompileStrategy::kLazy;
case WasmCompilationHintStrategy::kEager:
return CompileStrategy::kEager;
case WasmCompilationHintStrategy::kLazyBaselineEagerTopTier:
return CompileStrategy::kLazyBaselineEagerTopTier;
case WasmCompilationHintStrategy::kDefault:
return CompileStrategy::kDefault;
}
......@@ -629,6 +638,19 @@ class CompilationUnitBuilder {
}
}
void AddTopTierUnit(int func_index) {
ExecutionTierPair tiers = GetRequestedExecutionTiers(
native_module_->module(), compilation_state()->compile_mode(),
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));
tiering_units_.emplace_back(CreateUnit(func_index, tiers.top_tier));
}
bool Commit() {
if (baseline_units_.empty() && tiering_units_.empty()) return false;
compilation_state()->AddCompilationUnits(VectorOf(baseline_units_),
......@@ -711,7 +733,10 @@ void ValidateSequentially(
if (only_lazy_functions) {
CompileStrategy strategy = GetCompileStrategy(
module, native_module, enabled_features, func_index);
if (strategy != CompileStrategy::kLazy) continue;
if (strategy != CompileStrategy::kLazy &&
strategy != CompileStrategy::kLazyBaselineEagerTopTier) {
continue;
}
}
ModuleWireBytes wire_bytes{native_module->wire_bytes()};
......@@ -787,7 +812,9 @@ 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 (tiers.baseline_tier < tiers.top_tier) {
if (GetCompileStrategy(module, enabled_features, func_index) ==
CompileStrategy::kLazy &&
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));
......@@ -936,6 +963,9 @@ void InitializeCompilationUnits(NativeModule* native_module) {
module, native_module, native_module->enabled_features(), func_index);
if (strategy == CompileStrategy::kLazy) {
native_module->UseLazyStub(func_index);
} else if (strategy == CompileStrategy::kLazyBaselineEagerTopTier) {
builder.AddTopTierUnit(func_index);
native_module->UseLazyStub(func_index);
} else {
DCHECK_EQ(strategy, CompileStrategy::kEager);
builder.AddUnits(func_index);
......@@ -1488,7 +1518,10 @@ class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
CompileStrategy strategy =
GetCompileStrategy(module, enabled_features, func_index);
if (strategy == CompileStrategy::kLazy) {
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);
......@@ -1757,7 +1790,9 @@ bool AsyncStreamingProcessor::ProcessFunctionBody(Vector<const uint8_t> bytes,
CompileStrategy strategy =
GetCompileStrategy(module, enabled_features, func_index);
bool validate_lazily_compiled_function =
!FLAG_wasm_lazy_validation && strategy == CompileStrategy::kLazy;
!FLAG_wasm_lazy_validation &&
(strategy == CompileStrategy::kLazy ||
strategy == CompileStrategy::kLazyBaselineEagerTopTier);
if (validate_lazily_compiled_function) {
Counters* counters = Impl(native_module->compilation_state())->counters();
AccountingAllocator* allocator = native_module->engine()->allocator();
......@@ -1775,6 +1810,9 @@ bool AsyncStreamingProcessor::ProcessFunctionBody(Vector<const uint8_t> bytes,
if (strategy == CompileStrategy::kLazy) {
native_module->UseLazyStub(func_index);
} else if (strategy == CompileStrategy::kLazyBaselineEagerTopTier) {
compilation_unit_builder_->AddTopTierUnit(func_index);
native_module->UseLazyStub(func_index);
} else {
DCHECK_EQ(strategy, CompileStrategy::kEager);
compilation_unit_builder_->AddUnits(func_index);
......@@ -1916,7 +1954,10 @@ void CompilationStateImpl::InitializeCompilationProgress() {
enabled_features, func_index);
bool required_for_baseline = strategy == CompileStrategy::kEager;
bool required_for_top_tier = strategy == CompileStrategy::kEager;
bool required_for_top_tier = strategy != CompileStrategy::kLazy;
DCHECK_EQ(required_for_top_tier,
strategy == CompileStrategy::kEager ||
strategy == CompileStrategy::kLazyBaselineEagerTopTier);
// Count functions to complete baseline and top tier compilation.
if (required_for_baseline) outstanding_baseline_functions_++;
......
......@@ -1033,13 +1033,6 @@ class ModuleDecoderImpl : public Decoder {
hint.top_tier =
static_cast<WasmCompilationHintTier>(hint_byte >> 4 & 0x3);
// Check strategy.
if (hint.strategy > WasmCompilationHintStrategy::kEager) {
decoder.errorf(decoder.pc(),
"Invalid compilation hint %#x (unknown strategy)",
hint_byte);
}
// Ensure that the top tier never downgrades a compilation result.
// If baseline and top tier are the same compilation will be invoked only
// once.
......
......@@ -147,6 +147,7 @@ enum class WasmCompilationHintStrategy : uint8_t {
kDefault = 0,
kLazy = 1,
kEager = 2,
kLazyBaselineEagerTopTier = 3,
};
enum class WasmCompilationHintTier : uint8_t {
......
......@@ -265,6 +265,63 @@ TEST(Run_WasmModule_CompilationHintsTierUp) {
Cleanup();
}
TEST(Run_WasmModule_CompilationHintsLazyBaselineEagerTopTier) {
if (!FLAG_wasm_tier_up || !FLAG_liftoff) return;
{
EXPERIMENTAL_FLAG_SCOPE(compilation_hints);
static const int32_t kReturnValue = 114;
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Build module with tiering compilation hint.
WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
byte code[] = {WASM_I32V_2(kReturnValue)};
EMIT_CODE_WITH_END(f, code);
f->SetCompilationHint(
WasmCompilationHintStrategy::kLazyBaselineEagerTopTier,
WasmCompilationHintTier::kBaseline,
WasmCompilationHintTier::kOptimized);
// Compile module.
ZoneBuffer buffer(&zone);
builder->WriteTo(buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
MaybeHandle<WasmModuleObject> module = testing::CompileForTesting(
isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()));
CHECK(!module.is_null());
NativeModule* native_module = module.ToHandleChecked()->native_module();
auto* compilation_state = native_module->compilation_state();
// Busy wait for top tier compilation to finish.
while (!compilation_state->top_tier_compilation_finished()) {
}
// Expect top tier code.
static_assert(ExecutionTier::kInterpreter < ExecutionTier::kLiftoff &&
ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
"Assume an order on execution tiers");
static const int kFuncIndex = 0;
ExecutionTier top_tier = ExecutionTier::kTurbofan;
{
CHECK(native_module->HasCode(kFuncIndex));
WasmCodeRefScope code_ref_scope;
ExecutionTier actual_tier = native_module->GetCode(kFuncIndex)->tier();
CHECK_EQ(top_tier, actual_tier);
CHECK(compilation_state->baseline_compilation_finished());
CHECK(compilation_state->top_tier_compilation_finished());
}
}
Cleanup();
}
TEST(Run_WasmModule_CallAdd) {
{
v8::internal::AccountingAllocator allocator;
......
......@@ -58,3 +58,17 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
assertPromiseResult(WebAssembly.instantiate(bytes)
.then(({module, instance}) => assertEquals(42, instance.exports.id(42))));
})();
(function testCompileLazyBaselineEagerTopTierModule() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('id', kSig_i_i)
.addBody([kExprGetLocal, 0])
.setCompilationHint(kCompilationHintStrategyLazyBaselineEagerTopTier,
kCompilationHintTierDefault,
kCompilationHintTierDefault)
.exportFunc();
let bytes = builder.toBuffer();
assertPromiseResult(WebAssembly.instantiate(bytes)
.then(({module, instance}) => assertEquals(42, instance.exports.id(42))));
})();
......@@ -115,19 +115,16 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(9, instance.exports.sq(3))
})();
(function testDecodeCompilationHintsInvalidStrategy() {
(function testDecodeCompilationHintsLazyBaselineEagerTopTier() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('sq', kSig_i_i)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 0,
kExprI32Mul])
.setCompilationHint(0x3,
.setCompilationHint(kCompilationHintStrategyLazyBaselineEagerTopTier,
kCompilationHintTierOptimized,
kCompilationHintTierDefault)
.exportFunc();
assertThrows(() => builder.instantiate(),
WebAssembly.CompileError,
"WebAssembly.Module(): Invalid compilation hint 0xf " +
"(unknown strategy) @+49");
builder.instantiate();
})();
......@@ -103,3 +103,21 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
{mod: {pow: Math.pow}})
.then(({module, instance}) => assertEquals(27, instance.exports.upow(3))));
})();
(function testInstantiateStreamingLazyBaselineModule() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addImport('mod', 'pow', kSig_i_ii);
builder.addFunction('upow', kSig_i_i)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 0,
kExprCallFunction, 0])
.setCompilationHint(kCompilationHintStrategyLazyBaselineEagerTopTier,
kCompilationHintTierDefault,
kCompilationHintTierDefault)
.exportFunc();
let bytes = builder.toBuffer();
assertPromiseResult(WebAssembly.instantiateStreaming(Promise.resolve(bytes),
{mod: {pow: Math.pow}})
.then(({module, instance}) => assertEquals(27, instance.exports.upow(3))));
})();
......@@ -53,3 +53,15 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
.exportFunc();
assertEquals(42, builder.instantiate().exports.id(42));
})();
(function testCompileLazyBaselineEagerTopTierModule() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('id', kSig_i_i)
.addBody([kExprGetLocal, 0])
.setCompilationHint(kCompilationHintStrategyLazyBaselineEagerTopTier,
kCompilationHintTierDefault,
kCompilationHintTierDefault)
.exportFunc();
assertEquals(42, builder.instantiate().exports.id(42));
})();
......@@ -467,6 +467,7 @@ let kExprF32x4Min = 0x9e;
let kCompilationHintStrategyDefault = 0x00;
let kCompilationHintStrategyLazy = 0x01;
let kCompilationHintStrategyEager = 0x02;
let kCompilationHintStrategyLazyBaselineEagerTopTier = 0x03;
let kCompilationHintTierDefault = 0x00;
let kCompilationHintTierInterpreter = 0x01;
let kCompilationHintTierBaseline = 0x02;
......
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