From 17215438659d8ff2d7d55f95226bf8a1477ccd79 Mon Sep 17 00:00:00 2001 From: ahaas Date: Wed, 11 May 2016 07:06:54 -0700 Subject: [PATCH] [wasm] Implement parallel compilation. With this CL it is possible to compile a wasm module with multiple threads in parallel. Parallel compilation works as follows: 1) The main thread allocates a compilation unit for each wasm function. 2) The main thread spawns WasmCompilationTasks which run on the background threads. 3.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 compilation unit is stored in a result queue. 3.b) If the result queue contains a compilation unit, the main thread dequeues it and finishes its compilation. 4) After the execution of the parallel phase of all compilation units has started, the main thread waits for all WasmCompilationTasks to finish. 5) The main thread finalizes the compilation of the module. I'm going to add some additional tests before committing this CL. R=titzer@chromium.org, bmeurer@chromium.org, mlippautz@chromium.org, mstarzinger@chromium.org Review-Url: https://codereview.chromium.org/1961973002 Cr-Commit-Position: refs/heads/master@{#36178} --- src/compiler/wasm-compiler.cc | 64 ++-- src/compiler/wasm-compiler.h | 2 +- src/flag-definitions.h | 3 +- src/wasm/wasm-module.cc | 427 ++++++++++++++++------ test/mjsunit/wasm/parallel_compilation.js | 100 +++++ 5 files changed, 454 insertions(+), 142 deletions(-) create mode 100644 test/mjsunit/wasm/parallel_compilation.js diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index 8357635e01..1f46857363 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -232,14 +232,17 @@ class WasmTrapHelper : public ZoneObject { CallDescriptor* desc = Linkage::GetRuntimeCallDescriptor( jsgraph()->zone(), f, fun->nargs, Operator::kNoProperties, CallDescriptor::kNoFlags); + // CEntryStubConstant nodes have to be created and cached in the main + // thread. At the moment this is only done for CEntryStubConstant(1). + DCHECK_EQ(1, fun->result_size); Node* inputs[] = { jsgraph()->CEntryStubConstant(fun->result_size), // C entry trap_reason_smi, // message id trap_position_smi, // byte position jsgraph()->ExternalConstant( - ExternalReference(f, jsgraph()->isolate())), // ref - jsgraph()->Int32Constant(fun->nargs), // arity - jsgraph()->Constant(module->instance->context), // context + ExternalReference(f, jsgraph()->isolate())), // ref + jsgraph()->Int32Constant(fun->nargs), // arity + builder_->HeapConstant(module->instance->context), // context *effect_ptr, *control_ptr}; @@ -890,8 +893,8 @@ Node* WasmGraphBuilder::Float64Constant(double value) { return jsgraph()->Float64Constant(value); } -Node* WasmGraphBuilder::Constant(Handle value) { - return jsgraph()->Constant(value); +Node* WasmGraphBuilder::HeapConstant(Handle value) { + return jsgraph()->HeapConstant(value); } Node* WasmGraphBuilder::Branch(Node* cond, Node** true_node, @@ -1893,7 +1896,7 @@ Node* WasmGraphBuilder::CallDirect(uint32_t index, Node** args, DCHECK_NULL(args[0]); // Add code object as constant. - args[0] = Constant(module_->GetFunctionCode(index)); + args[0] = HeapConstant(module_->GetFunctionCode(index)); wasm::FunctionSig* sig = module_->GetFunctionSignature(index); return BuildWasmCall(sig, args, position); @@ -1904,7 +1907,7 @@ Node* WasmGraphBuilder::CallImport(uint32_t index, Node** args, DCHECK_NULL(args[0]); // Add code object as constant. - args[0] = Constant(module_->GetImportCode(index)); + args[0] = HeapConstant(module_->GetImportCode(index)); wasm::FunctionSig* sig = module_->GetImportSignature(index); return BuildWasmCall(sig, args, position); @@ -2382,7 +2385,7 @@ void WasmGraphBuilder::BuildJSToWasmWrapper(Handle wasm_code, graph()->start()); int pos = 0; - args[pos++] = Constant(wasm_code); + args[pos++] = HeapConstant(wasm_code); // Convert JS parameters to WASM numbers. for (int i = 0; i < wasm_count; i++) { @@ -2440,7 +2443,7 @@ void WasmGraphBuilder::BuildWasmToJSWrapper(Handle function, *effect_ = start; *control_ = start; // JS context is the last parameter. - Node* context = Constant(Handle(function->context(), isolate)); + Node* context = HeapConstant(Handle(function->context(), isolate)); Node** args = Buffer(wasm_count + 7); bool arg_count_before_args = false; @@ -2545,7 +2548,7 @@ Node* WasmGraphBuilder::FunctionTable() { DCHECK(module_ && module_->instance && !module_->instance->function_table.is_null()); if (!function_table_) { - function_table_ = jsgraph()->Constant(module_->instance->function_table); + function_table_ = HeapConstant(module_->instance->function_table); } return function_table_; } @@ -2885,7 +2888,7 @@ Handle CompileWasmToJSWrapper(Isolate* isolate, wasm::ModuleEnv* module, } std::pair BuildGraphForWasmFunction( - Zone* zone, wasm::ErrorThrower* thrower, Isolate* isolate, + JSGraph* jsgraph, wasm::ErrorThrower* thrower, Isolate* isolate, wasm::ModuleEnv*& module_env, const wasm::WasmFunction* function, double* decode_ms) { base::ElapsedTimer decode_timer; @@ -2893,16 +2896,13 @@ std::pair BuildGraphForWasmFunction( decode_timer.Start(); } // Create a TF graph during decoding. - Graph* graph = new (zone) Graph(zone); - CommonOperatorBuilder* common = new (zone) CommonOperatorBuilder(zone); - MachineOperatorBuilder* machine = new (zone) MachineOperatorBuilder( - zone, MachineType::PointerRepresentation(), - InstructionSelector::SupportedMachineOperatorFlags()); - JSGraph* jsgraph = - new (zone) JSGraph(isolate, graph, common, nullptr, nullptr, machine); + Graph* graph = jsgraph->graph(); + CommonOperatorBuilder* common = jsgraph->common(); + MachineOperatorBuilder* machine = jsgraph->machine(); SourcePositionTable* source_position_table = - new (zone) SourcePositionTable(graph); - WasmGraphBuilder builder(zone, jsgraph, function->sig, source_position_table); + new (jsgraph->zone()) SourcePositionTable(graph); + WasmGraphBuilder builder(jsgraph->zone(), jsgraph, function->sig, + source_position_table); wasm::FunctionBody body = { module_env, function->sig, module_env->module->module_start, module_env->module->module_start + function->code_start_offset, @@ -2911,7 +2911,7 @@ std::pair BuildGraphForWasmFunction( wasm::BuildTFGraph(isolate->allocator(), &builder, body); if (machine->Is32()) { - Int64Lowering r(graph, machine, common, zone, function->sig); + Int64Lowering r(graph, machine, common, jsgraph->zone(), function->sig); r.LowerGraph(); } @@ -2948,6 +2948,14 @@ class WasmCompilationUnit { isolate_(isolate), module_env_(module_env), function_(function), + graph_zone_(new Zone(isolate->allocator())), + jsgraph_(new (graph_zone()) JSGraph( + isolate, new (graph_zone()) Graph(graph_zone()), + new (graph_zone()) CommonOperatorBuilder(graph_zone()), nullptr, + nullptr, + new (graph_zone()) MachineOperatorBuilder( + graph_zone(), MachineType::PointerRepresentation(), + InstructionSelector::SupportedMachineOperatorFlags()))), compilation_zone_(isolate->allocator()), info_(function->name_length != 0 ? module_env->module->GetNameOrNull(function->name_offset, @@ -2957,7 +2965,12 @@ class WasmCompilationUnit { Code::ComputeFlags(Code::WASM_FUNCTION)), job_(), index_(index), - ok_(true) {} + ok_(true) { + // Create and cache this node in the main thread. + jsgraph_->CEntryStubConstant(1); + } + + Zone* graph_zone() { return graph_zone_.get(); } void ExecuteCompilation() { HistogramTimerScope wasm_compile_function_time_scope( @@ -2972,9 +2985,9 @@ class WasmCompilationUnit { double decode_ms = 0; size_t node_count = 0; - Zone zone(isolate_->allocator()); + base::SmartPointer graph_zone(graph_zone_.Detach()); std::pair graph_result = - BuildGraphForWasmFunction(&zone, thrower_, isolate_, module_env_, + BuildGraphForWasmFunction(jsgraph_, thrower_, isolate_, module_env_, function_, &decode_ms); JSGraph* jsgraph = graph_result.first; SourcePositionTable* source_positions = graph_result.second; @@ -3059,6 +3072,9 @@ class WasmCompilationUnit { Isolate* isolate_; wasm::ModuleEnv* module_env_; const wasm::WasmFunction* function_; + // The graph zone is deallocated at the end of ExecuteCompilation. + base::SmartPointer graph_zone_; + JSGraph* jsgraph_; Zone compilation_zone_; CompilationInfo info_; base::SmartPointer job_; diff --git a/src/compiler/wasm-compiler.h b/src/compiler/wasm-compiler.h index 31dea0358c..93c2ae91d1 100644 --- a/src/compiler/wasm-compiler.h +++ b/src/compiler/wasm-compiler.h @@ -98,7 +98,7 @@ class WasmGraphBuilder { Node* Int64Constant(int64_t value); Node* Float32Constant(float value); Node* Float64Constant(double value); - Node* Constant(Handle value); + Node* HeapConstant(Handle value); Node* Binop(wasm::WasmOpcode opcode, Node* left, Node* right, wasm::WasmCodePosition position = wasm::kNoCodePosition); Node* Unop(wasm::WasmOpcode opcode, Node* input, diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 871ada5945..ce659e2bf8 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -461,7 +461,8 @@ DEFINE_BOOL(turbo_stress_instruction_scheduling, false, // Flags for native WebAssembly. DEFINE_BOOL(expose_wasm, false, "expose WASM interface to JavaScript") -DEFINE_BOOL(wasm_parallel_compilation, false, "compile WASM code in parallel") +DEFINE_INT(wasm_num_compilation_tasks, 0, + "number of parallel compilation tasks for wasm") DEFINE_BOOL(trace_wasm_encoder, false, "trace encoding of wasm code") DEFINE_BOOL(trace_wasm_decoder, false, "trace decoding of wasm code") DEFINE_BOOL(trace_wasm_decode_time, false, "trace decoding time of wasm code") diff --git a/src/wasm/wasm-module.cc b/src/wasm/wasm-module.cc index 9ec439d4b5..f880d03e34 100644 --- a/src/wasm/wasm-module.cc +++ b/src/wasm/wasm-module.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "src/base/atomic-utils.h" #include "src/macro-assembler.h" #include "src/objects.h" #include "src/property-descriptor.h" @@ -129,11 +130,11 @@ class WasmLinker { // Create a placeholder code object and encode the corresponding index in // the {constant_pool_offset} field of the code object. // TODO(titzer): placeholder code objects are somewhat dangerous. - Handle self(nullptr, isolate_); byte buffer[] = {0, 0, 0, 0, 0, 0, 0, 0}; // fake instructions. CodeDesc desc = {buffer, 8, 8, 0, 0, nullptr}; Handle code = isolate_->factory()->NewCode( - desc, Code::KindField::encode(Code::WASM_FUNCTION), self); + desc, Code::KindField::encode(Code::WASM_FUNCTION), + Handle::null()); code->set_constant_pool_offset(index + kPlaceholderMarker); placeholder_code_[index] = code; function_code_[index] = code; @@ -395,6 +396,245 @@ static MaybeHandle LookupFunction( return Handle::cast(function); } +namespace { +// Fetches the compilation unit of a wasm function and executes its parallel +// phase. +bool FetchAndExecuteCompilationUnit( + Isolate* isolate, + std::vector* compilation_units, + std::queue* executed_units, + base::Mutex* result_mutex, base::AtomicNumber* next_unit) { + DisallowHeapAllocation no_allocation; + DisallowHandleAllocation no_handles; + DisallowHandleDereference no_deref; + DisallowCodeDependencyChange no_dependency_change; + + // - 1 because AtomicIntrement returns the value after the atomic increment. + size_t index = next_unit->Increment(1) - 1; + if (index >= compilation_units->size()) { + return false; + } + + compiler::WasmCompilationUnit* unit = compilation_units->at(index); + if (unit != nullptr) { + compiler::ExecuteCompilation(unit); + { + base::LockGuard guard(result_mutex); + executed_units->push(unit); + } + } + return true; +} + +class WasmCompilationTask : public CancelableTask { + public: + WasmCompilationTask( + Isolate* isolate, + std::vector* compilation_units, + std::queue* executed_units, + base::Semaphore* on_finished, base::Mutex* result_mutex, + base::AtomicNumber* next_unit) + : CancelableTask(isolate), + isolate_(isolate), + compilation_units_(compilation_units), + executed_units_(executed_units), + on_finished_(on_finished), + result_mutex_(result_mutex), + next_unit_(next_unit) {} + + void RunInternal() override { + while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, + executed_units_, result_mutex_, + next_unit_)) { + } + on_finished_->Signal(); + } + + Isolate* isolate_; + std::vector* compilation_units_; + std::queue* executed_units_; + base::Semaphore* on_finished_; + base::Mutex* result_mutex_; + base::AtomicNumber* next_unit_; +}; + +void record_code_size(uint32_t& total_code_size, Code* code) { + if (FLAG_print_wasm_code_size) { + total_code_size += code->body_size() + code->relocation_info()->length(); + } +} + +bool CompileWrappersToImportedFunctions(Isolate* isolate, WasmModule* module, + const Handle ffi, + WasmModuleInstance* instance, + ErrorThrower* thrower, Factory* factory, + ModuleEnv* module_env, + uint32_t& total_code_size) { + uint32_t index = 0; + if (module->import_table.size() > 0) { + instance->import_code.reserve(module->import_table.size()); + for (const WasmImport& import : module->import_table) { + WasmName module_name = module->GetNameOrNull(import.module_name_offset, + import.module_name_length); + WasmName function_name = module->GetNameOrNull( + import.function_name_offset, import.function_name_length); + MaybeHandle function = LookupFunction( + *thrower, factory, ffi, index, module_name, function_name); + if (function.is_null()) return false; + + Handle code = compiler::CompileWasmToJSWrapper( + isolate, module_env, function.ToHandleChecked(), import.sig, + module_name, function_name); + instance->import_code.push_back(code); + record_code_size(total_code_size, *code); + index++; + } + } + return true; +} + +void InitializeParallelCompilation( + Isolate* isolate, std::vector& functions, + std::vector& compilation_units, + ModuleEnv& module_env, ErrorThrower& thrower) { + // Create a placeholder code object for all functions. + // TODO(ahaas): Maybe we could skip this for external functions. + for (uint32_t i = 0; i < functions.size(); i++) { + module_env.linker->GetFunctionCode(i); + } + + for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); i++) { + if (!functions[i].external) { + compilation_units[i] = compiler::CreateWasmCompilationUnit( + &thrower, isolate, &module_env, &functions[i], i); + } else { + compilation_units[i] = nullptr; + } + } +} + +uint32_t* StartCompilationTasks( + Isolate* isolate, + std::vector& compilation_units, + std::queue& executed_units, + const base::SmartPointer& pending_tasks, + base::Mutex& result_mutex, base::AtomicNumber& next_unit) { + const size_t num_tasks = + Min(static_cast(FLAG_wasm_num_compilation_tasks), + V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); + uint32_t* task_ids = new uint32_t[num_tasks]; + for (size_t i = 0; i < num_tasks; i++) { + WasmCompilationTask* task = + new WasmCompilationTask(isolate, &compilation_units, &executed_units, + pending_tasks.get(), &result_mutex, &next_unit); + task_ids[i] = task->id(); + V8::GetCurrentPlatform()->CallOnBackgroundThread( + task, v8::Platform::kShortRunningTask); + } + return task_ids; +} + +void WaitForCompilationTasks( + Isolate* isolate, uint32_t* task_ids, + const base::SmartPointer& pending_tasks) { + const size_t num_tasks = + Min(static_cast(FLAG_wasm_num_compilation_tasks), + V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); + for (size_t i = 0; i < num_tasks; i++) { + // If the task has not started yet, then we abort it. Otherwise we wait for + // it to finish. + if (!isolate->cancelable_task_manager()->TryAbort(task_ids[i])) { + pending_tasks->Wait(); + } + } +} + +void FinishCompilationUnits( + WasmModule* module, + std::queue& executed_units, + std::vector>& results, base::Mutex& result_mutex) { + while (!executed_units.empty()) { + compiler::WasmCompilationUnit* unit = nullptr; + { + base::LockGuard guard(&result_mutex); + unit = executed_units.front(); + executed_units.pop(); + } + int j = compiler::GetIndexOfWasmCompilationUnit(unit); + if (!module->functions[j].external) { + results[j] = compiler::FinishCompilation(unit); + } + } +} + +bool FinishCompilation(Isolate* isolate, WasmModule* module, + const Handle ffi, + const std::vector>& results, + const WasmModuleInstance& instance, + const Handle& code_table, + ErrorThrower& thrower, Factory* factory, + ModuleEnv& module_env, uint32_t& total_code_size, + PropertyDescriptor& desc) { + for (uint32_t i = FLAG_skip_compiling_wasm_funcs; + i < module->functions.size(); i++) { + const WasmFunction& func = module->functions[i]; + if (thrower.error()) break; + + DCHECK_EQ(i, func.func_index); + WasmName str = module->GetName(func.name_offset, func.name_length); + WasmName str_null = {nullptr, 0}; + Handle name = factory->InternalizeUtf8String(str); + Handle code = Handle::null(); + Handle function = Handle::null(); + if (func.external) { + // Lookup external function in FFI object. + MaybeHandle function = + LookupFunction(thrower, factory, ffi, i, str, str_null); + if (function.is_null()) { + return false; + } + code = compiler::CompileWasmToJSWrapper(isolate, &module_env, + function.ToHandleChecked(), + func.sig, str, str_null); + } else { + if (FLAG_wasm_num_compilation_tasks != 0) { + code = results[i]; + } else { + // Compile the function. + code = compiler::CompileWasmFunction(&thrower, isolate, &module_env, + &func); + } + if (code.is_null()) { + thrower.Error("Compilation of #%d:%.*s failed.", i, str.length(), + str.start()); + return false; + } + if (func.exported) { + function = compiler::CompileJSToWasmWrapper( + isolate, &module_env, name, code, instance.js_object, i); + record_code_size(total_code_size, function->code()); + } + } + if (!code.is_null()) { + // Install the code into the linker table. + module_env.linker->Finish(i, code); + code_table->set(i, *code); + record_code_size(total_code_size, *code); + } + if (func.exported) { + // Exported functions are installed as read-only properties on the + // module. + desc.set_value(function); + Maybe status = JSReceiver::DefineOwnProperty( + isolate, instance.js_object, name, &desc, Object::THROW_ON_ERROR); + if (!status.IsJust()) + thrower.Error("export of %.*s failed.", str.length(), str.start()); + } + } + return true; +} +} // namespace + // Instantiates a wasm module as a JSObject. // * allocates a backing store of {mem_size} bytes. // * installs a named property "memory" for that buffer if exported @@ -416,10 +656,6 @@ MaybeHandle WasmModule::Instantiate(Isolate* isolate, // objects created for this module. // TODO(titzer): switch this to TRACE_EVENT uint32_t total_code_size = 0; - auto record_code_size = [&total_code_size](Code* code) { - if (FLAG_print_wasm_code_size) - total_code_size += code->body_size() + code->relocation_info()->length(); - }; //------------------------------------------------------------------------- // Allocate the instance and its JS counterpart. @@ -466,10 +702,6 @@ MaybeHandle WasmModule::Instantiate(Isolate* isolate, HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); - //------------------------------------------------------------------------- - // Compile wrappers to imported functions. - //------------------------------------------------------------------------- - uint32_t index = 0; instance.function_table = BuildFunctionTable(isolate, this); WasmLinker linker(isolate, functions.size()); ModuleEnv module_env; @@ -478,25 +710,14 @@ MaybeHandle WasmModule::Instantiate(Isolate* isolate, module_env.linker = &linker; module_env.origin = origin; - if (import_table.size() > 0) { - instance.import_code.reserve(import_table.size()); - for (const WasmImport& import : import_table) { - WasmName module_name = - GetNameOrNull(import.module_name_offset, import.module_name_length); - WasmName function_name = GetNameOrNull(import.function_name_offset, - import.function_name_length); - MaybeHandle function = LookupFunction( - thrower, factory, ffi, index, module_name, function_name); - if (function.is_null()) return MaybeHandle(); - Handle code = compiler::CompileWasmToJSWrapper( - isolate, &module_env, function.ToHandleChecked(), import.sig, - module_name, function_name); - instance.import_code.push_back(code); - record_code_size(*code); - index++; - } + //------------------------------------------------------------------------- + // Compile wrappers to imported functions. + //------------------------------------------------------------------------- + if (!CompileWrappersToImportedFunctions(isolate, this, ffi, &instance, + &thrower, factory, &module_env, + total_code_size)) { + return MaybeHandle(); } - //------------------------------------------------------------------------- // Compile all functions in the module. //------------------------------------------------------------------------- @@ -504,103 +725,77 @@ MaybeHandle WasmModule::Instantiate(Isolate* isolate, isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); + // Data structures for the parallel compilation. std::vector compilation_units( functions.size()); std::queue executed_units; std::vector> results(functions.size()); - if (FLAG_wasm_parallel_compilation) { - // Create a placeholder code object for all functions. - // TODO(ahaas): Maybe we could skip this for external functions. - for (uint32_t i = 0; i < functions.size(); i++) { - linker.GetFunctionCode(i); - } + if (FLAG_wasm_num_compilation_tasks != 0) { + //----------------------------------------------------------------------- + // For parallel compilation: + // 1) The main thread allocates a compilation unit for each wasm function + // and stores them in the vector {compilation_units}. + // 2) The main thread spawns {WasmCompilationTask} instances which run on + // the background threads. + // 3.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 {executed_units}. + // 3.b) If {executed_units} contains a compilation unit, the main thread + // dequeues it and finishes the compilation. + // 4) After the parallel phase of all compilation units has started, the + // main thread waits for all {WasmCompilationTask} instances to finish. + // 5) The main thread finishes the compilation. - for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); - i++) { - if (!functions[i].external) { - compilation_units[i] = compiler::CreateWasmCompilationUnit( - &thrower, isolate, &module_env, &functions[i], i); - } - } + // Turn on the {CanonicalHandleScope} so that the background threads can + // use the node cache. + CanonicalHandleScope canonical(isolate); - index = FLAG_skip_compiling_wasm_funcs; - while (true) { - while (!executed_units.empty()) { - compiler::WasmCompilationUnit* unit = executed_units.front(); - executed_units.pop(); - int i = compiler::GetIndexOfWasmCompilationUnit(unit); - results[i] = compiler::FinishCompilation(unit); - } - if (index < functions.size()) { - if (!functions[index].external) { - compiler::ExecuteCompilation(compilation_units[index]); - executed_units.push(compilation_units[index]); - index++; - } - } else { - break; - } + // 1) The main thread allocates a compilation unit for each wasm function + // and stores them in the vector {compilation_units}. + InitializeParallelCompilation(isolate, functions, compilation_units, + module_env, thrower); + + // Objects for the synchronization with the background threads. + base::SmartPointer pending_tasks(new base::Semaphore(0)); + base::Mutex result_mutex; + base::AtomicNumber next_unit( + static_cast(FLAG_skip_compiling_wasm_funcs)); + + // 2) The main thread spawns {WasmCompilationTask} instances which run on + // the background threads. + base::SmartArrayPointer task_ids( + StartCompilationTasks(isolate, compilation_units, executed_units, + pending_tasks, result_mutex, next_unit)); + + // 3.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 {executed_units}. + while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, + &executed_units, &result_mutex, + &next_unit)) { + // 3.b) If {executed_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(this, executed_units, results, result_mutex); } + // 4) After the parallel phase of all compilation units has started, the + // main thread waits for all {WasmCompilationTask} instances to finish. + WaitForCompilationTasks(isolate, task_ids.get(), pending_tasks); + // Finish the compilation of the remaining compilation units. + FinishCompilationUnits(this, executed_units, results, result_mutex); + } + // 5) The main thread finishes the compilation. + if (!FinishCompilation(isolate, this, ffi, results, instance, code_table, + thrower, factory, module_env, total_code_size, + desc)) { + return MaybeHandle(); } - // First pass: compile each function and initialize the code table. - for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); - i++) { - const WasmFunction& func = functions[i]; - if (thrower.error()) break; - DCHECK_EQ(i, func.func_index); - - WasmName str = GetName(func.name_offset, func.name_length); - WasmName str_null = {nullptr, 0}; - Handle name = factory->InternalizeUtf8String(str); - Handle code = Handle::null(); - Handle function = Handle::null(); - if (func.external) { - // Lookup external function in FFI object. - MaybeHandle function = - LookupFunction(thrower, factory, ffi, i, str, str_null); - if (function.is_null()) return MaybeHandle(); - code = compiler::CompileWasmToJSWrapper(isolate, &module_env, - function.ToHandleChecked(), - func.sig, str, str_null); - } else { - if (FLAG_wasm_parallel_compilation) { - code = results[i]; - } else { - // Compile the function. - code = compiler::CompileWasmFunction(&thrower, isolate, &module_env, - &func); - } - if (code.is_null()) { - thrower.Error("Compilation of #%d:%.*s failed.", i, str.length(), - str.start()); - return MaybeHandle(); - } - if (func.exported) { - function = compiler::CompileJSToWasmWrapper( - isolate, &module_env, name, code, instance.js_object, i); - record_code_size(function->code()); - } - } - if (!code.is_null()) { - // Install the code into the linker table. - linker.Finish(i, code); - code_table->set(i, *code); - record_code_size(*code); - } - if (func.exported) { - // Exported functions are installed as read-only properties on the - // module. - desc.set_value(function); - Maybe status = JSReceiver::DefineOwnProperty( - isolate, instance.js_object, name, &desc, Object::THROW_ON_ERROR); - if (!status.IsJust()) - thrower.Error("export of %.*s failed.", str.length(), str.start()); - } - } - - // Second pass: patch all direct call sites. + // Patch all direct call sites. linker.Link(instance.function_table, this->function_table); instance.js_object->SetInternalField(kWasmModuleFunctionTable, Smi::FromInt(0)); @@ -627,7 +822,7 @@ MaybeHandle WasmModule::Instantiate(Isolate* isolate, Handle function = compiler::CompileJSToWasmWrapper( isolate, &module_env, name, code, instance.js_object, exp.func_index); - record_code_size(function->code()); + record_code_size(total_code_size, function->code()); desc.set_value(function); Maybe status = JSReceiver::DefineOwnProperty( isolate, exports_object, name, &desc, Object::THROW_ON_ERROR); diff --git a/test/mjsunit/wasm/parallel_compilation.js b/test/mjsunit/wasm/parallel_compilation.js new file mode 100644 index 0000000000..23c5658dcd --- /dev/null +++ b/test/mjsunit/wasm/parallel_compilation.js @@ -0,0 +1,100 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --expose-wasm --wasm-num-compilation-tasks=10 + +load("test/mjsunit/wasm/wasm-constants.js"); +load("test/mjsunit/wasm/wasm-module-builder.js"); + +function assertModule(module, memsize) { + // Check the module exists. + assertFalse(module === undefined); + assertFalse(module === null); + assertFalse(module === 0); + assertEquals("object", typeof module); + + // Check the memory is an ArrayBuffer. + var mem = module.exports.memory; + assertFalse(mem === undefined); + assertFalse(mem === null); + assertFalse(mem === 0); + assertEquals("object", typeof mem); + assertTrue(mem instanceof ArrayBuffer); + for (var i = 0; i < 4; i++) { + module.exports.memory = 0; // should be ignored + assertEquals(mem, module.exports.memory); + } + + assertEquals(memsize, module.exports.memory.byteLength); +} + +function assertFunction(module, func) { + assertEquals("object", typeof module.exports); + + var exp = module.exports[func]; + assertFalse(exp === undefined); + assertFalse(exp === null); + assertFalse(exp === 0); + assertEquals("function", typeof exp); + return exp; +} + +(function CompileFunctionsTest() { + + var builder = new WasmModuleBuilder(); + + builder.addMemory(1, 1, true); + for (i = 0; i < 1000; i++) { + builder.addFunction("sub" + i, kSig_i_i) + .addBody([ // -- + kExprGetLocal, 0, // -- + kExprI32Const, i % 61, // -- + kExprI32Sub]) // -- + .exportFunc() + } + + var module = builder.instantiate(); + assertModule(module, kPageSize); + + // Check the properties of the functions. + for (i = 0; i < 1000; i++) { + var sub = assertFunction(module, "sub" + i); + assertEquals(33 - (i % 61), sub(33)); + } +})(); + +(function CallFunctionsTest() { + + var builder = new WasmModuleBuilder(); + + var f = [] + + f[0] = builder.addFunction("add0", kSig_i_ii) + .addBody([ + kExprGetLocal, 0, // -- + kExprGetLocal, 1, // -- + kExprI32Add, // -- + ]) + .exportFunc() + + builder.addMemory(1, 1, true); + for (i = 1; i < 256; i++) { + f[i] = builder.addFunction("add" + i, kSig_i_ii) + .addBody([ // -- + kExprGetLocal, 0, // -- + kExprGetLocal, 1, // -- + kExprCallFunction, kArity2, f[i >>> 1].index]) // -- + .exportFunc() + } + var module = builder.instantiate(); + assertModule(module, kPageSize); + + // Check the properties of the functions. + for (i = 0; i < 256; i++) { + var add = assertFunction(module, "add" + i); + assertEquals(88, add(33, 55)); + assertEquals(88888, add(33333, 55555)); + assertEquals(8888888, add(3333333, 5555555)); + } +})();