blob: cd3a3d839a3d6f2e8f48fa7854ce8a8f4a2710cc [file] [log] [blame]
// Copyright 2017 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.
#include "src/wasm/module-compiler.h"
#include <atomic>
#include "src/api.h"
#include "src/asmjs/asm-js.h"
#include "src/assembler-inl.h"
#include "src/base/template-utils.h"
#include "src/code-stubs.h"
#include "src/counters.h"
#include "src/property-descriptor.h"
#include "src/wasm/compilation-manager.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-result.h"
#define TRACE(...) \
do { \
if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \
} while (false)
#define TRACE_CHAIN(instance) \
do { \
instance->PrintInstancesChain(); \
} while (false)
#define TRACE_COMPILE(...) \
do { \
if (FLAG_trace_wasm_compiler) PrintF(__VA_ARGS__); \
} while (false)
#define TRACE_STREAMING(...) \
do { \
if (FLAG_trace_wasm_streaming) PrintF(__VA_ARGS__); \
} while (false)
static const int kInvalidSigIndex = -1;
namespace v8 {
namespace internal {
namespace wasm {
ModuleCompiler::CodeGenerationSchedule::CodeGenerationSchedule(
base::RandomNumberGenerator* random_number_generator, size_t max_memory)
: random_number_generator_(random_number_generator),
max_memory_(max_memory) {
DCHECK_NOT_NULL(random_number_generator_);
DCHECK_GT(max_memory_, 0);
}
void ModuleCompiler::CodeGenerationSchedule::Schedule(
std::unique_ptr<compiler::WasmCompilationUnit>&& item) {
size_t cost = item->memory_cost();
schedule_.push_back(std::move(item));
allocated_memory_.Increment(cost);
}
bool ModuleCompiler::CodeGenerationSchedule::CanAcceptWork() const {
return (!throttle_ || allocated_memory_.Value() <= max_memory_);
}
bool ModuleCompiler::CodeGenerationSchedule::ShouldIncreaseWorkload() const {
// Half the memory is unused again, we can increase the workload again.
return (!throttle_ || allocated_memory_.Value() <= max_memory_ / 2);
}
std::unique_ptr<compiler::WasmCompilationUnit>
ModuleCompiler::CodeGenerationSchedule::GetNext() {
DCHECK(!IsEmpty());
size_t index = GetRandomIndexInSchedule();
auto ret = std::move(schedule_[index]);
std::swap(schedule_[schedule_.size() - 1], schedule_[index]);
schedule_.pop_back();
allocated_memory_.Decrement(ret->memory_cost());
return ret;
}
size_t ModuleCompiler::CodeGenerationSchedule::GetRandomIndexInSchedule() {
double factor = random_number_generator_->NextDouble();
size_t index = (size_t)(factor * schedule_.size());
DCHECK_GE(index, 0);
DCHECK_LT(index, schedule_.size());
return index;
}
ModuleCompiler::ModuleCompiler(Isolate* isolate, WasmModule* module,
Handle<Code> centry_stub)
: isolate_(isolate),
module_(module),
async_counters_(isolate->async_counters()),
executed_units_(
isolate->random_number_generator(),
(isolate->heap()->memory_allocator()->code_range()->valid()
? isolate->heap()->memory_allocator()->code_range()->size()
: isolate->heap()->code_space()->Capacity()) /
2),
num_background_tasks_(
Min(static_cast<size_t>(FLAG_wasm_num_compilation_tasks),
V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads())),
stopped_compilation_tasks_(num_background_tasks_),
centry_stub_(centry_stub) {}
// The actual runnable task that performs compilations in the background.
ModuleCompiler::CompilationTask::CompilationTask(ModuleCompiler* compiler)
: CancelableTask(&compiler->background_task_manager_),
compiler_(compiler) {}
void ModuleCompiler::CompilationTask::RunInternal() {
while (compiler_->executed_units_.CanAcceptWork() &&
compiler_->FetchAndExecuteCompilationUnit()) {
}
compiler_->OnBackgroundTaskStopped();
}
void ModuleCompiler::OnBackgroundTaskStopped() {
base::LockGuard<base::Mutex> guard(&tasks_mutex_);
++stopped_compilation_tasks_;
DCHECK_LE(stopped_compilation_tasks_, num_background_tasks_);
}
// Run by each compilation task The no_finisher_callback is called
// within the result_mutex_ lock when no finishing task is running,
// i.e. when the finisher_is_running_ flag is not set.
bool ModuleCompiler::FetchAndExecuteCompilationUnit(
std::function<void()> no_finisher_callback) {
DisallowHeapAllocation no_allocation;
DisallowHandleAllocation no_handles;
DisallowHandleDereference no_deref;
DisallowCodeDependencyChange no_dependency_change;
std::unique_ptr<compiler::WasmCompilationUnit> unit;
{
base::LockGuard<base::Mutex> guard(&compilation_units_mutex_);
if (compilation_units_.empty()) return false;
unit = std::move(compilation_units_.back());
compilation_units_.pop_back();
}
unit->ExecuteCompilation();
{
base::LockGuard<base::Mutex> guard(&result_mutex_);
executed_units_.Schedule(std::move(unit));
if (no_finisher_callback != nullptr && !finisher_is_running_) {
no_finisher_callback();
// We set the flag here so that not more than one finisher is started.
finisher_is_running_ = true;
}
}
return true;
}
size_t ModuleCompiler::InitializeCompilationUnits(
const std::vector<WasmFunction>& functions,
const ModuleWireBytes& wire_bytes, compiler::ModuleEnv* module_env) {
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 funcs_to_compile = start > num_funcs ? 0 : num_funcs - start;
CompilationUnitBuilder builder(this);
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);
builder.AddUnit(module_env, func, buffer_offset, bytes, name);
}
builder.Commit();
return funcs_to_compile;
}
void ModuleCompiler::RestartCompilationTasks() {
base::LockGuard<base::Mutex> guard(&tasks_mutex_);
for (; stopped_compilation_tasks_ > 0; --stopped_compilation_tasks_) {
V8::GetCurrentPlatform()->CallOnBackgroundThread(
new CompilationTask(this),
v8::Platform::ExpectedRuntime::kShortRunningTask);
}
}
size_t ModuleCompiler::FinishCompilationUnits(
std::vector<Handle<Code>>& results, ErrorThrower* thrower) {
size_t finished = 0;
while (true) {
int func_index = -1;
MaybeHandle<Code> result = FinishCompilationUnit(thrower, &func_index);
if (func_index < 0) break;
++finished;
DCHECK_IMPLIES(result.is_null(), thrower->error());
if (result.is_null()) break;
results[func_index] = result.ToHandleChecked();
}
bool do_restart;
{
base::LockGuard<base::Mutex> guard(&compilation_units_mutex_);
do_restart = !compilation_units_.empty();
}
if (do_restart) RestartCompilationTasks();
return finished;
}
void ModuleCompiler::SetFinisherIsRunning(bool value) {
base::LockGuard<base::Mutex> guard(&result_mutex_);
finisher_is_running_ = value;
}
MaybeHandle<Code> ModuleCompiler::FinishCompilationUnit(ErrorThrower* thrower,
int* func_index) {
std::unique_ptr<compiler::WasmCompilationUnit> unit;
{
base::LockGuard<base::Mutex> guard(&result_mutex_);
if (executed_units_.IsEmpty()) return {};
unit = executed_units_.GetNext();
}
*func_index = unit->func_index();
return unit->FinishCompilation(thrower);
}
void ModuleCompiler::CompileInParallel(const ModuleWireBytes& wire_bytes,
compiler::ModuleEnv* module_env,
std::vector<Handle<Code>>& results,
ErrorThrower* thrower) {
const WasmModule* module = module_env->module;
// Data structures for the parallel compilation.
//-----------------------------------------------------------------------
// 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 {CompilationTask} 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 {CompilationTask} instances to finish.
// 5) The main thread finishes the compilation.
// Turn on the {CanonicalHandleScope} so that the background threads can
// use the node cache.
CanonicalHandleScope canonical(isolate_);
// 1) The main thread allocates a compilation unit for each wasm function
// and stores them in the vector {compilation_units}.
InitializeCompilationUnits(module->functions, wire_bytes, module_env);
executed_units_.EnableThrottling();
// 2) The main thread spawns {CompilationTask} instances which run on
// the background threads.
RestartCompilationTasks();
// 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}.
// The foreground task bypasses waiting on memory threshold, because
// its results will immediately be converted to code (below).
while (FetchAndExecuteCompilationUnit()) {
// 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(results, thrower);
}
// 4) After the parallel phase of all compilation units has started, the
// main thread waits for all {CompilationTask} instances to finish - which
// happens once they all realize there's no next work item to process.
background_task_manager_.CancelAndWait();
// Finish all compilation units which have been executed while we waited.
FinishCompilationUnits(results, thrower);
}
void ModuleCompiler::CompileSequentially(const ModuleWireBytes& wire_bytes,
compiler::ModuleEnv* module_env,
std::vector<Handle<Code>>& results,
ErrorThrower* thrower) {
DCHECK(!thrower->error());
const WasmModule* module = module_env->module;
for (uint32_t i = FLAG_skip_compiling_wasm_funcs;
i < module->functions.size(); ++i) {
const WasmFunction& func = module->functions[i];
if (func.imported) continue; // Imports are compiled at instantiation time.
// Compile the function.
MaybeHandle<Code> code = compiler::WasmCompilationUnit::CompileWasmFunction(
thrower, isolate_, wire_bytes, module_env, &func);
if (code.is_null()) {
TruncatedUserString<> name(wire_bytes.GetName(&func));
thrower->CompileError("Compilation of #%d:%.*s failed.", i, name.length(),
name.start());
break;
}
results[i] = code.ToHandleChecked();
}
}
void ModuleCompiler::ValidateSequentially(const ModuleWireBytes& wire_bytes,
compiler::ModuleEnv* module_env,
ErrorThrower* thrower) {
DCHECK(!thrower->error());
const WasmModule* module = module_env->module;
for (uint32_t i = 0; i < module->functions.size(); ++i) {
const WasmFunction& func = module->functions[i];
if (func.imported) continue;
const byte* base = wire_bytes.start();
FunctionBody body{func.sig, func.code.offset(), base + func.code.offset(),
base + func.code.end_offset()};
DecodeResult result = VerifyWasmCodeWithStats(
isolate_->allocator(), module, body, module->is_wasm(), counters());
if (result.failed()) {
TruncatedUserString<> name(wire_bytes.GetName(&func));
thrower->CompileError("Compiling function #%d:%.*s failed: %s @+%u", i,
name.length(), name.start(),
result.error_msg().c_str(), result.error_offset());
break;
}
}
}
// static
MaybeHandle<WasmModuleObject> ModuleCompiler::CompileToModuleObject(
Isolate* isolate, ErrorThrower* thrower, std::unique_ptr<WasmModule> module,
const ModuleWireBytes& wire_bytes, Handle<Script> asm_js_script,
Vector<const byte> asm_js_offset_table_bytes) {
Handle<Code> centry_stub = CEntryStub(isolate, 1).GetCode();
ModuleCompiler compiler(isolate, module.get(), centry_stub);
return compiler.CompileToModuleObjectInternal(thrower, std::move(module),
wire_bytes, asm_js_script,
asm_js_offset_table_bytes);
}
namespace {
bool compile_lazy(const WasmModule* module) {
return FLAG_wasm_lazy_compilation ||
(FLAG_asm_wasm_lazy_compilation && module->is_asm_js());
}
void FlushICache(Isolate* isolate, Handle<FixedArray> code_table) {
for (int i = 0; i < code_table->length(); ++i) {
Handle<Code> code = code_table->GetValueChecked<Code>(isolate, i);
Assembler::FlushICache(isolate, code->instruction_start(),
code->instruction_size());
}
}
byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) {
return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset;
}
void RecordStats(Code* code, Counters* counters) {
counters->wasm_generated_code_size()->Increment(code->body_size());
counters->wasm_reloc_size()->Increment(code->relocation_info()->length());
}
void RecordStats(Handle<FixedArray> functions, Counters* counters) {
DisallowHeapAllocation no_gc;
for (int i = 0; i < functions->length(); ++i) {
Object* val = functions->get(i);
if (val->IsCode()) RecordStats(Code::cast(val), counters);
}
}
Handle<Script> CreateWasmScript(Isolate* isolate,
const ModuleWireBytes& wire_bytes) {
Handle<Script> script =
isolate->factory()->NewScript(isolate->factory()->empty_string());
script->set_context_data(isolate->native_context()->debug_context_id());
script->set_type(Script::TYPE_WASM);
int hash = StringHasher::HashSequentialString(
reinterpret_cast<const char*>(wire_bytes.start()),
static_cast<int>(wire_bytes.length()), kZeroHashSeed);
const int kBufferSize = 32;
char buffer[kBufferSize];
int url_chars = SNPrintF(ArrayVector(buffer), "wasm://wasm/%08x", hash);
DCHECK(url_chars >= 0 && url_chars < kBufferSize);
MaybeHandle<String> url_str = isolate->factory()->NewStringFromOneByte(
Vector<const uint8_t>(reinterpret_cast<uint8_t*>(buffer), url_chars),
TENURED);
script->set_source_url(*url_str.ToHandleChecked());
int name_chars = SNPrintF(ArrayVector(buffer), "wasm-%08x", hash);
DCHECK(name_chars >= 0 && name_chars < kBufferSize);
MaybeHandle<String> name_str = isolate->factory()->NewStringFromOneByte(
Vector<const uint8_t>(reinterpret_cast<uint8_t*>(buffer), name_chars),
TENURED);
script->set_name(*name_str.ToHandleChecked());
return script;
}
// 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<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
// might be extended later for table exports (see
// EnsureTableExportLazyDeoptData).
Handle<FixedArray> deopt_data(code->deoptimization_data());
DCHECK_EQ(0, deopt_data->length() % 2);
if (deopt_data->length() == 0) {
code = isolate->factory()->CopyCode(code);
code_table->set(func_index, *code);
deopt_data = isolate->factory()->NewFixedArray(2, 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::ToInt(code->deoptimization_data()->get(1)));
return code;
}
// 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> EnsureTableExportLazyDeoptData(
Isolate* isolate, Handle<WasmInstanceObject> instance,
Handle<FixedArray> code_table, int func_index,
Handle<FixedArray> export_table, int export_index,
std::unordered_map<uint32_t, uint32_t>& table_export_count) {
Handle<Code> code =
EnsureExportedLazyDeoptData(isolate, instance, code_table, func_index);
if (code->builtin_index() != Builtins::kWasmCompileLazy) return code;
// deopt_data:
// #0: weak instance
// #1: func_index
// [#2: export table
// #3: export table index]
// [#4: export table
// #5: export table index]
// ...
// table_export_count counts down and determines the index for the new export
// table entry.
auto table_export_entry = table_export_count.find(func_index);
DCHECK(table_export_entry != table_export_count.end());
DCHECK_LT(0, table_export_entry->second);
uint32_t this_idx = 2 * table_export_entry->second;
--table_export_entry->second;
Handle<FixedArray> deopt_data(code->deoptimization_data());
DCHECK_EQ(0, deopt_data->length() % 2);
if (deopt_data->length() == 2) {
// Then only the "header" (#0 and #1) exists. Extend for the export table
// entries (make space for this_idx + 2 elements).
deopt_data = isolate->factory()->CopyFixedArrayAndGrow(deopt_data, this_idx,
TENURED);
code->set_deoptimization_data(*deopt_data);
}
DCHECK_LE(this_idx + 2, deopt_data->length());
DCHECK(deopt_data->get(this_idx)->IsUndefined(isolate));
DCHECK(deopt_data->get(this_idx + 1)->IsUndefined(isolate));
deopt_data->set(this_idx, *export_table);
deopt_data->set(this_idx + 1, Smi::FromInt(export_index));
return code;
}
bool in_bounds(uint32_t offset, uint32_t size, uint32_t upper) {
return offset + size <= upper && offset + size >= offset;
}
using WasmInstanceMap =
IdentityMap<Handle<WasmInstanceObject>, FreeStoreAllocationPolicy>;
Handle<Code> UnwrapExportOrCompileImportWrapper(
Isolate* isolate, int index, FunctionSig* sig, Handle<JSReceiver> target,
Handle<String> module_name, MaybeHandle<String> import_name,
ModuleOrigin origin, WasmInstanceMap* imported_instances,
Handle<FixedArray> js_imports_table) {
WasmFunction* other_func = GetWasmFunctionForExport(isolate, target);
if (other_func) {
if (!sig->Equals(other_func->sig)) return Handle<Code>::null();
// Signature matched. Unwrap the import wrapper and return the raw wasm
// function code.
// Remember the wasm instance of the import. We have to keep it alive.
Handle<WasmInstanceObject> imported_instance(
Handle<WasmExportedFunction>::cast(target)->instance(), isolate);
imported_instances->Set(imported_instance, imported_instance);
return UnwrapExportWrapper(Handle<JSFunction>::cast(target));
}
// No wasm function or being debugged. Compile a new wrapper for the new
// signature.
return compiler::CompileWasmToJSWrapper(isolate, target, sig, index,
module_name, import_name, origin,
js_imports_table);
}
double MonotonicallyIncreasingTimeInMs() {
return V8::GetCurrentPlatform()->MonotonicallyIncreasingTime() *
base::Time::kMillisecondsPerSecond;
}
void FunctionTableFinalizer(const v8::WeakCallbackInfo<void>& data) {
GlobalHandles::Destroy(reinterpret_cast<Object**>(
reinterpret_cast<JSObject**>(data.GetParameter())));
}
std::unique_ptr<compiler::ModuleEnv> CreateDefaultModuleEnv(
Isolate* isolate, WasmModule* module, Handle<Code> illegal_builtin) {
std::vector<GlobalHandleAddress> function_tables;
std::vector<GlobalHandleAddress> signature_tables;
std::vector<SignatureMap*> signature_maps;
for (size_t i = 0; i < module->function_tables.size(); i++) {
Handle<Object> func_table =
isolate->global_handles()->Create(isolate->heap()->undefined_value());
Handle<Object> sig_table =
isolate->global_handles()->Create(isolate->heap()->undefined_value());
GlobalHandles::MakeWeak(func_table.location(), func_table.location(),
&FunctionTableFinalizer,
v8::WeakCallbackType::kFinalizer);
GlobalHandles::MakeWeak(sig_table.location(), sig_table.location(),
&FunctionTableFinalizer,
v8::WeakCallbackType::kFinalizer);
function_tables.push_back(func_table.address());
signature_tables.push_back(sig_table.address());
signature_maps.push_back(&module->function_tables[i].map);
}
std::vector<Handle<Code>> empty_code;
compiler::ModuleEnv result = {
module, // --
function_tables, // --
signature_tables, // --
signature_maps, // --
empty_code, // --
illegal_builtin, // --
0, // --
0, // --
0 // --
};
return std::unique_ptr<compiler::ModuleEnv>(new compiler::ModuleEnv(result));
}
Handle<WasmCompiledModule> NewCompiledModule(
Isolate* isolate, Handle<WasmSharedModuleData> shared,
Handle<FixedArray> code_table, Handle<FixedArray> export_wrappers,
compiler::ModuleEnv* env) {
Handle<WasmCompiledModule> compiled_module =
WasmCompiledModule::New(isolate, shared, code_table, export_wrappers,
env->function_tables, env->signature_tables);
return compiled_module;
}
template <typename T>
void ReopenHandles(Isolate* isolate, const std::vector<Handle<T>>& vec) {
auto& mut = const_cast<std::vector<Handle<T>>&>(vec);
for (size_t i = 0; i < mut.size(); i++) {
mut[i] = Handle<T>(*mut[i], isolate);
}
}
} // namespace
MaybeHandle<WasmModuleObject> ModuleCompiler::CompileToModuleObjectInternal(
ErrorThrower* thrower, std::unique_ptr<WasmModule> module,
const ModuleWireBytes& wire_bytes, Handle<Script> asm_js_script,
Vector<const byte> asm_js_offset_table_bytes) {
TimedHistogramScope wasm_compile_module_time_scope(
module_->is_wasm() ? counters()->wasm_compile_wasm_module_time()
: counters()->wasm_compile_asm_module_time());
// The {module> parameter is passed in to transfer ownership of the WasmModule
// to this function. The WasmModule itself existed already as an instance
// variable of the ModuleCompiler. We check here that the parameter and the
// instance variable actually point to the same object.
DCHECK_EQ(module.get(), module_);
// Check whether lazy compilation is enabled for this module.
bool lazy_compile = compile_lazy(module_);
Factory* factory = isolate_->factory();
// 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> init_builtin = lazy_compile
? BUILTIN_CODE(isolate_, WasmCompileLazy)
: BUILTIN_CODE(isolate_, Illegal);
auto env = CreateDefaultModuleEnv(isolate_, module_, init_builtin);
// The {code_table} array contains import wrappers and functions (which
// are both included in {functions.size()}, and export wrappers).
int code_table_size = static_cast<int>(module_->functions.size());
int export_wrappers_size = static_cast<int>(module_->num_exported_functions);
Handle<FixedArray> code_table =
factory->NewFixedArray(static_cast<int>(code_table_size), TENURED);
Handle<FixedArray> export_wrappers =
factory->NewFixedArray(static_cast<int>(export_wrappers_size), TENURED);
// Initialize the code table.
for (int i = 0, e = code_table->length(); i < e; ++i) {
code_table->set(i, *init_builtin);
}
for (int i = 0, e = export_wrappers->length(); i < e; ++i) {
export_wrappers->set(i, *init_builtin);
}
if (!lazy_compile) {
size_t funcs_to_compile =
module_->functions.size() - module_->num_imported_functions;
bool compile_parallel =
!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks > 0 &&
funcs_to_compile > 1 &&
V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads() > 0;
// Avoid a race condition by collecting results into a second vector.
std::vector<Handle<Code>> results(env->module->functions.size());
if (compile_parallel) {
CompileInParallel(wire_bytes, env.get(), results, thrower);
} else {
CompileSequentially(wire_bytes, env.get(), results, thrower);
}
if (thrower->error()) return {};
// At this point, compilation has completed. Update the code table.
for (size_t i =
module_->num_imported_functions + FLAG_skip_compiling_wasm_funcs;
i < results.size(); ++i) {
Code* code = *results[i];
code_table->set(static_cast<int>(i), code);
RecordStats(code, counters());
}
} else if (module_->is_wasm()) {
// Validate wasm modules for lazy compilation. Don't validate asm.js
// modules, they are valid by construction (otherwise a CHECK will fail
// during lazy compilation).
// TODO(clemensh): According to the spec, we can actually skip validation
// at module creation time, and return a function that always traps at
// (lazy) compilation time.
ValidateSequentially(wire_bytes, env.get(), thrower);
}
if (thrower->error()) return {};
// Create heap objects for script, module bytes and asm.js offset table to
// be stored in the shared module data.
Handle<Script> script;
Handle<ByteArray> asm_js_offset_table;
if (asm_js_script.is_null()) {
script = CreateWasmScript(isolate_, wire_bytes);
} else {
script = asm_js_script;
asm_js_offset_table =
isolate_->factory()->NewByteArray(asm_js_offset_table_bytes.length());
asm_js_offset_table->copy_in(0, asm_js_offset_table_bytes.start(),
asm_js_offset_table_bytes.length());
}
// TODO(wasm): only save the sections necessary to deserialize a
// {WasmModule}. E.g. function bodies could be omitted.
Handle<String> module_bytes =
factory
->NewStringFromOneByte({wire_bytes.start(), wire_bytes.length()},
TENURED)
.ToHandleChecked();
DCHECK(module_bytes->IsSeqOneByteString());
// The {module_wrapper} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object.
Handle<WasmModuleWrapper> module_wrapper =
WasmModuleWrapper::From(isolate_, module.release());
// Create the shared module data.
// TODO(clemensh): For the same module (same bytes / same hash), we should
// only have one WasmSharedModuleData. Otherwise, we might only set
// breakpoints on a (potentially empty) subset of the instances.
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
// serializable. Instantiation may occur off a deserialized version of this
// object.
Handle<WasmCompiledModule> compiled_module = NewCompiledModule(
isolate_, shared, code_table, export_wrappers, env.get());
// If we created a wasm script, finish it now and make it public to the
// debugger.
if (asm_js_script.is_null()) {
script->set_wasm_compiled_module(*compiled_module);
isolate_->debug()->OnAfterCompile(script);
}
// Compile JS->wasm wrappers for exported functions.
JSToWasmWrapperCache js_to_wasm_cache;
int wrapper_index = 0;
for (auto exp : module_->export_table) {
if (exp.kind != kExternalFunction) continue;
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);
export_wrappers->set(wrapper_index, *wrapper_code);
RecordStats(*wrapper_code, counters());
++wrapper_index;
}
return WasmModuleObject::New(isolate_, compiled_module);
}
Handle<Code> JSToWasmWrapperCache::CloneOrCompileJSToWasmWrapper(
Isolate* isolate, wasm::WasmModule* module, Handle<Code> wasm_code,
uint32_t index) {
const wasm::WasmFunction* func = &module->functions[index];
int cached_idx = sig_map_.Find(func->sig);
if (cached_idx >= 0) {
Handle<Code> code = isolate->factory()->CopyCode(code_cache_[cached_idx]);
// Now patch the call to wasm code.
for (RelocIterator it(*code, RelocInfo::kCodeTargetMask);; it.next()) {
DCHECK(!it.done());
Code* target =
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::kWasmCompileLazy) {
it.rinfo()->set_target_address(isolate, wasm_code->instruction_start());
break;
}
}
return code;
}
Handle<Code> code =
compiler::CompileJSToWasmWrapper(isolate, module, wasm_code, index);
uint32_t new_cache_idx = sig_map_.FindOrInsert(func->sig);
DCHECK_EQ(code_cache_.size(), new_cache_idx);
USE(new_cache_idx);
code_cache_.push_back(code);
return code;
}
InstanceBuilder::InstanceBuilder(
Isolate* isolate, ErrorThrower* thrower,
Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> ffi,
MaybeHandle<JSArrayBuffer> memory,
WeakCallbackInfo<void>::Callback instance_finalizer_callback)
: isolate_(isolate),
module_(module_object->compiled_module()->module()),
async_counters_(isolate->async_counters()),
thrower_(thrower),
module_object_(module_object),
ffi_(ffi),
memory_(memory),
instance_finalizer_callback_(instance_finalizer_callback) {
sanitized_imports_.reserve(module_->import_table.size());
}
// Build an instance, in all of its glory.
MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// Check that an imports argument was provided, if the module requires it.
// No point in continuing otherwise.
if (!module_->import_table.empty() && ffi_.is_null()) {
thrower_->TypeError(
"Imports argument must be present and must be an object");
return {};
}
SanitizeImports();
if (thrower_->error()) return {};
// From here on, we expect the build pipeline to run without exiting to JS.
// Exception is when we run the startup function.
DisallowJavascriptExecution no_js(isolate_);
// Record build time into correct bucket, then build instance.
TimedHistogramScope wasm_instantiate_module_time_scope(
module_->is_wasm() ? counters()->wasm_instantiate_wasm_module_time()
: counters()->wasm_instantiate_asm_module_time());
Factory* factory = isolate_->factory();
//--------------------------------------------------------------------------
// Reuse the compiled module (if no owner), otherwise clone.
//--------------------------------------------------------------------------
Handle<FixedArray> code_table;
Handle<FixedArray> wrapper_table;
// We keep around a copy of the old code table, because we'll be replacing
// imports for the new instance, and then we need the old imports to be
// able to relocate.
Handle<FixedArray> old_code_table;
MaybeHandle<WasmInstanceObject> owner;
TRACE("Starting new module instantiation\n");
{
// Root the owner, if any, before doing any allocations, which
// may trigger GC.
// Both owner and original template need to be in sync. Even
// after we lose the original template handle, the code
// objects we copied from it have data relative to the
// instance - such as globals addresses.
Handle<WasmCompiledModule> original;
{
DisallowHeapAllocation no_gc;
original = handle(module_object_->compiled_module());
if (original->has_weak_owning_instance()) {
owner = handle(WasmInstanceObject::cast(
original->weak_owning_instance()->value()));
}
}
DCHECK(!original.is_null());
if (original->has_weak_owning_instance()) {
// Clone, but don't insert yet the clone in the instances chain.
// We do that last. Since we are holding on to the owner instance,
// the owner + original state used for cloning and patching
// won't be mutated by possible finalizer runs.
DCHECK(!owner.is_null());
TRACE("Cloning from %d\n", original->instance_id());
old_code_table = original->code_table();
compiled_module_ = WasmCompiledModule::Clone(isolate_, original);
code_table = compiled_module_->code_table();
wrapper_table = compiled_module_->export_wrappers();
// Avoid creating too many handles in the outer scope.
HandleScope scope(isolate_);
// Clone the code for wasm functions and exports.
for (int i = 0; i < code_table->length(); ++i) {
Handle<Code> orig_code(Code::cast(code_table->get(i)), isolate_);
switch (orig_code->kind()) {
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::WASM_FUNCTION: {
Handle<Code> code = factory->CopyCode(orig_code);
code_table->set(i, *code);
break;
}
default:
UNREACHABLE();
}
}
for (int i = 0; i < wrapper_table->length(); ++i) {
Handle<Code> orig_code(Code::cast(wrapper_table->get(i)), isolate_);
DCHECK_EQ(orig_code->kind(), Code::JS_TO_WASM_FUNCTION);
Handle<Code> code = factory->CopyCode(orig_code);
wrapper_table->set(i, *code);
}
RecordStats(code_table, counters());
RecordStats(wrapper_table, counters());
} else {
// There was no owner, so we can reuse the original.
compiled_module_ = original;
old_code_table = factory->CopyFixedArray(compiled_module_->code_table());
code_table = compiled_module_->code_table();
wrapper_table = compiled_module_->export_wrappers();
TRACE("Reusing existing instance %d\n", compiled_module_->instance_id());
}
compiled_module_->set_native_context(isolate_->native_context());
}
//--------------------------------------------------------------------------
// Allocate the instance object.
//--------------------------------------------------------------------------
Zone instantiation_zone(isolate_->allocator(), ZONE_NAME);
CodeSpecialization code_specialization(isolate_, &instantiation_zone);
Handle<WasmInstanceObject> instance =
WasmInstanceObject::New(isolate_, compiled_module_);
//--------------------------------------------------------------------------
// Set up the globals for the new instance.
//--------------------------------------------------------------------------
MaybeHandle<JSArrayBuffer> old_globals;
uint32_t globals_size = module_->globals_size;
if (globals_size > 0) {
const bool enable_guard_regions = false;
Handle<JSArrayBuffer> global_buffer =
NewArrayBuffer(isolate_, globals_size, enable_guard_regions);
globals_ = global_buffer;
if (globals_.is_null()) {
thrower_->RangeError("Out of memory: wasm globals");
return {};
}
Address old_globals_start = compiled_module_->GetGlobalsStartOrNull();
Address new_globals_start =
static_cast<Address>(global_buffer->backing_store());
code_specialization.RelocateGlobals(old_globals_start, new_globals_start);
// The address of the backing buffer for the golbals is in native memory
// and, thus, not moving. We need it saved for
// serialization/deserialization purposes - so that the other end
// understands how to relocate the references. We still need to save the
// JSArrayBuffer on the instance, to keep it all alive.
WasmCompiledModule::SetGlobalsStartAddressFrom(factory, compiled_module_,
global_buffer);
instance->set_globals_buffer(*global_buffer);
}
//--------------------------------------------------------------------------
// Prepare for initialization of function tables.
//--------------------------------------------------------------------------
int function_table_count = static_cast<int>(module_->function_tables.size());
table_instances_.reserve(module_->function_tables.size());
for (int index = 0; index < function_table_count; ++index) {
table_instances_.push_back(
{Handle<WasmTableObject>::null(), Handle<FixedArray>::null(),
Handle<FixedArray>::null(), Handle<FixedArray>::null()});
}
//--------------------------------------------------------------------------
// Process the imports for the module.
//--------------------------------------------------------------------------
int num_imported_functions = ProcessImports(code_table, instance);
if (num_imported_functions < 0) return {};
//--------------------------------------------------------------------------
// Process the initialization for the module's globals.
//--------------------------------------------------------------------------
InitGlobals();
//--------------------------------------------------------------------------
// Set up the indirect function tables for the new instance.
//--------------------------------------------------------------------------
if (function_table_count > 0)
InitializeTables(instance, &code_specialization);
//--------------------------------------------------------------------------
// Set up the memory for the new instance.
//--------------------------------------------------------------------------
uint32_t initial_pages = module_->initial_pages;
(module_->is_wasm() ? counters()->wasm_wasm_min_mem_pages_count()
: counters()->wasm_asm_min_mem_pages_count())
->AddSample(initial_pages);
if (!memory_.is_null()) {
Handle<JSArrayBuffer> memory = memory_.ToHandleChecked();
// Set externally passed ArrayBuffer non neuterable.
memory->set_is_neuterable(false);
memory->set_is_wasm_buffer(true);
DCHECK_IMPLIES(EnableGuardRegions(),
module_->is_asm_js() || memory->has_guard_region());
} else if (initial_pages > 0) {
memory_ = AllocateMemory(initial_pages);
if (memory_.is_null()) return {}; // failed to allocate memory
}
//--------------------------------------------------------------------------
// Check that indirect function table segments are within bounds.
//--------------------------------------------------------------------------
for (WasmTableInit& table_init : module_->table_inits) {
DCHECK(table_init.table_index < table_instances_.size());
uint32_t base = EvalUint32InitExpr(table_init.offset);
uint32_t table_size =
table_instances_[table_init.table_index].function_table->length();
if (!in_bounds(base, static_cast<uint32_t>(table_init.entries.size()),
table_size)) {
thrower_->LinkError("table initializer is out of bounds");
return {};
}
}
//--------------------------------------------------------------------------
// Check that memory segments are within bounds.
//--------------------------------------------------------------------------
for (WasmDataSegment& seg : module_->data_segments) {
uint32_t base = EvalUint32InitExpr(seg.dest_addr);
uint32_t mem_size = 0;
if (!memory_.is_null()) {
CHECK(memory_.ToHandleChecked()->byte_length()->ToUint32(&mem_size));
}
if (!in_bounds(base, seg.source.length(), mem_size)) {
thrower_->LinkError("data segment is out of bounds");
return {};
}
}
//--------------------------------------------------------------------------
// Initialize memory.
//--------------------------------------------------------------------------
if (!memory_.is_null()) {
Handle<JSArrayBuffer> memory = memory_.ToHandleChecked();
Address mem_start = static_cast<Address>(memory->backing_store());
uint32_t mem_size;
CHECK(memory->byte_length()->ToUint32(&mem_size));
LoadDataSegments(mem_start, mem_size);
uint32_t old_mem_size = compiled_module_->GetEmbeddedMemSizeOrZero();
Address old_mem_start = compiled_module_->GetEmbeddedMemStartOrNull();
// We might get instantiated again with the same memory. No patching
// needed in this case.
if (old_mem_start != mem_start || old_mem_size != mem_size) {
code_specialization.RelocateMemoryReferences(old_mem_start, old_mem_size,
mem_start, mem_size);
}
// Just like with globals, we need to keep both the JSArrayBuffer
// and save the start pointer.
instance->set_memory_buffer(*memory);
WasmCompiledModule::SetSpecializationMemInfoFrom(factory, compiled_module_,
memory);
}
//--------------------------------------------------------------------------
// Set up the runtime support for the new instance.
//--------------------------------------------------------------------------
Handle<WeakCell> weak_link = factory->NewWeakCell(instance);
for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs,
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) {
Handle<FixedArray> deopt_data = factory->NewFixedArray(2, TENURED);
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());
int deopt_len = code->deoptimization_data()->length();
if (deopt_len == 0) continue;
DCHECK_LE(2, deopt_len);
DCHECK_EQ(i, Smi::ToInt(code->deoptimization_data()->get(1)));
code->deoptimization_data()->set(0, *weak_link);
// Entries [2, deopt_len) encode information about table exports of this
// function. This is rebuilt in {LoadTableSegments}, so reset it here.
for (int i = 2; i < deopt_len; ++i) {
code->deoptimization_data()->set_undefined(isolate_, i);
}
}
//--------------------------------------------------------------------------
// Set up the exports object for the new instance.
//--------------------------------------------------------------------------
ProcessExports(instance, compiled_module_);
if (thrower_->error()) return {};
//--------------------------------------------------------------------------
// Add instance to Memory object
//--------------------------------------------------------------------------
if (instance->has_memory_object()) {
Handle<WasmMemoryObject> memory(instance->memory_object(), isolate_);
WasmMemoryObject::AddInstance(isolate_, memory, instance);
}
//--------------------------------------------------------------------------
// Initialize the indirect function tables.
//--------------------------------------------------------------------------
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);
FlushICache(isolate_, code_table);
//--------------------------------------------------------------------------
// Unpack and notify signal handler of protected instructions.
//--------------------------------------------------------------------------
if (trap_handler::UseTrapHandler()) {
UnpackAndRegisterProtectedInstructions(isolate_, code_table);
}
//--------------------------------------------------------------------------
// Set up and link the new instance.
//--------------------------------------------------------------------------
{
Handle<Object> global_handle =
isolate_->global_handles()->Create(*instance);
Handle<WeakCell> link_to_clone = factory->NewWeakCell(compiled_module_);
Handle<WeakCell> link_to_owning_instance = factory->NewWeakCell(instance);
MaybeHandle<WeakCell> link_to_original;
MaybeHandle<WasmCompiledModule> original;
if (!owner.is_null()) {
// prepare the data needed for publishing in a chain, but don't link
// just yet, because
// we want all the publishing to happen free from GC interruptions, and
// so we do it in
// one GC-free scope afterwards.
original = handle(owner.ToHandleChecked()->compiled_module());
link_to_original = factory->NewWeakCell(original.ToHandleChecked());
}
// Publish the new instance to the instances chain.
{
DisallowHeapAllocation no_gc;
if (!link_to_original.is_null()) {
compiled_module_->set_weak_next_instance(
link_to_original.ToHandleChecked());
original.ToHandleChecked()->set_weak_prev_instance(link_to_clone);
compiled_module_->set_weak_wasm_module(
original.ToHandleChecked()->weak_wasm_module());
}
module_object_->set_compiled_module(*compiled_module_);
compiled_module_->set_weak_owning_instance(link_to_owning_instance);
GlobalHandles::MakeWeak(
global_handle.location(), global_handle.location(),
instance_finalizer_callback_, v8::WeakCallbackType::kFinalizer);
}
}
//--------------------------------------------------------------------------
// Debugging support.
//--------------------------------------------------------------------------
// Set all breakpoints that were set on the shared module.
WasmSharedModuleData::SetBreakpointsOnNewInstance(compiled_module_->shared(),
instance);
if (FLAG_wasm_interpret_all && module_->is_wasm()) {
Handle<WasmDebugInfo> debug_info =
WasmInstanceObject::GetOrCreateDebugInfo(instance);
std::vector<int> func_indexes;
for (int func_index = num_imported_functions,
num_wasm_functions = static_cast<int>(module_->functions.size());
func_index < num_wasm_functions; ++func_index) {
func_indexes.push_back(func_index);
}
WasmDebugInfo::RedirectToInterpreter(
debug_info, Vector<int>(func_indexes.data(),
static_cast<int>(func_indexes.size())));
}
//--------------------------------------------------------------------------
// Run the start function if one was specified.
//--------------------------------------------------------------------------
if (module_->start_function_index >= 0) {
HandleScope scope(isolate_);
int start_index = module_->start_function_index;
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(
isolate_, module_, startup_code, start_index);
Handle<WasmExportedFunction> startup_fct = WasmExportedFunction::New(
isolate_, instance, MaybeHandle<String>(), start_index,
static_cast<int>(sig->parameter_count()), wrapper_code);
RecordStats(*startup_code, counters());
// Call the JS function.
Handle<Object> undefined = factory->undefined_value();
{
// We're OK with JS execution here. The instance is fully setup.
AllowJavascriptExecution allow_js(isolate_);
MaybeHandle<Object> retval =
Execution::Call(isolate_, startup_fct, undefined, 0, nullptr);
if (retval.is_null()) {
DCHECK(isolate_->has_pending_exception());
// It's unfortunate that the new instance is already linked in the
// chain. However, we need to set up everything before executing the
// startup unction, such that stack trace information can be generated
// correctly already in the start function.
return {};
}
}
}
DCHECK(!isolate_->has_pending_exception());
TRACE("Finishing instance %d\n", compiled_module_->instance_id());
TRACE_CHAIN(module_object_->compiled_module());
return instance;
}
// Look up an import value in the {ffi_} object.
MaybeHandle<Object> InstanceBuilder::LookupImport(uint32_t index,
Handle<String> module_name,
Handle<String> import_name) {
// We pre-validated in the js-api layer that the ffi object is present, and
// a JSObject, if the module has imports.
DCHECK(!ffi_.is_null());
// Look up the module first.
MaybeHandle<Object> result =
Object::GetPropertyOrElement(ffi_.ToHandleChecked(), module_name);
if (result.is_null()) {
return ReportTypeError("module not found", index, module_name);
}
Handle<Object> module = result.ToHandleChecked();
// Look up the value in the module.
if (!module->IsJSReceiver()) {
return ReportTypeError("module is not an object or function", index,
module_name);
}
result = Object::GetPropertyOrElement(module, import_name);
if (result.is_null()) {
ReportLinkError("import not found", index, module_name, import_name);
return MaybeHandle<JSFunction>();
}
return result;
}
// Look up an import value in the {ffi_} object specifically for linking an
// asm.js module. This only performs non-observable lookups, which allows
// falling back to JavaScript proper (and hence re-executing all lookups) if
// module instantiation fails.
MaybeHandle<Object> InstanceBuilder::LookupImportAsm(
uint32_t index, Handle<String> import_name) {
// Check that a foreign function interface object was provided.
if (ffi_.is_null()) {
return ReportLinkError("missing imports object", index, import_name);
}
// Perform lookup of the given {import_name} without causing any observable
// side-effect. We only accept accesses that resolve to data properties,
// which is indicated by the asm.js spec in section 7 ("Linking") as well.
Handle<Object> result;
LookupIterator it = LookupIterator::PropertyOrElement(
isolate_, ffi_.ToHandleChecked(), import_name);
switch (it.state()) {
case LookupIterator::ACCESS_CHECK:
case LookupIterator::INTEGER_INDEXED_EXOTIC:
case LookupIterator::INTERCEPTOR:
case LookupIterator::JSPROXY:
case LookupIterator::ACCESSOR:
case LookupIterator::TRANSITION:
return ReportLinkError("not a data property", index, import_name);
case LookupIterator::NOT_FOUND:
// Accepting missing properties as undefined does not cause any
// observable difference from JavaScript semantics, we are lenient.
result = isolate_->factory()->undefined_value();
break;
case LookupIterator::DATA:
result = it.GetDataValue();
break;
}
return result;
}
uint32_t InstanceBuilder::EvalUint32InitExpr(const WasmInitExpr& expr) {
switch (expr.kind) {
case WasmInitExpr::kI32Const:
return expr.val.i32_const;
case WasmInitExpr::kGlobalIndex: {
uint32_t offset = module_->globals[expr.val.global_index].offset;
return *reinterpret_cast<uint32_t*>(raw_buffer_ptr(globals_, offset));
}
default:
UNREACHABLE();
}
}
// Load data segments into the memory.
void InstanceBuilder::LoadDataSegments(Address mem_addr, size_t mem_size) {
Handle<SeqOneByteString> module_bytes(compiled_module_->module_bytes(),
isolate_);
for (const WasmDataSegment& segment : module_->data_segments) {
uint32_t source_size = segment.source.length();
// Segments of size == 0 are just nops.
if (source_size == 0) continue;
uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr);
DCHECK(
in_bounds(dest_offset, source_size, static_cast<uint32_t>(mem_size)));
byte* dest = mem_addr + dest_offset;
const byte* src = reinterpret_cast<const byte*>(
module_bytes->GetCharsAddress() + segment.source.offset());
memcpy(dest, src, source_size);
}
}
void InstanceBuilder::WriteGlobalValue(WasmGlobal& global,
Handle<Object> value) {
double num = value->Number();
TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num,
WasmOpcodes::TypeName(global.type));
switch (global.type) {
case kWasmI32:
*GetRawGlobalPtr<int32_t>(global) = static_cast<int32_t>(num);
break;
case kWasmI64:
// TODO(titzer): initialization of imported i64 globals.
UNREACHABLE();
break;
case kWasmF32:
*GetRawGlobalPtr<float>(global) = static_cast<float>(num);
break;
case kWasmF64:
*GetRawGlobalPtr<double>(global) = static_cast<double>(num);
break;
default:
UNREACHABLE();
}
}
void InstanceBuilder::SanitizeImports() {
Handle<SeqOneByteString> module_bytes(
module_object_->compiled_module()->module_bytes());
for (size_t index = 0; index < module_->import_table.size(); ++index) {
WasmImport& import = module_->import_table[index];
Handle<String> module_name;
MaybeHandle<String> maybe_module_name =
WasmCompiledModule::ExtractUtf8StringFromModuleBytes(
isolate_, module_bytes, import.module_name);
if (!maybe_module_name.ToHandle(&module_name)) {
thrower_->LinkError("Could not resolve module name for import %zu",
index);
return;
}
Handle<String> import_name;
MaybeHandle<String> maybe_import_name =
WasmCompiledModule::ExtractUtf8StringFromModuleBytes(
isolate_, module_bytes, import.field_name);
if (!maybe_import_name.ToHandle(&import_name)) {
thrower_->LinkError("Could not resolve import name for import %zu",
index);
return;
}
int int_index = static_cast<int>(index);
MaybeHandle<Object> result =
module_->is_asm_js()
? LookupImportAsm(int_index, import_name)
: LookupImport(int_index, module_name, import_name);
if (thrower_->error()) {
thrower_->LinkError("Could not find value for import %zu", index);
return;
}
Handle<Object> value = result.ToHandleChecked();
sanitized_imports_.push_back({module_name, import_name, value});
}
}
// Process the imports, including functions, tables, globals, and memory, in
// order, loading them from the {ffi_} object. Returns the number of imported
// functions.
int InstanceBuilder::ProcessImports(Handle<FixedArray> code_table,
Handle<WasmInstanceObject> instance) {
int num_imported_functions = 0;
int num_imported_tables = 0;
Handle<FixedArray> func_table = isolate_->factory()->NewFixedArray(
static_cast<int>(module_->import_table.size()), TENURED);
Handle<FixedArray> js_imports_table =
isolate_->global_handles()->Create(*func_table);
GlobalHandles::MakeWeak(
reinterpret_cast<Object**>(js_imports_table.location()),
js_imports_table.location(), &FunctionTableFinalizer,
v8::WeakCallbackType::kFinalizer);
instance->set_js_imports_table(*func_table);
WasmInstanceMap imported_wasm_instances(isolate_->heap());
DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size());
for (int index = 0; index < static_cast<int>(module_->import_table.size());
++index) {
WasmImport& import = module_->import_table[index];
Handle<String> module_name = sanitized_imports_[index].module_name;
Handle<String> import_name = sanitized_imports_[index].import_name;
Handle<Object> value = sanitized_imports_[index].value;
switch (import.kind) {
case kExternalFunction: {
// Function imports must be callable.
if (!value->IsCallable()) {
ReportLinkError("function import requires a callable", index,
module_name, import_name);
return -1;
}
Handle<Code> import_code = UnwrapExportOrCompileImportWrapper(
isolate_, index, module_->functions[import.index].sig,
Handle<JSReceiver>::cast(value), module_name, import_name,
module_->origin(), &imported_wasm_instances, js_imports_table);
if (import_code.is_null()) {
ReportLinkError("imported function does not match the expected type",
index, module_name, import_name);
return -1;
}
code_table->set(num_imported_functions, *import_code);
RecordStats(*import_code, counters());
num_imported_functions++;
break;
}
case kExternalTable: {
if (!value->IsWasmTableObject()) {
ReportLinkError("table import requires a WebAssembly.Table", index,
module_name, import_name);
return -1;
}
WasmIndirectFunctionTable& table =
module_->function_tables[num_imported_tables];
TableInstance& table_instance = table_instances_[num_imported_tables];
table_instance.table_object = Handle<WasmTableObject>::cast(value);
table_instance.js_wrappers = Handle<FixedArray>(
table_instance.table_object->functions(), isolate_);
int imported_cur_size = table_instance.js_wrappers->length();
if (imported_cur_size < static_cast<int>(table.initial_size)) {
thrower_->LinkError(
"table import %d is smaller than initial %d, got %u", index,
table.initial_size, imported_cur_size);
return -1;
}
if (table.has_maximum_size) {
int64_t imported_maximum_size =
table_instance.table_object->maximum_length()->Number();
if (imported_maximum_size < 0) {
thrower_->LinkError(
"table import %d has no maximum length, expected %d", index,
table.maximum_size);
return -1;
}
if (imported_maximum_size > table.maximum_size) {
thrower_->LinkError(
" table import %d has a larger maximum size %" PRIx64
" than the module's declared maximum %u",
index, imported_maximum_size, table.maximum_size);
return -1;
}
}
// Allocate a new dispatch table and signature table.
int table_size = imported_cur_size;
table_instance.function_table =
isolate_->factory()->NewFixedArray(table_size);
table_instance.signature_table =
isolate_->factory()->NewFixedArray(table_size);
for (int i = 0; i < table_size; ++i) {
table_instance.signature_table->set(i,
Smi::FromInt(kInvalidSigIndex));
}
// Initialize the dispatch table with the (foreign) JS functions
// that are already in the table.
for (int i = 0; i < table_size; ++i) {
Handle<Object> val(table_instance.js_wrappers->get(i), isolate_);
if (!val->IsJSFunction()) continue;
WasmFunction* function = GetWasmFunctionForExport(isolate_, val);
if (function == nullptr) {
thrower_->LinkError("table import %d[%d] is not a wasm function",
index, i);
return -1;
}
int sig_index = table.map.FindOrInsert(function->sig);
table_instance.signature_table->set(i, Smi::FromInt(sig_index));
table_instance.function_table->set(
i, *UnwrapExportWrapper(Handle<JSFunction>::cast(val)));
}
num_imported_tables++;
break;
}
case kExternalMemory: {
// Validation should have failed if more than one memory object was
// provided.
DCHECK(!instance->has_memory_object());
if (!value->IsWasmMemoryObject()) {
ReportLinkError("memory import must be a WebAssembly.Memory object",
index, module_name, import_name);
return -1;
}
auto memory = Handle<WasmMemoryObject>::cast(value);
instance->set_memory_object(*memory);
Handle<JSArrayBuffer> buffer(memory->array_buffer(), isolate_);
memory_ = buffer;
uint32_t imported_cur_pages = static_cast<uint32_t>(
buffer->byte_length()->Number() / WasmModule::kPageSize);
if (imported_cur_pages < module_->initial_pages) {
thrower_->LinkError(
"memory import %d is smaller than initial %u, got %u", index,
module_->initial_pages, imported_cur_pages);
}
int32_t imported_maximum_pages = memory->maximum_pages();
if (module_->has_maximum_pages) {
if (imported_maximum_pages < 0) {
thrower_->LinkError(
"memory import %d has no maximum limit, expected at most %u",
index, imported_maximum_pages);
return -1;
}
if (static_cast<uint32_t>(imported_maximum_pages) >
module_->maximum_pages) {
thrower_->LinkError(
"memory import %d has a larger maximum size %u than the "
"module's declared maximum %u",
index, imported_maximum_pages, module_->maximum_pages);
return -1;
}
}
if (module_->has_shared_memory != buffer->is_shared()) {
thrower_->LinkError(
"mismatch in shared state of memory, declared = %d, imported = "
"%d",
module_->has_shared_memory, buffer->is_shared());
return -1;
}
break;
}
case kExternalGlobal: {
// Global imports are converted to numbers and written into the
// {globals_} array buffer.
if (module_->globals[import.index].type == kWasmI64) {
ReportLinkError("global import cannot have type i64", index,
module_name, import_name);
return -1;
}
if (module_->is_asm_js()) {
// Accepting {JSFunction} on top of just primitive values here is a
// workaround to support legacy asm.js code with broken binding. Note
// that using {NaN} (or Smi::kZero) here is what using the observable
// conversion via {ToPrimitive} would produce as well.
// TODO(mstarzinger): Still observable if Function.prototype.valueOf
// or friends are patched, we might need to check for that as well.
if (value->IsJSFunction()) value = isolate_->factory()->nan_value();
if (value->IsPrimitive() && !value->IsSymbol()) {
if (module_->globals[import.index].type == kWasmI32) {
value = Object::ToInt32(isolate_, value).ToHandleChecked();
} else {
value = Object::ToNumber(value).ToHandleChecked();
}
}
}
if (!value->IsNumber()) {
ReportLinkError("global import must be a number", index, module_name,
import_name);
return -1;
}
WriteGlobalValue(module_->globals[import.index], value);
break;
}
default:
UNREACHABLE();
break;
}
}
if (!imported_wasm_instances.empty()) {
WasmInstanceMap::IteratableScope iteratable_scope(&imported_wasm_instances);
Handle<FixedArray> instances_array = isolate_->factory()->NewFixedArray(
imported_wasm_instances.size(), TENURED);
instance->set_directly_called_instances(*instances_array);
int index = 0;
for (auto it = iteratable_scope.begin(), end = iteratable_scope.end();
it != end; ++it, ++index) {
instances_array->set(index, ***it);
}
}
return num_imported_functions;
}
template <typename T>
T* InstanceBuilder::GetRawGlobalPtr(WasmGlobal& global) {
return reinterpret_cast<T*>(raw_buffer_ptr(globals_, global.offset));
}
// Process initialization of globals.
void InstanceBuilder::InitGlobals() {
for (auto global : module_->globals) {
switch (global.init.kind) {
case WasmInitExpr::kI32Const:
*GetRawGlobalPtr<int32_t>(global) = global.init.val.i32_const;
break;
case WasmInitExpr::kI64Const:
*GetRawGlobalPtr<int64_t>(global) = global.init.val.i64_const;
break;
case WasmInitExpr::kF32Const:
*GetRawGlobalPtr<float>(global) = global.init.val.f32_const;
break;
case WasmInitExpr::kF64Const:
*GetRawGlobalPtr<double>(global) = global.init.val.f64_const;
break;
case WasmInitExpr::kGlobalIndex: {
// Initialize with another global.
uint32_t new_offset = global.offset;
uint32_t old_offset =
module_->globals[global.init.val.global_index].offset;
TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset);
size_t size = (global.type == kWasmI64 || global.type == kWasmF64)
? sizeof(double)
: sizeof(int32_t);
memcpy(raw_buffer_ptr(globals_, new_offset),
raw_buffer_ptr(globals_, old_offset), size);
break;
}
case WasmInitExpr::kNone:
// Happens with imported globals.
break;
default:
UNREACHABLE();
break;
}
}
}
// Allocate memory for a module instance as a new JSArrayBuffer.
Handle<JSArrayBuffer> InstanceBuilder::AllocateMemory(uint32_t num_pages) {
if (num_pages > FLAG_wasm_max_mem_pages) {
thrower_->RangeError("Out of memory: wasm memory too large");
return Handle<JSArrayBuffer>::null();
}
const bool enable_guard_regions = EnableGuardRegions();
Handle<JSArrayBuffer> mem_buffer = NewArrayBuffer(
isolate_, num_pages * WasmModule::kPageSize, enable_guard_regions);
if (mem_buffer.is_null()) {
thrower_->RangeError("Out of memory: wasm memory");
}
return mem_buffer;
}
bool InstanceBuilder::NeedsWrappers() const {
if (module_->num_exported_functions > 0) return true;
for (auto& table_instance : table_instances_) {
if (!table_instance.js_wrappers.is_null()) return true;
}
for (auto& table : module_->function_tables) {
if (table.exported) return true;
}
return false;
}
// Process the exports, creating wrappers for functions, tables, memories,
// and globals.
void InstanceBuilder::ProcessExports(
Handle<WasmInstanceObject> instance,
Handle<WasmCompiledModule> compiled_module) {
Handle<FixedArray> wrapper_table = compiled_module->export_wrappers();
if (NeedsWrappers()) {
// Fill the table to cache the exported JSFunction wrappers.
js_wrappers_.insert(js_wrappers_.begin(), module_->functions.size(),
Handle<JSFunction>::null());
}
Handle<JSObject> exports_object;
if (module_->is_wasm()) {
// Create the "exports" object.
exports_object = isolate_->factory()->NewJSObjectWithNullProto();
} else if (module_->is_asm_js()) {
Handle<JSFunction> object_function = Handle<JSFunction>(
isolate_->native_context()->object_function(), isolate_);
exports_object = isolate_->factory()->NewJSObject(object_function);
} else {
UNREACHABLE();
}
instance->set_exports_object(*exports_object);
Handle<String> single_function_name =
isolate_->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName);
PropertyDescriptor desc;
desc.set_writable(module_->is_asm_js());
desc.set_enumerable(true);
desc.set_configurable(module_->is_asm_js());
// Store weak references to all exported functions.
Handle<FixedArray> weak_exported_functions;
if (compiled_module->has_weak_exported_functions()) {
weak_exported_functions = compiled_module->weak_exported_functions();
} else {
int export_count = 0;
for (WasmExport& exp : module_->export_table) {
if (exp.kind == kExternalFunction) ++export_count;
}
weak_exported_functions = isolate_->factory()->NewFixedArray(export_count);
compiled_module->set_weak_exported_functions(weak_exported_functions);
}
// Process each export in the export table.
int export_index = 0; // Index into {weak_exported_functions}.
for (WasmExport& exp : module_->export_table) {
Handle<String> name = WasmCompiledModule::ExtractUtf8StringFromModuleBytes(
isolate_, compiled_module_, exp.name)
.ToHandleChecked();
Handle<JSObject> export_to;
if (module_->is_asm_js() && exp.kind == kExternalFunction &&
String::Equals(name, single_function_name)) {
export_to = instance;
} else {
export_to = exports_object;
}
switch (exp.kind) {
case kExternalFunction: {
// Wrap and export the code as a JSFunction.
WasmFunction& function = module_->functions[exp.index];
Handle<JSFunction> js_function = js_wrappers_[exp.index];
if (js_function.is_null()) {
// Wrap the exported code as a JSFunction.
Handle<Code> export_code =
wrapper_table->GetValueChecked<Code>(isolate_, export_index);
MaybeHandle<String> func_name;
if (module_->is_asm_js()) {
// For modules arising from asm.js, honor the names section.
func_name = WasmCompiledModule::ExtractUtf8StringFromModuleBytes(
isolate_, compiled_module_, function.name)
.ToHandleChecked();
}
js_function = WasmExportedFunction::New(
isolate_, instance, func_name, function.func_index,
static_cast<int>(function.sig->parameter_count()), export_code);
js_wrappers_[exp.index] = js_function;
}
desc.set_value(js_function);
Handle<WeakCell> weak_export =
isolate_->factory()->NewWeakCell(js_function);
DCHECK_GT(weak_exported_functions->length(), export_index);
weak_exported_functions->set(export_index, *weak_export);
export_index++;
break;
}
case kExternalTable: {
// Export a table as a WebAssembly.Table object.
TableInstance& table_instance = table_instances_[exp.index];
WasmIndirectFunctionTable& table = module_->function_tables[exp.index];
if (table_instance.table_object.is_null()) {
uint32_t maximum = table.has_maximum_size ? table.maximum_size
: FLAG_wasm_max_table_size;
table_instance.table_object =
WasmTableObject::New(isolate_, table.initial_size, maximum,
&table_instance.js_wrappers);
}
desc.set_value(table_instance.table_object);
break;
}
case kExternalMemory: {
// Export the memory as a WebAssembly.Memory object.
Handle<WasmMemoryObject> memory_object;
if (!instance->has_memory_object()) {
// If there was no imported WebAssembly.Memory object, create one.
memory_object = WasmMemoryObject::New(
isolate_,
(instance->has_memory_buffer())
? handle(instance->memory_buffer())
: Handle<JSArrayBuffer>::null(),
(module_->maximum_pages != 0) ? module_->maximum_pages : -1);
instance->set_memory_object(*memory_object);
} else {
memory_object =
Handle<WasmMemoryObject>(instance->memory_object(), isolate_);
}
desc.set_value(memory_object);
break;
}
case kExternalGlobal: {
// Export the value of the global variable as a number.
WasmGlobal& global = module_->globals[exp.index];
double num = 0;
switch (global.type) {
case kWasmI32:
num = *GetRawGlobalPtr<int32_t>(global);
break;
case kWasmF32:
num = *GetRawGlobalPtr<float>(global);
break;
case kWasmF64:
num = *GetRawGlobalPtr<double>(global);
break;
case kWasmI64:
thrower_->LinkError(
"export of globals of type I64 is not allowed.");
return;
default:
UNREACHABLE();
}
desc.set_value(isolate_->factory()->NewNumber(num));
break;
}
default:
UNREACHABLE();
break;
}
v8::Maybe<bool> status = JSReceiver::DefineOwnProperty(
isolate_, export_to, name, &desc, Object::THROW_ON_ERROR);
if (!status.IsJust()) {
TruncatedUserString<> trunc_name(name->GetCharVector<uint8_t>());
thrower_->LinkError("export of %.*s failed.", trunc_name.length(),
trunc_name.start());
return;
}
}
DCHECK_EQ(export_index, weak_exported_functions->length());
if (module_->is_wasm()) {
v8::Maybe<bool> success = JSReceiver::SetIntegrityLevel(
exports_object, FROZEN, Object::DONT_THROW);
DCHECK(success.FromMaybe(false));
USE(success);
}
}
void InstanceBuilder::InitializeTables(
Handle<WasmInstanceObject> instance,
CodeSpecialization* code_specialization) {
int function_table_count = static_cast<int>(module_->function_tables.size());
Handle<FixedArray> new_function_tables =
isolate_->factory()->NewFixedArray(function_table_count, TENURED);
Handle<FixedArray> new_signature_tables =
isolate_->factory()->NewFixedArray(function_table_count, TENURED);
Handle<FixedArray> old_function_tables = compiled_module_->function_tables();
Handle<FixedArray> old_signature_tables =
compiled_module_->signature_tables();
// These go on the instance.
Handle<FixedArray> rooted_function_tables =
isolate_->factory()->NewFixedArray(function_table_count, TENURED);
Handle<FixedArray> rooted_signature_tables =
isolate_->factory()->NewFixedArray(function_table_count, TENURED);
instance->set_function_tables(*rooted_function_tables);
instance->set_signature_tables(*rooted_signature_tables);
DCHECK_EQ(old_function_tables->length(), new_function_tables->length());
DCHECK_EQ(old_signature_tables->length(), new_signature_tables->length());
for (int index = 0; index < function_table_count; ++index) {
WasmIndirectFunctionTable& table = module_->function_tables[index];
TableInstance& table_instance = table_instances_[index];
int table_size = static_cast<int>(table.initial_size);
if (table_instance.function_table.is_null()) {
// Create a new dispatch table if necessary.
table_instance.function_table =
isolate_->factory()->NewFixedArray(table_size);
table_instance.signature_table =
isolate_->factory()->NewFixedArray(table_size);
for (int i = 0; i < table_size; ++i) {
// Fill the table with invalid signature indexes so that
// uninitialized entries will always fail the signature check.
table_instance.signature_table->set(i, Smi::FromInt(kInvalidSigIndex));
}
} else {
// Table is imported, patch table bounds check
DCHECK_LE(table_size, table_instance.function_table->length());
code_specialization->PatchTableSize(
table_size, table_instance.function_table->length());
}
int int_index = static_cast<int>(index);
Handle<FixedArray> global_func_table =
isolate_->global_handles()->Create(*table_instance.function_table);
Handle<FixedArray> global_sig_table =
isolate_->global_handles()->Create(*table_instance.signature_table);
// Make the handles weak. The table objects are rooted on the instance, as
// they belong to it. We need the global handles in order to have stable
// pointers to embed in the instance's specialization (wasm compiled code).
// The order of finalization doesn't matter, in that the instance finalizer
// may be called before each table's finalizer, or vice-versa.
// This is because values used for embedding are only interesting should we
// {Reset} a specialization, in which case they are interesting as values,
// they are not dereferenced.
GlobalHandles::MakeWeak(
reinterpret_cast<Object**>(global_func_table.location()),
global_func_table.location(), &FunctionTableFinalizer,
v8::WeakCallbackType::kFinalizer);
GlobalHandles::MakeWeak(
reinterpret_cast<Object**>(global_sig_table.location()),
global_sig_table.location(), &FunctionTableFinalizer,
v8::WeakCallbackType::kFinalizer);
rooted_function_tables->set(int_index, *global_func_table);
rooted_signature_tables->set(int_index, *global_sig_table);
GlobalHandleAddress new_func_table_addr = global_func_table.address();
GlobalHandleAddress new_sig_table_addr = global_sig_table.address();
WasmCompiledModule::SetTableValue(isolate_, new_function_tables, int_index,
new_func_table_addr);
WasmCompiledModule::SetTableValue(isolate_, new_signature_tables, int_index,
new_sig_table_addr);
GlobalHandleAddress old_func_table_addr =
WasmCompiledModule::GetTableValue(*old_function_tables, int_index);
GlobalHandleAddress old_sig_table_addr =
WasmCompiledModule::GetTableValue(*old_signature_tables, int_index);
code_specialization->RelocatePointer(old_func_table_addr,
new_func_table_addr);
code_specialization->RelocatePointer(old_sig_table_addr,
new_sig_table_addr);
}
compiled_module_->set_function_tables(new_function_tables);
compiled_module_->set_signature_tables(new_signature_tables);
}
void InstanceBuilder::LoadTableSegments(Handle<FixedArray> code_table,
Handle<WasmInstanceObject> instance) {
int function_table_count = static_cast<int>(module_->function_tables.size());
for (int index = 0; index < function_table_count; ++index) {
WasmIndirectFunctionTable& table = module_->function_tables[index];
TableInstance& table_instance = table_instances_[index];
Handle<FixedArray> all_dispatch_tables;
if (!table_instance.table_object.is_null()) {
// Get the existing dispatch table(s) with the WebAssembly.Table object.
all_dispatch_tables =
handle(table_instance.table_object->dispatch_tables());
}
// Count the number of table exports for each function (needed for lazy
// compilation).
std::unordered_map<uint32_t, uint32_t> num_table_exports;
if (compile_lazy(module_)) {
for (auto& table_init : module_->table_inits) {
for (uint32_t func_index : table_init.entries) {
Code* code =
Code::cast(code_table->get(static_cast<int>(func_index)));
// Only increase the counter for lazy compile builtins (it's not
// needed otherwise).
if (code->is_wasm_code()) continue;
DCHECK_EQ(Builtins::kWasmCompileLazy, code->builtin_index());
++num_table_exports[func_index];
}
}
}
// TODO(titzer): this does redundant work if there are multiple tables,
// since initializations are not sorted by table index.
for (auto& table_init : module_->table_inits) {
uint32_t base = EvalUint32InitExpr(table_init.offset);
uint32_t num_entries = static_cast<uint32_t>(table_init.entries.size());
DCHECK(in_bounds(base, num_entries,
table_instance.function_table->length()));
for (uint32_t i = 0; i < num_entries; ++i) {
uint32_t func_index = table_init.entries[i];
WasmFunction* function = &module_->functions[func_index];
int table_index = static_cast<int>(i + base);
int32_t sig_index = table.map.Find(function->sig);
DCHECK_GE(sig_index, 0);
table_instance.signature_table->set(table_index,
Smi::FromInt(sig_index));
Handle<Code> wasm_code = EnsureTableExportLazyDeoptData(
isolate_, instance, code_table, func_index,
table_instance.function_table, table_index, num_table_exports);
table_instance.function_table->set(table_index, *wasm_code);
if (!all_dispatch_tables.is_null()) {
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
// not exported but are in an exported table. This should be done
// at module compile time and cached instead.
Handle<Code> wrapper_code =
js_to_wasm_cache_.CloneOrCompileJSToWasmWrapper(
isolate_, module_, wasm_code, func_index);
MaybeHandle<String> func_name;
if (module_->is_asm_js()) {
// For modules arising from asm.js, honor the names section.
func_name = WasmCompiledModule::ExtractUtf8StringFromModuleBytes(
isolate_, compiled_module_, function->name)
.ToHandleChecked();
}
Handle<WasmExportedFunction> js_function =
WasmExportedFunction::New(
isolate_, instance, func_name, func_index,
static_cast<int>(function->sig->parameter_count()),
wrapper_code);
js_wrappers_[func_index] = js_function;
}
table_instance.js_wrappers->set(table_index,
*js_wrappers_[func_index]);
UpdateDispatchTables(isolate_, all_dispatch_tables, table_index,
function, wasm_code);
}
}
}
#ifdef DEBUG
// Check that the count of table exports was accurate. The entries are
// decremented on each export, so all should be zero now.
for (auto e : num_table_exports) {
DCHECK_EQ(0, e.second);
}
#endif
// TODO(titzer): we add the new dispatch table at the end to avoid
// redundant work and also because the new instance is not yet fully
// initialized.
if (!table_instance.table_object.is_null()) {
// Add the new dispatch table to the WebAssembly.Table object.
all_dispatch_tables = WasmTableObject::AddDispatchTable(
isolate_, table_instance.table_object, instance, index,
table_instance.function_table, table_instance.signature_table);
}
}
}
AsyncCompileJob::AsyncCompileJob(Isolate* isolate,
std::unique_ptr<byte[]> bytes_copy,
size_t length, Handle<Context> context,
Handle<JSPromise> promise)
: isolate_(isolate),
async_counters_(isolate->async_counters()),
bytes_copy_(std::move(bytes_copy)),
wire_bytes_(bytes_copy_.get(), bytes_copy_.get() + length) {
// The handles for the context and promise must be deferred.
DeferredHandleScope deferred(isolate);
context_ = Handle<Context>(*context);
module_promise_ = Handle<JSPromise>(*promise);
deferred_handles_.push_back(deferred.Detach());
}
void AsyncCompileJob::Start() {
DoAsync<DecodeModule>(); // --
}
void AsyncCompileJob::Abort() {
background_task_manager_.CancelAndWait();
if (num_pending_foreground_tasks_ == 0) {
// No task is pending, we can just remove the AsyncCompileJob.
isolate_->wasm_compilation_manager()->RemoveJob(this);
} else {
// There is still a compilation task in the task queue. We enter the
// AbortCompilation state and wait for this compilation task to abort the
// AsyncCompileJob.
NextStep<AbortCompilation>();
}
}
std::shared_ptr<StreamingDecoder> AsyncCompileJob::CreateStreamingDecoder() {
DCHECK_NULL(stream_);
stream_.reset(
new StreamingDecoder(base::make_unique<AsyncStreamingProcessor>(this)));
return stream_;
}
AsyncCompileJob::~AsyncCompileJob() {
background_task_manager_.CancelAndWait();
for (auto d : deferred_handles_) delete d;
}
void AsyncCompileJob::AsyncCompileFailed(ErrorThrower& thrower) {
if (stream_) stream_->NotifyError();
// {job} keeps the {this} pointer alive.
std::shared_ptr<AsyncCompileJob> job =
isolate_->wasm_compilation_manager()->RemoveJob(this);
RejectPromise(isolate_, context_, thrower, module_promise_);
}
void AsyncCompileJob::AsyncCompileSucceeded(Handle<Object> result) {
// {job} keeps the {this} pointer alive.
std::shared_ptr<AsyncCompileJob> job =
isolate_->wasm_compilation_manager()->RemoveJob(this);
ResolvePromise(isolate_, context_, module_promise_, result);
}
// A closure to run a compilation step (either as foreground or background
// task) and schedule the next step(s), if any.
class AsyncCompileJob::CompileStep {
public:
explicit CompileStep(size_t num_background_tasks = 0)
: num_background_tasks_(num_background_tasks) {}
virtual ~CompileStep() {}
void Run(bool on_foreground) {
if (on_foreground) {
HandleScope scope(job_->isolate_);
--job_->num_pending_foreground_tasks_;
DCHECK_EQ(0, job_->num_pending_foreground_tasks_);
SaveContext saved_context(job_->isolate_);
job_->isolate_->set_context(*job_->context_);
RunInForeground();
} else {
RunInBackground();
}
}
virtual void RunInForeground() { UNREACHABLE(); }
virtual void RunInBackground() { UNREACHABLE(); }
size_t NumberOfBackgroundTasks() { return num_background_tasks_; }
AsyncCompileJob* job_ = nullptr;
const size_t num_background_tasks_;
};
class AsyncCompileJob::CompileTask : public CancelableTask {
public:
CompileTask(AsyncCompileJob* job, bool on_foreground)
// We only manage the background tasks with the {CancelableTaskManager} of
// the {AsyncCompileJob}. Foreground tasks are managed by the system's
// {CancelableTaskManager}. Background tasks cannot spawn tasks managed by
// their own task manager.
: CancelableTask(on_foreground ? job->isolate_->cancelable_task_manager()
: &job->background_task_manager_),
job_(job),
on_foreground_(on_foreground) {}
void RunInternal() override { job_->step_->Run(on_foreground_); }
private:
AsyncCompileJob* job_;
bool on_foreground_;
};
void AsyncCompileJob::StartForegroundTask() {
++num_pending_foreground_tasks_;
DCHECK_EQ(1, num_pending_foreground_tasks_);
v8::Platform* platform = V8::GetCurrentPlatform();
// TODO(ahaas): This is a CHECK to debug issue 764313.
CHECK(platform);
platform->CallOnForegroundThread(reinterpret_cast<v8::Isolate*>(isolate_),
new CompileTask(this, true));
}
template <typename Step, typename... Args>
void AsyncCompileJob::DoSync(Args&&... args) {
NextStep<Step>(std::forward<Args>(args)...);
StartForegroundTask();
}
void AsyncCompileJob::StartBackgroundTask() {
V8::GetCurrentPlatform()->CallOnBackgroundThread(
new CompileTask(this, false), v8::Platform::kShortRunningTask);
}
void AsyncCompileJob::RestartBackgroundTasks() {
size_t num_restarts = stopped_tasks_.Value();
stopped_tasks_.Decrement(num_restarts);
for (size_t i = 0; i < num_restarts; ++i) {
StartBackgroundTask();
}
}
template <typename Step, typename... Args>
void AsyncCompileJob::DoAsync(Args&&... args) {
NextStep<Step>(std::forward<Args>(args)...);
size_t end = step_->NumberOfBackgroundTasks();
for (size_t i = 0; i < end; ++i) {
StartBackgroundTask();
}
}
template <typename Step, typename... Args>
void AsyncCompileJob::NextStep(Args&&... args) {
step_.reset(new Step(std::forward<Args>(args)...));
step_->job_ = this;
}
//==========================================================================
// Step 1: (async) Decode the module.
//==========================================================================
class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
public:
DecodeModule() : CompileStep(1) {}
void RunInBackground() override {
ModuleResult result;
{
DisallowHandleAllocation no_handle;
DisallowHeapAllocation no_allocation;
// Decode the module bytes.
TRACE_COMPILE("(1) Decoding module...\n");
result = AsyncDecodeWasmModule(job_->isolate_, job_->wire_bytes_.start(),
job_->wire_bytes_.end(), false,
kWasmOrigin, job_->async_counters());
}
if (result.failed()) {
// Decoding failure; reject the promise and clean up.
job_->DoSync<DecodeFail>(std::move(result));
} else {
// Decode passed.
job_->module_ = std::move(result.val);
job_->DoSync<PrepareAndStartCompile>(job_->module_.get(), true);
}
}
};
//==========================================================================
// Step 1b: (sync) Fail decoding the module.
//==========================================================================
class AsyncCompileJob::DecodeFail : public CompileStep {
public:
explicit DecodeFail(ModuleResult result) : result_(std::move(result)) {}
private:
ModuleResult result_;
void RunInForeground() override {
TRACE_COMPILE("(1b) Decoding failed.\n");
ErrorThrower thrower(job_->isolate_, "AsyncCompile");
thrower.CompileFailed("Wasm decoding failed", result_);
// {job_} is deleted in AsyncCompileFailed, therefore the {return}.
return job_->AsyncCompileFailed(thrower);
}
};
//==========================================================================
// Step 2 (sync): Create heap-allocated data and start compile.
//==========================================================================
class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
public:
explicit PrepareAndStartCompile(WasmModule* module, bool start_compilation)
: module_(module), start_compilation_(start_compilation) {}
private:
WasmModule* module_;
bool start_compilation_;
void RunInForeground() override {
TRACE_COMPILE("(2) Prepare and start compile...\n");
Isolate* isolate = job_->isolate_;
Factory* factory = isolate->factory();
Handle<Code> illegal_builtin = BUILTIN_CODE(isolate, Illegal);
job_->module_env_ =
CreateDefaultModuleEnv(isolate, module_, illegal_builtin);
// The {code_table} array contains import wrappers and functions (which
// are both included in {functions.size()}.
// The results of compilation will be written into it.
// Initialize {code_table_} with the illegal builtin. All call sites
// will be patched at instantiation.
int code_table_size = static_cast<int>(module_->functions.size());
int export_wrapper_size = static_cast<int>(module_->num_exported_functions);
job_->code_table_ = factory->NewFixedArray(code_table_size, TENURED);
job_->export_wrappers_ =
factory->NewFixedArray(export_wrapper_size, TENURED);
for (int i = 0, e = module_->num_imported_functions; i < e; ++i) {
job_->code_table_->set(i, *illegal_builtin);
}
// Transfer ownership of the {WasmModule} to the {ModuleCompiler}, but
// keep a pointer.
Handle<Code> centry_stub = CEntryStub(isolate, 1).GetCode();
{
// Now reopen the handles in a deferred scope in order to use
// them in the concurrent steps.
DeferredHandleScope deferred(isolate);
centry_stub = Handle<Code>(*centry_stub, isolate);
job_->code_table_ = Handle<FixedArray>(*job_->code_table_, isolate);
job_->export_wrappers_ =
Handle<FixedArray>(*job_->export_wrappers_, isolate);
compiler::ModuleEnv* env = job_->module_env_.get();
ReopenHandles(isolate, env->function_code);
Handle<Code>* mut =
const_cast<Handle<Code>*>(&env->default_function_code);
*mut = Handle<Code>(**mut, isolate);
job_->deferred_handles_.push_back(deferred.Detach());
}
job_->compiler_.reset(new ModuleCompiler(isolate, module_, centry_stub));
job_->compiler_->EnableThrottling();
DCHECK_LE(module_->num_imported_functions, module_->functions.size());
size_t num_functions =
module_->functions.size() - module_->num_imported_functions;
if (num_functions == 0) {
// Degenerate case of an empty module.
job_->DoSync<FinishCompile>();
return;
}
// Start asynchronous compilation tasks.
size_t num_background_tasks =
Max(static_cast<size_t>(1),
Min(num_functions,
Min(static_cast<size_t>(FLAG_wasm_num_compilation_tasks),
V8::GetCurrentPlatform()
->NumberOfAvailableBackgroundThreads())));
if (start_compilation_) {
// TODO(ahaas): Try to remove the {start_compilation_} check when
// streaming decoding is done in the background. If
// InitializeCompilationUnits always returns 0 for streaming compilation,
// then DoAsync would do the same as NextStep already.
job_->outstanding_units_ = job_->compiler_->InitializeCompilationUnits(
module_->functions, job_->wire_bytes_, job_->module_env_.get());
job_->DoAsync<ExecuteAndFinishCompilationUnits>(num_background_tasks);
} else {
job_->stopped_tasks_ = num_background_tasks;
job_->NextStep<ExecuteAndFinishCompilationUnits>(num_background_tasks);
}
}
};
//==========================================================================
// Step 3 (async x K tasks): Execute compilation units.
//==========================================================================
class AsyncCompileJob::ExecuteAndFinishCompilationUnits : public CompileStep {
public:
explicit ExecuteAndFinishCompilationUnits(size_t num_compile_tasks)
: CompileStep(num_compile_tasks) {}
void RunInBackground() override {
std::function<void()> StartFinishCompilationUnit = [this]() {
if (!failed_) job_->StartForegroundTask();
};
TRACE_COMPILE("(3) Compiling...\n");
while (job_->compiler_->CanAcceptWork()) {
if (failed_) break;
DisallowHandleAllocation no_handle;
DisallowHeapAllocation no_allocation;
if (!job_->compiler_->FetchAndExecuteCompilationUnit(
StartFinishCompilationUnit)) {
finished_ = true;
break;
}
}
job_->stopped_tasks_.Increment(1);
}
void RunInForeground() override {
TRACE_COMPILE("(4a) Finishing compilation units...\n");
if (failed_) {
// The job failed already, no need to do more work.
job_->compiler_->SetFinisherIsRunning(false);
return;
}
ErrorThrower thrower(job_->isolate_, "AsyncCompile");
// We execute for 1 ms and then reschedule the task, same as the GC.
double deadline = MonotonicallyIncreasingTimeInMs() + 1.0;
while (true) {
if (!finished_ && job_->compiler_->ShouldIncreaseWorkload()) {
job_->RestartBackgroundTasks();
}
int func_index = -1;
MaybeHandle<Code> result =
job_->compiler_->FinishCompilationUnit(&thrower, &func_index);
if (thrower.error()) {
// An error was detected, we stop compiling and wait for the
// background tasks to finish.
failed_ = true;
break;
} else if (result.is_null()) {
// The working queue was empty, we break the loop. If new work units
// are enqueued, the background task will start this
// FinishCompilationUnits task again.
break;
} else {
DCHECK(func_index >= 0);
job_->code_table_->set(func_index, *result.ToHandleChecked());
--job_->outstanding_units_;
}
if (deadline < MonotonicallyIncreasingTimeInMs()) {
// We reached the deadline. We reschedule this task and return
// immediately. Since we rescheduled this task already, we do not set
// the FinisherIsRunning flat to false.
job_->StartForegroundTask();
return;
}
}
// This task finishes without being rescheduled. Therefore we set the
// FinisherIsRunning flag to false.
job_->compiler_->SetFinisherIsRunning(false);
if (thrower.error()) {
// Make sure all compilation tasks stopped running.
job_->background_task_manager_.CancelAndWait();
return job_->AsyncCompileFailed(thrower);
}
if (job_->outstanding_units_ == 0) {
// Make sure all compilation tasks stopped running.
job_->background_task_manager_.CancelAndWait();
if (job_->DecrementAndCheckFinisherCount()) job_->DoSync<FinishCompile>();
}
}
private:
std::atomic<bool> failed_{false};
std::atomic<bool> finished_{false};
};
//==========================================================================
// Step 5 (sync): Finish heap-allocated data structures.
//==========================================================================
class AsyncCompileJob::FinishCompile : public CompileStep {
void RunInForeground() override {
TRACE_COMPILE("(5b) Finish compile...\n");
// At this point, compilation has completed. Update the code table.
for (int i = FLAG_skip_compiling_wasm_funcs,
e = job_->code_table_->length();
i < e; ++i) {
Object* val = job_->code_table_->get(i);
if (val->IsCode()) RecordStats(Code::cast(val), job_->counters());
}
// Create heap objects for script and module bytes to be stored in the
// shared module data. Asm.js is not compiled asynchronously.
Handle<Script> script = CreateWasmScript(job_->isolate_, job_->wire_bytes_);
Handle<ByteArray> asm_js_offset_table;
// TODO(wasm): Improve efficiency of storing module wire bytes.
// 1. Only store relevant sections, not function bodies
// 2. Don't make a second copy of the bytes here; reuse the copy made
// for asynchronous compilation and store it as an external one
// byte string for serialization/deserialization.
Handle<String> module_bytes =
job_->isolate_->factory()
->NewStringFromOneByte(
{job_->wire_bytes_.start(), job_->wire_bytes_.length()},
TENURED)
.ToHandleChecked();
DCHECK(module_bytes->IsSeqOneByteString());
// The {module_wrapper} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object.
Handle<WasmModuleWrapper> module_wrapper =
WasmModuleWrapper::From(job_->isolate_, job_->module_.release());
// Create the shared module data.
// TODO(clemensh): For the same module (same bytes / same hash), we should
// only have one WasmSharedModuleData. Otherwise, we might only set
// breakpoints on a (potentially empty) subset of the instances.
Handle<WasmSharedModuleData> shared =
WasmSharedModuleData::New(job_->isolate_, module_wrapper,
Handle<SeqOneByteString>::cast(module_bytes),
script, asm_js_offset_table);
// 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.
job_->compiled_module_ =
NewCompiledModule(job_->isolate_, shared, job_->code_table_,
job_->export_wrappers_, job_->module_env_.get());
// Finish the wasm script now and make it public to the debugger.
script->set_wasm_compiled_module(*job_->compiled_module_);
job_->isolate_->debug()->OnAfterCompile(script);
DeferredHandleScope deferred(job_->isolate_);
job_->compiled_module_ = handle(*job_->compiled_module_, job_->isolate_);
job_->deferred_handles_.push_back(deferred.Detach());
// TODO(wasm): compiling wrappers should be made async as well.
job_->DoSync<CompileWrappers>();
}
};
//==========================================================================
// Step 6 (sync): Compile JS->wasm wrappers.
//==========================================================================
class AsyncCompileJob::CompileWrappers : public CompileStep {
void RunInForeground() override {
TRACE_COMPILE("(6) Compile wrappers...\n");
// Compile JS->wasm wrappers for exported functions.
JSToWasmWrapperCache js_to_wasm_cache;
int wrapper_index = 0;
WasmModule* module = job_->compiled_module_->module();
for (auto exp : module->export_table) {
if (exp.kind != kExternalFunction) continue;
Handle<Code> wasm_code(Code::cast(job_->code_table_->get(exp.index)),
job_->isolate_);
Handle<Code> wrapper_code =
js_to_wasm_cache.CloneOrCompileJSToWasmWrapper(job_->isolate_, module,
wasm_code, exp.index);
job_->export_wrappers_->set(wrapper_index, *wrapper_code);
RecordStats(*wrapper_code, job_->counters());
++wrapper_index;
}
job_->DoSync<FinishModule>();
}
};
//==========================================================================
// Step 7 (sync): Finish the module and resolve the promise.
//==========================================================================
class AsyncCompileJob::FinishModule : public CompileStep {
void RunInForeground() override {
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);
}
};
class AsyncCompileJob::AbortCompilation : public CompileStep {
void RunInForeground() override {
TRACE_COMPILE("Abort asynchronous compilation ...\n");
job_->isolate_->wasm_compilation_manager()->RemoveJob(job_);
}
};
AsyncStreamingProcessor::AsyncStreamingProcessor(AsyncCompileJob* job)
: job_(job), compilation_unit_builder_(nullptr) {}
void AsyncStreamingProcessor::FinishAsyncCompileJobWithError(ResultBase error) {
// Make sure all background tasks stopped executing before we change the state
// of the AsyncCompileJob to DecodeFail.
job_->background_task_manager_.CancelAndWait();
// Create a ModuleResult from the result we got as parameter. Since there was
// no error, we don't have to provide a real wasm module to the ModuleResult.
ModuleResult result(nullptr);
result.MoveErrorFrom(error);
// Check if there is already a ModuleCompiler, in which case we have to clean
// it up as well.
if (job_->compiler_) {
// If {IsFinisherRunning} is true, then there is already a foreground task
// in the task queue to execute the DecodeFail step. We do not have to start
// a new task ourselves with DoSync.
if (job_->compiler_->IsFinisherRunning()) {
job_->NextStep<AsyncCompileJob::DecodeFail>(std::move(result));
} else {
job_->DoSync<AsyncCompileJob::DecodeFail>(std::move(result));
}
compilation_unit_builder_->Clear();
} else {
job_->DoSync<AsyncCompileJob::DecodeFail>(std::move(result));
}
}
// Process the module header.
bool AsyncStreamingProcessor::ProcessModuleHeader(Vector<const uint8_t> bytes,
uint32_t offset) {
TRACE_STREAMING("Process module header...\n");
decoder_.StartDecoding(job_->isolate());
decoder_.DecodeModuleHeader(bytes, offset);
if (!decoder_.ok()) {
FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false));
return false;
}
return true;
}
// Process all sections except for the code section.
bool AsyncStreamingProcessor::ProcessSection(SectionCode section_code,
Vector<const uint8_t> bytes,
uint32_t offset) {
TRACE_STREAMING("Process section %d ...\n", section_code);
if (section_code == SectionCode::kUnknownSectionCode) {
// No need to decode unknown sections, even the names section. If decoding
// of the unknown section fails, compilation should succeed anyways, and
// even decoding the names section is unnecessary because the result comes
// too late for streaming compilation.
return true;
}
constexpr bool verify_functions = false;
decoder_.DecodeSection(section_code, bytes, offset, verify_functions);
if (!decoder_.ok()) {
FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false));
return false;
}
return true;
}
// Start the code section.
bool AsyncStreamingProcessor::ProcessCodeSectionHeader(size_t functions_count,
uint32_t offset) {
TRACE_STREAMING("Start the code section with %zu functions...\n",
functions_count);
if (!decoder_.CheckFunctionsCount(static_cast<uint32_t>(functions_count),
offset)) {
FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false));
return false;
}
job_->NextStep<AsyncCompileJob::PrepareAndStartCompile>(decoder_.module(),
false);
// Execute the PrepareAndStartCompile step immediately and not in a separate
// task. The step expects to be run on a separate foreground thread though, so
// we to increment {num_pending_foreground_tasks_} to look like one.
++job_->num_pending_foreground_tasks_;
DCHECK_EQ(1, job_->num_pending_foreground_tasks_);
constexpr bool on_foreground = true;
job_->step_->Run(on_foreground);
job_->outstanding_units_ = functions_count;
// Set outstanding_finishers_ to 2, because both the AsyncCompileJob and the
// AsyncStreamingProcessor have to finish.
job_->outstanding_finishers_.SetValue(2);
next_function_ = decoder_.module()->num_imported_functions +
FLAG_skip_compiling_wasm_funcs;
compilation_unit_builder_.reset(
new ModuleCompiler::CompilationUnitBuilder(job_->compiler_.get()));
return true;
}
// Process a function body.
bool AsyncStreamingProcessor::ProcessFunctionBody(Vector<const uint8_t> bytes,
uint32_t offset) {
TRACE_STREAMING("Process function body %d ...\n", next_function_);
decoder_.DecodeFunctionBody(
next_function_, static_cast<uint32_t>(bytes.length()), offset, false);
if (next_function_ >= decoder_.module()->num_imported_functions +
FLAG_skip_compiling_wasm_funcs) {
const WasmFunction* func = &decoder_.module()->functions[next_function_];
WasmName name = {nullptr, 0};
compilation_unit_builder_->AddUnit(job_->module_env_.get(), func, offset,
bytes, name);
}
++next_function_;
return true;
}
void AsyncStreamingProcessor::OnFinishedChunk() {
// TRACE_STREAMING("FinishChunk...\n");
if (compilation_unit_builder_) {
compilation_unit_builder_->Commit();
job_->RestartBackgroundTasks();
}
}
// Finish the processing of the stream.
void AsyncStreamingProcessor::OnFinishedStream(std::unique_ptr<uint8_t[]> bytes,
size_t length) {
TRACE_STREAMING("Finish stream...\n");
job_->bytes_copy_ = std::move(bytes);
job_->wire_bytes_ = ModuleWireBytes(job_->bytes_copy_.get(),
job_->bytes_copy_.get() + length);
ModuleResult result = decoder_.FinishDecoding(false);
DCHECK(result.ok());
job_->module_ = std::move(result.val);
if (job_->DecrementAndCheckFinisherCount())
job_->DoSync<AsyncCompileJob::FinishCompile>();
}
// Report an error detected in the StreamingDecoder.
void AsyncStreamingProcessor::OnError(DecodeResult result) {
TRACE_STREAMING("Stream error...\n");
FinishAsyncCompileJobWithError(std::move(result));
}
void AsyncStreamingProcessor::OnAbort() {
TRACE_STREAMING("Abort stream...\n");
job_->Abort();
}
} // namespace wasm
} // namespace internal
} // namespace v8
#undef TRACE
#undef TRACE_COMPILE
#undef TRACE_STREAMING
#undef TRACE_CHAIN