| Index: src/wasm/wasm-module.cc
|
| diff --git a/src/wasm/wasm-module.cc b/src/wasm/wasm-module.cc
|
| index d37a11540d75b7aa673e78f3cfafb53a89007d02..6033521ce36feedf18c1306a10a3d87c868bd7dc 100644
|
| --- a/src/wasm/wasm-module.cc
|
| +++ b/src/wasm/wasm-module.cc
|
| @@ -10,6 +10,7 @@
|
| #include "src/code-stubs.h"
|
| #include "src/compiler/wasm-compiler.h"
|
| #include "src/debug/interface-types.h"
|
| +#include "src/frames-inl.h"
|
| #include "src/objects.h"
|
| #include "src/property-descriptor.h"
|
| #include "src/simulator.h"
|
| @@ -182,7 +183,8 @@ class JSToWasmWrapperCache {
|
| Code::GetCodeFromTargetAddress(it.rinfo()->target_address());
|
| if (target->kind() == Code::WASM_FUNCTION ||
|
| target->kind() == Code::WASM_TO_JS_FUNCTION ||
|
| - target->builtin_index() == Builtins::kIllegal) {
|
| + target->builtin_index() == Builtins::kIllegal ||
|
| + target->builtin_index() == Builtins::kWasmCompileLazy) {
|
| it.rinfo()->set_target_address(wasm_code->instruction_start());
|
| break;
|
| }
|
| @@ -205,6 +207,75 @@ class JSToWasmWrapperCache {
|
| std::vector<Handle<Code>> code_cache_;
|
| };
|
|
|
| +// Ensure that the code object in <code_table> at offset <func_index> has
|
| +// deoptimization data attached. This is needed for lazy compile stubs which are
|
| +// called from JS_TO_WASM functions or via exported function tables. The deopt
|
| +// data is used to determine which function this lazy compile stub belongs to.
|
| +Handle<Code> EnsureExportedLazyDeoptData(
|
| + Isolate* isolate, Handle<WasmInstanceObject> instance,
|
| + Handle<FixedArray> code_table, int func_index,
|
| + Handle<FixedArray> exp_table = Handle<FixedArray>::null(),
|
| + int exp_index = -1) {
|
| + Handle<Code> code(Code::cast(code_table->get(func_index)), isolate);
|
| + if (code->builtin_index() != Builtins::kWasmCompileLazy) {
|
| + // No special deopt data needed for compiled functions, and imported
|
| + // functions, which map to Illegal at this point (they get compiled at
|
| + // instantiation time).
|
| + DCHECK(code->kind() == Code::WASM_FUNCTION ||
|
| + code->kind() == Code::WASM_TO_JS_FUNCTION ||
|
| + code->builtin_index() == Builtins::kIllegal);
|
| + return code;
|
| + }
|
| + // deopt_data:
|
| + // #0: weak instance
|
| + // #1: func_index
|
| + // [#2: export table
|
| + // #3: export table index]
|
| + // [#4: export table
|
| + // #5: export table index]
|
| + // ...
|
| + Handle<FixedArray> deopt_data(code->deoptimization_data());
|
| + if (deopt_data->length() == 0) {
|
| + code = isolate->factory()->CopyCode(code);
|
| + code_table->set(func_index, *code);
|
| + deopt_data =
|
| + isolate->factory()->NewFixedArray(exp_table.is_null() ? 2 : 4, TENURED);
|
| + code->set_deoptimization_data(*deopt_data);
|
| + if (!instance.is_null()) {
|
| + Handle<WeakCell> weak_instance =
|
| + isolate->factory()->NewWeakCell(instance);
|
| + deopt_data->set(0, *weak_instance);
|
| + }
|
| + deopt_data->set(1, Smi::FromInt(func_index));
|
| + }
|
| + DCHECK_IMPLIES(!instance.is_null(),
|
| + WeakCell::cast(code->deoptimization_data()->get(0))->value() ==
|
| + *instance);
|
| + DCHECK_EQ(func_index,
|
| + Smi::cast(code->deoptimization_data()->get(1))->value());
|
| + if (!exp_table.is_null()) {
|
| + int idx = 2;
|
| + // TODO(clemensh): Make this linear instead of quadratic in the number of
|
| + // exports per function. We observe four-digit number of exports in unity
|
| + // for a single function.
|
| + for (int l = deopt_data->length(); idx < l; idx += 2) {
|
| + if (deopt_data->get(idx)->IsUndefined(isolate)) break;
|
| + // We should not produce duplicates.
|
| + DCHECK(deopt_data->get(idx) != *exp_table ||
|
| + Smi::cast(deopt_data->get(idx + 1))->value() != exp_index);
|
| + }
|
| + if (idx >= deopt_data->length()) {
|
| + int add = Max(2, deopt_data->length() / 4 * 2);
|
| + deopt_data =
|
| + isolate->factory()->CopyFixedArrayAndGrow(deopt_data, add, TENURED);
|
| + code->set_deoptimization_data(*deopt_data);
|
| + }
|
| + deopt_data->set(idx, *exp_table);
|
| + deopt_data->set(idx + 1, Smi::FromInt(exp_index));
|
| + }
|
| + return code;
|
| +}
|
| +
|
| // A helper for compiling an entire module.
|
| class CompilationHelper {
|
| public:
|
| @@ -437,31 +508,40 @@ class CompilationHelper {
|
| Handle<FixedArray> code_table =
|
| factory->NewFixedArray(static_cast<int>(code_table_size), TENURED);
|
|
|
| - // Initialize the code table with the illegal builtin. All call sites will
|
| - // be
|
| + // Check whether lazy compilation is enabled for this module.
|
| + bool lazy_compile =
|
| + FLAG_asm_wasm_lazy_compilation && module_->origin == wasm::kAsmJsOrigin;
|
| +
|
| + // If lazy compile: Initialize the code table with the lazy compile builtin.
|
| + // Otherwise: Initialize with the illegal builtin. All call sites will be
|
| // patched at instantiation.
|
| - Handle<Code> illegal_builtin = isolate_->builtins()->Illegal();
|
| - for (uint32_t i = 0; i < module_->functions.size(); ++i) {
|
| - code_table->set(static_cast<int>(i), *illegal_builtin);
|
| - temp_instance.function_code[i] = illegal_builtin;
|
| + Handle<Code> init_builtin = lazy_compile
|
| + ? isolate_->builtins()->WasmCompileLazy()
|
| + : isolate_->builtins()->Illegal();
|
| + for (int i = 0, e = static_cast<int>(module_->functions.size()); i < e;
|
| + ++i) {
|
| + code_table->set(i, *init_builtin);
|
| + temp_instance.function_code[i] = init_builtin;
|
| }
|
|
|
| isolate_->counters()->wasm_functions_per_module()->AddSample(
|
| static_cast<int>(module_->functions.size()));
|
| - CompilationHelper helper(isolate_, module_);
|
| - size_t funcs_to_compile =
|
| - module_->functions.size() - module_->num_imported_functions;
|
| - if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0 &&
|
| - funcs_to_compile > 1) {
|
| - // Avoid a race condition by collecting results into a second vector.
|
| - std::vector<Handle<Code>> results(temp_instance.function_code);
|
| - helper.CompileInParallel(&module_env, results, thrower);
|
| - temp_instance.function_code.swap(results);
|
| - } else {
|
| - helper.CompileSequentially(&module_env, temp_instance.function_code,
|
| - thrower);
|
| + if (!lazy_compile) {
|
| + CompilationHelper helper(isolate_, module_);
|
| + size_t funcs_to_compile =
|
| + module_->functions.size() - module_->num_imported_functions;
|
| + if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0 &&
|
| + funcs_to_compile > 1) {
|
| + // Avoid a race condition by collecting results into a second vector.
|
| + std::vector<Handle<Code>> results(temp_instance.function_code);
|
| + helper.CompileInParallel(&module_env, results, thrower);
|
| + temp_instance.function_code.swap(results);
|
| + } else {
|
| + helper.CompileSequentially(&module_env, temp_instance.function_code,
|
| + thrower);
|
| + }
|
| + if (thrower->error()) return {};
|
| }
|
| - if (thrower->error()) return {};
|
|
|
| // At this point, compilation has completed. Update the code table.
|
| for (size_t i = FLAG_skip_compiling_wasm_funcs;
|
| @@ -502,6 +582,7 @@ class CompilationHelper {
|
| Handle<WasmSharedModuleData> shared = WasmSharedModuleData::New(
|
| isolate_, module_wrapper, Handle<SeqOneByteString>::cast(module_bytes),
|
| script, asm_js_offset_table);
|
| + if (lazy_compile) WasmSharedModuleData::PrepareForLazyCompilation(shared);
|
|
|
| // Create the compiled module object, and populate with compiled functions
|
| // and information needed at instantiation time. This object needs to be
|
| @@ -532,7 +613,8 @@ class CompilationHelper {
|
| int func_index = 0;
|
| for (auto exp : module_->export_table) {
|
| if (exp.kind != kExternalFunction) continue;
|
| - Handle<Code> wasm_code(Code::cast(code_table->get(exp.index)), isolate_);
|
| + Handle<Code> wasm_code = EnsureExportedLazyDeoptData(
|
| + isolate_, Handle<WasmInstanceObject>::null(), code_table, exp.index);
|
| Handle<Code> wrapper_code =
|
| js_to_wasm_cache.CloneOrCompileJSToWasmWrapper(isolate_, module_,
|
| wasm_code, exp.index);
|
| @@ -595,6 +677,8 @@ static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner,
|
| end = functions->length();
|
| i < end; ++i) {
|
| Code* code = Code::cast(functions->get(i));
|
| + // Skip lazy compile stubs.
|
| + if (code->builtin_index() == Builtins::kWasmCompileLazy) continue;
|
| if (code->kind() != Code::WASM_FUNCTION) {
|
| // From here on, there should only be wrappers for exported functions.
|
| for (; i < end; ++i) {
|
| @@ -740,6 +824,7 @@ std::pair<int, int> GetFunctionOffsetAndLength(
|
| return {static_cast<int>(func.code_start_offset),
|
| static_cast<int>(func.code_end_offset - func.code_start_offset)};
|
| }
|
| +
|
| } // namespace
|
|
|
| Handle<JSArrayBuffer> SetupArrayBuffer(Isolate* isolate, void* backing_store,
|
| @@ -1028,6 +1113,19 @@ class InstantiationHelper {
|
| case Code::WASM_TO_JS_FUNCTION:
|
| // Imports will be overwritten with newly compiled wrappers.
|
| break;
|
| + case Code::BUILTIN:
|
| + DCHECK_EQ(Builtins::kWasmCompileLazy, orig_code->builtin_index());
|
| + // If this code object has deoptimization data, then we need a
|
| + // unique copy to attach updated deoptimization data.
|
| + if (orig_code->deoptimization_data()->length() > 0) {
|
| + Handle<Code> code = factory->CopyCode(orig_code);
|
| + Handle<FixedArray> deopt_data =
|
| + factory->NewFixedArray(2, TENURED);
|
| + deopt_data->set(1, Smi::FromInt(i));
|
| + code->set_deoptimization_data(*deopt_data);
|
| + code_table->set(i, *code);
|
| + }
|
| + break;
|
| case Code::JS_TO_WASM_FUNCTION:
|
| case Code::WASM_FUNCTION: {
|
| Handle<Code> code = factory->CopyCode(orig_code);
|
| @@ -1190,7 +1288,7 @@ class InstantiationHelper {
|
| Handle<WeakCell> weak_link = factory->NewWeakCell(instance);
|
|
|
| for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs,
|
| - num_functions = code_table->length();
|
| + num_functions = static_cast<int>(module_->functions.size());
|
| i < num_functions; ++i) {
|
| Handle<Code> code = handle(Code::cast(code_table->get(i)), isolate_);
|
| if (code->kind() == Code::WASM_FUNCTION) {
|
| @@ -1198,7 +1296,13 @@ class InstantiationHelper {
|
| deopt_data->set(0, *weak_link);
|
| deopt_data->set(1, Smi::FromInt(i));
|
| code->set_deoptimization_data(*deopt_data);
|
| + continue;
|
| }
|
| + DCHECK_EQ(Builtins::kWasmCompileLazy, code->builtin_index());
|
| + if (code->deoptimization_data()->length() == 0) continue;
|
| + DCHECK_LE(2, code->deoptimization_data()->length());
|
| + DCHECK_EQ(i, Smi::cast(code->deoptimization_data()->get(1))->value());
|
| + code->deoptimization_data()->set(0, *weak_link);
|
| }
|
|
|
| //--------------------------------------------------------------------------
|
| @@ -1220,10 +1324,8 @@ class InstantiationHelper {
|
| if (function_table_count > 0) LoadTableSegments(code_table, instance);
|
|
|
| // Patch all code with the relocations registered in code_specialization.
|
| - {
|
| - code_specialization.RelocateDirectCalls(instance);
|
| - code_specialization.ApplyToWholeInstance(*instance, SKIP_ICACHE_FLUSH);
|
| - }
|
| + code_specialization.RelocateDirectCalls(instance);
|
| + code_specialization.ApplyToWholeInstance(*instance, SKIP_ICACHE_FLUSH);
|
|
|
| FlushICache(isolate_, code_table);
|
|
|
| @@ -1305,8 +1407,8 @@ class InstantiationHelper {
|
| if (module_->start_function_index >= 0) {
|
| HandleScope scope(isolate_);
|
| int start_index = module_->start_function_index;
|
| - Handle<Code> startup_code(Code::cast(code_table->get(start_index)),
|
| - isolate_);
|
| + Handle<Code> startup_code = EnsureExportedLazyDeoptData(
|
| + isolate_, instance, code_table, start_index);
|
| FunctionSig* sig = module_->functions[start_index].sig;
|
| Handle<Code> wrapper_code =
|
| js_to_wasm_cache_.CloneOrCompileJSToWasmWrapper(
|
| @@ -2013,12 +2115,12 @@ class InstantiationHelper {
|
| DCHECK_GE(sig_index, 0);
|
| table_instance.signature_table->set(table_index,
|
| Smi::FromInt(sig_index));
|
| - table_instance.function_table->set(table_index,
|
| - code_table->get(func_index));
|
| + Handle<Code> wasm_code = EnsureExportedLazyDeoptData(
|
| + isolate_, instance, code_table, func_index,
|
| + table_instance.function_table, table_index);
|
| + table_instance.function_table->set(table_index, *wasm_code);
|
|
|
| if (!all_dispatch_tables.is_null()) {
|
| - Handle<Code> wasm_code(Code::cast(code_table->get(func_index)),
|
| - isolate_);
|
| if (js_wrappers_[func_index].is_null()) {
|
| // No JSFunction entry yet exists for this function. Create one.
|
| // TODO(titzer): We compile JS->WASM wrappers for functions are
|
| @@ -2728,3 +2830,77 @@ void wasm::AsyncCompileAndInstantiate(Isolate* isolate,
|
|
|
| ResolvePromise(isolate, promise, ret);
|
| }
|
| +
|
| +Handle<Code> wasm::CompileLazy(Isolate* isolate) {
|
| + HistogramTimerScope lazy_time_scope(
|
| + isolate->counters()->asm_wasm_lazy_compilation_time());
|
| +
|
| + // Find the wasm frame which triggered the lazy compile, to get the wasm
|
| + // instance.
|
| + StackFrameIterator it(isolate);
|
| + // First frame: C entry stub.
|
| + DCHECK(!it.done());
|
| + DCHECK_EQ(StackFrame::EXIT, it.frame()->type());
|
| + it.Advance();
|
| + // Second frame: WasmCompileLazy builtin.
|
| + DCHECK(!it.done());
|
| + Handle<Code> lazy_compile_code(it.frame()->LookupCode(), isolate);
|
| + DCHECK_EQ(Builtins::kWasmCompileLazy, lazy_compile_code->builtin_index());
|
| + Handle<WasmInstanceObject> instance;
|
| + Handle<FixedArray> exp_deopt_data;
|
| + int func_index = -1;
|
| + if (lazy_compile_code->deoptimization_data()->length() > 0) {
|
| + // Then it's an indirect call or via JS->WASM wrapper.
|
| + DCHECK_LE(2, lazy_compile_code->deoptimization_data()->length());
|
| + exp_deopt_data = handle(lazy_compile_code->deoptimization_data(), isolate);
|
| + auto* weak_cell = WeakCell::cast(exp_deopt_data->get(0));
|
| + instance = handle(WasmInstanceObject::cast(weak_cell->value()), isolate);
|
| + func_index = Smi::cast(exp_deopt_data->get(1))->value();
|
| + }
|
| + it.Advance();
|
| + // Third frame: The calling wasm code.
|
| + DCHECK(!it.done());
|
| + DCHECK(it.frame()->is_js_to_wasm() || it.frame()->is_wasm_compiled());
|
| + Handle<Code> caller_code = handle(it.frame()->LookupCode(), isolate);
|
| + if (it.frame()->is_js_to_wasm()) {
|
| + DCHECK(!instance.is_null());
|
| + } else if (instance.is_null()) {
|
| + instance = handle(wasm::GetOwningWasmInstance(*caller_code), isolate);
|
| + } else {
|
| + DCHECK(*instance == wasm::GetOwningWasmInstance(*caller_code));
|
| + }
|
| + int offset =
|
| + static_cast<int>(it.frame()->pc() - caller_code->instruction_start());
|
| + // Only patch the caller code if this is *no* indirect call.
|
| + // exp_deopt_data will be null if the called function is not exported at all,
|
| + // and its length will be <= 2 if all entries in tables were already patched.
|
| + // Note that this check is conservative: If the first call to an exported
|
| + // function is direct, we will just patch the export tables, and only on the
|
| + // second call we will patch the caller.
|
| + bool patch_caller = caller_code->kind() == Code::JS_TO_WASM_FUNCTION ||
|
| + exp_deopt_data.is_null() || exp_deopt_data->length() <= 2;
|
| +
|
| + MaybeHandle<Code> maybe_compiled_code = WasmCompiledModule::CompileLazy(
|
| + isolate, instance, caller_code, offset, func_index, patch_caller);
|
| + if (maybe_compiled_code.is_null()) {
|
| + DCHECK(isolate->has_pending_exception());
|
| + return isolate->builtins()->Illegal();
|
| + }
|
| + Handle<Code> compiled_code = maybe_compiled_code.ToHandleChecked();
|
| + if (!exp_deopt_data.is_null() && exp_deopt_data->length() > 2) {
|
| + for (int idx = 2, end = exp_deopt_data->length(); idx < end; idx += 2) {
|
| + if (exp_deopt_data->get(idx)->IsUndefined(isolate)) break;
|
| + FixedArray* exp_table = FixedArray::cast(exp_deopt_data->get(idx));
|
| + int exp_index = Smi::cast(exp_deopt_data->get(idx + 1))->value();
|
| + DCHECK(exp_table->get(exp_index) == *lazy_compile_code);
|
| + exp_table->set(exp_index, *compiled_code);
|
| + }
|
| + // After processing, remove the list of exported entries, such that we don't
|
| + // do the patching redundantly.
|
| + Handle<FixedArray> new_deopt_data =
|
| + isolate->factory()->CopyFixedArrayUpTo(exp_deopt_data, 2, TENURED);
|
| + lazy_compile_code->set_deoptimization_data(*new_deopt_data);
|
| + }
|
| +
|
| + return compiled_code;
|
| +}
|
|
|