blob: 4a2e610b99baacdc63a70556479754fb7872e4e2 [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/optional.h"
#include "src/base/template-utils.h"
#include "src/base/utils/random-number-generator.h"
#include "src/code-stubs.h"
#include "src/compiler/wasm-compiler.h"
#include "src/counters.h"
#include "src/property-descriptor.h"
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/compilation-manager.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-code-specialization.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-memory.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)
#define TRACE_LAZY(...) \
do { \
if (FLAG_trace_wasm_lazy_compilation) PrintF(__VA_ARGS__); \
} while (false)
namespace v8 {
namespace internal {
namespace wasm {
static constexpr int kInvalidSigIndex = -1;
// A class compiling an entire module.
class ModuleCompiler {
public:
ModuleCompiler(Isolate* isolate, WasmModule* module, Handle<Code> centry_stub,
wasm::NativeModule* native_module);
// The actual runnable task that performs compilations in the background.
class CompilationTask : public CancelableTask {
public:
ModuleCompiler* compiler_;
explicit CompilationTask(ModuleCompiler* compiler)
: CancelableTask(&compiler->background_task_manager_),
compiler_(compiler) {}
void RunInternal() override {
while (compiler_->executed_units_.CanAcceptWork() &&
compiler_->FetchAndExecuteCompilationUnit()) {
}
compiler_->OnBackgroundTaskStopped();
}
};
// The CompilationUnitBuilder builds compilation units and stores them in an
// internal buffer. The buffer is moved into the working queue of the
// ModuleCompiler when {Commit} is called.
class CompilationUnitBuilder {
public:
explicit CompilationUnitBuilder(ModuleCompiler* compiler)
: compiler_(compiler) {}
~CompilationUnitBuilder() { DCHECK(units_.empty()); }
void AddUnit(compiler::ModuleEnv* module_env,
wasm::NativeModule* native_module,
const WasmFunction* function, uint32_t buffer_offset,
Vector<const uint8_t> bytes, WasmName name) {
units_.emplace_back(new compiler::WasmCompilationUnit(
compiler_->isolate_, module_env, native_module,
wasm::FunctionBody{function->sig, buffer_offset, bytes.begin(),
bytes.end()},
name, function->func_index, compiler_->centry_stub_,
compiler::WasmCompilationUnit::GetDefaultCompilationMode(),
compiler_->counters()));
}
bool Commit() {
if (units_.empty()) return false;
{
base::LockGuard<base::Mutex> guard(
&compiler_->compilation_units_mutex_);
compiler_->compilation_units_.insert(
compiler_->compilation_units_.end(),
std::make_move_iterator(units_.begin()),
std::make_move_iterator(units_.end()));
}
units_.clear();
return true;
}
void Clear() { units_.clear(); }
private:
ModuleCompiler* compiler_;
std::vector<std::unique_ptr<compiler::WasmCompilationUnit>> units_;
};
class CodeGenerationSchedule {
public:
explicit CodeGenerationSchedule(
base::RandomNumberGenerator* random_number_generator,
size_t max_memory = 0);
void Schedule(std::unique_ptr<compiler::WasmCompilationUnit>&& item);
bool IsEmpty() const { return schedule_.empty(); }
std::unique_ptr<compiler::WasmCompilationUnit> GetNext();
bool CanAcceptWork() const;
bool ShouldIncreaseWorkload() const;
void EnableThrottling() { throttle_ = true; }
private:
size_t GetRandomIndexInSchedule();
base::RandomNumberGenerator* random_number_generator_ = nullptr;
std::vector<std::unique_ptr<compiler::WasmCompilationUnit>> schedule_;
const size_t max_memory_;
bool throttle_ = false;
base::AtomicNumber<size_t> allocated_memory_{0};
};
Counters* counters() const { return async_counters_.get(); }
// Run by each compilation task and by the main thread (i.e. in both
// foreground and background threads). 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 FetchAndExecuteCompilationUnit(
std::function<void()> no_finisher_callback = nullptr);
void OnBackgroundTaskStopped();
void EnableThrottling() { executed_units_.EnableThrottling(); }
bool CanAcceptWork() const { return executed_units_.CanAcceptWork(); }
bool ShouldIncreaseWorkload() {
if (executed_units_.ShouldIncreaseWorkload()) {
// Check if it actually makes sense to increase the workload.
base::LockGuard<base::Mutex> guard(&compilation_units_mutex_);
return !compilation_units_.empty();
}
return false;
}
size_t InitializeCompilationUnits(const std::vector<WasmFunction>& functions,
const ModuleWireBytes& wire_bytes,
compiler::ModuleEnv* module_env);
void RestartCompilationTasks();
size_t FinishCompilationUnits(std::vector<Handle<Code>>& results,
ErrorThrower* thrower);
bool IsFinisherRunning() const { return finisher_is_running_; }
void SetFinisherIsRunning(bool value);
WasmCodeWrapper FinishCompilationUnit(ErrorThrower* thrower, int* func_index);
void CompileInParallel(const ModuleWireBytes& wire_bytes,
compiler::ModuleEnv* module_env,
std::vector<Handle<Code>>& results,
ErrorThrower* thrower);
void CompileSequentially(const ModuleWireBytes& wire_bytes,
compiler::ModuleEnv* module_env,
std::vector<Handle<Code>>& results,
ErrorThrower* thrower);
void ValidateSequentially(const ModuleWireBytes& wire_bytes,
compiler::ModuleEnv* module_env,
ErrorThrower* thrower);
static MaybeHandle<WasmModuleObject> 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);
private:
MaybeHandle<WasmModuleObject> 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);
Isolate* isolate_;
WasmModule* module_;
const std::shared_ptr<Counters> async_counters_;
std::vector<std::unique_ptr<compiler::WasmCompilationUnit>>
compilation_units_;
base::Mutex compilation_units_mutex_;
CodeGenerationSchedule executed_units_;
base::Mutex result_mutex_;
const size_t num_background_tasks_;
// This flag should only be set while holding result_mutex_.
bool finisher_is_running_ = false;
CancelableTaskManager background_task_manager_;
size_t stopped_compilation_tasks_ = 0;
base::Mutex tasks_mutex_;
Handle<Code> centry_stub_;
wasm::NativeModule* native_module_;
};
namespace {
class JSToWasmWrapperCache {
public:
void SetContextAddress(Address context_address) {
// Prevent to have different context addresses in the cache.
DCHECK(code_cache_.empty());
context_address_ = context_address;
}
Handle<Code> CloneOrCompileJSToWasmWrapper(Isolate* isolate,
wasm::WasmModule* module,
WasmCodeWrapper wasm_code,
uint32_t index,
bool use_trap_handler) {
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.
if (wasm_code.IsCodeObject()) {
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->kind() == Code::WASM_TO_WASM_FUNCTION ||
target->builtin_index() == Builtins::kIllegal ||
target->builtin_index() == Builtins::kWasmCompileLazy) {
it.rinfo()->set_target_address(
isolate, wasm_code.GetCode()->instruction_start());
break;
}
}
} else {
RelocIterator it(*code,
RelocInfo::ModeMask(RelocInfo::JS_TO_WASM_CALL));
DCHECK(!it.done());
it.rinfo()->set_js_to_wasm_address(
isolate, wasm_code.is_null()
? nullptr
: wasm_code.GetWasmCode()->instructions().start());
}
return code;
}
Handle<Code> code = compiler::CompileJSToWasmWrapper(
isolate, module, wasm_code, index, context_address_, use_trap_handler);
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;
}
private:
// sig_map_ maps signatures to an index in code_cache_.
wasm::SignatureMap sig_map_;
std::vector<Handle<Code>> code_cache_;
Address context_address_ = nullptr;
};
// A helper class to simplify instantiating a module from a compiled module.
// It closes over the {Isolate}, the {ErrorThrower}, the {WasmCompiledModule},
// etc.
class InstanceBuilder {
public:
InstanceBuilder(Isolate* isolate, ErrorThrower* thrower,
Handle<WasmModuleObject> module_object,
MaybeHandle<JSReceiver> ffi,
MaybeHandle<JSArrayBuffer> memory,
WeakCallbackInfo<void>::Callback instance_finalizer_callback);
// Build an instance, in all of its glory.
MaybeHandle<WasmInstanceObject> Build();
private:
// Represents the initialized state of a table.
struct TableInstance {
Handle<WasmTableObject> table_object; // WebAssembly.Table instance
Handle<FixedArray> js_wrappers; // JSFunctions exported
Handle<FixedArray> function_table; // internal array of <sig,code> pairs
};
// A pre-evaluated value to use in import binding.
struct SanitizedImport {
Handle<String> module_name;
Handle<String> import_name;
Handle<Object> value;
};
Isolate* isolate_;
WasmModule* const module_;
const std::shared_ptr<Counters> async_counters_;
ErrorThrower* thrower_;
Handle<WasmModuleObject> module_object_;
MaybeHandle<JSReceiver> ffi_;
MaybeHandle<JSArrayBuffer> memory_;
Handle<JSArrayBuffer> globals_;
Handle<WasmCompiledModule> compiled_module_;
std::vector<TableInstance> table_instances_;
std::vector<Handle<JSFunction>> js_wrappers_;
JSToWasmWrapperCache js_to_wasm_cache_;
WeakCallbackInfo<void>::Callback instance_finalizer_callback_;
std::vector<SanitizedImport> sanitized_imports_;
const std::shared_ptr<Counters>& async_counters() const {
return async_counters_;
}
Counters* counters() const { return async_counters().get(); }
bool use_trap_handler() const { return compiled_module_->use_trap_handler(); }
// Helper routines to print out errors with imports.
#define ERROR_THROWER_WITH_MESSAGE(TYPE) \
void Report##TYPE(const char* error, uint32_t index, \
Handle<String> module_name, Handle<String> import_name) { \
thrower_->TYPE("Import #%d module=\"%s\" function=\"%s\" error: %s", \
index, module_name->ToCString().get(), \
import_name->ToCString().get(), error); \
} \
\
MaybeHandle<Object> Report##TYPE(const char* error, uint32_t index, \
Handle<String> module_name) { \
thrower_->TYPE("Import #%d module=\"%s\" error: %s", index, \
module_name->ToCString().get(), error); \
return MaybeHandle<Object>(); \
}
ERROR_THROWER_WITH_MESSAGE(LinkError)
ERROR_THROWER_WITH_MESSAGE(TypeError)
#undef ERROR_THROWER_WITH_MESSAGE
// Look up an import value in the {ffi_} object.
MaybeHandle<Object> LookupImport(uint32_t index, Handle<String> module_name,
Handle<String> import_name);
// 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> LookupImportAsm(uint32_t index,
Handle<String> import_name);
uint32_t EvalUint32InitExpr(const WasmInitExpr& expr);
// Load data segments into the memory.
void LoadDataSegments(WasmContext* wasm_context);
void WriteGlobalValue(WasmGlobal& global, Handle<Object> value);
void SanitizeImports();
Handle<FixedArray> SetupWasmToJSImportsTable(
Handle<WasmInstanceObject> instance);
// Process the imports, including functions, tables, globals, and memory, in
// order, loading them from the {ffi_} object. Returns the number of imported
// functions.
int ProcessImports(Handle<FixedArray> code_table,
Handle<WasmInstanceObject> instance);
template <typename T>
T* GetRawGlobalPtr(WasmGlobal& global);
// Process initialization of globals.
void InitGlobals();
// Allocate memory for a module instance as a new JSArrayBuffer.
Handle<JSArrayBuffer> AllocateMemory(uint32_t num_pages);
bool NeedsWrappers() const;
// Process the exports, creating wrappers for functions, tables, memories,
// and globals.
void ProcessExports(Handle<WasmInstanceObject> instance,
Handle<WasmCompiledModule> compiled_module);
void InitializeTables(Handle<WasmInstanceObject> instance,
CodeSpecialization* code_specialization);
void LoadTableSegments(Handle<FixedArray> code_table,
Handle<WasmInstanceObject> instance);
};
// TODO(titzer): move to wasm-objects.cc
void InstanceFinalizer(const v8::WeakCallbackInfo<void>& data) {
DisallowHeapAllocation no_gc;
JSObject** p = reinterpret_cast<JSObject**>(data.GetParameter());
WasmInstanceObject* owner = reinterpret_cast<WasmInstanceObject*>(*p);
Isolate* isolate = reinterpret_cast<Isolate*>(data.GetIsolate());
// If a link to shared memory instances exists, update the list of memory
// instances before the instance is destroyed.
WasmCompiledModule* compiled_module = owner->compiled_module();
wasm::NativeModule* native_module = compiled_module->GetNativeModule();
if (FLAG_wasm_jit_to_native) {
if (native_module) {
TRACE("Finalizing %zu {\n", native_module->instance_id);
} else {
TRACE("Finalized already cleaned up compiled module\n");
}
} else {
TRACE("Finalizing %d {\n", compiled_module->instance_id());
if (compiled_module->use_trap_handler()) {
// TODO(6792): No longer needed once WebAssembly code is off heap.
CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
DisallowHeapAllocation no_gc;
FixedArray* code_table = compiled_module->code_table();
for (int i = 0; i < code_table->length(); ++i) {
Code* code = Code::cast(code_table->get(i));
int index = code->trap_handler_index()->value();
if (index >= 0) {
trap_handler::ReleaseHandlerData(index);
code->set_trap_handler_index(
Smi::FromInt(trap_handler::kInvalidIndex));
}
}
}
}
WeakCell* weak_wasm_module = compiled_module->weak_wasm_module();
// Since the order of finalizers is not guaranteed, it can be the case
// that {instance->compiled_module()->module()}, which is a
// {Managed<WasmModule>} has been collected earlier in this GC cycle.
// Weak references to this instance won't be cleared until
// the next GC cycle, so we need to manually break some links (such as
// the weak references from {WasmMemoryObject::instances}.
if (owner->has_memory_object()) {
Handle<WasmMemoryObject> memory(owner->memory_object(), isolate);
Handle<WasmInstanceObject> instance(owner, isolate);
WasmMemoryObject::RemoveInstance(isolate, memory, instance);
}
// weak_wasm_module may have been cleared, meaning the module object
// was GC-ed. We still want to maintain the links between instances, to
// release the WasmCompiledModule corresponding to the WasmModuleInstance
// being finalized here.
WasmModuleObject* wasm_module = nullptr;
if (!weak_wasm_module->cleared()) {
wasm_module = WasmModuleObject::cast(weak_wasm_module->value());
WasmCompiledModule* current_template = wasm_module->compiled_module();
TRACE("chain before {\n");
TRACE_CHAIN(current_template);
TRACE("}\n");
DCHECK(!current_template->has_prev_instance());
if (current_template == compiled_module) {
if (!compiled_module->has_next_instance()) {
WasmCompiledModule::Reset(isolate, compiled_module);
} else {
WasmModuleObject::cast(wasm_module)
->set_compiled_module(compiled_module->next_instance());
}
}
}
compiled_module->RemoveFromChain();
if (wasm_module != nullptr) {
TRACE("chain after {\n");
TRACE_CHAIN(wasm_module->compiled_module());
TRACE("}\n");
}
compiled_module->reset_weak_owning_instance();
GlobalHandles::Destroy(reinterpret_cast<Object**>(p));
TRACE("}\n");
}
// This is used in ProcessImports.
// When importing other modules' exports, we need to ask
// the exporter for a WasmToWasm wrapper. To do that, we need to
// switch that module to RW. To avoid flip-floping the same module
// RW <->RX, we create a scope for a set of NativeModules.
class SetOfNativeModuleModificationScopes final {
public:
void Add(NativeModule* module) {
module->SetExecutable(false);
native_modules_.insert(module);
}
~SetOfNativeModuleModificationScopes() {
for (NativeModule* module : native_modules_) {
module->SetExecutable(true);
}
}
private:
std::unordered_set<NativeModule*> native_modules_;
};
} // namespace
MaybeHandle<WasmModuleObject> SyncCompileTranslatedAsmJs(
Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes,
Handle<Script> asm_js_script,
Vector<const byte> asm_js_offset_table_bytes) {
ModuleResult result = SyncDecodeWasmModule(isolate, bytes.start(),
bytes.end(), false, kAsmJsOrigin);
if (result.failed()) {
thrower->CompileFailed("Wasm decoding failed", result);
return {};
}
// Transfer ownership of the WasmModule to the {WasmModuleWrapper} generated
// in {CompileToModuleObject}.
return ModuleCompiler::CompileToModuleObject(
isolate, thrower, std::move(result.val), bytes, asm_js_script,
asm_js_offset_table_bytes);
}
MaybeHandle<WasmModuleObject> SyncCompile(Isolate* isolate,
ErrorThrower* thrower,
const ModuleWireBytes& bytes) {
ModuleResult result = SyncDecodeWasmModule(isolate, bytes.start(),
bytes.end(), false, kWasmOrigin);
if (result.failed()) {
thrower->CompileFailed("Wasm decoding failed", result);
return {};
}
// Transfer ownership of the WasmModule to the {WasmModuleWrapper} generated
// in {CompileToModuleObject}.
return ModuleCompiler::CompileToModuleObject(
isolate, thrower, std::move(result.val), bytes, Handle<Script>(),
Vector<const byte>());
}
MaybeHandle<WasmInstanceObject> SyncInstantiate(
Isolate* isolate, ErrorThrower* thrower,
Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports,
MaybeHandle<JSArrayBuffer> memory) {
InstanceBuilder builder(isolate, thrower, module_object, imports, memory,
&InstanceFinalizer);
return builder.Build();
}
MaybeHandle<WasmInstanceObject> SyncCompileAndInstantiate(
Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes,
MaybeHandle<JSReceiver> imports, MaybeHandle<JSArrayBuffer> memory) {
MaybeHandle<WasmModuleObject> module = SyncCompile(isolate, thrower, bytes);
DCHECK_EQ(thrower->error(), module.is_null());
if (module.is_null()) return {};
return SyncInstantiate(isolate, thrower, module.ToHandleChecked(), imports,
memory);
}
void RejectPromise(Isolate* isolate, Handle<Context> context,
ErrorThrower& thrower, Handle<JSPromise> promise) {
Local<Promise::Resolver> resolver =
Utils::PromiseToLocal(promise).As<Promise::Resolver>();
auto maybe = resolver->Reject(Utils::ToLocal(context),
Utils::ToLocal(thrower.Reify()));
CHECK_IMPLIES(!maybe.FromMaybe(false), isolate->has_scheduled_exception());
}
void ResolvePromise(Isolate* isolate, Handle<Context> context,
Handle<JSPromise> promise, Handle<Object> result) {
Local<Promise::Resolver> resolver =
Utils::PromiseToLocal(promise).As<Promise::Resolver>();
auto maybe =
resolver->Resolve(Utils::ToLocal(context), Utils::ToLocal(result));
CHECK_IMPLIES(!maybe.FromMaybe(false), isolate->has_scheduled_exception());
}
void AsyncInstantiate(Isolate* isolate, Handle<JSPromise> promise,
Handle<WasmModuleObject> module_object,
MaybeHandle<JSReceiver> imports) {
ErrorThrower thrower(isolate, nullptr);
MaybeHandle<WasmInstanceObject> instance_object = SyncInstantiate(
isolate, &thrower, module_object, imports, Handle<JSArrayBuffer>::null());
if (thrower.error()) {
RejectPromise(isolate, handle(isolate->context()), thrower, promise);
return;
}
ResolvePromise(isolate, handle(isolate->context()), promise,
instance_object.ToHandleChecked());
}
void AsyncCompile(Isolate* isolate, Handle<JSPromise> promise,
const ModuleWireBytes& bytes, bool is_shared) {
if (!FLAG_wasm_async_compilation) {
// Asynchronous compilation disabled; fall back on synchronous compilation.
ErrorThrower thrower(isolate, "WasmCompile");
MaybeHandle<WasmModuleObject> module_object;
if (is_shared) {
// Make a copy of the wire bytes to avoid concurrent modification.
std::unique_ptr<uint8_t[]> copy(new uint8_t[bytes.length()]);
memcpy(copy.get(), bytes.start(), bytes.length());
i::wasm::ModuleWireBytes bytes_copy(copy.get(),
copy.get() + bytes.length());
module_object = SyncCompile(isolate, &thrower, bytes_copy);
} else {
// The wire bytes are not shared, OK to use them directly.
module_object = SyncCompile(isolate, &thrower, bytes);
}
if (thrower.error()) {
RejectPromise(isolate, handle(isolate->context()), thrower, promise);
return;
}
Handle<WasmModuleObject> module = module_object.ToHandleChecked();
ResolvePromise(isolate, handle(isolate->context()), promise, module);
return;
}
if (FLAG_wasm_test_streaming) {
std::shared_ptr<StreamingDecoder> streaming_decoder =
isolate->wasm_engine()
->compilation_manager()
->StartStreamingCompilation(isolate, handle(isolate->context()),
promise);
streaming_decoder->OnBytesReceived(bytes.module_bytes());
streaming_decoder->Finish();
return;
}
// Make a copy of the wire bytes in case the user program changes them
// during asynchronous compilation.
std::unique_ptr<byte[]> copy(new byte[bytes.length()]);
memcpy(copy.get(), bytes.start(), bytes.length());
isolate->wasm_engine()->compilation_manager()->StartAsyncCompileJob(
isolate, std::move(copy), bytes.length(), handle(isolate->context()),
promise);
}
Handle<Code> CompileLazyOnGCHeap(Isolate* isolate) {
HistogramTimerScope lazy_time_scope(
isolate->counters()->wasm_lazy_compilation_time());
// Find the wasm frame which triggered the lazy compile, to get the wasm
// instance.
StackFrameIterator it(isolate);
// First frame: C entry stub.
DCHECK(!it.done());
DCHECK_EQ(StackFrame::EXIT, it.frame()->type());
it.Advance();
// Second frame: WasmCompileLazy builtin.
DCHECK(!it.done());
Handle<Code> lazy_compile_code(it.frame()->LookupCode(), isolate);
DCHECK_EQ(Builtins::kWasmCompileLazy, lazy_compile_code->builtin_index());
Handle<WasmInstanceObject> instance;
Handle<FixedArray> exp_deopt_data;
int func_index = -1;
// If the lazy compile stub has deopt data, use that to determine the
// instance and function index. Otherwise this must be a wasm->wasm call
// within one instance, so extract the information from the caller.
if (lazy_compile_code->deoptimization_data()->length() > 0) {
exp_deopt_data = handle(lazy_compile_code->deoptimization_data(), isolate);
auto func_info = GetWasmFunctionInfo(isolate, lazy_compile_code);
instance = func_info.instance.ToHandleChecked();
func_index = func_info.func_index;
}
it.Advance();
// Third frame: The calling wasm code or js-to-wasm wrapper.
DCHECK(!it.done());
DCHECK(it.frame()->is_js_to_wasm() || it.frame()->is_wasm_compiled());
Handle<Code> caller_code = handle(it.frame()->LookupCode(), isolate);
if (it.frame()->is_js_to_wasm()) {
DCHECK(!instance.is_null());
} else if (instance.is_null()) {
// Then this is a direct call (otherwise we would have attached the instance
// via deopt data to the lazy compile stub). Just use the instance of the
// caller.
instance =
handle(WasmInstanceObject::GetOwningInstanceGC(*caller_code), isolate);
}
int offset =
static_cast<int>(it.frame()->pc() - caller_code->instruction_start());
// Only patch the caller code if this is *no* indirect call.
// exp_deopt_data will be null if the called function is not exported at all,
// and its length will be <= 2 if all entries in tables were already patched.
// Note that this check is conservative: If the first call to an exported
// function is direct, we will just patch the export tables, and only on the
// second call we will patch the caller.
bool patch_caller = caller_code->kind() == Code::JS_TO_WASM_FUNCTION ||
exp_deopt_data.is_null() || exp_deopt_data->length() <= 2;
wasm::LazyCompilationOrchestrator* orchestrator =
Managed<wasm::LazyCompilationOrchestrator>::cast(
instance->compiled_module()
->shared()
->lazy_compilation_orchestrator())
->get();
DCHECK(!orchestrator->IsFrozenForTesting());
Handle<Code> compiled_code = orchestrator->CompileLazyOnGCHeap(
isolate, instance, caller_code, offset, func_index, patch_caller);
if (!exp_deopt_data.is_null() && exp_deopt_data->length() > 2) {
TRACE_LAZY("Patching %d position(s) in function tables.\n",
(exp_deopt_data->length() - 2) / 2);
// See EnsureExportedLazyDeoptData: exp_deopt_data[2...(len-1)] are pairs of
// <export_table, index> followed by undefined values.
// Use this information here to patch all export tables.
DCHECK_EQ(0, exp_deopt_data->length() % 2);
for (int idx = 2, end = exp_deopt_data->length(); idx < end; idx += 2) {
if (exp_deopt_data->get(idx)->IsUndefined(isolate)) break;
FixedArray* exp_table = FixedArray::cast(exp_deopt_data->get(idx));
int exp_index = Smi::ToInt(exp_deopt_data->get(idx + 1));
int table_index = compiler::FunctionTableCodeOffset(exp_index);
DCHECK(exp_table->get(table_index) == *lazy_compile_code);
exp_table->set(table_index, *compiled_code);
}
// TODO(6792): No longer needed once WebAssembly code is off heap.
CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
// After processing, remove the list of exported entries, such that we don't
// do the patching redundantly.
Handle<FixedArray> new_deopt_data =
isolate->factory()->CopyFixedArrayUpTo(exp_deopt_data, 2, TENURED);
lazy_compile_code->set_deoptimization_data(*new_deopt_data);
}
return compiled_code;
}
Address CompileLazy(Isolate* isolate) {
HistogramTimerScope lazy_time_scope(
isolate->counters()->wasm_lazy_compilation_time());
// Find the wasm frame which triggered the lazy compile, to get the wasm
// instance.
StackFrameIterator it(isolate);
// First frame: C entry stub.
DCHECK(!it.done());
DCHECK_EQ(StackFrame::EXIT, it.frame()->type());
it.Advance();
// Second frame: WasmCompileLazy builtin.
DCHECK(!it.done());
Handle<WasmInstanceObject> instance;
Maybe<uint32_t> func_index_to_compile = Nothing<uint32_t>();
Handle<Object> exp_deopt_data_entry;
const wasm::WasmCode* lazy_stub_or_copy =
isolate->wasm_engine()->code_manager()->LookupCode(it.frame()->pc());
DCHECK_EQ(wasm::WasmCode::kLazyStub, lazy_stub_or_copy->kind());
if (!lazy_stub_or_copy->IsAnonymous()) {
// Then it's an indirect call or via JS->wasm wrapper.
instance =
handle(lazy_stub_or_copy->owner()->compiled_module()->owning_instance(),
isolate);
func_index_to_compile = Just(lazy_stub_or_copy->index());
exp_deopt_data_entry =
handle(instance->compiled_module()->lazy_compile_data()->get(
static_cast<int>(lazy_stub_or_copy->index())),
isolate);
}
it.Advance();
// Third frame: The calling wasm code (direct or indirect), or js-to-wasm
// wrapper.
DCHECK(!it.done());
DCHECK(it.frame()->is_js_to_wasm() || it.frame()->is_wasm_compiled());
Handle<Code> js_to_wasm_caller_code;
const WasmCode* wasm_caller_code = nullptr;
Maybe<uint32_t> offset = Nothing<uint32_t>();
if (it.frame()->is_js_to_wasm()) {
DCHECK(!instance.is_null());
js_to_wasm_caller_code = handle(it.frame()->LookupCode(), isolate);
} else {
wasm_caller_code =
isolate->wasm_engine()->code_manager()->LookupCode(it.frame()->pc());
offset = Just(static_cast<uint32_t>(
it.frame()->pc() - wasm_caller_code->instructions().start()));
if (instance.is_null()) {
// Then this is a direct call (otherwise we would have attached the
// instance via deopt data to the lazy compile stub). Just use the
// instance of the caller.
instance = handle(
wasm_caller_code->owner()->compiled_module()->owning_instance(),
isolate);
}
}
Handle<WasmCompiledModule> compiled_module(instance->compiled_module());
wasm::LazyCompilationOrchestrator* orchestrator =
Managed<wasm::LazyCompilationOrchestrator>::cast(
compiled_module->shared()->lazy_compilation_orchestrator())
->get();
DCHECK(!orchestrator->IsFrozenForTesting());
NativeModuleModificationScope native_module_modification_scope(
compiled_module->GetNativeModule());
const wasm::WasmCode* result = nullptr;
// The caller may be js to wasm calling a function
// also available for indirect calls.
if (!js_to_wasm_caller_code.is_null()) {
result = orchestrator->CompileFromJsToWasm(
isolate, instance, js_to_wasm_caller_code,
func_index_to_compile.ToChecked());
} else {
DCHECK_NOT_NULL(wasm_caller_code);
if (func_index_to_compile.IsNothing() ||
(!exp_deopt_data_entry.is_null() &&
!exp_deopt_data_entry->IsFixedArray())) {
result = orchestrator->CompileDirectCall(
isolate, instance, func_index_to_compile, wasm_caller_code,
offset.ToChecked());
} else {
result = orchestrator->CompileIndirectCall(
isolate, instance, func_index_to_compile.ToChecked());
}
}
DCHECK_NOT_NULL(result);
int func_index = static_cast<int>(result->index());
if (!exp_deopt_data_entry.is_null() && exp_deopt_data_entry->IsFixedArray()) {
Handle<FixedArray> exp_deopt_data =
Handle<FixedArray>::cast(exp_deopt_data_entry);
TRACE_LAZY("Patching %d position(s) in function tables.\n",
exp_deopt_data->length() / 2);
// See EnsureExportedLazyDeoptData: exp_deopt_data[0...(len-1)] are pairs
// of <export_table, index> followed by undefined values. Use this
// information here to patch all export tables.
Handle<Foreign> foreign_holder =
isolate->factory()->NewForeign(result->instructions().start(), TENURED);
for (int idx = 0, end = exp_deopt_data->length(); idx < end; idx += 2) {
if (exp_deopt_data->get(idx)->IsUndefined(isolate)) break;
DisallowHeapAllocation no_gc;
int exp_index = Smi::ToInt(exp_deopt_data->get(idx + 1));
FixedArray* exp_table = FixedArray::cast(exp_deopt_data->get(idx));
exp_table->set(compiler::FunctionTableCodeOffset(exp_index),
*foreign_holder);
}
// TODO(6792): No longer needed once WebAssembly code is off heap.
CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
// After processing, remove the list of exported entries, such that we don't
// do the patching redundantly.
compiled_module->lazy_compile_data()->set(
func_index, isolate->heap()->undefined_value());
}
return result->instructions().start();
}
compiler::ModuleEnv CreateModuleEnvFromCompiledModule(
Isolate* isolate, Handle<WasmCompiledModule> compiled_module) {
DisallowHeapAllocation no_gc;
WasmModule* module = compiled_module->shared()->module();
if (FLAG_wasm_jit_to_native) {
NativeModule* native_module = compiled_module->GetNativeModule();
compiler::ModuleEnv result(module, native_module->function_tables(),
std::vector<Handle<Code>>{},
BUILTIN_CODE(isolate, WasmCompileLazy),
compiled_module->use_trap_handler());
return result;
}
std::vector<GlobalHandleAddress> function_tables;
int num_function_tables = static_cast<int>(module->function_tables.size());
FixedArray* ft =
num_function_tables == 0 ? nullptr : compiled_module->function_tables();
for (int i = 0; i < num_function_tables; ++i) {
// TODO(clemensh): defer these handles for concurrent compilation.
function_tables.push_back(WasmCompiledModule::GetTableValue(ft, i));
}
compiler::ModuleEnv result(module, std::move(function_tables),
std::vector<Handle<Code>>{},
BUILTIN_CODE(isolate, WasmCompileLazy),
compiled_module->use_trap_handler());
return result;
}
const wasm::WasmCode* LazyCompilationOrchestrator::CompileFunction(
Isolate* isolate, Handle<WasmInstanceObject> instance, int func_index) {
base::ElapsedTimer compilation_timer;
compilation_timer.Start();
Handle<WasmCompiledModule> compiled_module(instance->compiled_module(),
isolate);
if (FLAG_wasm_jit_to_native) {
wasm::WasmCode* existing_code = compiled_module->GetNativeModule()->GetCode(
static_cast<uint32_t>(func_index));
if (existing_code != nullptr &&
existing_code->kind() == wasm::WasmCode::kFunction) {
TRACE_LAZY("Function %d already compiled.\n", func_index);
return existing_code;
}
} else {
if (Code::cast(compiled_module->code_table()->get(func_index))->kind() ==
Code::WASM_FUNCTION) {
TRACE_LAZY("Function %d already compiled.\n", func_index);
return nullptr;
}
}
compiler::ModuleEnv module_env =
CreateModuleEnvFromCompiledModule(isolate, compiled_module);
const uint8_t* module_start =
compiled_module->shared()->module_bytes()->GetChars();
const WasmFunction* func = &module_env.module->functions[func_index];
FunctionBody body{func->sig, func->code.offset(),
module_start + func->code.offset(),
module_start + func->code.end_offset()};
// TODO(wasm): Refactor this to only get the name if it is really needed for
// tracing / debugging.
std::string func_name;
{
WasmName name = Vector<const char>::cast(
compiled_module->shared()->GetRawFunctionName(func_index));
// Copy to std::string, because the underlying string object might move on
// the heap.
func_name.assign(name.start(), static_cast<size_t>(name.length()));
}
ErrorThrower thrower(isolate, "WasmLazyCompile");
compiler::WasmCompilationUnit unit(isolate, &module_env,
compiled_module->GetNativeModule(), body,
CStrVector(func_name.c_str()), func_index,
CEntryStub(isolate, 1).GetCode());
unit.ExecuteCompilation();
// TODO(6792): No longer needed once WebAssembly code is off heap.
CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
WasmCodeWrapper code_wrapper = unit.FinishCompilation(&thrower);
// If there is a pending error, something really went wrong. The module was
// verified before starting execution with lazy compilation.
// This might be OOM, but then we cannot continue execution anyway.
// TODO(clemensh): According to the spec, we can actually skip validation at
// module creation time, and return a function that always traps here.
CHECK(!thrower.error());
// Now specialize the generated code for this instance.
// {code} is used only when !FLAG_wasm_jit_to_native, so it may be removed
// when that flag is removed.
Handle<Code> code;
if (code_wrapper.IsCodeObject()) {
code = code_wrapper.GetCode();
AttachWasmFunctionInfo(isolate, code, instance, func_index);
DCHECK_EQ(Builtins::kWasmCompileLazy,
Code::cast(compiled_module->code_table()->get(func_index))
->builtin_index());
compiled_module->code_table()->set(func_index, *code);
}
Zone specialization_zone(isolate->allocator(), ZONE_NAME);
CodeSpecialization code_specialization(isolate, &specialization_zone);
code_specialization.RelocateDirectCalls(instance);
code_specialization.ApplyToWasmCode(code_wrapper, SKIP_ICACHE_FLUSH);
int64_t func_size =
static_cast<int64_t>(func->code.end_offset() - func->code.offset());
int64_t compilation_time = compilation_timer.Elapsed().InMicroseconds();
auto counters = isolate->counters();
counters->wasm_lazily_compiled_functions()->Increment();
if (!code_wrapper.IsCodeObject()) {
const wasm::WasmCode* wasm_code = code_wrapper.GetWasmCode();
Assembler::FlushICache(isolate, wasm_code->instructions().start(),
wasm_code->instructions().size());
counters->wasm_generated_code_size()->Increment(
static_cast<int>(wasm_code->instructions().size()));
counters->wasm_reloc_size()->Increment(
static_cast<int>(wasm_code->reloc_info().size()));
} else {
Assembler::FlushICache(isolate, code->instruction_start(),
code->instruction_size());
counters->wasm_generated_code_size()->Increment(code->body_size());
counters->wasm_reloc_size()->Increment(code->relocation_info()->length());
}
counters->wasm_lazy_compilation_throughput()->AddSample(
compilation_time != 0 ? static_cast<int>(func_size / compilation_time)
: 0);
return !code_wrapper.IsCodeObject() ? code_wrapper.GetWasmCode() : nullptr;
}
namespace {
int AdvanceSourcePositionTableIterator(SourcePositionTableIterator& iterator,
int offset) {
DCHECK(!iterator.done());
int byte_pos;
do {
byte_pos = iterator.source_position().ScriptOffset();
iterator.Advance();
} while (!iterator.done() && iterator.code_offset() <= offset);
return byte_pos;
}
Code* ExtractWasmToWasmCallee(Code* wasm_to_wasm) {
DCHECK_EQ(Code::WASM_TO_WASM_FUNCTION, wasm_to_wasm->kind());
// Find the one code target in this wrapper.
RelocIterator it(wasm_to_wasm, RelocInfo::kCodeTargetMask);
DCHECK(!it.done());
Code* callee = Code::GetCodeFromTargetAddress(it.rinfo()->target_address());
#ifdef DEBUG
it.next();
DCHECK(it.done());
#endif
return callee;
}
const WasmCode* WasmExtractWasmToWasmCallee(const WasmCodeManager* code_manager,
const WasmCode* wasm_to_wasm) {
DCHECK_EQ(WasmCode::kWasmToWasmWrapper, wasm_to_wasm->kind());
// Find the one code target in this wrapper.
RelocIterator it(wasm_to_wasm->instructions(), wasm_to_wasm->reloc_info(),
wasm_to_wasm->constant_pool(),
RelocInfo::ModeMask(RelocInfo::JS_TO_WASM_CALL));
DCHECK(!it.done());
const WasmCode* callee =
code_manager->LookupCode(it.rinfo()->js_to_wasm_address());
#ifdef DEBUG
it.next();
DCHECK(it.done());
#endif
return callee;
}
// TODO(mtrofin): this should be a function again, when chromium:761307
// is addressed. chromium:771171 is also related.
#define WasmPatchWasmToWasmWrapper(isolate, wasm_to_wasm, new_target) \
do { \
TRACE_LAZY("Patching wasm-to-wasm wrapper.\n"); \
DCHECK_EQ(WasmCode::kWasmToWasmWrapper, wasm_to_wasm->kind()); \
NativeModuleModificationScope scope(wasm_to_wasm->owner()); \
RelocIterator it(wasm_to_wasm->instructions(), wasm_to_wasm->reloc_info(), \
wasm_to_wasm->constant_pool(), \
RelocInfo::ModeMask(RelocInfo::JS_TO_WASM_CALL)); \
DCHECK(!it.done()); \
it.rinfo()->set_js_to_wasm_address(isolate, \
new_target->instructions().start()); \
it.next(); \
DCHECK(it.done()); \
} while (0)
void PatchWasmToWasmWrapper(Isolate* isolate, Code* wasm_to_wasm,
Code* new_target) {
DCHECK_EQ(Code::WASM_TO_WASM_FUNCTION, wasm_to_wasm->kind());
// Find the one code target in this wrapper.
RelocIterator it(wasm_to_wasm, RelocInfo::kCodeTargetMask);
DCHECK(!it.done());
DCHECK_EQ(Builtins::kWasmCompileLazy,
Code::GetCodeFromTargetAddress(it.rinfo()->target_address())
->builtin_index());
it.rinfo()->set_target_address(isolate, new_target->instruction_start());
#ifdef DEBUG
it.next();
DCHECK(it.done());
#endif
}
} // namespace
Handle<Code> LazyCompilationOrchestrator::CompileLazyOnGCHeap(
Isolate* isolate, Handle<WasmInstanceObject> instance, Handle<Code> caller,
int call_offset, int exported_func_index, bool patch_caller) {
struct NonCompiledFunction {
int offset;
int func_index;
};
std::vector<NonCompiledFunction> non_compiled_functions;
int func_to_return_idx = exported_func_index;
Decoder decoder(nullptr, nullptr);
bool is_js_to_wasm = caller->kind() == Code::JS_TO_WASM_FUNCTION;
Handle<WasmCompiledModule> compiled_module(instance->compiled_module(),
isolate);
TRACE_LAZY(
"Starting lazy compilation (func %d @%d, js-to-wasm: %d, "
"patch caller: %d).\n",
exported_func_index, call_offset, is_js_to_wasm, patch_caller);
// If this lazy compile stub is being called through a wasm-to-wasm wrapper,
// remember that code object.
Handle<Code> wasm_to_wasm_callee;
// For js-to-wasm wrappers, don't iterate the reloc info. There is just one
// call site in there anyway.
if (patch_caller && !is_js_to_wasm) {
DisallowHeapAllocation no_gc;
SourcePositionTableIterator source_pos_iterator(
caller->SourcePositionTable());
auto caller_func_info = GetWasmFunctionInfo(isolate, caller);
Handle<WasmCompiledModule> caller_module(
caller_func_info.instance.ToHandleChecked()->compiled_module(),
isolate);
SeqOneByteString* module_bytes = caller_module->shared()->module_bytes();
const byte* func_bytes =
module_bytes->GetChars() + caller_module->shared()
->module()
->functions[caller_func_info.func_index]
.code.offset();
Code* lazy_callee = nullptr;
for (RelocIterator it(*caller, RelocInfo::kCodeTargetMask); !it.done();
it.next()) {
Code* callee =
Code::GetCodeFromTargetAddress(it.rinfo()->target_address());
// TODO(clemensh): Introduce safe_cast<T, bool> which (D)CHECKS
// (depending on the bool) against limits of T and then static_casts.
size_t offset_l = it.rinfo()->pc() - caller->instruction_start();
DCHECK_GE(kMaxInt, offset_l);
int offset = static_cast<int>(offset_l);
// Call offset points to the instruction after the call. Remember the last
// called code object before that offset.
if (offset < call_offset) lazy_callee = callee;
if (callee->builtin_index() != Builtins::kWasmCompileLazy) continue;
int byte_pos =
AdvanceSourcePositionTableIterator(source_pos_iterator, offset);
int called_func_index =
ExtractDirectCallIndex(decoder, func_bytes + byte_pos);
non_compiled_functions.push_back({offset, called_func_index});
if (offset < call_offset) func_to_return_idx = called_func_index;
}
TRACE_LAZY("Found %zu non-compiled functions in caller.\n",
non_compiled_functions.size());
DCHECK_NOT_NULL(lazy_callee);
if (lazy_callee->kind() == Code::WASM_TO_WASM_FUNCTION) {
TRACE_LAZY("Callee is a wasm-to-wasm.\n");
wasm_to_wasm_callee = handle(lazy_callee, isolate);
// If we call a wasm-to-wasm wrapper, then this wrapper actually
// tail-called the lazy compile stub. Find it in the wrapper.
lazy_callee = ExtractWasmToWasmCallee(lazy_callee);
// This lazy compile stub belongs to the instance that was passed.
DCHECK_EQ(*instance,
*GetWasmFunctionInfo(isolate, handle(lazy_callee, isolate))
.instance.ToHandleChecked());
DCHECK_LE(2, lazy_callee->deoptimization_data()->length());
func_to_return_idx =
Smi::ToInt(lazy_callee->deoptimization_data()->get(1));
}
DCHECK_EQ(Builtins::kWasmCompileLazy, lazy_callee->builtin_index());
// There must be at least one call to patch (the one that lead to calling
// the lazy compile stub).
DCHECK(!non_compiled_functions.empty() || !wasm_to_wasm_callee.is_null());
}
TRACE_LAZY("Compiling function %d.\n", func_to_return_idx);
// TODO(clemensh): compile all functions in non_compiled_functions in
// background, wait for func_to_return_idx.
CompileFunction(isolate, instance, func_to_return_idx);
Handle<Code> compiled_function(
Code::cast(compiled_module->code_table()->get(func_to_return_idx)),
isolate);
DCHECK_EQ(Code::WASM_FUNCTION, compiled_function->kind());
if (patch_caller || is_js_to_wasm) {
DisallowHeapAllocation no_gc;
// TODO(6792): No longer needed once WebAssembly code is off heap.
CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
// Now patch the code object with all functions which are now compiled.
int idx = 0;
int patched = 0;
for (RelocIterator it(*caller, RelocInfo::kCodeTargetMask); !it.done();
it.next()) {
Code* callee =
Code::GetCodeFromTargetAddress(it.rinfo()->target_address());
if (callee->builtin_index() != Builtins::kWasmCompileLazy) {
// If the callee is the wasm-to-wasm wrapper triggering this lazy
// compilation, patch it. If is_js_to_wasm is set, we did not set the
// wasm_to_wasm_callee, so just check the code kind (this is the only
// call in that wrapper anyway).
if ((is_js_to_wasm && callee->kind() == Code::WASM_TO_WASM_FUNCTION) ||
(!wasm_to_wasm_callee.is_null() &&
callee == *wasm_to_wasm_callee)) {
TRACE_LAZY("Patching wasm-to-wasm wrapper.\n");
PatchWasmToWasmWrapper(isolate, callee, *compiled_function);
++patched;
}
continue;
}
int called_func_index = func_to_return_idx;
if (!is_js_to_wasm) {
DCHECK_GT(non_compiled_functions.size(), idx);
called_func_index = non_compiled_functions[idx].func_index;
DCHECK_EQ(non_compiled_functions[idx].offset,
it.rinfo()->pc() - caller->instruction_start());
++idx;
}
// Check that the callee agrees with our assumed called_func_index.
DCHECK_IMPLIES(callee->deoptimization_data()->length() > 0,
Smi::ToInt(callee->deoptimization_data()->get(1)) ==
called_func_index);
Handle<Code> callee_compiled(
Code::cast(compiled_module->code_table()->get(called_func_index)));
if (callee_compiled->builtin_index() == Builtins::kWasmCompileLazy) {
DCHECK_NE(func_to_return_idx, called_func_index);
continue;
}
DCHECK_EQ(Code::WASM_FUNCTION, callee_compiled->kind());
it.rinfo()->set_target_address(isolate,
callee_compiled->instruction_start());
++patched;
}
DCHECK_EQ(non_compiled_functions.size(), idx);
TRACE_LAZY("Patched %d location(s) in the caller.\n", patched);
DCHECK_LT(0, patched);
USE(patched);
}
return compiled_function;
}
const wasm::WasmCode* LazyCompilationOrchestrator::CompileFromJsToWasm(
Isolate* isolate, Handle<WasmInstanceObject> instance,
Handle<Code> js_to_wasm_caller, uint32_t exported_func_index) {
Decoder decoder(nullptr, nullptr);
Handle<WasmCompiledModule> compiled_module(instance->compiled_module(),
isolate);
TRACE_LAZY(
"Starting lazy compilation (func %u, js_to_wasm: true, patch caller: "
"true). \n",
exported_func_index);
CompileFunction(isolate, instance, exported_func_index);
{
DisallowHeapAllocation no_gc;
CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
RelocIterator it(*js_to_wasm_caller,
RelocInfo::ModeMask(RelocInfo::JS_TO_WASM_CALL));
DCHECK(!it.done());
wasm::WasmCode* current_callee =
isolate->wasm_engine()->code_manager()->LookupCode(
it.rinfo()->js_to_wasm_address());
const wasm::WasmCode* callee_compiled =
compiled_module->GetNativeModule()->GetCode(exported_func_index);
DCHECK_NOT_NULL(callee_compiled);
if (current_callee->kind() == WasmCode::kWasmToWasmWrapper) {
WasmPatchWasmToWasmWrapper(isolate, current_callee, callee_compiled);
} else {
it.rinfo()->set_js_to_wasm_address(
isolate, callee_compiled->instructions().start());
}
#ifdef DEBUG
it.next();
DCHECK(it.done());
#endif
}
wasm::WasmCode* ret =
compiled_module->GetNativeModule()->GetCode(exported_func_index);
DCHECK_NOT_NULL(ret);
DCHECK_EQ(wasm::WasmCode::kFunction, ret->kind());
return ret;
}
const wasm::WasmCode* LazyCompilationOrchestrator::CompileIndirectCall(
Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t func_index) {
TRACE_LAZY(
"Starting lazy compilation (func %u, js_to_wasm: false, patch caller: "
"false). \n",
func_index);
return CompileFunction(isolate, instance, func_index);
}
const wasm::WasmCode* LazyCompilationOrchestrator::CompileDirectCall(
Isolate* isolate, Handle<WasmInstanceObject> instance,
Maybe<uint32_t> maybe_func_to_return_idx, const wasm::WasmCode* wasm_caller,
int call_offset) {
std::vector<Maybe<uint32_t>> non_compiled_functions;
Decoder decoder(nullptr, nullptr);
WasmCode* last_callee = nullptr;
{
DisallowHeapAllocation no_gc;
Handle<WasmCompiledModule> caller_module(
wasm_caller->owner()->compiled_module(), isolate);
SeqOneByteString* module_bytes = caller_module->shared()->module_bytes();
uint32_t caller_func_index = wasm_caller->index();
SourcePositionTableIterator source_pos_iterator(
Handle<ByteArray>(ByteArray::cast(
caller_module->source_positions()->get(caller_func_index))));
const byte* func_bytes =
module_bytes->GetChars() + caller_module->shared()
->module()
->functions[caller_func_index]
.code.offset();
for (RelocIterator it(wasm_caller->instructions(),
wasm_caller->reloc_info(),
wasm_caller->constant_pool(),
RelocInfo::ModeMask(RelocInfo::WASM_CALL));
!it.done(); it.next()) {
// TODO(clemensh): Introduce safe_cast<T, bool> which (D)CHECKS
// (depending on the bool) against limits of T and then static_casts.
size_t offset_l = it.rinfo()->pc() - wasm_caller->instructions().start();
DCHECK_GE(kMaxInt, offset_l);
int offset = static_cast<int>(offset_l);
int byte_pos =
AdvanceSourcePositionTableIterator(source_pos_iterator, offset);
WasmCode* callee = isolate->wasm_engine()->code_manager()->LookupCode(
it.rinfo()->target_address());
if (offset < call_offset) last_callee = callee;
if (callee->kind() != WasmCode::kLazyStub) {
non_compiled_functions.push_back(Nothing<uint32_t>());
continue;
}
uint32_t called_func_index =
ExtractDirectCallIndex(decoder, func_bytes + byte_pos);
DCHECK_LT(called_func_index,
caller_module->GetNativeModule()->FunctionCount());
non_compiled_functions.push_back(Just(called_func_index));
// Call offset one instruction after the call. Remember the last called
// function before that offset.
if (offset < call_offset) {
maybe_func_to_return_idx = Just(called_func_index);
}
}
}
uint32_t func_to_return_idx = 0;
if (last_callee->kind() == WasmCode::kWasmToWasmWrapper) {
const WasmCode* actual_callee = WasmExtractWasmToWasmCallee(
isolate->wasm_engine()->code_manager(), last_callee);
func_to_return_idx = actual_callee->index();
} else {
func_to_return_idx = maybe_func_to_return_idx.ToChecked();
}
TRACE_LAZY(
"Starting lazy compilation (func %u @%d, js_to_wasm: false, patch "
"caller: true). \n",
func_to_return_idx, call_offset);
// TODO(clemensh): compile all functions in non_compiled_functions in
// background, wait for func_to_return_idx.
const WasmCode* ret = CompileFunction(isolate, instance, func_to_return_idx);
DCHECK_NOT_NULL(ret);
if (last_callee->kind() == WasmCode::kWasmToWasmWrapper) {
// We can finish it all here by compiling the target wasm function and
// patching the wasm_to_wasm caller.
WasmPatchWasmToWasmWrapper(isolate, last_callee, ret);
} else {
Handle<WasmCompiledModule> compiled_module(instance->compiled_module(),
isolate);
DisallowHeapAllocation no_gc;
// Now patch the code object with all functions which are now compiled. This
// will pick up any other compiled functions, not only {ret}.
size_t idx = 0;
size_t patched = 0;
for (RelocIterator
it(wasm_caller->instructions(), wasm_caller->reloc_info(),
wasm_caller->constant_pool(),
RelocInfo::ModeMask(RelocInfo::WASM_CALL));
!it.done(); it.next(), ++idx) {
auto& info = non_compiled_functions[idx];
if (info.IsNothing()) continue;
uint32_t lookup = info.ToChecked();
const WasmCode* callee_compiled =
compiled_module->GetNativeModule()->GetCode(lookup);
if (callee_compiled->kind() != WasmCode::kFunction) continue;
it.rinfo()->set_wasm_call_address(
isolate, callee_compiled->instructions().start());
++patched;
}
DCHECK_EQ(non_compiled_functions.size(), idx);
TRACE_LAZY("Patched %zu location(s) in the caller.\n", patched);
}
return ret;
}
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,
wasm::NativeModule* native_module)
: 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),
native_module_(native_module) {}
// The actual runnable task that performs compilations in the background.
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);
DCHECK_IMPLIES(FLAG_wasm_jit_to_native, native_module_ != nullptr);
builder.AddUnit(module_env, native_module_, func, buffer_offset, bytes,
name);
}
builder.Commit();
return funcs_to_compile;
}
void ModuleCompiler::RestartCompilationTasks() {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
std::shared_ptr<v8::TaskRunner> task_runner =
V8::GetCurrentPlatform()->GetBackgroundTaskRunner(v8_isolate);
base::LockGuard<base::Mutex> guard(&tasks_mutex_);
for (; stopped_compilation_tasks_ > 0; --stopped_compilation_tasks_) {
task_runner->PostTask(base::make_unique<CompilationTask>(this));
}
}
size_t ModuleCompiler::FinishCompilationUnits(
std::vector<Handle<Code>>& results, ErrorThrower* thrower) {
size_t finished = 0;
while (true) {
int func_index = -1;
WasmCodeWrapper result = FinishCompilationUnit(thrower, &func_index);
if (func_index < 0) break;
++finished;
DCHECK_IMPLIES(result.is_null(), thrower->error());
if (result.is_null()) break;
if (result.IsCodeObject()) {
results[func_index] = result.GetCode();
}
}
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;
}
WasmCodeWrapper 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.
WasmCodeWrapper code = compiler::WasmCompilationUnit::CompileWasmFunction(
native_module_, 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;
}
if (code.IsCodeObject()) {
results[i] = code.GetCode();
}
}
}
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();
// TODO(mtrofin): the wasm::NativeModule parameter to the ModuleCompiler
// constructor is null here, and initialized in CompileToModuleObjectInternal.
// This is a point-in-time, until we remove the FLAG_wasm_jit_to_native flag,
// and stop needing a FixedArray for code for the non-native case. Otherwise,
// we end up moving quite a bit of initialization logic here that is also
// needed in CompileToModuleObjectInternal, complicating the change.
ModuleCompiler compiler(isolate, module.get(), centry_stub, nullptr);
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, const wasm::NativeModule* native_module) {
for (uint32_t i = 0, e = native_module->FunctionCount(); i < e; ++i) {
const wasm::WasmCode* code = native_module->GetCode(i);
if (code == nullptr) continue;
Assembler::FlushICache(isolate, code->instructions().start(),
code->instructions().size());
}
}
void FlushICache(Isolate* isolate, Handle<FixedArray> functions) {
for (int i = 0, e = functions->length(); i < e; ++i) {
if (!functions->get(i)->IsCode()) continue;
Code* code = Code::cast(functions->get(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(const Code* code, Counters* counters) {
counters->wasm_generated_code_size()->Increment(code->body_size());
counters->wasm_reloc_size()->Increment(code->relocation_info()->length());
}
void RecordStats(const wasm::WasmCode* code, Counters* counters) {
counters->wasm_generated_code_size()->Increment(
static_cast<int>(code->instructions().size()));
counters->wasm_reloc_size()->Increment(
static_cast<int>(code->reloc_info().size()));
}
void RecordStats(WasmCodeWrapper wrapper, Counters* counters) {
if (wrapper.IsCodeObject()) {
RecordStats(*wrapper.GetCode(), counters);
} else {
RecordStats(wrapper.GetWasmCode(), counters);
}
}
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);
}
}
void RecordStats(const wasm::NativeModule* native_module, Counters* counters) {
for (uint32_t i = 0, e = native_module->FunctionCount(); i < e; ++i) {
const wasm::WasmCode* code = native_module->GetCode(i);
if (code != nullptr) RecordStats(code, counters);
}
}
// 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.
// TODO(mtrofin): remove the instance and code_table members once we remove the
// FLAG_wasm_jit_to_native
WasmCodeWrapper EnsureExportedLazyDeoptData(Isolate* isolate,
Handle<WasmInstanceObject> instance,
Handle<FixedArray> code_table,
wasm::NativeModule* native_module,
uint32_t func_index) {
if (!FLAG_wasm_jit_to_native) {
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->kind() == Code::WASM_TO_WASM_FUNCTION ||
code->builtin_index() == Builtins::kIllegal);
return WasmCodeWrapper(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);
AttachWasmFunctionInfo(isolate, code, instance, func_index);
}
#ifdef DEBUG
auto func_info = GetWasmFunctionInfo(isolate, code);
DCHECK_IMPLIES(!instance.is_null(),
*func_info.instance.ToHandleChecked() == *instance);
DCHECK_EQ(func_index, func_info.func_index);
#endif
return WasmCodeWrapper(code);
} else {
wasm::WasmCode* code = native_module->GetCode(func_index);
// {code} will be nullptr when exporting imports.
if (code == nullptr || code->kind() != wasm::WasmCode::kLazyStub ||
!code->IsAnonymous()) {
return WasmCodeWrapper(code);
}
// Clone the lazy builtin into the native module.
return WasmCodeWrapper(native_module->CloneLazyBuiltinInto(func_index));
}
}
// 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.
// TODO(mtrofin): remove the instance and code_table members once we remove the
// FLAG_wasm_jit_to_native
WasmCodeWrapper EnsureTableExportLazyDeoptData(
Isolate* isolate, Handle<WasmInstanceObject> instance,
Handle<FixedArray> code_table, wasm::NativeModule* native_module,
uint32_t func_index, Handle<FixedArray> export_table, int export_index,
std::unordered_map<uint32_t, uint32_t>* table_export_count) {
if (!FLAG_wasm_jit_to_native) {
Handle<Code> code =
EnsureExportedLazyDeoptData(isolate, instance, code_table,
native_module, func_index)
.GetCode();
if (code->builtin_index() != Builtins::kWasmCompileLazy)
return WasmCodeWrapper(code);
// TODO(6792): No longer needed once WebAssembly code is off heap.
CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
// 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 WasmCodeWrapper(code);
} else {
const wasm::WasmCode* code =
EnsureExportedLazyDeoptData(isolate, instance, code_table,
native_module, func_index)
.GetWasmCode();
if (code == nullptr || code->kind() != wasm::WasmCode::kLazyStub)
return WasmCodeWrapper(code);
// deopt_data:
// [#0: export table
// #1: export table index]
// [#2: export table
// #3: 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);
--table_export_entry->second;
uint32_t this_idx = 2 * table_export_entry->second;
int int_func_index = static_cast<int>(func_index);
Object* deopt_entry =
native_module->compiled_module()->lazy_compile_data()->get(
int_func_index);
FixedArray* deopt_data = nullptr;
if (!deopt_entry->IsFixedArray()) {
// we count indices down, so we enter here first for the
// largest index.
deopt_data = *isolate->factory()->NewFixedArray(this_idx + 2, TENURED);
native_module->compiled_module()->lazy_compile_data()->set(int_func_index,
deopt_data);
} else {
deopt_data = FixedArray::cast(deopt_entry);
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 WasmCodeWrapper(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>;
WasmCodeWrapper MakeWasmToWasmWrapper(
Isolate* isolate, Handle<WasmExportedFunction> imported_function,
FunctionSig* expected_sig, FunctionSig** sig,
WasmInstanceMap* imported_instances, Handle<WasmInstanceObject> instance,
uint32_t index) {
// TODO(wasm): cache WASM-to-WASM wrappers by signature and clone+patch.
Handle<WasmInstanceObject> imported_instance(imported_function->instance(),
isolate);
imported_instances->Set(imported_instance, imported_instance);
WasmContext* new_wasm_context = imported_instance->wasm_context()->get();
Address new_wasm_context_address =
reinterpret_cast<Address>(new_wasm_context);
*sig = imported_instance->module()
->functions[imported_function->function_index()]
.sig;
if (expected_sig && !expected_sig->Equals(*sig)) return {};
if (!FLAG_wasm_jit_to_native) {
Handle<Code> wrapper_code = compiler::CompileWasmToWasmWrapper(
isolate, imported_function->GetWasmCode(), *sig,
new_wasm_context_address);
// Set the deoptimization data for the WasmToWasm wrapper. This is
// needed by the interpreter to find the imported instance for
// a cross-instance call.
AttachWasmFunctionInfo(isolate, wrapper_code, imported_instance,
imported_function->function_index());
return WasmCodeWrapper(wrapper_code);
} else {
Handle<Code> code = compiler::CompileWasmToWasmWrapper(
isolate, imported_function->GetWasmCode(), *sig,
new_wasm_context_address);
return WasmCodeWrapper(
instance->compiled_module()->GetNativeModule()->AddCodeCopy(
code, wasm::WasmCode::kWasmToWasmWrapper, index));
}
}
WasmCodeWrapper UnwrapExportOrCompileImportWrapper(
Isolate* isolate, FunctionSig* sig, Handle<JSReceiver> target,
uint32_t import_index, ModuleOrigin origin,
WasmInstanceMap* imported_instances, Handle<FixedArray> js_imports_table,
Handle<WasmInstanceObject> instance) {
if (WasmExportedFunction::IsWasmExportedFunction(*target)) {
FunctionSig* unused = nullptr;
return MakeWasmToWasmWrapper(
isolate, Handle<WasmExportedFunction>::cast(target), sig, &unused,
imported_instances, instance, import_index);
}
// No wasm function or being debugged. Compile a new wrapper for the new
// signature.
if (FLAG_wasm_jit_to_native) {
Handle<Code> temp_code = compiler::CompileWasmToJSWrapper(
isolate, target, sig, import_index, origin,
instance->compiled_module()->use_trap_handler(), js_imports_table);
return WasmCodeWrapper(
instance->compiled_module()->GetNativeModule()->AddCodeCopy(
temp_code, wasm::WasmCode::kWasmToJsWrapper, import_index));
} else {
return WasmCodeWrapper(compiler::CompileWasmToJSWrapper(
isolate, target, sig, import_index, origin,
instance->compiled_module()->use_trap_handler(), 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;
for (size_t i = module->function_tables.size(); i > 0; --i) {
Handle<Object> func_table =
isolate->global_handles()->Create(isolate->heap()->undefined_value());
GlobalHandles::MakeWeak(func_table.location(), func_table.location(),
&FunctionTableFinalizer,
v8::WeakCallbackType::kFinalizer);
function_tables.push_back(func_table.address());
}
// TODO(kschimpf): Add module-specific policy handling here (see v8:7143)?
bool use_trap_handler = trap_handler::IsTrapHandlerEnabled();
return base::make_unique<compiler::ModuleEnv>(
module, function_tables, std::vector<Handle<Code>>{}, illegal_builtin,
use_trap_handler);
}
// TODO(mtrofin): remove code_table when we don't need FLAG_wasm_jit_to_native
Handle<WasmCompiledModule> NewCompiledModule(Isolate* isolate,
WasmModule* module,
Handle<FixedArray> code_table,
Handle<FixedArray> export_wrappers,
compiler::ModuleEnv* env) {
Handle<WasmCompiledModule> compiled_module =
WasmCompiledModule::New(isolate, module, code_table, export_wrappers,
env->function_tables, env->use_trap_handler);
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());
// TODO(6792): No longer needed once WebAssembly code is off heap. Use
// base::Optional to be able to close the scope before notifying the debugger.
base::Optional<CodeSpaceMemoryModificationScope> modification_scope(
base::in_place_t(), isolate_->heap());
// 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();
// 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);
Handle<Code> init_builtin = lazy_compile
? BUILTIN_CODE(isolate_, WasmCompileLazy)
: BUILTIN_CODE(isolate_, Illegal);
// TODO(mtrofin): remove code_table and code_table_size when we don't
// need FLAG_wasm_jit_to_native anymore. Keep 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);
}
auto env = CreateDefaultModuleEnv(isolate_, module_, init_builtin);
// Create the compiled module object and populate with compiled functions
// and information needed at instantiation time. This object needs to be
// serializable. Instantiation may occur off a deserialized version of this
// object.
Handle<WasmCompiledModule> compiled_module = NewCompiledModule(
isolate_, shared->module(), code_table, export_wrappers, env.get());
native_module_ = compiled_module->GetNativeModule();
compiled_module->OnWasmModuleDecodingComplete(shared);
if (lazy_compile && FLAG_wasm_jit_to_native) {
Handle<FixedArray> lazy_compile_data = isolate_->factory()->NewFixedArray(
static_cast<int>(module_->functions.size()), TENURED);
compiled_module->set_lazy_compile_data(*lazy_compile_data);
}
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(
FLAG_wasm_jit_to_native ? 0 : 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 {};
if (!FLAG_wasm_jit_to_native) {
// 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 {
RecordStats(native_module_, 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 (FLAG_wasm_jit_to_native) {
native_module_->SetLazyBuiltin(BUILTIN_CODE(isolate_, WasmCompileLazy));
}
}
if (thrower->error()) return {};
// Compile JS->wasm wrappers for exported functions.
CompileJsToWasmWrappers(isolate_, compiled_module, counters());
Handle<WasmModuleObject> result =
WasmModuleObject::New(isolate_, compiled_module);
// If we created a wasm script, finish it now and make it public to the
// debugger.
if (asm_js_script.is_null()) {
// Close the CodeSpaceMemoryModificationScope before calling into the
// debugger.
modification_scope.reset();
script->set_wasm_compiled_module(*compiled_module);
isolate_->debug()->OnAfterCompile(script);
}
return result;
}
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()->shared()->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 {};
// TODO(6792): No longer needed once WebAssembly code is off heap.
// Use base::Optional to be able to close the scope before executing the start
// function.
base::Optional<CodeSpaceMemoryModificationScope> modification_scope(
base::in_place_t(), isolate_->heap());
// 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.
//--------------------------------------------------------------------------
// TODO(mtrofin): remove code_table and old_code_table
// when FLAG_wasm_jit_to_native is not needed
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;
// native_module is the one we're building now, old_module
// is the one we clone from. They point to the same place if
// we don't need to clone.
wasm::NativeModule* native_module = nullptr;
wasm::NativeModule* old_module = nullptr;
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());
if (FLAG_wasm_jit_to_native) {
TRACE("Cloning from %zu\n", original->GetNativeModule()->instance_id);
compiled_module_ = WasmCompiledModule::Clone(isolate_, original);
native_module = compiled_module_->GetNativeModule();
wrapper_table = handle(compiled_module_->export_wrappers(), isolate_);
} else {
TRACE("Cloning from %d\n", original->instance_id());
old_code_table = handle(original->code_table(), isolate_);
compiled_module_ = WasmCompiledModule::Clone(isolate_, original);
code_table = handle(compiled_module_->code_table(), isolate_);
wrapper_table = handle(compiled_module_->export_wrappers(), isolate_);
// 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:
case Code::WASM_TO_WASM_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);
AttachWasmFunctionInfo(isolate_, code,
Handle<WasmInstanceObject>(), i);
code_table->set(i, *code);
}
break;
case Code::WASM_FUNCTION: {
Handle<Code> code = factory->CopyCode(orig_code);
AttachWasmFunctionInfo(isolate_, code,
Handle<WasmInstanceObject>(), i);
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);
}
if (FLAG_wasm_jit_to_native) {
RecordStats(native_module, counters());
} else {
RecordStats(code_table, counters());
}
RecordStats(wrapper_table, counters());
} else {
// There was no owner, so we can reuse the original.
compiled_module_ = original;
wrapper_table = handle(compiled_module_->export_wrappers(), isolate_);
if (FLAG_wasm_jit_to_native) {
old_module = compiled_module_->GetNativeModule();
native_module = old_module;
TRACE("Reusing existing instance %zu\n",
compiled_module_->GetNativeModule()->instance_id);
} else {
code_table = handle(compiled_module_->code_table(), isolate_);
old_code_table = factory->CopyFixedArray(code_table);
TRACE("Reusing existing instance %d\n",
compiled_module_->instance_id());
}
}
Handle<WeakCell> weak_native_context =
isolate_->factory()->NewWeakCell(isolate_->native_context());
compiled_module_->set_weak_native_context(*weak_native_context);
}
base::Optional<wasm::NativeModuleModificationScope>
native_module_modification_scope;
if (native_module != nullptr) {
native_module_modification_scope.emplace(native_module);
}
//--------------------------------------------------------------------------
// Create the WebAssembly.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.
//--------------------------------------------------------------------------
WasmContext* wasm_context = instance->wasm_context()->get();
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 {};
}
wasm_context->globals_start =
reinterpret_cast<byte*>(global_buffer->backing_store());
instance->set_globals_buffer(*global_buffer);
}
//--------------------------------------------------------------------------
// Reserve the metadata for indirect 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_.emplace_back();
}
//--------------------------------------------------------------------------
// 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();
//--------------------------------------------------------------------------
// Initialize the indirect tables.
//--------------------------------------------------------------------------
if (function_table_count > 0) {
InitializeTables(instance, &code_specialization);
}
//--------------------------------------------------------------------------
// Allocate the memory array buffer.
//--------------------------------------------------------------------------
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()) {
// Set externally passed ArrayBuffer non neuterable.
Handle<JSArrayBuffer> memory = memory_.ToHandleChecked();
memory->set_is_neuterable(false);
DCHECK_IMPLIES(use_trap_handler(),
module_->is_asm_js() || memory->has_guard_region());
} else if (initial_pages > 0) {
// Allocate memory if the initial size is more than 0 pages.
memory_ = AllocateMemory(initial_pages);
if (memory_.is_null()) return {}; // failed to allocate memory
}
//--------------------------------------------------------------------------
// Create the WebAssembly.Memory object.
//--------------------------------------------------------------------------
if (module_->has_memory) {
if (!instance->has_memory_object()) {
// No memory object exists. Create one.
Handle<WasmMemoryObject> memory_object = WasmMemoryObject::New(
isolate_, memory_,
module_->maximum_pages != 0 ? module_->maximum_pages : -1);
instance->set_memory_object(*memory_object);
}
// Add the instance object to the list of instances for this memory.
Handle<WasmMemoryObject> memory_object(instance->memory_object(), isolate_);
WasmMemoryObject::AddInstance(isolate_, memory_object, instance);
if (!memory_.is_null()) {
// Double-check the {memory} array buffer matches the context.
Handle<JSArrayBuffer> memory = memory_.ToHandleChecked();
uint32_t mem_size = 0;
CHECK(memory->byte_length()->ToUint32(&mem_size));
CHECK_EQ(wasm_context->mem_size, mem_size);
CHECK_EQ(wasm_context->mem_start, memory->backing_store());
}
}
//--------------------------------------------------------------------------
// 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() /
compiler::kFunctionTableEntrySize;
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);
if (!in_bounds(base, seg.source.length(), wasm_context->mem_size)) {
thrower_->LinkError("data segment is out of bounds");
return {};
}
}
// Set the WasmContext address in wrappers.
// TODO(wasm): the wasm context should only appear as a constant in wrappers;
// this code specialization is applied to the whole instance.
Address wasm_context_address = reinterpret_cast<Address>(wasm_context);
code_specialization.RelocateWasmContextReferences(wasm_context_address);
js_to_wasm_cache_.SetContextAddress(wasm_context_address);
if (!FLAG_wasm_jit_to_native) {
//--------------------------------------------------------------------------
// 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) {
AttachWasmFunctionInfo(isolate_, code, weak_link, i);
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 {};
//--------------------------------------------------------------------------
// Initialize the indirect function tables.
//--------------------------------------------------------------------------
if (function_table_count > 0) {
LoadTableSegments(code_table, instance);
}
//--------------------------------------------------------------------------
// Initialize the memory by loading data segments.
//--------------------------------------------------------------------------
if (module_->data_segments.size() > 0) {
LoadDataSegments(wasm_context);
}
// Patch all code with the relocations registered in code_specialization.
code_specialization.RelocateDirectCalls(instance);
code_specialization.ApplyToWholeInstance(*instance, SKIP_ICACHE_FLUSH);
if (FLAG_wasm_jit_to_native) {
FlushICache(isolate_, native_module);
} else {
FlushICache(isolate_, code_table);
}
FlushICache(isolate_, wrapper_table);
//--------------------------------------------------------------------------
// Unpack and notify signal handler of protected instructions.
//--------------------------------------------------------------------------
if (use_trap_handler()) {
if (FLAG_wasm_jit_to_native) {
UnpackAndRegisterProtectedInstructions(isolate_, native_module);
} else {
UnpackAndRegisterProtectedInstructionsGC(isolate_, code_table);
}
}
//--------------------------------------------------------------------------
// Insert the compiled module into the weak list of compiled modules.
//--------------------------------------------------------------------------
{
Handle<Object> global_handle =
isolate_->global_handles()->Create(*instance);
Handle<WeakCell> link_to_owning_instance = factory->NewWeakCell(instance);
if (!owner.is_null()) {
// Publish the new instance to the instances chain.
DisallowHeapAllocation no_gc;
compiled_module_->InsertInChain(*module_object_);
}
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(
handle(compiled_module_->shared(), isolate_), 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())));
}
//--------------------------------------------------------------------------
// Execute the start function if one was specified.
//--------------------------------------------------------------------------
if (module_->start_function_index >= 0) {
HandleScope scope(isolate_);
int start_index = module_->start_function_index;
WasmCodeWrapper startup_code = EnsureExportedLazyDeoptData(
isolate_, instance, code_table, native_module, start_index);
FunctionSig* sig = module_->functions[start_index].sig;
Handle<Code> wrapper_code = js_to_wasm_cache_.CloneOrCompileJSToWasmWrapper(
isolate_, module_, startup_code, start_index,
compiled_module_->use_trap_handler());
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();
// Close the modification scopes, so we can execute the start function.
modification_scope.reset();
native_module_modification_scope.reset();
{
// 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());
if (FLAG_wasm_jit_to_native) {
TRACE("Successfully built instance %zu\n",
compiled_module_->GetNativeModule()->instance_id);
} else {
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;
}