blob: 8a8871929fc0f77bc035584197ea6c49d4282a37 [file]
// Copyright 2019 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-instantiate.h"
#include "src/api/api-inl.h"
#include "src/asmjs/asm-js.h"
#include "src/base/atomicops.h"
#include "src/codegen/compiler.h"
#include "src/compiler/wasm-compiler.h"
#include "src/logging/counters-scopes.h"
#include "src/logging/metrics.h"
#include "src/numbers/conversions-inl.h"
#include "src/objects/descriptor-array-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/tracing/trace-event.h"
#include "src/utils/utils.h"
#include "src/wasm/code-space-access.h"
#include "src/wasm/constant-expression-interface.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/module-decoder-impl.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-external-refs.h"
#include "src/wasm/wasm-import-wrapper-cache.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes-inl.h"
#include "src/wasm/wasm-subtyping.h"
#define TRACE(...) \
do { \
if (v8_flags.trace_wasm_instances) PrintF(__VA_ARGS__); \
} while (false)
namespace v8 {
namespace internal {
namespace wasm {
namespace {
byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) {
return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset;
}
using ImportWrapperQueue =
WrapperQueue<WasmImportWrapperCache::CacheKey, const FunctionSig*,
WasmImportWrapperCache::CacheKeyHash>;
class CompileImportWrapperJob final : public JobTask {
public:
CompileImportWrapperJob(
Counters* counters, NativeModule* native_module,
ImportWrapperQueue* queue,
WasmImportWrapperCache::ModificationScope* cache_scope)
: counters_(counters),
native_module_(native_module),
queue_(queue),
cache_scope_(cache_scope) {}
size_t GetMaxConcurrency(size_t worker_count) const override {
size_t flag_limit = static_cast<size_t>(
std::max(1, v8_flags.wasm_num_compilation_tasks.value()));
// Add {worker_count} to the queue size because workers might still be
// processing units that have already been popped from the queue.
return std::min(flag_limit, worker_count + queue_->size());
}
void Run(JobDelegate* delegate) override {
TRACE_EVENT0("v8.wasm", "wasm.CompileImportWrapperJob.Run");
while (base::Optional<std::pair<const WasmImportWrapperCache::CacheKey,
const FunctionSig*>>
key = queue_->pop()) {
// TODO(wasm): Batch code publishing, to avoid repeated locking and
// permission switching.
CompileImportWrapper(native_module_, counters_, key->first.kind,
key->second, key->first.canonical_type_index,
key->first.expected_arity, key->first.suspend,
cache_scope_);
if (delegate->ShouldYield()) return;
}
}
private:
Counters* const counters_;
NativeModule* const native_module_;
ImportWrapperQueue* const queue_;
WasmImportWrapperCache::ModificationScope* const cache_scope_;
};
Handle<DescriptorArray> CreateArrayDescriptorArray(
Isolate* isolate, const wasm::ArrayType* type) {
uint32_t kDescriptorsCount = 1;
Handle<DescriptorArray> descriptors =
isolate->factory()->NewDescriptorArray(kDescriptorsCount);
// TODO(ishell): cache Wasm field type in FieldType value.
MaybeObject any_type = MaybeObject::FromObject(FieldType::Any());
DCHECK(any_type->IsSmi());
// Add descriptor for length property.
PropertyDetails details(PropertyKind::kData, FROZEN, PropertyLocation::kField,
PropertyConstness::kConst,
Representation::WasmValue(), static_cast<int>(0));
descriptors->Set(InternalIndex(0), *isolate->factory()->length_string(),
any_type, details);
descriptors->Sort();
return descriptors;
}
Handle<Map> CreateStructMap(Isolate* isolate, const WasmModule* module,
int struct_index, Handle<Map> opt_rtt_parent,
Handle<WasmInstanceObject> instance) {
const wasm::StructType* type = module->struct_type(struct_index);
const int inobject_properties = 0;
// We have to use the variable size sentinel because the instance size
// stored directly in a Map is capped at 255 pointer sizes.
const int map_instance_size = kVariableSizeSentinel;
const int real_instance_size = WasmStruct::Size(type);
const InstanceType instance_type = WASM_STRUCT_TYPE;
// TODO(jkummerow): If NO_ELEMENTS were supported, we could use that here.
const ElementsKind elements_kind = TERMINAL_FAST_ELEMENTS_KIND;
Handle<WasmTypeInfo> type_info = isolate->factory()->NewWasmTypeInfo(
reinterpret_cast<Address>(type), opt_rtt_parent, real_instance_size,
instance, struct_index);
Handle<Map> map = isolate->factory()->NewMap(
instance_type, map_instance_size, elements_kind, inobject_properties);
map->set_wasm_type_info(*type_info);
map->SetInstanceDescriptors(isolate,
*isolate->factory()->empty_descriptor_array(), 0);
map->set_is_extensible(false);
WasmStruct::EncodeInstanceSizeInMap(real_instance_size, *map);
return map;
}
Handle<Map> CreateArrayMap(Isolate* isolate, const WasmModule* module,
int array_index, Handle<Map> opt_rtt_parent,
Handle<WasmInstanceObject> instance) {
const wasm::ArrayType* type = module->array_type(array_index);
const int inobject_properties = 0;
const int instance_size = kVariableSizeSentinel;
// Wasm Arrays don't have a static instance size.
const int cached_instance_size = 0;
const InstanceType instance_type = WASM_ARRAY_TYPE;
const ElementsKind elements_kind = TERMINAL_FAST_ELEMENTS_KIND;
Handle<WasmTypeInfo> type_info = isolate->factory()->NewWasmTypeInfo(
reinterpret_cast<Address>(type), opt_rtt_parent, cached_instance_size,
instance, array_index);
// TODO(ishell): get canonical descriptor array for WasmArrays from roots.
Handle<DescriptorArray> descriptors =
CreateArrayDescriptorArray(isolate, type);
Handle<Map> map = isolate->factory()->NewMap(
instance_type, instance_size, elements_kind, inobject_properties);
map->set_wasm_type_info(*type_info);
map->SetInstanceDescriptors(isolate, *descriptors,
descriptors->number_of_descriptors());
map->set_is_extensible(false);
WasmArray::EncodeElementSizeInMap(type->element_type().value_kind_size(),
*map);
return map;
}
Handle<Map> CreateFuncRefMap(Isolate* isolate, const WasmModule* module,
Handle<Map> opt_rtt_parent,
Handle<WasmInstanceObject> instance) {
const int inobject_properties = 0;
const int instance_size =
Map::cast(isolate->root(RootIndex::kWasmInternalFunctionMap))
.instance_size();
const InstanceType instance_type = WASM_INTERNAL_FUNCTION_TYPE;
const ElementsKind elements_kind = TERMINAL_FAST_ELEMENTS_KIND;
constexpr uint32_t kNoIndex = ~0u;
Handle<WasmTypeInfo> type_info = isolate->factory()->NewWasmTypeInfo(
kNullAddress, opt_rtt_parent, instance_size, instance, kNoIndex);
Handle<Map> map = isolate->factory()->NewMap(
instance_type, instance_size, elements_kind, inobject_properties);
map->set_wasm_type_info(*type_info);
return map;
}
void CreateMapForType(Isolate* isolate, const WasmModule* module,
int type_index, Handle<WasmInstanceObject> instance,
Handle<FixedArray> maps) {
// Recursive calls for supertypes may already have created this map.
if (maps->get(type_index).IsMap()) return;
Handle<WeakArrayList> canonical_rtts;
uint32_t canonical_type_index =
module->isorecursive_canonical_type_ids[type_index];
// Try to find the canonical map for this type in the isolate store.
canonical_rtts = handle(isolate->heap()->wasm_canonical_rtts(), isolate);
DCHECK_GT(static_cast<uint32_t>(canonical_rtts->length()),
canonical_type_index);
MaybeObject maybe_canonical_map = canonical_rtts->Get(canonical_type_index);
if (maybe_canonical_map.IsStrongOrWeak() &&
maybe_canonical_map.GetHeapObject().IsMap()) {
maps->set(type_index, maybe_canonical_map.GetHeapObject());
return;
}
Handle<Map> rtt_parent;
// If the type with {type_index} has an explicit supertype, make sure the
// map for that supertype is created first, so that the supertypes list
// that's cached on every RTT can be set up correctly.
uint32_t supertype = module->supertype(type_index);
if (supertype != kNoSuperType) {
// This recursion is safe, because kV8MaxRttSubtypingDepth limits the
// number of recursive steps, so we won't overflow the stack.
CreateMapForType(isolate, module, supertype, instance, maps);
rtt_parent = handle(Map::cast(maps->get(supertype)), isolate);
}
Handle<Map> map;
switch (module->types[type_index].kind) {
case TypeDefinition::kStruct:
map = CreateStructMap(isolate, module, type_index, rtt_parent, instance);
break;
case TypeDefinition::kArray:
map = CreateArrayMap(isolate, module, type_index, rtt_parent, instance);
break;
case TypeDefinition::kFunction:
map = CreateFuncRefMap(isolate, module, rtt_parent, instance);
break;
}
canonical_rtts->Set(canonical_type_index, HeapObjectReference::Weak(*map));
maps->set(type_index, *map);
}
MachineRepresentation NormalizeFastApiRepresentation(const CTypeInfo& info) {
MachineType t = MachineType::TypeForCType(info);
// Wasm representation of bool is i32 instead of i1.
if (t.semantic() == MachineSemantic::kBool) {
return MachineRepresentation::kWord32;
}
return t.representation();
}
bool IsSupportedWasmFastApiFunction(Isolate* isolate,
const wasm::FunctionSig* expected_sig,
Handle<SharedFunctionInfo> shared) {
if (!shared->IsApiFunction()) {
return false;
}
if (shared->get_api_func_data().GetCFunctionsCount() == 0) {
return false;
}
if (!shared->get_api_func_data().accept_any_receiver()) {
return false;
}
if (!shared->get_api_func_data().signature().IsUndefined()) {
// TODO(wasm): CFunctionInfo* signature check.
return false;
}
const CFunctionInfo* info = shared->get_api_func_data().GetCSignature(0);
if (!compiler::IsFastCallSupportedSignature(info)) {
return false;
}
const auto log_imported_function_mismatch = [&shared,
isolate](const char* reason) {
if (v8_flags.trace_opt) {
CodeTracer::Scope scope(isolate->GetCodeTracer());
PrintF(scope.file(), "[disabled optimization for ");
shared->ShortPrint(scope.file());
PrintF(scope.file(),
", reason: the signature of the imported function in the Wasm "
"module doesn't match that of the Fast API function (%s)]\n",
reason);
}
};
// C functions only have one return value.
if (expected_sig->return_count() > 1) {
// Here and below, we log when the function we call is declared as an Api
// function but we cannot optimize the call, which might be unxepected. In
// that case we use the "slow" path making a normal Wasm->JS call and
// calling the "slow" callback specified in FunctionTemplate::New().
log_imported_function_mismatch("too many return values");
return false;
}
CTypeInfo return_info = info->ReturnInfo();
// Unsupported if return type doesn't match.
if (expected_sig->return_count() == 0 &&
return_info.GetType() != CTypeInfo::Type::kVoid) {
log_imported_function_mismatch("too few return values");
return false;
}
// Unsupported if return type doesn't match.
if (expected_sig->return_count() == 1) {
if (return_info.GetType() == CTypeInfo::Type::kVoid) {
log_imported_function_mismatch("too many return values");
return false;
}
if (NormalizeFastApiRepresentation(return_info) !=
expected_sig->GetReturn(0).machine_type().representation()) {
log_imported_function_mismatch("mismatching return value");
return false;
}
}
// Unsupported if arity doesn't match.
if (expected_sig->parameter_count() != info->ArgumentCount() - 1) {
log_imported_function_mismatch("mismatched arity");
return false;
}
// Unsupported if any argument types don't match.
for (unsigned int i = 0; i < expected_sig->parameter_count(); i += 1) {
// Arg 0 is the receiver, skip over it since wasm doesn't
// have a concept of receivers.
CTypeInfo arg = info->ArgumentInfo(i + 1);
if (NormalizeFastApiRepresentation(arg) !=
expected_sig->GetParam(i).machine_type().representation()) {
log_imported_function_mismatch("parameter type mismatch");
return false;
}
}
return true;
}
bool ResolveBoundJSFastApiFunction(const wasm::FunctionSig* expected_sig,
Handle<JSReceiver> callable) {
Handle<JSFunction> target;
if (callable->IsJSBoundFunction()) {
Handle<JSBoundFunction> bound_target =
Handle<JSBoundFunction>::cast(callable);
// Nested bound functions and arguments not supported yet.
if (bound_target->bound_arguments().length() > 0) {
return false;
}
if (bound_target->bound_target_function().IsJSBoundFunction()) {
return false;
}
Handle<JSReceiver> bound_target_function =
handle(bound_target->bound_target_function(), callable->GetIsolate());
if (!bound_target_function->IsJSFunction()) {
return false;
}
target = Handle<JSFunction>::cast(bound_target_function);
} else if (callable->IsJSFunction()) {
target = Handle<JSFunction>::cast(callable);
} else {
return false;
}
Isolate* isolate = target->GetIsolate();
Handle<SharedFunctionInfo> shared(target->shared(), isolate);
return IsSupportedWasmFastApiFunction(isolate, expected_sig, shared);
}
namespace {
bool IsStringRef(wasm::ValueType type) {
return type.is_reference_to(wasm::HeapType::kString);
}
} // namespace
// This detects imports of the form: `Function.prototype.call.bind(foo)`, where
// `foo` is something that has a Builtin id.
WellKnownImport CheckForWellKnownImport(Handle<JSReceiver> callable,
const wasm::FunctionSig* sig) {
WellKnownImport kGeneric = WellKnownImport::kGeneric; // "using" is C++20.
// First part: check that the callable is a bound function whose target
// is {Function.prototype.call}, and which only binds a receiver.
if (!callable->IsJSBoundFunction()) return kGeneric;
Handle<JSBoundFunction> bound = Handle<JSBoundFunction>::cast(callable);
if (bound->bound_arguments().length() != 0) return kGeneric;
if (!bound->bound_target_function().IsJSFunction()) return kGeneric;
SharedFunctionInfo sfi =
JSFunction::cast(bound->bound_target_function()).shared();
if (!sfi.HasBuiltinId()) return kGeneric;
if (sfi.builtin_id() != Builtin::kFunctionPrototypeCall) return kGeneric;
// Second part: check if the bound receiver is one of the builtins for which
// we have special-cased support.
Object bound_this = bound->bound_this();
if (!bound_this.IsJSFunction()) return kGeneric;
sfi = JSFunction::cast(bound_this).shared();
if (!sfi.HasBuiltinId()) return kGeneric;
switch (sfi.builtin_id()) {
#if V8_INTL_SUPPORT
case Builtin::kStringPrototypeToLowerCaseIntl:
// TODO(jkummerow): Consider caching signatures to compare with, similar
// to {wasm::WasmOpcodes::Signature(...)}.
if (sig->parameter_count() == 1 && sig->return_count() == 1 &&
IsStringRef(sig->GetParam(0)) && IsStringRef(sig->GetReturn(0))) {
return WellKnownImport::kStringToLowerCaseStringref;
}
return kGeneric;
#endif
case Builtin::kNumberPrototypeToString:
if (sig->parameter_count() == 2 && sig->return_count() == 1 &&
sig->GetParam(0) == wasm::kWasmI32 &&
sig->GetParam(1) == wasm::kWasmI32 &&
// We could relax the return type check to permit anyref (and even
// externref) as well, if we encounter a reason to do so.
IsStringRef(sig->GetReturn(0))) {
return WellKnownImport::kIntToString;
}
break;
default:
break;
}
return kGeneric;
}
} // namespace
WasmImportData::WasmImportData(Handle<JSReceiver> callable,
const wasm::FunctionSig* expected_sig,
uint32_t expected_canonical_type_index)
: callable_(callable) {
kind_ = ComputeKind(expected_sig, expected_canonical_type_index);
}
ImportCallKind WasmImportData::ComputeKind(
const wasm::FunctionSig* expected_sig,
uint32_t expected_canonical_type_index) {
Isolate* isolate = callable_->GetIsolate();
if (WasmExportedFunction::IsWasmExportedFunction(*callable_)) {
auto imported_function = Handle<WasmExportedFunction>::cast(callable_);
if (!imported_function->MatchesSignature(expected_canonical_type_index)) {
return ImportCallKind::kLinkError;
}
uint32_t func_index =
static_cast<uint32_t>(imported_function->function_index());
if (func_index >=
imported_function->instance().module()->num_imported_functions) {
return ImportCallKind::kWasmToWasm;
}
// Resolve the shortcut to the underlying callable and continue.
Handle<WasmInstanceObject> instance(imported_function->instance(), isolate);
ImportedFunctionEntry entry(instance, func_index);
callable_ = handle(entry.callable(), isolate);
}
if (WasmJSFunction::IsWasmJSFunction(*callable_)) {
auto js_function = Handle<WasmJSFunction>::cast(callable_);
suspend_ = js_function->GetSuspend();
if (!js_function->MatchesSignature(expected_canonical_type_index)) {
return ImportCallKind::kLinkError;
}
// Resolve the short-cut to the underlying callable and continue.
callable_ = handle(js_function->GetCallable(), isolate);
}
if (WasmCapiFunction::IsWasmCapiFunction(*callable_)) {
auto capi_function = Handle<WasmCapiFunction>::cast(callable_);
if (!capi_function->MatchesSignature(expected_canonical_type_index)) {
return ImportCallKind::kLinkError;
}
return ImportCallKind::kWasmToCapi;
}
// Assuming we are calling to JS, check whether this would be a runtime error.
if (!wasm::IsJSCompatibleSignature(expected_sig)) {
return ImportCallKind::kRuntimeTypeError;
}
// Check if this can be a JS fast API call.
if (v8_flags.turbo_fast_api_calls &&
ResolveBoundJSFastApiFunction(expected_sig, callable_)) {
return ImportCallKind::kWasmToJSFastApi;
}
well_known_status_ = CheckForWellKnownImport(callable_, expected_sig);
// For JavaScript calls, determine whether the target has an arity match.
if (callable_->IsJSFunction()) {
Handle<JSFunction> function = Handle<JSFunction>::cast(callable_);
Handle<SharedFunctionInfo> shared(function->shared(),
function->GetIsolate());
// Check for math intrinsics.
#define COMPARE_SIG_FOR_BUILTIN(name) \
{ \
const wasm::FunctionSig* sig = \
wasm::WasmOpcodes::Signature(wasm::kExpr##name); \
if (!sig) sig = wasm::WasmOpcodes::AsmjsSignature(wasm::kExpr##name); \
DCHECK_NOT_NULL(sig); \
if (*expected_sig == *sig) { \
return ImportCallKind::k##name; \
} \
}
#define COMPARE_SIG_FOR_BUILTIN_F64(name) \
case Builtin::kMath##name: \
COMPARE_SIG_FOR_BUILTIN(F64##name); \
break;
#define COMPARE_SIG_FOR_BUILTIN_F32_F64(name) \
case Builtin::kMath##name: \
COMPARE_SIG_FOR_BUILTIN(F64##name); \
COMPARE_SIG_FOR_BUILTIN(F32##name); \
break;
if (v8_flags.wasm_math_intrinsics && shared->HasBuiltinId()) {
switch (shared->builtin_id()) {
COMPARE_SIG_FOR_BUILTIN_F64(Acos);
COMPARE_SIG_FOR_BUILTIN_F64(Asin);
COMPARE_SIG_FOR_BUILTIN_F64(Atan);
COMPARE_SIG_FOR_BUILTIN_F64(Cos);
COMPARE_SIG_FOR_BUILTIN_F64(Sin);
COMPARE_SIG_FOR_BUILTIN_F64(Tan);
COMPARE_SIG_FOR_BUILTIN_F64(Exp);
COMPARE_SIG_FOR_BUILTIN_F64(Log);
COMPARE_SIG_FOR_BUILTIN_F64(Atan2);
COMPARE_SIG_FOR_BUILTIN_F64(Pow);
COMPARE_SIG_FOR_BUILTIN_F32_F64(Min);
COMPARE_SIG_FOR_BUILTIN_F32_F64(Max);
COMPARE_SIG_FOR_BUILTIN_F32_F64(Abs);
COMPARE_SIG_FOR_BUILTIN_F32_F64(Ceil);
COMPARE_SIG_FOR_BUILTIN_F32_F64(Floor);
COMPARE_SIG_FOR_BUILTIN_F32_F64(Sqrt);
case Builtin::kMathFround:
COMPARE_SIG_FOR_BUILTIN(F32ConvertF64);
break;
default:
break;
}
}
#undef COMPARE_SIG_FOR_BUILTIN
#undef COMPARE_SIG_FOR_BUILTIN_F64
#undef COMPARE_SIG_FOR_BUILTIN_F32_F64
if (IsClassConstructor(shared->kind())) {
// Class constructor will throw anyway.
return ImportCallKind::kUseCallBuiltin;
}
if (shared->internal_formal_parameter_count_without_receiver() ==
expected_sig->parameter_count() - suspend_) {
return ImportCallKind::kJSFunctionArityMatch;
}
// If function isn't compiled, compile it now.
Isolate* isolate = callable_->GetIsolate();
IsCompiledScope is_compiled_scope(shared->is_compiled_scope(isolate));
if (!is_compiled_scope.is_compiled()) {
Compiler::Compile(isolate, function, Compiler::CLEAR_EXCEPTION,
&is_compiled_scope);
}
return ImportCallKind::kJSFunctionArityMismatch;
}
// Unknown case. Use the call builtin.
return ImportCallKind::kUseCallBuiltin;
}
// A helper class to simplify instantiating a module from a module object.
// It closes over the {Isolate}, the {ErrorThrower}, etc.
class InstanceBuilder {
public:
InstanceBuilder(Isolate* isolate, v8::metrics::Recorder::ContextId context_id,
ErrorThrower* thrower, Handle<WasmModuleObject> module_object,
MaybeHandle<JSReceiver> ffi,
MaybeHandle<JSArrayBuffer> memory_buffer);
// Build an instance, in all of its glory.
MaybeHandle<WasmInstanceObject> Build();
// Run the start function, if any.
bool ExecuteStartFunction();
private:
// A pre-evaluated value to use in import binding.
struct SanitizedImport {
Handle<String> module_name;
Handle<String> import_name;
Handle<Object> value;
};
Isolate* isolate_;
v8::metrics::Recorder::ContextId context_id_;
const WasmFeatures enabled_;
const WasmModule* const module_;
ErrorThrower* thrower_;
Handle<WasmModuleObject> module_object_;
MaybeHandle<JSReceiver> ffi_;
MaybeHandle<JSArrayBuffer> memory_buffer_;
Handle<WasmMemoryObject> memory_object_;
Handle<JSArrayBuffer> untagged_globals_;
Handle<FixedArray> tagged_globals_;
std::vector<Handle<WasmTagObject>> tags_wrappers_;
Handle<WasmExportedFunction> start_function_;
std::vector<SanitizedImport> sanitized_imports_;
std::vector<WellKnownImport> well_known_imports_;
// We pass this {Zone} to the temporary {WasmFullDecoder} we allocate during
// each call to {EvaluateConstantExpression}. This has been found to improve
// performance a bit over allocating a new {Zone} each time.
Zone init_expr_zone_;
// 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);
// Load data segments into the memory.
void LoadDataSegments(Handle<WasmInstanceObject> instance);
void WriteGlobalValue(const WasmGlobal& global, const WasmValue& value);
void SanitizeImports();
// Find the imported memory if there is one.
bool FindImportedMemory();
// Allocate the memory.
bool AllocateMemory();
// Processes a single imported function.
bool ProcessImportedFunction(Handle<WasmInstanceObject> instance,
int import_index, int func_index,
Handle<String> module_name,
Handle<String> import_name,
Handle<Object> value);
// Initialize imported tables of type funcref.
bool InitializeImportedIndirectFunctionTable(
Handle<WasmInstanceObject> instance, int table_index, int import_index,
Handle<WasmTableObject> table_object);
// Process a single imported table.
bool ProcessImportedTable(Handle<WasmInstanceObject> instance,
int import_index, int table_index,
Handle<String> module_name,
Handle<String> import_name, Handle<Object> value);
// Process a single imported memory.
bool ProcessImportedMemory(Handle<WasmInstanceObject> instance,
int import_index, Handle<String> module_name,
Handle<String> import_name, Handle<Object> value);
// Process a single imported global.
bool ProcessImportedGlobal(Handle<WasmInstanceObject> instance,
int import_index, int global_index,
Handle<String> module_name,
Handle<String> import_name, Handle<Object> value);
// Process a single imported WasmGlobalObject.
bool ProcessImportedWasmGlobalObject(Handle<WasmInstanceObject> instance,
int import_index,
Handle<String> module_name,
Handle<String> import_name,
const WasmGlobal& global,
Handle<WasmGlobalObject> global_object);
// Compile import wrappers in parallel. The result goes into the native
// module's import_wrapper_cache.
void CompileImportWrappers(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, or {-1} on error.
int ProcessImports(Handle<WasmInstanceObject> instance);
template <typename T>
T* GetRawUntaggedGlobalPtr(const WasmGlobal& global);
// Process initialization of globals.
void InitGlobals(Handle<WasmInstanceObject> instance);
// Process the exports, creating wrappers for functions, tables, memories,
// and globals.
void ProcessExports(Handle<WasmInstanceObject> instance);
void SetTableInitialValues(Handle<WasmInstanceObject> instance);
void LoadTableSegments(Handle<WasmInstanceObject> instance);
// Creates new tags. Note that some tags might already exist if they were
// imported, those tags will be re-used.
void InitializeTags(Handle<WasmInstanceObject> instance);
};
namespace {
class ReportLazyCompilationTimesTask : public v8::Task {
public:
ReportLazyCompilationTimesTask(std::weak_ptr<Counters> counters,
std::weak_ptr<NativeModule> native_module,
int delay_in_seconds)
: counters_(std::move(counters)),
native_module_(std::move(native_module)),
delay_in_seconds_(delay_in_seconds) {}
void Run() final {
std::shared_ptr<NativeModule> native_module = native_module_.lock();
if (!native_module) return;
std::shared_ptr<Counters> counters = counters_.lock();
if (!counters) return;
int num_compilations = native_module->num_lazy_compilations();
// If no compilations happened, we don't add samples. Experiments showed
// many cases of num_compilations == 0, and adding these cases would make
// other cases less visible.
if (!num_compilations) return;
if (delay_in_seconds_ == 5) {
counters->wasm_num_lazy_compilations_5sec()->AddSample(num_compilations);
counters->wasm_sum_lazy_compilation_time_5sec()->AddSample(
static_cast<int>(native_module->sum_lazy_compilation_time_in_ms()));
counters->wasm_max_lazy_compilation_time_5sec()->AddSample(
static_cast<int>(native_module->max_lazy_compilation_time_in_ms()));
return;
}
if (delay_in_seconds_ == 20) {
counters->wasm_num_lazy_compilations_20sec()->AddSample(num_compilations);
counters->wasm_sum_lazy_compilation_time_20sec()->AddSample(
static_cast<int>(native_module->sum_lazy_compilation_time_in_ms()));
counters->wasm_max_lazy_compilation_time_20sec()->AddSample(
static_cast<int>(native_module->max_lazy_compilation_time_in_ms()));
return;
}
if (delay_in_seconds_ == 60) {
counters->wasm_num_lazy_compilations_60sec()->AddSample(num_compilations);
counters->wasm_sum_lazy_compilation_time_60sec()->AddSample(
static_cast<int>(native_module->sum_lazy_compilation_time_in_ms()));
counters->wasm_max_lazy_compilation_time_60sec()->AddSample(
static_cast<int>(native_module->max_lazy_compilation_time_in_ms()));
return;
}
if (delay_in_seconds_ == 120) {
counters->wasm_num_lazy_compilations_120sec()->AddSample(
num_compilations);
counters->wasm_sum_lazy_compilation_time_120sec()->AddSample(
static_cast<int>(native_module->sum_lazy_compilation_time_in_ms()));
counters->wasm_max_lazy_compilation_time_120sec()->AddSample(
static_cast<int>(native_module->max_lazy_compilation_time_in_ms()));
return;
}
UNREACHABLE();
}
private:
std::weak_ptr<Counters> counters_;
std::weak_ptr<NativeModule> native_module_;
int delay_in_seconds_;
};
} // namespace
MaybeHandle<WasmInstanceObject> InstantiateToInstanceObject(
Isolate* isolate, ErrorThrower* thrower,
Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports,
MaybeHandle<JSArrayBuffer> memory_buffer) {
v8::metrics::Recorder::ContextId context_id =
isolate->GetOrRegisterRecorderContextId(isolate->native_context());
InstanceBuilder builder(isolate, context_id, thrower, module_object, imports,
memory_buffer);
auto instance = builder.Build();
if (!instance.is_null()) {
// Post tasks for lazy compilation metrics before we call the start
// function.
if (v8_flags.wasm_lazy_compilation &&
module_object->native_module()
->ShouldLazyCompilationMetricsBeReported()) {
V8::GetCurrentPlatform()->CallDelayedOnWorkerThread(
std::make_unique<ReportLazyCompilationTimesTask>(
isolate->async_counters(), module_object->shared_native_module(),
5),
5.0);
V8::GetCurrentPlatform()->CallDelayedOnWorkerThread(
std::make_unique<ReportLazyCompilationTimesTask>(
isolate->async_counters(), module_object->shared_native_module(),
20),
20.0);
V8::GetCurrentPlatform()->CallDelayedOnWorkerThread(
std::make_unique<ReportLazyCompilationTimesTask>(
isolate->async_counters(), module_object->shared_native_module(),
60),
60.0);
V8::GetCurrentPlatform()->CallDelayedOnWorkerThread(
std::make_unique<ReportLazyCompilationTimesTask>(
isolate->async_counters(), module_object->shared_native_module(),
120),
120.0);
}
if (builder.ExecuteStartFunction()) {
return instance;
}
}
DCHECK(isolate->has_pending_exception() || thrower->error());
return {};
}
InstanceBuilder::InstanceBuilder(Isolate* isolate,
v8::metrics::Recorder::ContextId context_id,
ErrorThrower* thrower,
Handle<WasmModuleObject> module_object,
MaybeHandle<JSReceiver> ffi,
MaybeHandle<JSArrayBuffer> memory_buffer)
: isolate_(isolate),
context_id_(context_id),
enabled_(module_object->native_module()->enabled_features()),
module_(module_object->module()),
thrower_(thrower),
module_object_(module_object),
ffi_(ffi),
memory_buffer_(memory_buffer),
init_expr_zone_(isolate_->allocator(), "constant expression zone") {
sanitized_imports_.reserve(module_->import_table.size());
well_known_imports_.reserve(module_->num_imported_functions);
}
// Build an instance, in all of its glory.
MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
"wasm.InstanceBuilder.Build");
// Check that an imports argument was provided, if the module requires it.
// No point in continuing otherwise.
if (!module_->import_table.empty() && ffi_.is_null()) {
thrower_->TypeError(
"Imports argument must be present and must be an object");
return {};
}
SanitizeImports();
if (thrower_->error()) return {};
// From here on, we expect the build pipeline to run without exiting to JS.
DisallowJavascriptExecution no_js(isolate_);
// Start a timer for instantiation time, if we have a high resolution timer.
base::ElapsedTimer timer;
if (base::TimeTicks::IsHighResolution()) {
timer.Start();
}
v8::metrics::WasmModuleInstantiated wasm_module_instantiated;
NativeModule* native_module = module_object_->native_module();
//--------------------------------------------------------------------------
// Set up the memory buffer and memory objects.
//--------------------------------------------------------------------------
uint32_t initial_pages = module_->initial_pages;
auto initial_pages_counter = SELECT_WASM_COUNTER(
isolate_->counters(), module_->origin, wasm, min_mem_pages_count);
initial_pages_counter->AddSample(initial_pages);
if (module_->has_maximum_pages) {
DCHECK_EQ(kWasmOrigin, module_->origin);
auto max_pages_counter =
isolate_->counters()->wasm_wasm_max_mem_pages_count();
max_pages_counter->AddSample(module_->maximum_pages);
}
if (is_asmjs_module(module_)) {
Handle<JSArrayBuffer> buffer;
if (memory_buffer_.ToHandle(&buffer)) {
// asm.js instantiation should have changed the state of the buffer.
CHECK(!buffer->is_detachable());
CHECK(buffer->is_asmjs_memory());
} else {
// Use an empty JSArrayBuffer for degenerate asm.js modules.
memory_buffer_ = isolate_->factory()->NewJSArrayBufferAndBackingStore(
0, InitializedFlag::kUninitialized);
if (!memory_buffer_.ToHandle(&buffer)) {
thrower_->RangeError("Out of memory: asm.js memory");
return {};
}
buffer->set_is_asmjs_memory(true);
buffer->set_is_detachable(false);
}
// The maximum number of pages isn't strictly necessary for memory
// objects used for asm.js, as they are never visible, but we might
// as well make it accurate.
auto maximum_pages =
static_cast<int>(RoundUp(buffer->byte_length(), wasm::kWasmPageSize) /
wasm::kWasmPageSize);
memory_object_ = WasmMemoryObject::New(isolate_, buffer, maximum_pages)
.ToHandleChecked();
} else {
// Actual wasm module must have either imported or created memory.
CHECK(memory_buffer_.is_null());
if (!FindImportedMemory()) {
if (module_->has_memory && !AllocateMemory()) {
DCHECK(isolate_->has_pending_exception() || thrower_->error());
return {};
}
}
}
//--------------------------------------------------------------------------
// Create the WebAssembly.Instance object.
//--------------------------------------------------------------------------
TRACE("New module instantiation for %p\n", native_module);
Handle<WasmInstanceObject> instance =
WasmInstanceObject::New(isolate_, module_object_);
//--------------------------------------------------------------------------
// Attach the memory to the instance.
//--------------------------------------------------------------------------
if (module_->has_memory) {
DCHECK(!memory_object_.is_null());
if (!instance->has_memory_object()) {
instance->set_memory_object(*memory_object_);
}
// Add the instance object to the list of instances for this memory.
WasmMemoryObject::AddInstance(isolate_, memory_object_, instance);
// Double-check the {memory} array buffer matches the instance.
Handle<JSArrayBuffer> memory = memory_buffer_.ToHandleChecked();
CHECK_EQ(instance->memory_size(), memory->byte_length());
CHECK_EQ(instance->memory_start(), memory->backing_store());
}
//--------------------------------------------------------------------------
// Set up the globals for the new instance.
//--------------------------------------------------------------------------
uint32_t untagged_globals_buffer_size = module_->untagged_globals_buffer_size;
if (untagged_globals_buffer_size > 0) {
MaybeHandle<JSArrayBuffer> result =
isolate_->factory()->NewJSArrayBufferAndBackingStore(
untagged_globals_buffer_size, InitializedFlag::kZeroInitialized,
AllocationType::kOld);
if (!result.ToHandle(&untagged_globals_)) {
thrower_->RangeError("Out of memory: wasm globals");
return {};
}
instance->set_untagged_globals_buffer(*untagged_globals_);
instance->set_globals_start(
reinterpret_cast<byte*>(untagged_globals_->backing_store()));
}
uint32_t tagged_globals_buffer_size = module_->tagged_globals_buffer_size;
if (tagged_globals_buffer_size > 0) {
tagged_globals_ = isolate_->factory()->NewFixedArray(
static_cast<int>(tagged_globals_buffer_size));
instance->set_tagged_globals_buffer(*tagged_globals_);
}
//--------------------------------------------------------------------------
// Set up the array of references to imported globals' array buffers.
//--------------------------------------------------------------------------
if (module_->num_imported_mutable_globals > 0) {
// TODO(binji): This allocates one slot for each mutable global, which is
// more than required if multiple globals are imported from the same
// module.
Handle<FixedArray> buffers_array = isolate_->factory()->NewFixedArray(
module_->num_imported_mutable_globals, AllocationType::kOld);
instance->set_imported_mutable_globals_buffers(*buffers_array);
}
//--------------------------------------------------------------------------
// Set up the tag table used for exception tag checks.
//--------------------------------------------------------------------------
int tags_count = static_cast<int>(module_->tags.size());
if (tags_count > 0) {
Handle<FixedArray> tag_table =
isolate_->factory()->NewFixedArray(tags_count, AllocationType::kOld);
instance->set_tags_table(*tag_table);
tags_wrappers_.resize(tags_count);
}
//--------------------------------------------------------------------------
// Set up table storage space.
//--------------------------------------------------------------------------
instance->set_isorecursive_canonical_types(
module_->isorecursive_canonical_type_ids.data());
int table_count = static_cast<int>(module_->tables.size());
{
for (int i = 0; i < table_count; i++) {
const WasmTable& table = module_->tables[i];
if (table.initial_size > v8_flags.wasm_max_table_size) {
thrower_->RangeError(
"initial table size (%u elements) is larger than implementation "
"limit (%u elements)",
table.initial_size, v8_flags.wasm_max_table_size.value());
return {};
}
}
Handle<FixedArray> tables = isolate_->factory()->NewFixedArray(table_count);
for (int i = module_->num_imported_tables; i < table_count; i++) {
const WasmTable& table = module_->tables[i];
// Initialize tables with null for now. We will initialize non-defaultable
// tables later, in {SetTableInitialValues}.
Handle<WasmTableObject> table_obj = WasmTableObject::New(
isolate_, instance, table.type, table.initial_size,
table.has_maximum_size, table.maximum_size, nullptr,
IsSubtypeOf(table.type, kWasmExternRef, module_)
? Handle<Object>::cast(isolate_->factory()->null_value())
: Handle<Object>::cast(isolate_->factory()->wasm_null()));
tables->set(i, *table_obj);
}
instance->set_tables(*tables);
}
{
Handle<FixedArray> tables = isolate_->factory()->NewFixedArray(table_count);
for (int i = 0; i < table_count; ++i) {
const WasmTable& table = module_->tables[i];
if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) {
Handle<WasmIndirectFunctionTable> table_obj =
WasmIndirectFunctionTable::New(isolate_, table.initial_size);
tables->set(i, *table_obj);
}
}
instance->set_indirect_function_tables(*tables);
}
instance->SetIndirectFunctionTableShortcuts(isolate_);
//--------------------------------------------------------------------------
// Process the imports for the module.
//--------------------------------------------------------------------------
if (!module_->import_table.empty()) {
int num_imported_functions = ProcessImports(instance);
if (num_imported_functions < 0) return {};
wasm_module_instantiated.imported_function_count = num_imported_functions;
}
//--------------------------------------------------------------------------
// Create maps for managed objects (GC proposal).
// Must happen before {InitGlobals} because globals can refer to these maps.
// We do not need to cache the canonical rtts to (rtt.canon any)'s subtype
// list.
//--------------------------------------------------------------------------
if (enabled_.has_gc()) {
if (module_->isorecursive_canonical_type_ids.size() > 0) {
// Make sure all canonical indices have been set.
DCHECK_NE(module_->MaxCanonicalTypeIndex(), kNoSuperType);
isolate_->heap()->EnsureWasmCanonicalRttsSize(
module_->MaxCanonicalTypeIndex() + 1);
}
Handle<FixedArray> maps = isolate_->factory()->NewFixedArray(
static_cast<int>(module_->types.size()));
for (uint32_t index = 0; index < module_->types.size(); index++) {
CreateMapForType(isolate_, module_, index, instance, maps);
}
instance->set_managed_object_maps(*maps);
}
//--------------------------------------------------------------------------
// Allocate the array that will hold type feedback vectors.
//--------------------------------------------------------------------------
if (enabled_.has_inlining()) {
int num_functions = static_cast<int>(module_->num_declared_functions);
// Zero-fill the array so we can do a quick Smi-check to test if a given
// slot was initialized.
Handle<FixedArray> vectors = isolate_->factory()->NewFixedArrayWithZeroes(
num_functions, AllocationType::kOld);
instance->set_feedback_vectors(*vectors);
}
//--------------------------------------------------------------------------
// Process the initialization for the module's globals.
//--------------------------------------------------------------------------
InitGlobals(instance);
//--------------------------------------------------------------------------
// Initialize the indirect function tables and dispatch tables. We do this
// before initializing non-defaultable tables and loading element segments, so
// that indirect function tables in this module are included in the updates
// when we do so.
//--------------------------------------------------------------------------
for (int table_index = 0;
table_index < static_cast<int>(module_->tables.size()); ++table_index) {
const WasmTable& table = module_->tables[table_index];
if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) {
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, table_index, table.initial_size);
if (thrower_->error()) return {};
auto table_object = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate_);
WasmTableObject::AddDispatchTable(isolate_, table_object, instance,
table_index);
}
}
//--------------------------------------------------------------------------
// Initialize non-defaultable tables.
//--------------------------------------------------------------------------
if (enabled_.has_typed_funcref()) {
SetTableInitialValues(instance);
}
//--------------------------------------------------------------------------
// Initialize the tags table.
//--------------------------------------------------------------------------
if (tags_count > 0) {
InitializeTags(instance);
}
//--------------------------------------------------------------------------
// Set up the exports object for the new instance.
//--------------------------------------------------------------------------
ProcessExports(instance);
if (thrower_->error()) return {};
//--------------------------------------------------------------------------
// Set up uninitialized element segments.
//--------------------------------------------------------------------------
if (!module_->elem_segments.empty()) {
Handle<FixedArray> elements = isolate_->factory()->NewFixedArray(
static_cast<int>(module_->elem_segments.size()));
for (int i = 0; i < static_cast<int>(module_->elem_segments.size()); i++) {
// Initialize declarative segments as empty. The rest remain
// uninitialized.
bool is_declarative = module_->elem_segments[i].status ==
WasmElemSegment::kStatusDeclarative;
elements->set(
i, is_declarative
? Object::cast(*isolate_->factory()->empty_fixed_array())
: *isolate_->factory()->undefined_value());
}
instance->set_element_segments(*elements);
}
//--------------------------------------------------------------------------
// Load element segments into tables.
//--------------------------------------------------------------------------
if (table_count > 0) {
LoadTableSegments(instance);
if (thrower_->error()) return {};
}
//--------------------------------------------------------------------------
// Initialize the memory by loading data segments.
//--------------------------------------------------------------------------
if (module_->data_segments.size() > 0) {
LoadDataSegments(instance);
if (thrower_->error()) return {};
}
//--------------------------------------------------------------------------
// Create a wrapper for the start function.
//--------------------------------------------------------------------------
if (module_->start_function_index >= 0) {
int start_index = module_->start_function_index;
auto& function = module_->functions[start_index];
// TODO(clemensb): Don't generate an exported function for the start
// function. Use CWasmEntry instead.
Handle<WasmInternalFunction> internal =
WasmInstanceObject::GetOrCreateWasmInternalFunction(isolate_, instance,
start_index);
start_function_ = Handle<WasmExportedFunction>::cast(
WasmInternalFunction::GetOrCreateExternal(internal));
if (function.imported) {
ImportedFunctionEntry entry(instance, module_->start_function_index);
Object callable = entry.maybe_callable();
if (callable.IsJSFunction()) {
// If the start function was imported and calls into Blink, we have
// to pretend that the V8 API was used to enter its correct context.
// To get that context to {ExecuteStartFunction} below, we install it
// as the context of the wrapper we just compiled. That's a bit of a
// hack because it's not really the wrapper's context, only its wrapped
// target's context, but the end result is the same, and since the
// start function wrapper doesn't leak, neither does this
// implementation detail.
start_function_->set_context(JSFunction::cast(callable).context());
}
}
}
DCHECK(!isolate_->has_pending_exception());
TRACE("Successfully built instance for module %p\n",
module_object_->native_module());
wasm_module_instantiated.success = true;
if (timer.IsStarted()) {
base::TimeDelta instantiation_time = timer.Elapsed();
wasm_module_instantiated.wall_clock_duration_in_us =
instantiation_time.InMicroseconds();
SELECT_WASM_COUNTER(isolate_->counters(), module_->origin, wasm_instantiate,
module_time)
->AddTimedSample(instantiation_time);
isolate_->metrics_recorder()->DelayMainThreadEvent(wasm_module_instantiated,
context_id_);
}
return instance;
}
bool InstanceBuilder::ExecuteStartFunction() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
"wasm.ExecuteStartFunction");
if (start_function_.is_null()) return true; // No start function.
HandleScope scope(isolate_);
// In case the start function calls out to Blink, we have to make sure that
// the correct "entered context" is available. This is the equivalent of
// v8::Context::Enter() and must happen in addition to the function call
// sequence doing the compiled version of "isolate->set_context(...)".
HandleScopeImplementer* hsi = isolate_->handle_scope_implementer();
hsi->EnterContext(start_function_->native_context());
// Call the JS function.
Handle<Object> undefined = isolate_->factory()->undefined_value();
MaybeHandle<Object> retval =
Execution::Call(isolate_, start_function_, undefined, 0, nullptr);
hsi->LeaveContext();
if (retval.is_null()) {
DCHECK(isolate_->has_pending_exception());
return false;
}
return true;
}
// 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(
isolate_, 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(isolate_, module, import_name);
if (result.is_null()) {
ReportLinkError("import not found", index, module_name, import_name);
return MaybeHandle<JSFunction>();
}
return result;
}
namespace {
bool HasDefaultToNumberBehaviour(Isolate* isolate,
Handle<JSFunction> function) {
// Disallow providing a [Symbol.toPrimitive] member.
LookupIterator to_primitive_it{isolate, function,
isolate->factory()->to_primitive_symbol()};
if (to_primitive_it.state() != LookupIterator::NOT_FOUND) return false;
// The {valueOf} member must be the default "ObjectPrototypeValueOf".
LookupIterator value_of_it{isolate, function,
isolate->factory()->valueOf_string()};
if (value_of_it.state() != LookupIterator::DATA) return false;
Handle<Object> value_of = value_of_it.GetDataValue();
if (!value_of->IsJSFunction()) return false;
Builtin value_of_builtin_id =
Handle<JSFunction>::cast(value_of)->code().builtin_id();
if (value_of_builtin_id != Builtin::kObjectPrototypeValueOf) return false;
// The {toString} member must be the default "FunctionPrototypeToString".
LookupIterator to_string_it{isolate, function,
isolate->factory()->toString_string()};
if (to_string_it.state() != LookupIterator::DATA) return false;
Handle<Object> to_string = to_string_it.GetDataValue();
if (!to_string->IsJSFunction()) return false;
Builtin to_string_builtin_id =
Handle<JSFunction>::cast(to_string)->code().builtin_id();
if (to_string_builtin_id != Builtin::kFunctionPrototypeToString) return false;
// Just a default function, which will convert to "Nan". Accept this.
return true;
}
bool MaybeMarkError(ValueOrError value, ErrorThrower* thrower) {
if (is_error(value)) {
thrower->RuntimeError("%s",
MessageFormatter::TemplateString(to_error(value)));
return true;
}
return false;
}
} // namespace
// 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.
PropertyKey key(isolate_, Handle<Name>::cast(import_name));
LookupIterator it(isolate_, ffi_.ToHandleChecked(), key);
switch (it.state()) {
case LookupIterator::ACCESS_CHECK:
case LookupIterator::INTEGER_INDEXED_EXOTIC:
case LookupIterator::INTERCEPTOR:
case LookupIterator::JSPROXY:
case LookupIterator::WASM_OBJECT:
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.
return isolate_->factory()->undefined_value();
case LookupIterator::DATA: {
Handle<Object> value = it.GetDataValue();
// For legacy reasons, we accept functions for imported globals (see
// {ProcessImportedGlobal}), but only if we can easily determine that
// their Number-conversion is side effect free and returns NaN (which is
// the case as long as "valueOf" (or others) are not overwritten).
if (value->IsJSFunction() &&
module_->import_table[index].kind == kExternalGlobal &&
!HasDefaultToNumberBehaviour(isolate_,
Handle<JSFunction>::cast(value))) {
return ReportLinkError("function has special ToNumber behaviour", index,
import_name);
}
return value;
}
}
}
// Load data segments into the memory.
void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> instance) {
base::Vector<const uint8_t> wire_bytes =
module_object_->native_module()->wire_bytes();
for (const WasmDataSegment& segment : module_->data_segments) {
uint32_t size = segment.source.length();
// Passive segments are not copied during instantiation.
if (!segment.active) continue;
size_t dest_offset;
if (module_->is_memory64) {
ValueOrError result = EvaluateConstantExpression(
&init_expr_zone_, segment.dest_addr, kWasmI64, isolate_, instance);
if (MaybeMarkError(result, thrower_)) return;
uint64_t dest_offset_64 = to_value(result).to_u64();
// Clamp to {std::numeric_limits<size_t>::max()}, which is always an
// invalid offset.
DCHECK_GT(std::numeric_limits<size_t>::max(), instance->memory_size());
dest_offset = static_cast<size_t>(std::min(
dest_offset_64, uint64_t{std::numeric_limits<size_t>::max()}));
} else {
ValueOrError result = EvaluateConstantExpression(
&init_expr_zone_, segment.dest_addr, kWasmI32, isolate_, instance);
if (MaybeMarkError(result, thrower_)) return;
dest_offset = to_value(result).to_u32();
}
if (!base::IsInBounds<size_t>(dest_offset, size, instance->memory_size())) {
thrower_->RuntimeError("data segment is out of bounds");
return;
}
std::memcpy(instance->memory_start() + dest_offset,
wire_bytes.begin() + segment.source.offset(), size);
}
}
void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global,
const WasmValue& value) {
TRACE("init [globals_start=%p + %u] = %s, type = %s\n",
global.type.is_reference()
? reinterpret_cast<byte*>(tagged_globals_->address())
: raw_buffer_ptr(untagged_globals_, 0),
global.offset, value.to_string().c_str(), global.type.name().c_str());
DCHECK(IsSubtypeOf(value.type(), global.type, module_));
if (global.type.is_numeric()) {
value.CopyTo(GetRawUntaggedGlobalPtr<byte>(global));
} else {
tagged_globals_->set(global.offset, *value.to_ref());
}
}
void InstanceBuilder::SanitizeImports() {
base::Vector<const uint8_t> wire_bytes =
module_object_->native_module()->wire_bytes();
for (size_t index = 0; index < module_->import_table.size(); ++index) {
const WasmImport& import = module_->import_table[index];
Handle<String> module_name =
WasmModuleObject::ExtractUtf8StringFromModuleBytes(
isolate_, wire_bytes, import.module_name, kInternalize);
Handle<String> import_name =
WasmModuleObject::ExtractUtf8StringFromModuleBytes(
isolate_, wire_bytes, import.field_name, kInternalize);
int int_index = static_cast<int>(index);
MaybeHandle<Object> result =
is_asmjs_module(module_)
? LookupImportAsm(int_index, import_name)
: LookupImport(int_index, module_name, import_name);
if (thrower_->error()) {
thrower_->LinkError("Could not find value for import %zu", index);
return;
}
Handle<Object> value = result.ToHandleChecked();
sanitized_imports_.push_back({module_name, import_name, value});
}
}
bool InstanceBuilder::FindImportedMemory() {
DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size());
for (size_t index = 0; index < module_->import_table.size(); index++) {
WasmImport import = module_->import_table[index];
if (import.kind == kExternalMemory) {
auto& value = sanitized_imports_[index].value;
if (!value->IsWasmMemoryObject()) return false;
memory_object_ = Handle<WasmMemoryObject>::cast(value);
memory_buffer_ =
Handle<JSArrayBuffer>(memory_object_->array_buffer(), isolate_);
return true;
}
}
return false;
}
bool InstanceBuilder::ProcessImportedFunction(
Handle<WasmInstanceObject> instance, int import_index, int func_index,
Handle<String> module_name, Handle<String> import_name,
Handle<Object> value) {
// Function imports must be callable.
if (!value->IsCallable()) {
ReportLinkError("function import requires a callable", import_index,
module_name, import_name);
return false;
}
// Store any {WasmExternalFunction} callable in the instance before the call
// is resolved to preserve its identity. This handles exported functions as
// well as functions constructed via other means (e.g. WebAssembly.Function).
if (WasmExternalFunction::IsWasmExternalFunction(*value)) {
WasmInstanceObject::SetWasmInternalFunction(
instance, func_index,
WasmInternalFunction::FromExternal(
Handle<WasmExternalFunction>::cast(value), isolate_)
.ToHandleChecked());
}
auto js_receiver = Handle<JSReceiver>::cast(value);
const FunctionSig* expected_sig = module_->functions[func_index].sig;
uint32_t sig_index = module_->functions[func_index].sig_index;
uint32_t canonical_type_index =
module_->isorecursive_canonical_type_ids[sig_index];
WasmImportData resolved(js_receiver, expected_sig, canonical_type_index);
if (resolved.well_known_status() != WellKnownImport::kGeneric &&
v8_flags.trace_wasm_inlining) {
PrintF("[import %d is well-known built-in %s]\n", import_index,
WellKnownImportName(resolved.well_known_status()));
}
well_known_imports_.push_back(resolved.well_known_status());
ImportCallKind kind = resolved.kind();
js_receiver = resolved.callable();
switch (kind) {
case ImportCallKind::kLinkError:
ReportLinkError("imported function does not match the expected type",
import_index, module_name, import_name);
return false;
case ImportCallKind::kWasmToWasm: {
// The imported function is a Wasm function from another instance.
auto imported_function = Handle<WasmExportedFunction>::cast(js_receiver);
Handle<WasmInstanceObject> imported_instance(
imported_function->instance(), isolate_);
// The import reference is the instance object itself.
Address imported_target = imported_function->GetWasmCallTarget();
ImportedFunctionEntry entry(instance, func_index);
entry.SetWasmToWasm(*imported_instance, imported_target);
break;
}
case ImportCallKind::kWasmToCapi: {
NativeModule* native_module = instance->module_object().native_module();
int expected_arity = static_cast<int>(expected_sig->parameter_count());
WasmImportWrapperCache* cache = native_module->import_wrapper_cache();
// TODO(jkummerow): Consider precompiling CapiCallWrappers in parallel,
// just like other import wrappers.
uint32_t canonical_type_index =
module_->isorecursive_canonical_type_ids
[module_->functions[func_index].sig_index];
WasmCode* wasm_code = cache->MaybeGet(kind, canonical_type_index,
expected_arity, kNoSuspend);
if (wasm_code == nullptr) {
WasmCodeRefScope code_ref_scope;
WasmImportWrapperCache::ModificationScope cache_scope(cache);
wasm_code =
compiler::CompileWasmCapiCallWrapper(native_module, expected_sig);
WasmImportWrapperCache::CacheKey key(kind, canonical_type_index,
expected_arity, kNoSuspend);
cache_scope[key] = wasm_code;
wasm_code->IncRef();
isolate_->counters()->wasm_generated_code_size()->Increment(
wasm_code->instructions().length());
isolate_->counters()->wasm_reloc_size()->Increment(
wasm_code->reloc_info().length());
}
ImportedFunctionEntry entry(instance, func_index);
// We re-use the SetWasmToJs infrastructure because it passes the
// callable to the wrapper, which we need to get the function data.
entry.SetWasmToJs(isolate_, js_receiver, wasm_code, kNoSuspend);
break;
}
case ImportCallKind::kWasmToJSFastApi: {
NativeModule* native_module = instance->module_object().native_module();
DCHECK(js_receiver->IsJSFunction() || js_receiver->IsJSBoundFunction());
WasmCodeRefScope code_ref_scope;
WasmCode* wasm_code = compiler::CompileWasmJSFastCallWrapper(
native_module, expected_sig, js_receiver);
ImportedFunctionEntry entry(instance, func_index);
entry.SetWasmToJs(isolate_, js_receiver, wasm_code, kNoSuspend);
break;
}
default: {
// The imported function is a callable.
int expected_arity = static_cast<int>(expected_sig->parameter_count());
if (kind == ImportCallKind::kJSFunctionArityMismatch) {
Handle<JSFunction> function = Handle<JSFunction>::cast(js_receiver);
SharedFunctionInfo shared = function->shared();
expected_arity =
shared.internal_formal_parameter_count_without_receiver();
}
NativeModule* native_module = instance->module_object().native_module();
uint32_t canonical_type_index =
module_->isorecursive_canonical_type_ids
[module_->functions[func_index].sig_index];
WasmCode* wasm_code = native_module->import_wrapper_cache()->Get(
kind, canonical_type_index, expected_arity, resolved.suspend());
DCHECK_NOT_NULL(wasm_code);
ImportedFunctionEntry entry(instance, func_index);
if (wasm_code->kind() == WasmCode::kWasmToJsWrapper) {
// Wasm to JS wrappers are treated specially in the import table.
entry.SetWasmToJs(isolate_, js_receiver, wasm_code, resolved.suspend());
} else {
// Wasm math intrinsics are compiled as regular Wasm functions.
DCHECK(kind >= ImportCallKind::kFirstMathIntrinsic &&
kind <= ImportCallKind::kLastMathIntrinsic);
entry.SetWasmToWasm(*instance, wasm_code->instruction_start());
}
break;
}
}
return true;
}
bool InstanceBuilder::InitializeImportedIndirectFunctionTable(
Handle<WasmInstanceObject> instance, int table_index, int import_index,
Handle<WasmTableObject> table_object) {
int imported_table_size = table_object->current_length();
// Allocate a new dispatch table.
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, table_index, imported_table_size);
// Initialize the dispatch table with the (foreign) JS functions
// that are already in the table.
for (int i = 0; i < imported_table_size; ++i) {
bool is_valid;
bool is_null;
MaybeHandle<WasmInstanceObject> maybe_target_instance;
int function_index;
MaybeHandle<WasmJSFunction> maybe_js_function;
WasmTableObject::GetFunctionTableEntry(
isolate_, module_, table_object, i, &is_valid, &is_null,
&maybe_target_instance, &function_index, &maybe_js_function);
if (!is_valid) {
thrower_->LinkError("table import %d[%d] is not a wasm function",
import_index, i);
return false;
}
if (is_null) continue;
Handle<WasmJSFunction> js_function;
if (maybe_js_function.ToHandle(&js_function)) {
WasmInstanceObject::ImportWasmJSFunctionIntoTable(
isolate_, instance, table_index, i, js_function);
continue;
}
Handle<WasmInstanceObject> target_instance =
maybe_target_instance.ToHandleChecked();
const WasmModule* target_module = target_instance->module_object().module();
const WasmFunction& function = target_module->functions[function_index];
FunctionTargetAndRef entry(target_instance, function_index);
uint32_t canonicalized_sig_index =
target_module->isorecursive_canonical_type_ids[function.sig_index];
instance->GetIndirectFunctionTable(isolate_, table_index)
->Set(i, canonicalized_sig_index, entry.call_target(), *entry.ref());
}
return true;
}
bool InstanceBuilder::ProcessImportedTable(Handle<WasmInstanceObject> instance,
int import_index, int table_index,
Handle<String> module_name,
Handle<String> import_name,
Handle<Object> value) {
if (!value->IsWasmTableObject()) {
ReportLinkError("table import requires a WebAssembly.Table", import_index,
module_name, import_name);
return false;
}
const WasmTable& table = module_->tables[table_index];
auto table_object = Handle<WasmTableObject>::cast(value);
uint32_t imported_table_size =
static_cast<uint32_t>(table_object->current_length());
if (imported_table_size < table.initial_size) {
thrower_->LinkError("table import %d is smaller than initial %u, got %u",
import_index, table.initial_size, imported_table_size);
return false;
}
if (table.has_maximum_size) {
if (table_object->maximum_length().IsUndefined(isolate_)) {
thrower_->LinkError("table import %d has no maximum length, expected %u",
import_index, table.maximum_size);
return false;
}
int64_t imported_maximum_size = table_object->maximum_length().Number();
if (imported_maximum_size < 0) {
thrower_->LinkError("table import %d has no maximum length, expected %u",
import_index, table.maximum_size);
return false;
}
if (imported_maximum_size > table.maximum_size) {
thrower_->LinkError("table import %d has a larger maximum size %" PRIx64
" than the module's declared maximum %u",
import_index, imported_maximum_size,
table.maximum_size);
return false;
}
}
const WasmModule* table_type_module =
!table_object->instance().IsUndefined()
? WasmInstanceObject::cast(table_object->instance()).module()
: instance->module();
if (!EquivalentTypes(table.type, table_object->type(), module_,
table_type_module)) {
ReportLinkError("imported table does not match the expected type",
import_index, module_name, import_name);
return false;
}
if (IsSubtypeOf(table.type, kWasmFuncRef, module_) &&
!InitializeImportedIndirectFunctionTable(instance, table_index,
import_index, table_object)) {
return false;
}
instance->tables().set(table_index, *value);
return true;
}
bool InstanceBuilder::ProcessImportedMemory(Handle<WasmInstanceObject> instance,
int import_index,
Handle<String> module_name,
Handle<String> import_name,
Handle<Object> value) {
if (!value->IsWasmMemoryObject()) {
ReportLinkError("memory import must be a WebAssembly.Memory object",
import_index, module_name, import_name);
return false;
}
auto memory_object = Handle<WasmMemoryObject>::cast(value);
// The imported memory should have been already set up early.
CHECK_EQ(instance->memory_object(), *memory_object);
Handle<JSArrayBuffer> buffer(memory_object_->array_buffer(), isolate_);
// memory_ should have already been assigned in Build().
DCHECK_EQ(*memory_buffer_.ToHandleChecked(), *buffer);
uint32_t imported_cur_pages =
static_cast<uint32_t>(buffer->byte_length() / kWasmPageSize);
if (imported_cur_pages < module_->initial_pages) {
thrower_->LinkError("memory import %d is smaller than initial %u, got %u",
import_index, module_->initial_pages,
imported_cur_pages);
return false;
}
int32_t imported_maximum_pages = memory_object_->maximum_pages();
if (module_->has_maximum_pages) {
if (imported_maximum_pages < 0) {
thrower_->LinkError(
"memory import %d has no maximum limit, expected at most %u",
import_index, imported_maximum_pages);
return false;
}
if (static_cast<uint32_t>(imported_maximum_pages) >
module_->maximum_pages) {
thrower_->LinkError(
"memory import %d has a larger maximum size %u than the "
"module's declared maximum %u",
import_index, imported_maximum_pages, module_->maximum_pages);
return false;
}
}
if (module_->has_shared_memory != buffer->is_shared()) {
thrower_->LinkError(
"mismatch in shared state of memory, declared = %d, imported = %d",
module_->has_shared_memory, buffer->is_shared());
return false;
}
return true;
}
bool InstanceBuilder::ProcessImportedWasmGlobalObject(
Handle<WasmInstanceObject> instance, int import_index,
Handle<String> module_name, Handle<String> import_name,
const WasmGlobal& global, Handle<WasmGlobalObject> global_object) {
if (static_cast<bool>(global_object->is_mutable()) != global.mutability) {
ReportLinkError("imported global does not match the expected mutability",
import_index, module_name, import_name);
return false;
}
const WasmModule* global_type_module =
!global_object->instance().IsUndefined()
? WasmInstanceObject::cast(global_object->instance()).module()
: instance->module();
bool valid_type =
global.mutability
? EquivalentTypes(global_object->type(), global.type,
global_type_module, instance->module())
: IsSubtypeOf(global_object->type(), global.type, global_type_module,
instance->module());
if (!valid_type) {
ReportLinkError("imported global does not match the expected type",
import_index, module_name, import_name);
return false;
}
if (global.mutability) {
DCHECK_LT(global.index, module_->num_imported_mutable_globals);
Handle<Object> buffer;
if (global.type.is_reference()) {
static_assert(sizeof(global_object->offset()) <= sizeof(Address),
"The offset into the globals buffer does not fit into "
"the imported_mutable_globals array");
buffer = handle(global_object->tagged_buffer(), isolate_);
// For externref globals we use a relative offset, not an absolute
// address.
instance->imported_mutable_globals().set_int(
global.index * kSystemPointerSize, global_object->offset());
} else {
buffer = handle(global_object->untagged_buffer(), isolate_);
// It is safe in this case to store the raw pointer to the buffer
// since the backing store of the JSArrayBuffer will not be
// relocated.
Address address = reinterpret_cast<Address>(raw_buffer_ptr(
Handle<JSArrayBuffer>::cast(buffer), global_object->offset()));
instance->imported_mutable_globals().set_sandboxed_pointer(
global.index * kSystemPointerSize, address);
}
instance->imported_mutable_globals_buffers().set(global.index, *buffer);
return true;
}
WasmValue value;
switch (global_object->type().kind()) {
case kI32:
value = WasmValue(global_object->GetI32());
break;
case kI64:
value = WasmValue(global_object->GetI64());
break;
case kF32:
value = WasmValue(global_object->GetF32());
break;
case kF64:
value = WasmValue(global_object->GetF64());
break;
case kS128:
value = WasmValue(global_object->GetS128RawBytes(), kWasmS128);
break;
case kRef:
case kRefNull:
value = WasmValue(global_object->GetRef(), global_object->type());
break;
case kVoid:
case kBottom:
case kRtt:
case kI8:
case kI16:
UNREACHABLE();
}
WriteGlobalValue(global, value);
return true;
}
bool InstanceBuilder::ProcessImportedGlobal(Handle<WasmInstanceObject> instance,
int import_index, int global_index,
Handle<String> module_name,
Handle<String> import_name,
Handle<Object> value) {
// Immutable global imports are converted to numbers and written into
// the {untagged_globals_} array buffer.
//
// Mutable global imports instead have their backing array buffers
// referenced by this instance, and store the address of the imported
// global in the {imported_mutable_globals_} array.
const WasmGlobal& global = module_->globals[global_index];
// SIMD proposal allows modules to define an imported v128 global, and only
// supports importing a WebAssembly.Global object for this global, but also
// defines constructing a WebAssembly.Global of v128 to be a TypeError.
// We *should* never hit this case in the JS API, but the module should should
// be allowed to declare such a global (no validation error).
if (global.type == kWasmS128 && !value->IsWasmGlobalObject()) {
ReportLinkError("global import of type v128 must be a WebAssembly.Global",
import_index, module_name, import_name);
return false;
}
if (is_asmjs_module(module_)) {
// Accepting {JSFunction} on top of just primitive values here is a
// workaround to support legacy asm.js code with broken binding. Note
// that using {NaN} (or Smi::zero()) here is what using the observable
// conversion via {ToPrimitive} would produce as well. {LookupImportAsm}
// checked via {HasDefaultToNumberBehaviour} that "valueOf" or friends have
// not been patched.
if (value->IsJSFunction()) value = isolate_->factory()->nan_value();
if (value->IsPrimitive()) {
MaybeHandle<Object> converted = global.type == kWasmI32
? Object::ToInt32(isolate_, value)
: Object::ToNumber(isolate_, value);
if (!converted.ToHandle(&value)) {
// Conversion is known to fail for Symbols and BigInts.
ReportLinkError("global import must be a number", import_index,
module_name, import_name);
return false;
}
}
}
if (value->IsWasmGlobalObject()) {
auto global_object = Handle<WasmGlobalObject>::cast(value);
return ProcessImportedWasmGlobalObject(instance, import_index, module_name,
import_name, global, global_object);
}
if (global.mutability) {
ReportLinkError(
"imported mutable global must be a WebAssembly.Global object",
import_index, module_name, import_name);
return false;
}
if (global.type.is_reference()) {
const char* error_message;
Handle<Object> wasm_value;
if (!wasm::JSToWasmObject(isolate_, module_, value, global.type,
&error_message)
.ToHandle(&wasm_value)) {
ReportLinkError(error_message, global_index, module_name, import_name);
return false;
}
WriteGlobalValue(global, WasmValue(wasm_value, global.type));
return true;
}
if (value->IsNumber() && global.type != kWasmI64) {
double number_value = value->Number();
// The Wasm-BigInt proposal currently says that i64 globals may
// only be initialized with BigInts. See:
// https://github.com/WebAssembly/JS-BigInt-integration/issues/12
WasmValue wasm_value = global.type == kWasmI32
? WasmValue(DoubleToInt32(number_value))
: global.type == kWasmF32
? WasmValue(DoubleToFloat32(number_value))
: WasmValue(number_value);
WriteGlobalValue(global, wasm_value);
return true;
}
if (global.type == kWasmI64 && value->IsBigInt()) {
WriteGlobalValue(global, WasmValue(BigInt::cast(*value).AsInt64()));
return true;
}
ReportLinkError(
"global import must be a number, valid Wasm reference, or "
"WebAssembly.Global object",
import_index, module_name, import_name);
return false;
}
void InstanceBuilder::CompileImportWrappers(
Handle<WasmInstanceObject> instance) {
int num_imports = static_cast<int>(module_->import_table.size());
TRACE_EVENT1("v8.wasm", "wasm.CompileImportWrappers", "num_imports",
num_imports);
NativeModule* native_module = instance->module_object().native_module();
WasmImportWrapperCache::ModificationScope cache_scope(
native_module->import_wrapper_cache());
// Compilation is done in two steps:
// 1) Insert nullptr entries in the cache for wrappers that need to be
// compiled. 2) Compile wrappers in background tasks using the
// ImportWrapperQueue. This way the cache won't invalidate other iterators
// when inserting a new WasmCode, since the key will already be there.
ImportWrapperQueue import_wrapper_queue;
for (int index = 0; index < num_imports; ++index) {
Handle<Object> value = sanitized_imports_[index].value;
if (module_->import_table[index].kind != kExternalFunction ||
!value->IsCallable()) {
continue;
}
auto js_receiver = Handle<JSReceiver>::cast(value);
uint32_t func_index = module_->import_table[index].index;
const FunctionSig* sig = module_->functions[func_index].sig;
uint32_t sig_index = module_->functions[func_index].sig_index;
uint32_t canonical_type_index =
module_->isorecursive_canonical_type_ids[sig_index];
WasmImportData resolved(js_receiver, sig, canonical_type_index);
ImportCallKind kind = resolved.kind();
if (kind == ImportCallKind::kWasmToWasm ||
kind == ImportCallKind::kLinkError ||
kind == ImportCallKind::kWasmToCapi ||
kind == ImportCallKind::kWasmToJSFastApi) {
continue;
}
int expected_arity = static_cast<int>(sig->parameter_count());
if (kind == ImportCallKind::kJSFunctionArityMismatch) {
Handle<JSFunction> function =
Handle<JSFunction>::cast(resolved.callable());
SharedFunctionInfo shared = function->shared();
expected_arity =
shared.internal_formal_parameter_count_without_receiver();
}
WasmImportWrapperCache::CacheKey key(kind, canonical_type_index,
expected_arity, resolved.suspend());
if (cache_scope[key] != nullptr) {
// Cache entry already exists, no need to compile it again.
continue;
}
import_wrapper_queue.insert(key, sig);
}
auto compile_job_task = std::make_unique<CompileImportWrapperJob>(
isolate_->counters(), native_module, &import_wrapper_queue, &cache_scope);
auto compile_job = V8::GetCurrentPlatform()->CreateJob(
TaskPriority::kUserVisible, std::move(compile_job_task));
// Wait for the job to finish, while contributing in this thread.
compile_job->Join();
}
// Process the imports, including functions, tables, globals, and memory, in
// order, loading them from the {ffi_} object. Returns the number of imported
// functions.
int InstanceBuilder::ProcessImports(Handle<WasmInstanceObject> instance) {
int num_imported_functions = 0;
int num_imported_tables = 0;
DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size());
CompileImportWrappers(instance);
int num_imports = static_cast<int>(module_->import_table.size());
for (int index = 0; index < num_imports; ++index) {
const WasmImport& import = module_->import_table[index];
Handle<String> module_name = sanitized_imports_[index].module_name;
Handle<String> import_name = sanitized_imports_[index].import_name;
Handle<Object> value = sanitized_imports_[index].value;
switch (import.kind) {
case kExternalFunction: {
uint32_t func_index = import.index;
DCHECK_EQ(num_imported_functions, func_index);
if (!ProcessImportedFunction(instance, index, func_index, module_name,
import_name, value)) {
return -1;
}
num_imported_functions++;
break;
}
case kExternalTable: {
uint32_t table_index = import.index;
DCHECK_EQ(table_index, num_imported_tables);
if (!ProcessImportedTable(instance, index, table_index, module_name,
import_name, value)) {
return -1;
}
num_imported_tables++;
USE(num_imported_tables);
break;
}
case kExternalMemory: {
if (!ProcessImportedMemory(instance, index, module_name, import_name,
value)) {
return -1;
}
break;
}
case kExternalGlobal: {
if (!ProcessImportedGlobal(instance, index, import.index, module_name,
import_name, value)) {
return -1;
}
break;
}
case kExternalTag: {
if (!value->IsWasmTagObject()) {
ReportLinkError("tag import requires a WebAssembly.Tag", index,
module_name, import_name);
return -1;
}
Handle<WasmTagObject> imported_tag = Handle<WasmTagObject>::cast(value);
if (!imported_tag->MatchesSignature(
module_->isorecursive_canonical_type_ids
[module_->tags[import.index].sig_index])) {
ReportLinkError("imported tag does not match the expected type",
index, module_name, import_name);
return -1;
}
Object tag = imported_tag->tag();
DCHECK(instance->tags_table().get(import.index).IsUndefined());
instance->tags_table().set(import.index, tag);
tags_wrappers_[import.index] = imported_tag;
break;
}
default:
UNREACHABLE();
}
}
if (num_imported_functions > 0) {
WellKnownImportsList::UpdateResult result =
module_->type_feedback.well_known_imports.Update(
base::VectorOf(well_known_imports_));
if (result == WellKnownImportsList::UpdateResult::kFoundIncompatibility) {
module_object_->native_module()->RemoveCompiledCode(
NativeModule::RemoveFilter::kRemoveTurbofanCode);
}
}
return num_imported_functions;
}
template <typename T>
T* InstanceBuilder::GetRawUntaggedGlobalPtr(const WasmGlobal& global) {
return reinterpret_cast<T*>(raw_buffer_ptr(untagged_globals_, global.offset));
}
// Process initialization of globals.
void InstanceBuilder::InitGlobals(Handle<WasmInstanceObject> instance) {
for (const WasmGlobal& global : module_->globals) {
if (global.mutability && global.imported) continue;
// Happens with imported globals.
if (!global.init.is_set()) continue;
ValueOrError result = EvaluateConstantExpression(
&init_expr_zone_, global.init, global.type, isolate_, instance);
if (MaybeMarkError(result, thrower_)) return;
if (global.type.is_reference()) {
tagged_globals_->set(global.offset, *to_value(result).to_ref());
} else {
to_value(result).CopyTo(GetRawUntaggedGlobalPtr<byte>(global));
}
}
}
// Allocate memory for a module instance as a new JSArrayBuffer.
bool InstanceBuilder::AllocateMemory() {
int initial_pages = static_cast<int>(module_->initial_pages);
int maximum_pages = module_->has_maximum_pages
? static_cast<int>(module_->maximum_pages)
: WasmMemoryObject::kNoMaximum;
auto shared =
module_->has_shared_memory ? SharedFlag::kShared : SharedFlag::kNotShared;
auto mem_type = module_->is_memory64 ? WasmMemoryFlag::kWasmMemory64
: WasmMemoryFlag::kWasmMemory32;
if (!WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared,
mem_type)
.ToHandle(&memory_object_)) {
thrower_->RangeError(
"Out of memory: Cannot allocate Wasm memory for new instance");
return false;
}
memory_buffer_ =
Handle<JSArrayBuffer>(memory_object_->array_buffer(), isolate_);
return true;
}
// Process the exports, creating wrappers for functions, tables, memories,
// globals, and exceptions.
void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
std::unordered_map<int, Handle<Object>> imported_globals;
// If an imported WebAssembly function or global gets exported, the export
// has to be identical to to import. Therefore we cache all imported
// WebAssembly functions in the instance, and all imported globals in a map
// here.
for (int index = 0, end = static_cast<int>(module_->import_table.size());
index < end; ++index) {
const WasmImport& import = module_->import_table[index];
if (import.kind == kExternalFunction) {
Handle<Object> value = sanitized_imports_[index].value;
if (WasmExternalFunction::IsWasmExternalFunction(*value)) {
WasmInstanceObject::SetWasmInternalFunction(
instance, import.index,
WasmInternalFunction::FromExternal(
Handle<WasmExternalFunction>::cast(value), isolate_)
.ToHandleChecked());
}
} else if (import.kind == kExternalGlobal) {
Handle<Object> value = sanitized_imports_[index].value;
if (value->IsWasmGlobalObject()) {
imported_globals[import.index] = value;
}
}
}
Handle<JSObject> exports_object;
MaybeHandle<String> single_function_name;
bool is_asm_js = is_asmjs_module(module_);
if (is_asm_js) {
Handle<JSFunction> object_function = Handle<JSFunction>(
isolate_->native_context()->object_function(), isolate_);
exports_object = isolate_->factory()->NewJSObject(object_function);
single_function_name =
isolate_->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName);
} else {
exports_object = isolate_->factory()->NewJSObjectWithNullProto();
}
instance->set_exports_object(*exports_object);
PropertyDescriptor desc;
desc.set_writable(is_asm_js);
desc.set_enumerable(true);
desc.set_configurable(is_asm_js);
// Process each export in the export table.
for (const WasmExport& exp : module_->export_table) {
Handle<String> name = WasmModuleObject::ExtractUtf8StringFromModuleBytes(
isolate_, module_object_, exp.name, kInternalize);
Handle<JSObject> export_to = exports_object;
switch (exp.kind) {
case kExternalFunction: {
// Wrap and export the code as a JSFunction.
Handle<WasmInternalFunction> internal =
WasmInstanceObject::GetOrCreateWasmInternalFunction(
isolate_, instance, exp.index);
Handle<JSFunction> wasm_external_function =
WasmInternalFunction::GetOrCreateExternal(internal);
desc.set_value(wasm_external_function);
if (is_asm_js &&
String::Equals(isolate_, name,
single_function_name.ToHandleChecked())) {
export_to = instance;
}
break;
}
case kExternalTable: {
desc.set_value(handle(instance->tables().get(exp.index), isolate_));
break;
}
case kExternalMemory: {
// Export the memory as a WebAssembly.Memory object. A WasmMemoryObject
// should already be available if the module has memory, since we always
// create or import it when building an WasmInstanceObject.
DCHECK(instance->has_memory_object());
desc.set_value(
Handle<WasmMemoryObject>(instance->memory_object(), isolate_));
break;
}
case kExternalGlobal: {
const WasmGlobal& global = module_->globals[exp.index];
if (global.imported) {
auto cached_global = imported_globals.find(exp.index);
if (cached_global != imported_globals.end()) {
desc.set_value(cached_global->second);
break;
}
}
Handle<JSArrayBuffer> untagged_buffer;
Handle<FixedArray> tagged_buffer;
uint32_t offset;
if (global.mutability && global.imported) {
Handle<FixedArray> buffers_array(
instance->imported_mutable_globals_buffers(), isolate_);
if (global.type.is_reference()) {
tagged_buffer = handle(
FixedArray::cast(buffers_array->get(global.index)), isolate_);
// For externref globals we store the relative offset in the
// imported_mutable_globals array instead of an absolute address.
offset = instance->imported_mutable_globals().get_int(
global.index * kSystemPointerSize);
} else {
untagged_buffer =
handle(JSArrayBuffer::cast(buffers_array->get(global.index)),
isolate_);
Address global_addr =
instance->imported_mutable_globals().get_sandboxed_pointer(
global.index * kSystemPointerSize);
size_t buffer_size = untagged_buffer->byte_length();
Address backing_store =
reinterpret_cast<Address>(untagged_buffer->backing_store());
CHECK(global_addr >= backing_store &&
global_addr < backing_store + buffer_size);
offset = static_cast<uint32_t>(global_addr - backing_store);
}
} else {
if (global.type.is_reference()) {
tagged_buffer = handle(instance->tagged_globals_buffer(), isolate_);
} else {
untagged_buffer =
handle(instance->untagged_globals_buffer(), isolate_);
}
offset = global.offset;
}
// Since the global's array untagged_buffer is always provided,
// allocation should never fail.
Handle<WasmGlobalObject> global_obj =
WasmGlobalObject::New(isolate_, instance, untagged_buffer,
tagged_buffer, global.type, offset,
global.mutability)
.ToHandleChecked();
desc.set_value(global_obj);
break;
}
case kExternalTag: {
const WasmTag& tag = module_->tags[exp.index];
Handle<WasmTagObject> wrapper = tags_wrappers_[exp.index];
if (wrapper.is_null()) {
Handle<HeapObject> tag_object(
HeapObject::cast(instance->tags_table().get(exp.index)),
isolate_);
uint32_t canonical_sig_index =
module_->isorecursive_canonical_type_ids[tag.sig_index];
wrapper = WasmTagObject::New(isolate_, tag.sig, canonical_sig_index,
tag_object);
tags_wrappers_[exp.index] = wrapper;
}
desc.set_value(wrapper);
break;
}
default:
UNREACHABLE();
}
v8::Maybe<bool> status = JSReceiver::DefineOwnProperty(
isolate_, export_to, name, &desc, Just(kThrowOnError));
if (!status.IsJust()) {
DisallowGarbageCollection no_gc;
TruncatedUserString<> trunc_name(name->GetCharVector<uint8_t>(no_gc));
thrower_->LinkError("export of %.*s failed.", trunc_name.length(),
trunc_name.start());
return;
}
}
if (module_->origin == kWasmOrigin) {
v8::Maybe<bool> success = JSReceiver::SetIntegrityLevel(
isolate_, exports_object, FROZEN, kDontThrow);
DCHECK(success.FromMaybe(false));
USE(success);
}
}
namespace {
V8_INLINE void SetFunctionTablePlaceholder(Isolate* isolate,
Handle<WasmInstanceObject> instance,
Handle<WasmTableObject> table_object,
uint32_t entry_index,
uint32_t func_index) {
const WasmModule* module = instance->module();
const WasmFunction* function = &module->functions[func_index];
MaybeHandle<WasmInternalFunction> wasm_internal_function =
WasmInstanceObject::GetWasmInternalFunction(isolate, instance,
func_index);
if (wasm_internal_function.is_null()) {
// No JSFunction entry yet exists for this function. Create a {Tuple2}
// holding the information to lazily allocate one.
WasmTableObject::SetFunctionTablePlaceholder(
isolate, table_object, entry_index, instance, func_index);
} else {
table_object->entries().set(entry_index,
*wasm_internal_function.ToHandleChecked());
}
WasmTableObject::UpdateDispatchTables(isolate, *table_object, entry_index,
function, *instance);
}
V8_INLINE void SetFunctionTableNullEntry(Isolate* isolate,
Handle<WasmTableObject> table_object,
uint32_t entry_index) {
table_object->entries().set(entry_index, *isolate->factory()->wasm_null());
WasmTableObject::ClearDispatchTables(isolate, table_object, entry_index);
}
} // namespace
void InstanceBuilder::SetTableInitialValues(
Handle<WasmInstanceObject> instance) {
for (int table_index = 0;
table_index < static_cast<int>(module_->tables.size()); ++table_index) {
const WasmTable& table = module_->tables[table_index];
if (table.initial_value.is_set()) {
auto table_object = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate_);
bool is_function_table = IsSubtypeOf(table.type, kWasmFuncRef, module_);
if (is_function_table &&
table.initial_value.kind() == ConstantExpression::kRefFunc) {
for (uint32_t entry_index = 0; entry_index < table.initial_size;
entry_index++) {
SetFunctionTablePlaceholder(isolate_, instance, table_object,
entry_index, table.initial_value.index());
}
} else if (is_function_table &&
table.initial_value.kind() == ConstantExpression::kRefNull) {
for (uint32_t entry_index = 0; entry_index < table.initial_size;
entry_index++) {
SetFunctionTableNullEntry(isolate_, table_object, entry_index);
}
} else {
ValueOrError result =
EvaluateConstantExpression(&init_expr_zone_, table.initial_value,
table.type, isolate_, instance);
if (MaybeMarkError(result, thrower_)) return;
for (uint32_t entry_index = 0; entry_index < table.initial_size;
entry_index++) {
WasmTableObject::Set(isolate_, table_object, entry_index,
to_value(result).to_ref());
}
}
}
}
}
namespace {
enum FunctionComputationMode { kLazyFunctionsAndNull, kStrictFunctionsAndNull };
// If {function_mode == kLazyFunctionsAndNull}, may return a function index
// instead of computing a function object, and {WasmValue(-1)} instead of null.
// Assumes the underlying module is verified.
ValueOrError ConsumeElementSegmentEntry(Zone* zone, Isolate* isolate,
Handle<WasmInstanceObject> instance,
const WasmElemSegment& segment,
Decoder& decoder,
FunctionComputationMode function_mode) {
if (segment.element_type == WasmElemSegment::kFunctionIndexElements) {
uint32_t function_index = decoder.consume_u32v();
return function_mode == kStrictFunctionsAndNull
? EvaluateConstantExpression(
zone, ConstantExpression::RefFunc(function_index),
segment.type, isolate, instance)
: ValueOrError(WasmValue(function_index));
}
switch (static_cast<WasmOpcode>(*decoder.pc())) {
case kExprRefFunc: {
auto [function_index, length] =
decoder.read_u32v<Decoder::FullValidationTag>(decoder.pc() + 1,
"ref.func");
if (V8_LIKELY(decoder.lookahead(1 + length, kExprEnd))) {
decoder.consume_bytes(length + 2);
return function_mode == kStrictFunctionsAndNull
? EvaluateConstantExpression(
zone, ConstantExpression::RefFunc(function_index),
segment.type, isolate, instance)
: ValueOrError(WasmValue(function_index));
}
break;
}
case kExprRefNull: {
auto [heap_type, length] =
value_type_reader::read_heap_type<Decoder::FullValidationTag>(
&decoder, decoder.pc() + 1, WasmFeatures::All());
if (V8_LIKELY(decoder.lookahead(1 + length, kExprEnd))) {
decoder.consume_bytes(length + 2);
return function_mode == kStrictFunctionsAndNull
? EvaluateConstantExpression(zone,
ConstantExpression::RefNull(
heap_type.representation()),
segment.type, isolate, instance)
: WasmValue(int32_t{-1});
}
break;
}
default:
break;
}
auto sig = FixedSizeSignature<ValueType>::Returns(segment.type);
FunctionBody body(&sig, decoder.pc_offset(), decoder.pc(), decoder.end());
WasmFeatures detected;
// We use FullValidationTag so we do not have to create another template
// instance of WasmFullDecoder, which would cost us >50Kb binary code
// size.
WasmFullDecoder<Decoder::FullValidationTag, ConstantExpressionInterface,
kConstantExpression>
full_decoder(zone, instance->module(), WasmFeatures::All(), &detected,
body, instance->module(), isolate, instance);
full_decoder.DecodeFunctionBody();
decoder.consume_bytes(static_cast<int>(full_decoder.pc() - decoder.pc()));
return full_decoder.interface().has_error()
? ValueOrError(full_decoder.interface().error())
: ValueOrError(full_decoder.interface().computed_value());
}
} // namespace
base::Optional<MessageTemplate> InitializeElementSegment(
Zone* zone, Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t segment_index) {
if (!instance->element_segments().get(segment_index).IsUndefined()) return {};
const WasmElemSegment& elem_segment =
instance->module()->elem_segments[segment_index];
base::Vector<const byte> module_bytes =
instance->module_object().native_module()->wire_bytes();
Decoder decoder(module_bytes);
decoder.consume_bytes(elem_segment.elements_wire_bytes_offset);
Handle<FixedArray> result =
isolate->factory()->NewFixedArray(elem_segment.element_count);
for (size_t i = 0; i < elem_segment.element_count; ++i) {
ValueOrError value =
ConsumeElementSegmentEntry(zone, isolate, instance, elem_segment,
decoder, kStrictFunctionsAndNull);
if (is_error(value)) return {to_error(value)};
result->set(static_cast<int>(i), *to_value(value).to_ref());
}
instance->element_segments().set(segment_index, *result);
return {};
}
void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) {
for (uint32_t segment_index = 0;
segment_index < module_->elem_segments.size(); ++segment_index) {
const WasmElemSegment& elem_segment =
instance->module()->elem_segments[segment_index];
// Passive segments are not copied during instantiation.
if (elem_segment.status != WasmElemSegment::kStatusActive) continue;
const uint32_t table_index = elem_segment.table_index;
ValueOrError maybe_dst = EvaluateConstantExpression(
&init_expr_zone_, elem_segment.offset, kWasmI32, isolate_, instance);
if (MaybeMarkError(maybe_dst, thrower_)) return;
const uint32_t dst = to_value(maybe_dst).to_u32();
const size_t count = elem_segment.element_count;
Handle<WasmTableObject> table_object = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate_);
if (!base::IsInBounds<size_t>(dst, count, table_object->current_length())) {
thrower_->RuntimeError("%s",
MessageFormatter::TemplateString(
MessageTemplate::kWasmTrapTableOutOfBounds));
return;
}
base::Vector<const byte> module_bytes =
instance->module_object().native_module()->wire_bytes();
Decoder decoder(module_bytes);
decoder.consume_bytes(elem_segment.elements_wire_bytes_offset);
bool is_function_table =
IsSubtypeOf(module_->tables[table_index].type, kWasmFuncRef, module_);
if (is_function_table) {
for (size_t i = 0; i < count; i++) {
int entry_index = static_cast<int>(dst + i);
ValueOrError computed_element = ConsumeElementSegmentEntry(
&init_expr_zone_, isolate_, instance, elem_segment, decoder,
kLazyFunctionsAndNull);
if (MaybeMarkError(computed_element, thrower_)) return;
WasmValue computed_value = to_value(computed_element);
if (computed_value.type() == kWasmI32) {
if (computed_value.to_i32() >= 0) {
SetFunctionTablePlaceholder(isolate_, instance, table_object,
entry_index, computed_value.to_i32());
} else {
SetFunctionTableNullEntry(isolate_, table_object, entry_index);
}
} else {
WasmTableObject::Set(isolate_, table_object, entry_index,
computed_value.to_ref());
}
}
} else {
for (size_t i = 0; i < count; i++) {
int entry_index = static_cast<int>(dst + i);
ValueOrError computed_element = ConsumeElementSegmentEntry(
&init_expr_zone_, isolate_, instance, elem_segment, decoder,
kStrictFunctionsAndNull);
if (MaybeMarkError(computed_element, thrower_)) return;
WasmTableObject::Set(isolate_, table_object, entry_index,
to_value(computed_element).to_ref());
}
}
// Active segment have to be set to empty after instance initialization
// (much like passive segments after dropping).
instance->element_segments().set(segment_index,
*isolate_->factory()->empty_fixed_array());
}
}
void InstanceBuilder::InitializeTags(Handle<WasmInstanceObject> instance) {
Handle<FixedArray> tags_table(instance->tags_table(), isolate_);
for (int index = 0; index < tags_table->length(); ++index) {
if (!tags_table->get(index).IsUndefined(isolate_)) continue;
Handle<WasmExceptionTag> tag = WasmExceptionTag::New(isolate_, index);
tags_table->set(index, *tag);
}
}
} // namespace wasm
} // namespace internal
} // namespace v8
#undef TRACE