[wasm] Basic wasm tier-up
Wasm tier-up first compiles the whole module using Liftoff, and then using Turbofan. The idea is to achieve fast start-up times by first running Liftoff-compiled code. In the meantime we finish compilation with Turbofan, and replace the Liftoff-compiled code as soon as Turbofan finished compilation, thus achieving high performance. Tier-up is enabled through the flag FLAG_wasm_tier_up. Bug: v8:6600 Change-Id: I70552969c53d909a591666a1e7ce1ee1419b2f34 Reviewed-on: https://chromium-review.googlesource.com/1010422 Commit-Queue: Kim-Anh Tran <kimanh@google.com> Reviewed-by: Andreas Haas <ahaas@chromium.org> Reviewed-by: Clemens Hammacher <clemensh@chromium.org> Cr-Commit-Position: refs/heads/master@{#52759}
This commit is contained in:
parent
3a56441a8c
commit
e47072c97a
@ -344,6 +344,11 @@ class LiftoffCompiler {
|
||||
}
|
||||
|
||||
void StartFunctionBody(Decoder* decoder, Control* block) {
|
||||
for (uint32_t i = 0; i < __ num_locals(); ++i) {
|
||||
if (!CheckSupportedType(decoder, kTypes_ilfd, __ local_type(i), "param"))
|
||||
return;
|
||||
}
|
||||
|
||||
// Input 0 is the call target, the instance is at 1.
|
||||
constexpr int kInstanceParameterIndex = 1;
|
||||
// Store the instance parameter to a special stack slot.
|
||||
@ -383,10 +388,6 @@ class LiftoffCompiler {
|
||||
// finish compilation without errors even if we hit unimplemented
|
||||
// LiftoffAssembler methods.
|
||||
if (DidAssemblerBailout(decoder)) return;
|
||||
for (uint32_t i = 0; i < __ num_locals(); ++i) {
|
||||
if (!CheckSupportedType(decoder, kTypes_ilfd, __ local_type(i), "param"))
|
||||
return;
|
||||
}
|
||||
|
||||
__ SpillInstance(instance_reg);
|
||||
// Input 0 is the code target, 1 is the instance. First parameter at 2.
|
||||
|
@ -14,6 +14,19 @@ namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
namespace {
|
||||
const char* GetCompilationModeAsString(
|
||||
WasmCompilationUnit::CompilationMode mode) {
|
||||
switch (mode) {
|
||||
case WasmCompilationUnit::CompilationMode::kLiftoff:
|
||||
return "liftoff";
|
||||
case WasmCompilationUnit::CompilationMode::kTurbofan:
|
||||
return "turbofan";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
WasmCompilationUnit::CompilationMode
|
||||
WasmCompilationUnit::GetDefaultCompilationMode() {
|
||||
@ -56,7 +69,8 @@ void WasmCompilationUnit::ExecuteCompilation() {
|
||||
TimedHistogramScope wasm_compile_function_time_scope(timed_histogram);
|
||||
|
||||
if (FLAG_trace_wasm_compiler) {
|
||||
PrintF("Compiling wasm function %d\n\n", func_index_);
|
||||
PrintF("Compiling wasm function %d with %s\n\n", func_index_,
|
||||
GetCompilationModeAsString(mode_));
|
||||
}
|
||||
|
||||
switch (mode_) {
|
||||
|
@ -80,6 +80,7 @@ class WasmCompilationUnit final {
|
||||
|
||||
size_t memory_cost() const { return memory_cost_; }
|
||||
wasm::NativeModule* native_module() const { return native_module_; }
|
||||
CompilationMode mode() const { return mode_; }
|
||||
|
||||
private:
|
||||
friend class LiftoffCompilationUnit;
|
||||
|
@ -58,10 +58,12 @@ namespace wasm {
|
||||
|
||||
enum class CompilationEvent : uint8_t {
|
||||
kFinishedBaselineCompilation,
|
||||
kFailedCompilation
|
||||
kFinishedTopTierCompilation,
|
||||
kFailedCompilation,
|
||||
kDestroyed
|
||||
};
|
||||
|
||||
enum class NotifyCompilationCallback : uint8_t { kNotify, kNoNotify };
|
||||
enum class CompileMode : uint8_t { kRegular, kTiering };
|
||||
|
||||
// The CompilationState keeps track of the compilation state of the
|
||||
// owning NativeModule, i.e. which functions are left to be compiled.
|
||||
@ -69,7 +71,7 @@ enum class NotifyCompilationCallback : uint8_t { kNotify, kNoNotify };
|
||||
// compilation of functions.
|
||||
class CompilationState {
|
||||
public:
|
||||
explicit CompilationState(internal::Isolate* isolate);
|
||||
CompilationState(internal::Isolate* isolate, ModuleEnv& env);
|
||||
~CompilationState();
|
||||
|
||||
// Needs to be set before {AddCompilationUnits} is run, which triggers
|
||||
@ -80,14 +82,17 @@ class CompilationState {
|
||||
|
||||
// Inserts new functions to compile and kicks off compilation.
|
||||
void AddCompilationUnits(
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& units);
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& baseline_units,
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& tiering_units);
|
||||
std::unique_ptr<WasmCompilationUnit> GetNextCompilationUnit();
|
||||
std::unique_ptr<WasmCompilationUnit> GetNextExecutedUnit();
|
||||
|
||||
bool HasCompilationUnitToFinish();
|
||||
|
||||
void OnError(Handle<Object> error, NotifyCompilationCallback notify);
|
||||
void OnFinishedUnit(NotifyCompilationCallback notify);
|
||||
void ScheduleUnitForFinishing(std::unique_ptr<WasmCompilationUnit> unit);
|
||||
void OnError(Handle<Object> error);
|
||||
void OnFinishedUnit();
|
||||
void ScheduleUnitForFinishing(std::unique_ptr<WasmCompilationUnit> unit,
|
||||
WasmCompilationUnit::CompilationMode mode);
|
||||
|
||||
void CancelAndWait();
|
||||
void OnBackgroundTaskStopped();
|
||||
@ -108,11 +113,48 @@ class CompilationState {
|
||||
return failed_;
|
||||
}
|
||||
|
||||
bool baseline_compilation_finished() const {
|
||||
return baseline_compilation_finished_;
|
||||
}
|
||||
|
||||
CompileMode compile_mode() const { return compile_mode_; }
|
||||
|
||||
ModuleEnv* module_env() { return &module_env_; }
|
||||
|
||||
const ModuleWireBytes& wire_bytes() const { return wire_bytes_; }
|
||||
|
||||
void SetWireBytes(const ModuleWireBytes& wire_bytes) {
|
||||
DCHECK_NULL(bytes_copy_);
|
||||
DCHECK_EQ(0, wire_bytes_.length());
|
||||
bytes_copy_ = std::unique_ptr<byte[]>(new byte[wire_bytes.length()]);
|
||||
memcpy(bytes_copy_.get(), wire_bytes.start(), wire_bytes.length());
|
||||
wire_bytes_ = ModuleWireBytes(bytes_copy_.get(),
|
||||
bytes_copy_.get() + wire_bytes.length());
|
||||
}
|
||||
|
||||
private:
|
||||
void NotifyOnEvent(CompilationEvent event, Handle<Object> error);
|
||||
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& finish_units() {
|
||||
return baseline_compilation_finished_ ? tiering_finish_units_
|
||||
: baseline_finish_units_;
|
||||
}
|
||||
|
||||
size_t GetNumCompilationUnitsScheduled() const {
|
||||
return baseline_compilation_units_.size() +
|
||||
tiering_compilation_units_.size();
|
||||
}
|
||||
|
||||
Isolate* const isolate_;
|
||||
ModuleEnv module_env_;
|
||||
const size_t max_memory_;
|
||||
const CompileMode compile_mode_;
|
||||
bool baseline_compilation_finished_ = false;
|
||||
|
||||
// TODO(wasm): eventually we want to get rid of this
|
||||
// additional copy (see AsyncCompileJob).
|
||||
std::unique_ptr<byte[]> bytes_copy_;
|
||||
ModuleWireBytes wire_bytes_;
|
||||
|
||||
// This mutex protects all information of this CompilationState which is being
|
||||
// accessed concurrently.
|
||||
@ -121,12 +163,16 @@ class CompilationState {
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Protected by {mutex_}:
|
||||
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> compilation_units_;
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> baseline_compilation_units_;
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> tiering_compilation_units_;
|
||||
|
||||
bool finisher_is_running_ = false;
|
||||
bool failed_ = false;
|
||||
size_t num_background_tasks_ = 0;
|
||||
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> finish_units_;
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> baseline_finish_units_;
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> tiering_finish_units_;
|
||||
|
||||
size_t allocated_memory_ = 0;
|
||||
|
||||
// End of fields protected by {mutex_}.
|
||||
@ -144,6 +190,7 @@ class CompilationState {
|
||||
const size_t max_background_tasks_ = 0;
|
||||
|
||||
size_t outstanding_units_ = 0;
|
||||
size_t num_tiering_units_ = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
@ -889,7 +936,7 @@ double MonotonicallyIncreasingTimeInMs() {
|
||||
base::Time::kMillisecondsPerSecond;
|
||||
}
|
||||
|
||||
ModuleEnv CreateDefaultModuleEnv(Isolate* isolate, WasmModule* module) {
|
||||
ModuleEnv CreateDefaultModuleEnv(WasmModule* module) {
|
||||
// TODO(kschimpf): Add module-specific policy handling here (see v8:7143)?
|
||||
UseTrapHandler use_trap_handler =
|
||||
trap_handler::IsTrapHandlerEnabled() ? kUseTrapHandler : kNoTrapHandler;
|
||||
@ -899,9 +946,9 @@ ModuleEnv CreateDefaultModuleEnv(Isolate* isolate, WasmModule* module) {
|
||||
Handle<WasmCompiledModule> NewCompiledModule(Isolate* isolate,
|
||||
WasmModule* module,
|
||||
Handle<FixedArray> export_wrappers,
|
||||
ModuleEnv* env) {
|
||||
Handle<WasmCompiledModule> compiled_module = WasmCompiledModule::New(
|
||||
isolate, module, export_wrappers, env->use_trap_handler);
|
||||
ModuleEnv& env) {
|
||||
Handle<WasmCompiledModule> compiled_module =
|
||||
WasmCompiledModule::New(isolate, module, export_wrappers, env);
|
||||
return compiled_module;
|
||||
}
|
||||
|
||||
@ -917,39 +964,62 @@ size_t GetMaxUsableMemorySize(Isolate* isolate) {
|
||||
class CompilationUnitBuilder {
|
||||
public:
|
||||
explicit CompilationUnitBuilder(NativeModule* native_module,
|
||||
ModuleEnv* module_env,
|
||||
Handle<Code> centry_stub)
|
||||
: native_module_(native_module),
|
||||
compilation_state_(native_module->compilation_state()),
|
||||
module_env_(module_env),
|
||||
centry_stub_(centry_stub) {}
|
||||
|
||||
void AddUnit(const WasmFunction* function, uint32_t buffer_offset,
|
||||
Vector<const uint8_t> bytes, WasmName name) {
|
||||
units_.emplace_back(new WasmCompilationUnit(
|
||||
compilation_state_->isolate(), module_env_, native_module_,
|
||||
wasm::FunctionBody{function->sig, buffer_offset, bytes.begin(),
|
||||
bytes.end()},
|
||||
name, function->func_index, centry_stub_,
|
||||
WasmCompilationUnit::GetDefaultCompilationMode(),
|
||||
compilation_state_->isolate()->async_counters().get()));
|
||||
switch (compilation_state_->compile_mode()) {
|
||||
case CompileMode::kTiering:
|
||||
tiering_units_.emplace_back(
|
||||
CreateUnit(function, buffer_offset, bytes, name,
|
||||
WasmCompilationUnit::CompilationMode::kTurbofan));
|
||||
baseline_units_.emplace_back(
|
||||
CreateUnit(function, buffer_offset, bytes, name,
|
||||
WasmCompilationUnit::CompilationMode::kLiftoff));
|
||||
return;
|
||||
case CompileMode::kRegular:
|
||||
baseline_units_.emplace_back(
|
||||
CreateUnit(function, buffer_offset, bytes, name,
|
||||
WasmCompilationUnit::GetDefaultCompilationMode()));
|
||||
return;
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool Commit() {
|
||||
if (units_.empty()) return false;
|
||||
compilation_state_->AddCompilationUnits(units_);
|
||||
units_.clear();
|
||||
if (baseline_units_.empty() && tiering_units_.empty()) return false;
|
||||
compilation_state_->AddCompilationUnits(baseline_units_, tiering_units_);
|
||||
Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Clear() { units_.clear(); }
|
||||
void Clear() {
|
||||
baseline_units_.clear();
|
||||
tiering_units_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<WasmCompilationUnit> CreateUnit(
|
||||
const WasmFunction* function, uint32_t buffer_offset,
|
||||
Vector<const uint8_t> bytes, WasmName name,
|
||||
WasmCompilationUnit::CompilationMode mode) {
|
||||
return base::make_unique<WasmCompilationUnit>(
|
||||
compilation_state_->isolate(), compilation_state_->module_env(),
|
||||
native_module_,
|
||||
wasm::FunctionBody{function->sig, buffer_offset, bytes.begin(),
|
||||
bytes.end()},
|
||||
name, function->func_index, centry_stub_, mode,
|
||||
compilation_state_->isolate()->async_counters().get());
|
||||
}
|
||||
|
||||
NativeModule* native_module_;
|
||||
CompilationState* compilation_state_;
|
||||
ModuleEnv* module_env_;
|
||||
Handle<Code> centry_stub_;
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> units_;
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> baseline_units_;
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>> tiering_units_;
|
||||
};
|
||||
|
||||
// Run by each compilation task and by the main thread (i.e. in both
|
||||
@ -966,39 +1036,45 @@ bool FetchAndExecuteCompilationUnit(CompilationState* compilation_state) {
|
||||
compilation_state->GetNextCompilationUnit();
|
||||
if (unit == nullptr) return false;
|
||||
|
||||
// TODO(kimanh): We need to find out in which mode the unit
|
||||
// should be compiled in before compiling it, as it might fallback
|
||||
// to Turbofan if it cannot be compiled using Liftoff. This can be removed
|
||||
// later as soon as Liftoff can compile any function. Then, we can directly
|
||||
// access {unit->mode()} within {ScheduleUnitForFinishing()}.
|
||||
WasmCompilationUnit::CompilationMode mode = unit->mode();
|
||||
unit->ExecuteCompilation();
|
||||
compilation_state->ScheduleUnitForFinishing(std::move(unit));
|
||||
compilation_state->ScheduleUnitForFinishing(std::move(unit), mode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t GetNumFunctionsToCompile(const std::vector<WasmFunction>& functions,
|
||||
ModuleEnv* module_env) {
|
||||
size_t GetNumFunctionsToCompile(const WasmModule* wasm_module) {
|
||||
// TODO(kimanh): Remove, FLAG_skip_compiling_wasm_funcs: previously used for
|
||||
// debugging, and now not necessarily working anymore.
|
||||
uint32_t start = module_env->module->num_imported_functions +
|
||||
FLAG_skip_compiling_wasm_funcs;
|
||||
uint32_t num_funcs = static_cast<uint32_t>(functions.size());
|
||||
uint32_t start =
|
||||
wasm_module->num_imported_functions + FLAG_skip_compiling_wasm_funcs;
|
||||
uint32_t num_funcs = static_cast<uint32_t>(wasm_module->functions.size());
|
||||
uint32_t funcs_to_compile = start > num_funcs ? 0 : num_funcs - start;
|
||||
return funcs_to_compile;
|
||||
}
|
||||
|
||||
void InitializeCompilationUnits(const std::vector<WasmFunction>& functions,
|
||||
const ModuleWireBytes& wire_bytes,
|
||||
ModuleEnv* module_env, Handle<Code> centry_stub,
|
||||
const WasmModule* wasm_module,
|
||||
Handle<Code> centry_stub,
|
||||
NativeModule* native_module) {
|
||||
uint32_t start = module_env->module->num_imported_functions +
|
||||
FLAG_skip_compiling_wasm_funcs;
|
||||
uint32_t start =
|
||||
wasm_module->num_imported_functions + FLAG_skip_compiling_wasm_funcs;
|
||||
uint32_t num_funcs = static_cast<uint32_t>(functions.size());
|
||||
|
||||
CompilationUnitBuilder builder(native_module, module_env, centry_stub);
|
||||
CompilationUnitBuilder builder(native_module, centry_stub);
|
||||
for (uint32_t i = start; i < num_funcs; ++i) {
|
||||
const WasmFunction* func = &functions[i];
|
||||
uint32_t buffer_offset = func->code.offset();
|
||||
Vector<const uint8_t> bytes(wire_bytes.start() + func->code.offset(),
|
||||
func->code.end_offset() - func->code.offset());
|
||||
|
||||
WasmName name = wire_bytes.GetName(func, module_env->module);
|
||||
WasmName name = wire_bytes.GetName(func, wasm_module);
|
||||
DCHECK_NOT_NULL(native_module);
|
||||
builder.AddUnit(func, buffer_offset, bytes, name);
|
||||
}
|
||||
@ -1014,8 +1090,13 @@ void FinishCompilationUnits(CompilationState* compilation_state,
|
||||
if (unit == nullptr) break;
|
||||
wasm::WasmCode* result = unit->FinishCompilation(thrower);
|
||||
|
||||
if (thrower->error()) {
|
||||
compilation_state->Abort();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the compilation state.
|
||||
compilation_state->OnFinishedUnit(NotifyCompilationCallback::kNoNotify);
|
||||
compilation_state->OnFinishedUnit();
|
||||
DCHECK_IMPLIES(result == nullptr, thrower->error());
|
||||
if (result == nullptr) break;
|
||||
}
|
||||
@ -1024,6 +1105,52 @@ void FinishCompilationUnits(CompilationState* compilation_state,
|
||||
}
|
||||
}
|
||||
|
||||
void PatchNativeModule(NativeModule* cloning_module,
|
||||
const NativeModule* source_module) {
|
||||
// Clone optimized code into {cloning_module}.
|
||||
if (source_module != cloning_module) {
|
||||
cloning_module->CloneHigherTierCodeFrom(source_module);
|
||||
}
|
||||
|
||||
// Link.
|
||||
CodeSpecialization code_specialization;
|
||||
code_specialization.RelocateDirectCalls(cloning_module);
|
||||
code_specialization.ApplyToWholeModule(cloning_module);
|
||||
}
|
||||
|
||||
void UpdateAllCompiledModulesWithTopTierCode(
|
||||
Handle<WasmCompiledModule> compiled_module) {
|
||||
// We want to disallow heap allocation here, as this might interfere with
|
||||
// iterating over the chain of compiled modules for updating all native
|
||||
// modules.
|
||||
DisallowHeapAllocation no_gc;
|
||||
|
||||
WasmModule* module = compiled_module->shared()->module();
|
||||
DCHECK_GT(module->functions.size() - module->num_imported_functions, 0);
|
||||
USE(module);
|
||||
|
||||
CodeSpaceMemoryModificationScope modification_scope(
|
||||
compiled_module->GetIsolate()->heap());
|
||||
NativeModule* updated_module = compiled_module->GetNativeModule();
|
||||
|
||||
Handle<WasmCompiledModule> current = compiled_module;
|
||||
PatchNativeModule(current->GetNativeModule(), updated_module);
|
||||
|
||||
// Go through the chain of compiled modules to update each (next in chain).
|
||||
while (current->has_next_instance()) {
|
||||
current = handle(current->next_instance());
|
||||
PatchNativeModule(current->GetNativeModule(), updated_module);
|
||||
}
|
||||
|
||||
// Go through the chain of compiled modules to update each (previous in
|
||||
// chain).
|
||||
current = compiled_module;
|
||||
while (current->has_prev_instance()) {
|
||||
current = handle(current->prev_instance());
|
||||
PatchNativeModule(current->GetNativeModule(), updated_module);
|
||||
}
|
||||
}
|
||||
|
||||
void CompileInParallel(Isolate* isolate, NativeModule* native_module,
|
||||
const ModuleWireBytes& wire_bytes, ModuleEnv* module_env,
|
||||
Handle<Code> centry_stub, ErrorThrower* thrower) {
|
||||
@ -1040,12 +1167,15 @@ void CompileInParallel(Isolate* isolate, NativeModule* native_module,
|
||||
// 2.a) The background threads and the main thread pick one compilation
|
||||
// unit at a time and execute the parallel phase of the compilation
|
||||
// unit. After finishing the execution of the parallel phase, the
|
||||
// result is enqueued in {finish_units_}.
|
||||
// 2.b) If {finish_units_} contains a compilation unit, the main thread
|
||||
// dequeues it and finishes the compilation.
|
||||
// result is enqueued in {baseline_finish_units_}.
|
||||
// 2.b) If {baseline_finish_units_} contains a compilation unit, the main
|
||||
// thread dequeues it and finishes the compilation.
|
||||
// 3) After the parallel phase of all compilation units has started, the
|
||||
// main thread waits for all {BackgroundCompileTasks} instances to finish.
|
||||
// 4) The main thread finishes the compilation.
|
||||
// main thread continues to finish all compilation units as long as
|
||||
// baseline-compilation units are left to be processed.
|
||||
// 4) If tier-up is enabled, the main thread restarts background tasks
|
||||
// that take care of compiling and finishing the top-tier compilation
|
||||
// units.
|
||||
|
||||
// Turn on the {CanonicalHandleScope} so that the background threads can
|
||||
// use the node cache.
|
||||
@ -1056,45 +1186,102 @@ void CompileInParallel(Isolate* isolate, NativeModule* native_module,
|
||||
// the compilation units. This foreground thread will be
|
||||
// responsible for finishing compilation.
|
||||
compilation_state->SetFinisherIsRunning(true);
|
||||
size_t functions_count =
|
||||
GetNumFunctionsToCompile(module->functions, module_env);
|
||||
size_t functions_count = GetNumFunctionsToCompile(module);
|
||||
compilation_state->SetNumberOfFunctionsToCompile(functions_count);
|
||||
compilation_state->SetWireBytes(wire_bytes);
|
||||
|
||||
DeferredHandles* deferred_handles = nullptr;
|
||||
Handle<Code> centry_deferred = centry_stub;
|
||||
Handle<WasmCompiledModule> compiled_module_deferred;
|
||||
if (compilation_state->compile_mode() == CompileMode::kTiering) {
|
||||
// Open a deferred handle scope for the centry_stub, in order to allow
|
||||
// for background tiering compilation.
|
||||
DeferredHandleScope deferred(isolate);
|
||||
centry_deferred = Handle<Code>(*centry_stub, isolate);
|
||||
compiled_module_deferred =
|
||||
handle(native_module->compiled_module(), compilation_state->isolate());
|
||||
deferred_handles = deferred.Detach();
|
||||
}
|
||||
compilation_state->AddCallback(
|
||||
[compiled_module_deferred, deferred_handles](
|
||||
// Callback is called from a foreground thread.
|
||||
CompilationEvent event, Handle<Object> error) mutable {
|
||||
switch (event) {
|
||||
case CompilationEvent::kFinishedBaselineCompilation:
|
||||
// Nothing to do, since we are finishing baseline compilation
|
||||
// in this foreground thread.
|
||||
return;
|
||||
case CompilationEvent::kFinishedTopTierCompilation:
|
||||
UpdateAllCompiledModulesWithTopTierCode(compiled_module_deferred);
|
||||
// TODO(wasm): Currently compilation has to finish before the
|
||||
// {deferred_handles} can be removed. We need to make sure that
|
||||
// we can clean it up at a time when the native module
|
||||
// should die (but currently cannot, since it's kept alive
|
||||
// through the {deferred_handles} themselves).
|
||||
delete deferred_handles;
|
||||
deferred_handles = nullptr;
|
||||
return;
|
||||
case CompilationEvent::kFailedCompilation:
|
||||
// If baseline compilation failed, we will reflect this without
|
||||
// a callback, in this thread through {thrower}.
|
||||
// Tier-up compilation should not fail if baseline compilation
|
||||
// did not fail.
|
||||
DCHECK(!compiled_module_deferred->GetNativeModule()
|
||||
->compilation_state()
|
||||
->baseline_compilation_finished());
|
||||
delete deferred_handles;
|
||||
deferred_handles = nullptr;
|
||||
return;
|
||||
case CompilationEvent::kDestroyed:
|
||||
if (deferred_handles) delete deferred_handles;
|
||||
return;
|
||||
}
|
||||
UNREACHABLE();
|
||||
});
|
||||
|
||||
// 1) The main thread allocates a compilation unit for each wasm function
|
||||
// and stores them in the vector {compilation_units} within the
|
||||
// {compilation_state}. By adding units to the {compilation_state}, new
|
||||
// {BackgroundCompileTask} instances are spawned which run on
|
||||
// background threads.
|
||||
InitializeCompilationUnits(module->functions, wire_bytes, module_env,
|
||||
centry_stub, native_module);
|
||||
InitializeCompilationUnits(module->functions, compilation_state->wire_bytes(),
|
||||
module, centry_deferred, native_module);
|
||||
|
||||
// 2.a) The background threads and the main thread pick one compilation
|
||||
// unit at a time and execute the parallel phase of the compilation
|
||||
// unit. After finishing the execution of the parallel phase, the
|
||||
// result is enqueued in {finish_units_}.
|
||||
// result is enqueued in {baseline_finish_units_}.
|
||||
// The foreground task bypasses waiting on memory threshold, because
|
||||
// its results will immediately be converted to code (below).
|
||||
while (FetchAndExecuteCompilationUnit(compilation_state)) {
|
||||
// 2.b) If {finish_units_} contains a compilation unit, the main thread
|
||||
// dequeues it and finishes the compilation unit. Compilation units
|
||||
// are finished concurrently to the background threads to save
|
||||
while (FetchAndExecuteCompilationUnit(compilation_state) &&
|
||||
!compilation_state->baseline_compilation_finished()) {
|
||||
// 2.b) If {baseline_finish_units_} contains a compilation unit, the main
|
||||
// thread dequeues it and finishes the compilation unit. Compilation
|
||||
// units are finished concurrently to the background threads to save
|
||||
// memory.
|
||||
FinishCompilationUnits(compilation_state, thrower);
|
||||
|
||||
if (compilation_state->failed()) break;
|
||||
}
|
||||
|
||||
// 3) After the parallel phase of all compilation units has started, the
|
||||
// main thread waits for all {BackgroundCompileTasks} instances to finish -
|
||||
// which happens once they all realize there's no next work item to
|
||||
// process. If compilation already failed, all background tasks have
|
||||
// already been canceled in {FinishCompilationUnits}, and there are
|
||||
// no units to finish.
|
||||
if (!compilation_state->failed()) {
|
||||
compilation_state->CancelAndWait();
|
||||
|
||||
// 4) Finish all compilation units which have been executed while we waited.
|
||||
while (!compilation_state->failed()) {
|
||||
// 3) After the parallel phase of all compilation units has started, the
|
||||
// main thread continues to finish compilation units as long as
|
||||
// baseline compilation units are left to be processed. If compilation
|
||||
// already failed, all background tasks have already been canceled
|
||||
// in {FinishCompilationUnits}, and there are no units to finish.
|
||||
FinishCompilationUnits(compilation_state, thrower);
|
||||
|
||||
if (compilation_state->baseline_compilation_finished()) break;
|
||||
}
|
||||
|
||||
// 4) If tiering-compilation is enabled, we need to set the finisher
|
||||
// to false, such that the background threads will spawn a foreground
|
||||
// thread to finish the top-tier compilation units.
|
||||
if (!compilation_state->failed() &&
|
||||
compilation_state->compile_mode() == CompileMode::kTiering) {
|
||||
compilation_state->SetFinisherIsRunning(false);
|
||||
compilation_state->RestartBackgroundTasks();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1209,14 +1396,14 @@ MaybeHandle<WasmModuleObject> CompileToModuleObjectInternal(
|
||||
for (int i = 0, e = export_wrappers->length(); i < e; ++i) {
|
||||
export_wrappers->set(i, *init_builtin);
|
||||
}
|
||||
ModuleEnv env = CreateDefaultModuleEnv(isolate, wasm_module);
|
||||
ModuleEnv env = CreateDefaultModuleEnv(wasm_module);
|
||||
|
||||
// Create the compiled module object and populate with compiled functions
|
||||
// and information needed at instantiation time. This object needs to be
|
||||
// serializable. Instantiation may occur off a deserialized version of this
|
||||
// object.
|
||||
Handle<WasmCompiledModule> compiled_module =
|
||||
NewCompiledModule(isolate, shared->module(), export_wrappers, &env);
|
||||
NewCompiledModule(isolate, shared->module(), export_wrappers, env);
|
||||
NativeModule* native_module = compiled_module->GetNativeModule();
|
||||
compiled_module->set_shared(*shared);
|
||||
if (lazy_compile) {
|
||||
@ -1314,21 +1501,33 @@ class FinishCompileTask : public CancelableTask {
|
||||
ErrorThrower thrower(compilation_state_->isolate(), "AsyncCompile");
|
||||
wasm::WasmCode* result = unit->FinishCompilation(&thrower);
|
||||
|
||||
NativeModule* native_module = unit->native_module();
|
||||
if (thrower.error()) {
|
||||
DCHECK_NULL(result);
|
||||
USE(result);
|
||||
SaveContext saved_context(isolate);
|
||||
isolate->set_context(
|
||||
unit->native_module()->compiled_module()->native_context());
|
||||
native_module->compiled_module()->native_context());
|
||||
Handle<Object> error = thrower.Reify();
|
||||
compilation_state_->OnError(error, NotifyCompilationCallback::kNotify);
|
||||
compilation_state_->OnError(error);
|
||||
compilation_state_->SetFinisherIsRunning(false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (compilation_state_->baseline_compilation_finished()) {
|
||||
// If Liftoff compilation finishes it will directly start executing.
|
||||
// As soon as we have Turbofan-compiled code available, it will
|
||||
// directly be used by Liftoff-compiled code. Therefore we need
|
||||
// to patch the compiled Turbofan function directly after finishing it.
|
||||
DCHECK_EQ(CompileMode::kTiering, compilation_state_->compile_mode());
|
||||
CodeSpecialization code_specialization;
|
||||
code_specialization.RelocateDirectCalls(native_module);
|
||||
code_specialization.ApplyToWasmCode(result);
|
||||
}
|
||||
|
||||
// Update the compilation state, and possibly notify
|
||||
// threads waiting for events.
|
||||
compilation_state_->OnFinishedUnit(NotifyCompilationCallback::kNotify);
|
||||
compilation_state_->OnFinishedUnit();
|
||||
|
||||
if (deadline < MonotonicallyIncreasingTimeInMs()) {
|
||||
// We reached the deadline. We reschedule this task and return
|
||||
@ -2498,7 +2697,7 @@ class AsyncStreamingProcessor final : public StreamingProcessor {
|
||||
void OnAbort() override;
|
||||
|
||||
private:
|
||||
// Finishes the AsyncCOmpileJob with an error.
|
||||
// Finishes the AsyncCompileJob with an error.
|
||||
void FinishAsyncCompileJobWithError(ResultBase result);
|
||||
|
||||
void CommitCompilationUnits();
|
||||
@ -2532,9 +2731,6 @@ void AsyncCompileJob::AsyncCompileFailed(Handle<Object> error_reason) {
|
||||
}
|
||||
|
||||
void AsyncCompileJob::AsyncCompileSucceeded(Handle<Object> result) {
|
||||
// {job} keeps the {this} pointer alive.
|
||||
std::shared_ptr<AsyncCompileJob> job =
|
||||
isolate_->wasm_engine()->compilation_manager()->RemoveJob(this);
|
||||
MaybeHandle<Object> promise_result =
|
||||
JSPromise::Resolve(module_promise_, result);
|
||||
CHECK_EQ(promise_result.is_null(), isolate_->has_pending_exception());
|
||||
@ -2695,10 +2891,6 @@ class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
|
||||
|
||||
Isolate* isolate = job_->isolate_;
|
||||
|
||||
DCHECK_NULL(job_->module_env_);
|
||||
job_->module_env_.reset(
|
||||
new ModuleEnv(CreateDefaultModuleEnv(isolate, module_)));
|
||||
|
||||
Handle<Code> centry_stub = CEntryStub(isolate, 1).GetCode();
|
||||
{
|
||||
// Now reopen the handles in a deferred scope in order to use
|
||||
@ -2717,8 +2909,9 @@ class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
|
||||
Handle<FixedArray> export_wrappers =
|
||||
job_->isolate_->factory()->NewFixedArray(export_wrapper_size, TENURED);
|
||||
|
||||
job_->compiled_module_ = NewCompiledModule(
|
||||
job_->isolate_, module_, export_wrappers, job_->module_env_.get());
|
||||
ModuleEnv env = CreateDefaultModuleEnv(module_);
|
||||
job_->compiled_module_ =
|
||||
NewCompiledModule(job_->isolate_, module_, export_wrappers, env);
|
||||
|
||||
{
|
||||
DeferredHandleScope deferred(job_->isolate_);
|
||||
@ -2729,6 +2922,9 @@ class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
|
||||
module_->functions.size() - module_->num_imported_functions;
|
||||
|
||||
if (num_functions == 0) {
|
||||
// Tiering has nothing to do if module is empty.
|
||||
job_->tiering_completed_ = true;
|
||||
|
||||
// Degenerate case of an empty module.
|
||||
job_->DoSync<FinishCompile>();
|
||||
return;
|
||||
@ -2744,18 +2940,42 @@ class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
|
||||
AsyncCompileJob* job = job_;
|
||||
compilation_state->AddCallback(
|
||||
[job](CompilationEvent event, Handle<Object> error) {
|
||||
// Callback is called from a foreground thread.
|
||||
switch (event) {
|
||||
case CompilationEvent::kFinishedBaselineCompilation:
|
||||
if (job->DecrementAndCheckFinisherCount()) {
|
||||
job->DoSync<FinishCompile>();
|
||||
}
|
||||
return;
|
||||
case CompilationEvent::kFailedCompilation:
|
||||
case CompilationEvent::kFinishedTopTierCompilation:
|
||||
// It is only safe to schedule the UpdateToTopTierCompiledCode
|
||||
// step if no foreground task is currently pending, and no
|
||||
// finisher is outstanding (streaming compilation).
|
||||
if (job->num_pending_foreground_tasks_ == 0 &&
|
||||
job->outstanding_finishers_.Value() == 0) {
|
||||
job->DoSync<UpdateToTopTierCompiledCode>();
|
||||
}
|
||||
// If a foreground task was pending or a finsher was pending,
|
||||
// we will rely on FinishModule to switch the step to
|
||||
// UpdateToTopTierCompiledCode.
|
||||
job->tiering_completed_ = true;
|
||||
return;
|
||||
case CompilationEvent::kFailedCompilation: {
|
||||
// Tier-up compilation should not fail if baseline compilation
|
||||
// did not fail.
|
||||
DCHECK(!job->compiled_module_->GetNativeModule()
|
||||
->compilation_state()
|
||||
->baseline_compilation_finished());
|
||||
|
||||
DeferredHandleScope deferred(job->isolate());
|
||||
error = handle(*error, job->isolate());
|
||||
job->deferred_handles_.push_back(deferred.Detach());
|
||||
job->DoSync<CompileFailed>(error);
|
||||
return;
|
||||
}
|
||||
case CompilationEvent::kDestroyed:
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
UNREACHABLE();
|
||||
});
|
||||
@ -2766,12 +2986,11 @@ class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
|
||||
// InitializeCompilationUnits always returns 0 for streaming compilation,
|
||||
// then DoAsync would do the same as NextStep already.
|
||||
|
||||
size_t functions_count =
|
||||
GetNumFunctionsToCompile(module_->functions, job_->module_env_.get());
|
||||
size_t functions_count = GetNumFunctionsToCompile(env.module);
|
||||
compilation_state->SetNumberOfFunctionsToCompile(functions_count);
|
||||
// Add compilation units and kick off compilation.
|
||||
InitializeCompilationUnits(module_->functions, job_->wire_bytes_,
|
||||
job_->module_env_.get(), job_->centry_stub_,
|
||||
env.module, job_->centry_stub_,
|
||||
job_->compiled_module_->GetNativeModule());
|
||||
}
|
||||
}
|
||||
@ -2870,8 +3089,40 @@ class AsyncCompileJob::FinishModule : public CompileStep {
|
||||
TRACE_COMPILE("(7) Finish module...\n");
|
||||
Handle<WasmModuleObject> result =
|
||||
WasmModuleObject::New(job_->isolate_, job_->compiled_module_);
|
||||
// {job_} is deleted in AsyncCompileSucceeded, therefore the {return}.
|
||||
return job_->AsyncCompileSucceeded(result);
|
||||
job_->AsyncCompileSucceeded(result);
|
||||
|
||||
WasmModule* module = job_->compiled_module_->shared()->module();
|
||||
size_t num_functions =
|
||||
module->functions.size() - module->num_imported_functions;
|
||||
if (job_->compiled_module_->GetNativeModule()
|
||||
->compilation_state()
|
||||
->compile_mode() == CompileMode::kRegular ||
|
||||
num_functions == 0) {
|
||||
// If we do not tier up, the async compile job is done here and
|
||||
// can be deleted.
|
||||
job_->isolate_->wasm_engine()->compilation_manager()->RemoveJob(job_);
|
||||
return;
|
||||
}
|
||||
// If background tiering compilation finished before we resolved the
|
||||
// promise, switch to patching now. Otherwise, patching will be scheduled
|
||||
// by a callback.
|
||||
DCHECK_EQ(CompileMode::kTiering, job_->compiled_module_->GetNativeModule()
|
||||
->compilation_state()
|
||||
->compile_mode());
|
||||
if (job_->tiering_completed_) {
|
||||
job_->DoSync<UpdateToTopTierCompiledCode>();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
// Step 8 (sync): Update with top tier code.
|
||||
//==========================================================================
|
||||
class AsyncCompileJob::UpdateToTopTierCompiledCode : public CompileStep {
|
||||
void RunInForeground() override {
|
||||
TRACE_COMPILE("(8) Update native module to use optimized code...\n");
|
||||
UpdateAllCompiledModulesWithTopTierCode(job_->compiled_module_);
|
||||
job_->isolate_->wasm_engine()->compilation_manager()->RemoveJob(job_);
|
||||
}
|
||||
};
|
||||
|
||||
@ -2987,8 +3238,8 @@ bool AsyncStreamingProcessor::ProcessCodeSectionHeader(size_t functions_count,
|
||||
// Set outstanding_finishers_ to 2, because both the AsyncCompileJob and the
|
||||
// AsyncStreamingProcessor have to finish.
|
||||
job_->outstanding_finishers_.SetValue(2);
|
||||
compilation_unit_builder_.reset(new CompilationUnitBuilder(
|
||||
native_module, job_->module_env_.get(), job_->centry_stub_));
|
||||
compilation_unit_builder_.reset(
|
||||
new CompilationUnitBuilder(native_module, job_->centry_stub_));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -3063,14 +3314,22 @@ void CompilationStateDeleter::operator()(
|
||||
}
|
||||
|
||||
std::unique_ptr<CompilationState, CompilationStateDeleter> NewCompilationState(
|
||||
Isolate* isolate) {
|
||||
Isolate* isolate, ModuleEnv& env) {
|
||||
return std::unique_ptr<CompilationState, CompilationStateDeleter>(
|
||||
new CompilationState(isolate));
|
||||
new CompilationState(isolate, env));
|
||||
}
|
||||
|
||||
CompilationState::CompilationState(internal::Isolate* isolate)
|
||||
ModuleEnv* GetModuleEnv(CompilationState* compilation_state) {
|
||||
return compilation_state->module_env();
|
||||
}
|
||||
|
||||
CompilationState::CompilationState(internal::Isolate* isolate, ModuleEnv& env)
|
||||
: isolate_(isolate),
|
||||
module_env_(env),
|
||||
max_memory_(GetMaxUsableMemorySize(isolate) / 2),
|
||||
compile_mode_(FLAG_wasm_tier_up ? CompileMode::kTiering
|
||||
: CompileMode::kRegular),
|
||||
wire_bytes_(ModuleWireBytes(nullptr, nullptr)),
|
||||
max_background_tasks_(std::max(
|
||||
1, std::min(FLAG_wasm_num_compilation_tasks,
|
||||
V8::GetCurrentPlatform()->NumberOfWorkerThreads()))) {
|
||||
@ -3082,16 +3341,24 @@ CompilationState::CompilationState(internal::Isolate* isolate)
|
||||
|
||||
// Register task manager for clean shutdown in case of an isolate shutdown.
|
||||
isolate_->wasm_engine()->Register(&background_task_manager_);
|
||||
isolate_->wasm_engine()->Register(&foreground_task_manager_);
|
||||
}
|
||||
|
||||
CompilationState::~CompilationState() {
|
||||
CancelAndWait();
|
||||
foreground_task_manager_.CancelAndWait();
|
||||
isolate_->wasm_engine()->Unregister(&foreground_task_manager_);
|
||||
NotifyOnEvent(CompilationEvent::kDestroyed, Handle<Object>::null());
|
||||
}
|
||||
|
||||
void CompilationState::SetNumberOfFunctionsToCompile(size_t num_functions) {
|
||||
DCHECK(!failed());
|
||||
outstanding_units_ = num_functions;
|
||||
|
||||
if (compile_mode_ == CompileMode::kTiering) {
|
||||
outstanding_units_ += num_functions;
|
||||
num_tiering_units_ = num_functions;
|
||||
}
|
||||
}
|
||||
|
||||
void CompilationState::AddCallback(
|
||||
@ -3100,23 +3367,43 @@ void CompilationState::AddCallback(
|
||||
}
|
||||
|
||||
void CompilationState::AddCompilationUnits(
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& units) {
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& baseline_units,
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& tiering_units) {
|
||||
{
|
||||
base::LockGuard<base::Mutex> guard(&mutex_);
|
||||
compilation_units_.insert(compilation_units_.end(),
|
||||
std::make_move_iterator(units.begin()),
|
||||
std::make_move_iterator(units.end()));
|
||||
|
||||
if (compile_mode_ == CompileMode::kTiering) {
|
||||
DCHECK_EQ(baseline_units.size(), tiering_units.size());
|
||||
DCHECK_EQ(tiering_units.back()->mode(),
|
||||
WasmCompilationUnit::CompilationMode::kTurbofan);
|
||||
tiering_compilation_units_.insert(
|
||||
tiering_compilation_units_.end(),
|
||||
std::make_move_iterator(tiering_units.begin()),
|
||||
std::make_move_iterator(tiering_units.end()));
|
||||
} else {
|
||||
DCHECK(tiering_compilation_units_.empty());
|
||||
}
|
||||
|
||||
baseline_compilation_units_.insert(
|
||||
baseline_compilation_units_.end(),
|
||||
std::make_move_iterator(baseline_units.begin()),
|
||||
std::make_move_iterator(baseline_units.end()));
|
||||
}
|
||||
RestartBackgroundTasks(units.size());
|
||||
|
||||
RestartBackgroundTasks(GetNumCompilationUnitsScheduled());
|
||||
}
|
||||
|
||||
std::unique_ptr<WasmCompilationUnit>
|
||||
CompilationState::GetNextCompilationUnit() {
|
||||
base::LockGuard<base::Mutex> guard(&mutex_);
|
||||
if (!compilation_units_.empty()) {
|
||||
std::unique_ptr<WasmCompilationUnit> unit =
|
||||
std::move(compilation_units_.back());
|
||||
compilation_units_.pop_back();
|
||||
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& units =
|
||||
baseline_compilation_units_.empty() ? tiering_compilation_units_
|
||||
: baseline_compilation_units_;
|
||||
|
||||
if (!units.empty()) {
|
||||
std::unique_ptr<WasmCompilationUnit> unit = std::move(units.back());
|
||||
units.pop_back();
|
||||
return unit;
|
||||
}
|
||||
|
||||
@ -3125,44 +3412,67 @@ CompilationState::GetNextCompilationUnit() {
|
||||
|
||||
std::unique_ptr<WasmCompilationUnit> CompilationState::GetNextExecutedUnit() {
|
||||
base::LockGuard<base::Mutex> guard(&mutex_);
|
||||
if (finish_units_.empty()) return {};
|
||||
std::unique_ptr<WasmCompilationUnit> ret = std::move(finish_units_.back());
|
||||
finish_units_.pop_back();
|
||||
std::vector<std::unique_ptr<WasmCompilationUnit>>& units = finish_units();
|
||||
if (units.empty()) return {};
|
||||
std::unique_ptr<WasmCompilationUnit> ret = std::move(units.back());
|
||||
units.pop_back();
|
||||
allocated_memory_ -= ret->memory_cost();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CompilationState::HasCompilationUnitToFinish() {
|
||||
base::LockGuard<base::Mutex> guard(&mutex_);
|
||||
return !finish_units_.empty();
|
||||
return !finish_units().empty();
|
||||
}
|
||||
|
||||
void CompilationState::OnError(Handle<Object> error,
|
||||
NotifyCompilationCallback notify) {
|
||||
void CompilationState::OnError(Handle<Object> error) {
|
||||
Abort();
|
||||
if (notify == NotifyCompilationCallback::kNotify) {
|
||||
NotifyOnEvent(CompilationEvent::kFailedCompilation, error);
|
||||
}
|
||||
NotifyOnEvent(CompilationEvent::kFailedCompilation, error);
|
||||
}
|
||||
|
||||
void CompilationState::OnFinishedUnit(NotifyCompilationCallback notify) {
|
||||
void CompilationState::OnFinishedUnit() {
|
||||
DCHECK_GT(outstanding_units_, 0);
|
||||
--outstanding_units_;
|
||||
|
||||
if (outstanding_units_ == 0) {
|
||||
CancelAndWait();
|
||||
if (notify == NotifyCompilationCallback::kNotify) {
|
||||
NotifyOnEvent(CompilationEvent::kFinishedBaselineCompilation,
|
||||
Handle<Object>::null());
|
||||
}
|
||||
baseline_compilation_finished_ = true;
|
||||
|
||||
DCHECK(compile_mode_ == CompileMode::kRegular ||
|
||||
compile_mode_ == CompileMode::kTiering);
|
||||
NotifyOnEvent(compile_mode_ == CompileMode::kRegular
|
||||
? CompilationEvent::kFinishedBaselineCompilation
|
||||
: CompilationEvent::kFinishedTopTierCompilation,
|
||||
Handle<Object>::null());
|
||||
|
||||
} else if (outstanding_units_ == num_tiering_units_) {
|
||||
DCHECK_EQ(compile_mode_, CompileMode::kTiering);
|
||||
baseline_compilation_finished_ = true;
|
||||
|
||||
// TODO(wasm): For streaming compilation, we want to start top tier
|
||||
// compilation before all functions have been compiled with Liftoff, e.g.
|
||||
// in the case when all received functions have been compiled with Liftoff
|
||||
// and we are waiting for new functions to compile.
|
||||
|
||||
// If we are in {kRegular} mode, {num_tiering_units_} is 0, therefore
|
||||
// this case is already caught by the previous check.
|
||||
NotifyOnEvent(CompilationEvent::kFinishedBaselineCompilation,
|
||||
Handle<Object>::null());
|
||||
RestartBackgroundTasks(GetNumCompilationUnitsScheduled());
|
||||
}
|
||||
}
|
||||
|
||||
void CompilationState::ScheduleUnitForFinishing(
|
||||
std::unique_ptr<WasmCompilationUnit> unit) {
|
||||
std::unique_ptr<WasmCompilationUnit> unit,
|
||||
WasmCompilationUnit::CompilationMode mode) {
|
||||
size_t cost = unit->memory_cost();
|
||||
base::LockGuard<base::Mutex> guard(&mutex_);
|
||||
finish_units_.push_back(std::move(unit));
|
||||
if (compile_mode_ == CompileMode::kTiering &&
|
||||
mode == WasmCompilationUnit::CompilationMode::kTurbofan) {
|
||||
tiering_finish_units_.push_back(std::move(unit));
|
||||
} else {
|
||||
baseline_finish_units_.push_back(std::move(unit));
|
||||
}
|
||||
allocated_memory_ += cost;
|
||||
|
||||
if (!finisher_is_running_ && !failed_) {
|
||||
@ -3192,7 +3502,7 @@ void CompilationState::RestartBackgroundTasks(size_t max) {
|
||||
DCHECK_LE(num_background_tasks_, max_background_tasks_);
|
||||
if (num_background_tasks_ == max_background_tasks_) return;
|
||||
num_restart = std::min(
|
||||
num_restart, std::min(compilation_units_.size(),
|
||||
num_restart, std::min(GetNumCompilationUnitsScheduled(),
|
||||
max_background_tasks_ - num_background_tasks_));
|
||||
num_background_tasks_ += num_restart;
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ namespace wasm {
|
||||
|
||||
class CompilationState;
|
||||
class ModuleCompiler;
|
||||
struct ModuleEnv;
|
||||
class WasmCode;
|
||||
|
||||
struct CompilationStateDeleter {
|
||||
@ -32,7 +31,9 @@ struct CompilationStateDeleter {
|
||||
// Wrapper to create a CompilationState exists in order to avoid having
|
||||
// the the CompilationState in the header file.
|
||||
std::unique_ptr<CompilationState, CompilationStateDeleter> NewCompilationState(
|
||||
Isolate* isolate);
|
||||
Isolate* isolate, ModuleEnv& env);
|
||||
|
||||
ModuleEnv* GetModuleEnv(CompilationState* compilation_state);
|
||||
|
||||
MaybeHandle<WasmModuleObject> CompileToModuleObject(
|
||||
Isolate* isolate, ErrorThrower* thrower, std::unique_ptr<WasmModule> module,
|
||||
@ -99,6 +100,7 @@ class AsyncCompileJob {
|
||||
class CompileWrappers;
|
||||
class FinishModule;
|
||||
class AbortCompilation;
|
||||
class UpdateToTopTierCompiledCode;
|
||||
|
||||
const std::shared_ptr<Counters>& async_counters() const {
|
||||
return async_counters_;
|
||||
@ -138,7 +140,6 @@ class AsyncCompileJob {
|
||||
ModuleWireBytes wire_bytes_;
|
||||
Handle<Context> context_;
|
||||
Handle<JSPromise> module_promise_;
|
||||
std::unique_ptr<ModuleEnv> module_env_;
|
||||
std::unique_ptr<WasmModule> module_;
|
||||
|
||||
std::vector<DeferredHandles*> deferred_handles_;
|
||||
@ -172,6 +173,8 @@ class AsyncCompileJob {
|
||||
// compilation. The AsyncCompileJob does not actively use the
|
||||
// StreamingDecoder.
|
||||
std::shared_ptr<StreamingDecoder> stream_;
|
||||
|
||||
bool tiering_completed_ = false;
|
||||
};
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
|
@ -330,10 +330,10 @@ WasmCode::~WasmCode() {
|
||||
// {source_native_module} into a {cloning_native_module}.
|
||||
class NativeModule::CloneCodeHelper {
|
||||
public:
|
||||
explicit CloneCodeHelper(NativeModule* source_native_module,
|
||||
explicit CloneCodeHelper(const NativeModule* source_native_module,
|
||||
NativeModule* cloning_native_module);
|
||||
|
||||
void SelectForCloning(int32_t code_index);
|
||||
void SelectForCloning(uint32_t code_index);
|
||||
|
||||
void CloneAndPatchCode();
|
||||
|
||||
@ -342,14 +342,34 @@ class NativeModule::CloneCodeHelper {
|
||||
WasmCode::FlushICache flush_icache);
|
||||
|
||||
private:
|
||||
NativeModule* source_native_module_;
|
||||
const NativeModule* source_native_module_;
|
||||
NativeModule* cloning_native_module_;
|
||||
std::vector<uint32_t> selection_;
|
||||
std::unordered_map<Address, Address, AddressHasher> reverse_lookup_;
|
||||
};
|
||||
|
||||
void NativeModule::CloneHigherTierCodeFrom(
|
||||
const NativeModule* source_native_module) {
|
||||
NativeModule::CloneCodeHelper helper(source_native_module, this);
|
||||
|
||||
for (uint32_t i = num_imported_functions_, e = FunctionCount(); i < e; ++i) {
|
||||
WasmCode* wasm_code = GetCode(i);
|
||||
if (!wasm_code->is_liftoff()) continue;
|
||||
helper.SelectForCloning(i);
|
||||
}
|
||||
|
||||
helper.CloneAndPatchCode();
|
||||
|
||||
// Sanity check: after cloning the code, every function in the
|
||||
// code table should not be Liftoff-compiled code anymore.
|
||||
for (uint32_t i = num_imported_functions(), e = FunctionCount(); i < e; ++i) {
|
||||
DCHECK(!GetCode(i)->is_liftoff());
|
||||
}
|
||||
}
|
||||
|
||||
NativeModule::CloneCodeHelper::CloneCodeHelper(
|
||||
NativeModule* source_native_module, NativeModule* cloning_native_module)
|
||||
const NativeModule* source_native_module,
|
||||
NativeModule* cloning_native_module)
|
||||
: source_native_module_(source_native_module),
|
||||
cloning_native_module_(cloning_native_module) {
|
||||
for (auto& pair : source_native_module_->trampolines_) {
|
||||
@ -361,7 +381,7 @@ NativeModule::CloneCodeHelper::CloneCodeHelper(
|
||||
}
|
||||
}
|
||||
|
||||
void NativeModule::CloneCodeHelper::SelectForCloning(int32_t code_index) {
|
||||
void NativeModule::CloneCodeHelper::SelectForCloning(uint32_t code_index) {
|
||||
selection_.emplace_back(code_index);
|
||||
}
|
||||
|
||||
@ -438,12 +458,12 @@ base::AtomicNumber<size_t> NativeModule::next_id_;
|
||||
|
||||
NativeModule::NativeModule(uint32_t num_functions, uint32_t num_imports,
|
||||
bool can_request_more, VirtualMemory* mem,
|
||||
WasmCodeManager* code_manager)
|
||||
WasmCodeManager* code_manager, ModuleEnv& env)
|
||||
: instance_id(next_id_.Increment(1)),
|
||||
code_table_(num_functions),
|
||||
num_imported_functions_(num_imports),
|
||||
compilation_state_(NewCompilationState(
|
||||
reinterpret_cast<Isolate*>(code_manager->isolate_))),
|
||||
reinterpret_cast<Isolate*>(code_manager->isolate_), env)),
|
||||
free_memory_(mem->address(), mem->end()),
|
||||
wasm_code_manager_(code_manager),
|
||||
can_request_more_memory_(can_request_more) {
|
||||
@ -1021,24 +1041,25 @@ size_t WasmCodeManager::GetAllocationChunk(const WasmModule& module) {
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeModule> WasmCodeManager::NewNativeModule(
|
||||
const WasmModule& module) {
|
||||
const WasmModule& module, ModuleEnv& env) {
|
||||
size_t code_size = GetAllocationChunk(module);
|
||||
return NewNativeModule(
|
||||
code_size, static_cast<uint32_t>(module.functions.size()),
|
||||
module.num_imported_functions, kModuleCanAllocateMoreMemory);
|
||||
module.num_imported_functions, kModuleCanAllocateMoreMemory, env);
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeModule> WasmCodeManager::NewNativeModule(
|
||||
size_t size_estimate, uint32_t num_functions,
|
||||
uint32_t num_imported_functions, bool can_request_more) {
|
||||
uint32_t num_imported_functions, bool can_request_more, ModuleEnv& env) {
|
||||
VirtualMemory mem;
|
||||
TryAllocate(size_estimate, &mem);
|
||||
if (mem.IsReserved()) {
|
||||
Address start = mem.address();
|
||||
size_t size = mem.size();
|
||||
Address end = mem.end();
|
||||
std::unique_ptr<NativeModule> ret(new NativeModule(
|
||||
num_functions, num_imported_functions, can_request_more, &mem, this));
|
||||
std::unique_ptr<NativeModule> ret(
|
||||
new NativeModule(num_functions, num_imported_functions,
|
||||
can_request_more, &mem, this, env));
|
||||
TRACE_HEAP("New Module: ID:%zu. Mem: %p,+%zu\n", ret->instance_id,
|
||||
reinterpret_cast<void*>(start), size);
|
||||
AssignRanges(start, end, ret.get());
|
||||
@ -1098,9 +1119,10 @@ bool NativeModule::SetExecutable(bool executable) {
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeModule> NativeModule::Clone() {
|
||||
ModuleEnv* module_env = GetModuleEnv(compilation_state());
|
||||
std::unique_ptr<NativeModule> ret = wasm_code_manager_->NewNativeModule(
|
||||
owned_memory_.front().size(), FunctionCount(), num_imported_functions(),
|
||||
can_request_more_memory_);
|
||||
can_request_more_memory_, *module_env);
|
||||
TRACE_HEAP("%zu cloned from %zu\n", ret->instance_id, instance_id);
|
||||
if (!ret) return ret;
|
||||
|
||||
|
@ -247,6 +247,10 @@ class V8_EXPORT_PRIVATE NativeModule final {
|
||||
WasmCode* GetCode(uint32_t index) const;
|
||||
void SetCode(uint32_t index, WasmCode* wasm_code);
|
||||
|
||||
// Clones higher tier code from a {source_native_module} to
|
||||
// this native module.
|
||||
void CloneHigherTierCodeFrom(const NativeModule* source_native_module);
|
||||
|
||||
// Register/release the protected instructions in all code objects with the
|
||||
// global trap handler for this process.
|
||||
void UnpackAndRegisterProtectedInstructions();
|
||||
@ -297,7 +301,7 @@ class V8_EXPORT_PRIVATE NativeModule final {
|
||||
static base::AtomicNumber<size_t> next_id_;
|
||||
NativeModule(uint32_t num_functions, uint32_t num_imports,
|
||||
bool can_request_more, VirtualMemory* vmem,
|
||||
WasmCodeManager* code_manager);
|
||||
WasmCodeManager* code_manager, ModuleEnv& env);
|
||||
|
||||
WasmCode* AddAnonymousCode(Handle<Code>, WasmCode::Kind kind);
|
||||
Address AllocateForCode(size_t size);
|
||||
@ -367,11 +371,13 @@ class V8_EXPORT_PRIVATE WasmCodeManager final {
|
||||
// which will be page size aligned. The size of the initial memory
|
||||
// is determined with a heuristic based on the total size of wasm
|
||||
// code. The native module may later request more memory.
|
||||
std::unique_ptr<NativeModule> NewNativeModule(const WasmModule&);
|
||||
std::unique_ptr<NativeModule> NewNativeModule(const WasmModule& module,
|
||||
ModuleEnv& env);
|
||||
std::unique_ptr<NativeModule> NewNativeModule(size_t memory_estimate,
|
||||
uint32_t num_functions,
|
||||
uint32_t num_imported_functions,
|
||||
bool can_request_more);
|
||||
bool can_request_more,
|
||||
ModuleEnv& env);
|
||||
|
||||
WasmCode* LookupCode(Address pc) const;
|
||||
WasmCode* GetCodeFromStartAddress(Address pc) const;
|
||||
|
@ -205,6 +205,7 @@ bool CodeSpecialization::ApplyToWasmCode(wasm::WasmCode* code,
|
||||
} break;
|
||||
case RelocInfo::WASM_CODE_TABLE_ENTRY: {
|
||||
DCHECK(FLAG_wasm_tier_up);
|
||||
DCHECK(code->is_liftoff());
|
||||
WasmCode* const* code_table_entry =
|
||||
native_module->code_table().data() + code->index();
|
||||
it.rinfo()->set_wasm_code_table_entry(
|
||||
|
@ -1357,13 +1357,13 @@ MaybeHandle<FixedArray> WasmSharedModuleData::CheckBreakPoints(
|
||||
|
||||
Handle<WasmCompiledModule> WasmCompiledModule::New(
|
||||
Isolate* isolate, WasmModule* module, Handle<FixedArray> export_wrappers,
|
||||
bool use_trap_handler) {
|
||||
wasm::ModuleEnv& env) {
|
||||
Handle<WasmCompiledModule> compiled_module = Handle<WasmCompiledModule>::cast(
|
||||
isolate->factory()->NewStruct(WASM_COMPILED_MODULE_TYPE, TENURED));
|
||||
Handle<WeakCell> weak_native_context =
|
||||
isolate->factory()->NewWeakCell(isolate->native_context());
|
||||
compiled_module->set_weak_native_context(*weak_native_context);
|
||||
compiled_module->set_use_trap_handler(use_trap_handler);
|
||||
compiled_module->set_use_trap_handler(env.use_trap_handler);
|
||||
if (!export_wrappers.is_null()) {
|
||||
compiled_module->set_export_wrappers(*export_wrappers);
|
||||
}
|
||||
@ -1371,7 +1371,7 @@ Handle<WasmCompiledModule> WasmCompiledModule::New(
|
||||
wasm::NativeModule* native_module = nullptr;
|
||||
{
|
||||
std::unique_ptr<wasm::NativeModule> native_module_ptr =
|
||||
isolate->wasm_engine()->code_manager()->NewNativeModule(*module);
|
||||
isolate->wasm_engine()->code_manager()->NewNativeModule(*module, env);
|
||||
native_module = native_module_ptr.release();
|
||||
Handle<Foreign> native_module_wrapper =
|
||||
Managed<wasm::NativeModule>::From(isolate, native_module);
|
||||
|
@ -26,6 +26,7 @@ namespace internal {
|
||||
namespace wasm {
|
||||
class InterpretedFrame;
|
||||
class NativeModule;
|
||||
struct ModuleEnv;
|
||||
class WasmCode;
|
||||
struct WasmModule;
|
||||
class SignatureMap;
|
||||
@ -545,10 +546,10 @@ class WasmCompiledModule : public Struct {
|
||||
WCM_SMALL_CONST_NUMBER(bool, use_trap_handler)
|
||||
|
||||
public:
|
||||
static Handle<WasmCompiledModule> New(
|
||||
Isolate* isolate, wasm::WasmModule* module,
|
||||
Handle<FixedArray> export_wrappers,
|
||||
bool use_trap_hander);
|
||||
static Handle<WasmCompiledModule> New(Isolate* isolate,
|
||||
wasm::WasmModule* module,
|
||||
Handle<FixedArray> export_wrappers,
|
||||
wasm::ModuleEnv& env);
|
||||
|
||||
static Handle<WasmCompiledModule> Clone(Isolate* isolate,
|
||||
Handle<WasmCompiledModule> module);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "src/snapshot/serializer-common.h"
|
||||
#include "src/utils.h"
|
||||
#include "src/version.h"
|
||||
#include "src/wasm/function-compiler.h"
|
||||
#include "src/wasm/module-compiler.h"
|
||||
#include "src/wasm/module-decoder.h"
|
||||
#include "src/wasm/wasm-code-manager.h"
|
||||
@ -702,9 +703,14 @@ MaybeHandle<WasmCompiledModule> DeserializeNativeModule(
|
||||
Handle<FixedArray> export_wrappers = isolate->factory()->NewFixedArray(
|
||||
static_cast<int>(export_wrappers_size), TENURED);
|
||||
|
||||
// TODO(eholk): We need to properly preserve the flag whether the trap
|
||||
// handler was used or not when serializing.
|
||||
UseTrapHandler use_trap_handler =
|
||||
trap_handler::IsTrapHandlerEnabled() ? kUseTrapHandler : kNoTrapHandler;
|
||||
wasm::ModuleEnv env(shared->module(), use_trap_handler,
|
||||
wasm::RuntimeExceptionSupport::kRuntimeExceptionSupport);
|
||||
Handle<WasmCompiledModule> compiled_module =
|
||||
WasmCompiledModule::New(isolate, shared->module(), export_wrappers,
|
||||
trap_handler::IsTrapHandlerEnabled());
|
||||
WasmCompiledModule::New(isolate, shared->module(), export_wrappers, env);
|
||||
compiled_module->set_shared(*shared);
|
||||
script->set_wasm_compiled_module(*compiled_module);
|
||||
NativeModuleDeserializer deserializer(isolate,
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "src/macro-assembler-inl.h"
|
||||
#include "src/macro-assembler.h"
|
||||
#include "src/objects-inl.h"
|
||||
#include "src/wasm/function-compiler.h"
|
||||
#include "src/wasm/wasm-engine.h"
|
||||
#include "src/wasm/wasm-objects-inl.h"
|
||||
#include "src/wasm/wasm-opcodes.h"
|
||||
@ -123,12 +124,15 @@ Node* ToInt32(RawMachineAssembler& m, MachineType type, Node* a) {
|
||||
|
||||
std::unique_ptr<wasm::NativeModule> AllocateNativeModule(Isolate* isolate,
|
||||
size_t code_size) {
|
||||
wasm::ModuleEnv env(
|
||||
nullptr, wasm::UseTrapHandler::kNoTrapHandler,
|
||||
wasm::RuntimeExceptionSupport::kNoRuntimeExceptionSupport);
|
||||
// We have to add the code object to a NativeModule, because the
|
||||
// WasmCallDescriptor assumes that code is on the native heap and not
|
||||
// within a code object.
|
||||
std::unique_ptr<wasm::NativeModule> module =
|
||||
isolate->wasm_engine()->code_manager()->NewNativeModule(code_size, 1, 0,
|
||||
false);
|
||||
false, env);
|
||||
return module;
|
||||
}
|
||||
|
||||
|
@ -225,9 +225,9 @@ Handle<WasmInstanceObject> TestingModuleBuilder::InitInstanceObject() {
|
||||
WasmSharedModuleData::New(isolate_, module_wrapper, empty_string, script,
|
||||
Handle<ByteArray>::null());
|
||||
Handle<FixedArray> export_wrappers = isolate_->factory()->NewFixedArray(0);
|
||||
ModuleEnv env = CreateModuleEnv();
|
||||
Handle<WasmCompiledModule> compiled_module =
|
||||
WasmCompiledModule::New(isolate_, test_module_ptr_, export_wrappers,
|
||||
trap_handler::IsTrapHandlerEnabled());
|
||||
WasmCompiledModule::New(isolate_, test_module_ptr_, export_wrappers, env);
|
||||
compiled_module->set_shared(*shared_module_data);
|
||||
// This method is called when we initialize TestEnvironment. We don't
|
||||
// have a memory yet, so we won't create it here. We'll update the
|
||||
|
@ -151,12 +151,16 @@ CallDescriptor* CreateRandomCallDescriptor(Zone* zone, size_t return_count,
|
||||
|
||||
std::unique_ptr<wasm::NativeModule> AllocateNativeModule(i::Isolate* isolate,
|
||||
size_t code_size) {
|
||||
wasm::ModuleEnv env(
|
||||
nullptr, wasm::UseTrapHandler::kNoTrapHandler,
|
||||
wasm::RuntimeExceptionSupport::kNoRuntimeExceptionSupport);
|
||||
|
||||
// We have to add the code object to a NativeModule, because the
|
||||
// WasmCallDescriptor assumes that code is on the native heap and not
|
||||
// within a code object.
|
||||
std::unique_ptr<wasm::NativeModule> module =
|
||||
isolate->wasm_engine()->code_manager()->NewNativeModule(code_size, 1, 0,
|
||||
false);
|
||||
false, env);
|
||||
return module;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "test/unittests/test-utils.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
|
||||
#include "src/wasm/function-compiler.h"
|
||||
#include "src/wasm/wasm-code-manager.h"
|
||||
|
||||
namespace v8 {
|
||||
@ -161,11 +162,15 @@ class WasmCodeManagerTest : public TestWithContext,
|
||||
// We pretend all our modules have 10 functions and no imports, just so
|
||||
// we can size up the code_table.
|
||||
NativeModulePtr AllocFixedModule(WasmCodeManager* manager, size_t size) {
|
||||
return manager->NewNativeModule(size, 10, 0, false);
|
||||
wasm::ModuleEnv env(nullptr, UseTrapHandler::kNoTrapHandler,
|
||||
RuntimeExceptionSupport::kNoRuntimeExceptionSupport);
|
||||
return manager->NewNativeModule(size, 10, 0, false, env);
|
||||
}
|
||||
|
||||
NativeModulePtr AllocGrowableModule(WasmCodeManager* manager, size_t size) {
|
||||
return manager->NewNativeModule(size, 10, 0, true);
|
||||
wasm::ModuleEnv env(nullptr, UseTrapHandler::kNoTrapHandler,
|
||||
RuntimeExceptionSupport::kNoRuntimeExceptionSupport);
|
||||
return manager->NewNativeModule(size, 10, 0, true, env);
|
||||
}
|
||||
|
||||
NativeModulePtr AllocModule(WasmCodeManager* manager, size_t size,
|
||||
|
Loading…
Reference in New Issue
Block a user