Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Unified Diff: src/wasm/wasm-module.cc

Issue 2731523005: [wasm] Lazy compilation for asm.js (Closed)
Patch Set: [wasm] Lazy compilation for asm.js Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/wasm/wasm-module.h ('k') | src/wasm/wasm-objects.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
+}
« no previous file with comments | « src/wasm/wasm-module.h ('k') | src/wasm/wasm-objects.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698