// Copyright 2015 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/wasm-objects.h" #include "src/utils.h" #include "src/base/iterator.h" #include "src/debug/debug-interface.h" #include "src/objects-inl.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-text.h" #define TRACE(...) \ do { \ if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \ } while (false) #define TRACE_CHAIN(instance) \ do { \ instance->PrintInstancesChain(); \ } while (false) using namespace v8::internal; using namespace v8::internal::wasm; #define DEFINE_GETTER0(getter, Container, name, field, type) \ type* Container::name() { return type::cast(getter(field)); } #define DEFINE_ACCESSORS0(getter, setter, Container, name, field, type) \ DEFINE_GETTER0(getter, Container, name, field, type) \ void Container::set_##name(type* value) { return setter(field, value); } #define DEFINE_OPTIONAL_ACCESSORS0(getter, setter, Container, name, field, \ type) \ DEFINE_ACCESSORS0(getter, setter, Container, name, field, type) \ bool Container::has_##name() { \ return !getter(field)->IsUndefined(GetIsolate()); \ } #define DEFINE_OPTIONAL_GETTER0(getter, Container, name, field, type) \ DEFINE_GETTER0(getter, Container, name, field, type) \ bool Container::has_##name() { \ return !getter(field)->IsUndefined(GetIsolate()); \ } #define DEFINE_GETTER0(getter, Container, name, field, type) \ type* Container::name() { return type::cast(getter(field)); } #define DEFINE_OBJ_GETTER(Container, name, field, type) \ DEFINE_GETTER0(GetInternalField, Container, name, field, type) #define DEFINE_OBJ_ACCESSORS(Container, name, field, type) \ DEFINE_ACCESSORS0(GetInternalField, SetInternalField, Container, name, \ field, type) #define DEFINE_OPTIONAL_OBJ_ACCESSORS(Container, name, field, type) \ DEFINE_OPTIONAL_ACCESSORS0(GetInternalField, SetInternalField, Container, \ name, field, type) #define DEFINE_ARR_GETTER(Container, name, field, type) \ DEFINE_GETTER0(get, Container, name, field, type) #define DEFINE_ARR_ACCESSORS(Container, name, field, type) \ DEFINE_ACCESSORS0(get, set, Container, name, field, type) #define DEFINE_OPTIONAL_ARR_ACCESSORS(Container, name, field, type) \ DEFINE_OPTIONAL_ACCESSORS0(get, set, Container, name, field, type) #define DEFINE_OPTIONAL_ARR_GETTER(Container, name, field, type) \ DEFINE_OPTIONAL_GETTER0(get, Container, name, field, type) namespace { uint32_t SafeUint32(Object* value) { if (value->IsSmi()) { int32_t val = Smi::cast(value)->value(); CHECK_GE(val, 0); return static_cast(val); } DCHECK(value->IsHeapNumber()); HeapNumber* num = HeapNumber::cast(value); CHECK_GE(num->value(), 0.0); CHECK_LE(num->value(), kMaxUInt32); return static_cast(num->value()); } int32_t SafeInt32(Object* value) { if (value->IsSmi()) { return Smi::cast(value)->value(); } DCHECK(value->IsHeapNumber()); HeapNumber* num = HeapNumber::cast(value); CHECK_GE(num->value(), Smi::kMinValue); CHECK_LE(num->value(), Smi::kMaxValue); return static_cast(num->value()); } // An iterator that returns first the module itself, then all modules linked via // next, then all linked via prev. class CompiledModulesIterator : public std::iterator> { public: CompiledModulesIterator(Isolate* isolate, Handle start_module, bool at_end) : isolate_(isolate), start_module_(start_module), current_(at_end ? Handle::null() : start_module) {} Handle operator*() const { DCHECK(!current_.is_null()); return current_; } void operator++() { Advance(); } bool operator!=(const CompiledModulesIterator& other) { DCHECK(start_module_.is_identical_to(other.start_module_)); return !current_.is_identical_to(other.current_); } private: void Advance() { DCHECK(!current_.is_null()); if (!is_backwards_) { if (current_->has_weak_next_instance()) { WeakCell* weak_next = current_->ptr_to_weak_next_instance(); if (!weak_next->cleared()) { current_ = handle(WasmCompiledModule::cast(weak_next->value()), isolate_); return; } } // No more modules in next-links, now try the previous-links. is_backwards_ = true; current_ = start_module_; } if (current_->has_weak_prev_instance()) { WeakCell* weak_prev = current_->ptr_to_weak_prev_instance(); if (!weak_prev->cleared()) { current_ = handle(WasmCompiledModule::cast(weak_prev->value()), isolate_); return; } } current_ = Handle::null(); } friend class CompiledModuleInstancesIterator; Isolate* isolate_; Handle start_module_; Handle current_; bool is_backwards_ = false; }; // An iterator based on the CompiledModulesIterator, but it returns all live // instances, not the WasmCompiledModules itself. class CompiledModuleInstancesIterator : public std::iterator> { public: CompiledModuleInstancesIterator(Isolate* isolate, Handle start_module, bool at_end) : it(isolate, start_module, at_end) { while (NeedToAdvance()) ++it; } Handle operator*() { return handle( WasmInstanceObject::cast((*it)->weak_owning_instance()->value()), it.isolate_); } void operator++() { do { ++it; } while (NeedToAdvance()); } bool operator!=(const CompiledModuleInstancesIterator& other) { return it != other.it; } private: bool NeedToAdvance() { return !it.current_.is_null() && (!it.current_->has_weak_owning_instance() || it.current_->ptr_to_weak_owning_instance()->cleared()); } CompiledModulesIterator it; }; v8::base::iterator_range iterate_compiled_module_instance_chain( Isolate* isolate, Handle compiled_module) { return {CompiledModuleInstancesIterator(isolate, compiled_module, false), CompiledModuleInstancesIterator(isolate, compiled_module, true)}; } #ifdef DEBUG bool IsBreakablePosition(Handle compiled_module, int func_index, int offset_in_func) { DisallowHeapAllocation no_gc; AccountingAllocator alloc; Zone tmp(&alloc, ZONE_NAME); BodyLocalDecls locals(&tmp); const byte* module_start = compiled_module->module_bytes()->GetChars(); WasmFunction& func = compiled_module->module()->functions[func_index]; BytecodeIterator iterator(module_start + func.code_start_offset, module_start + func.code_end_offset, &locals); DCHECK_LT(0, locals.encoded_size); for (uint32_t offset : iterator.offsets()) { if (offset > static_cast(offset_in_func)) break; if (offset == static_cast(offset_in_func)) return true; } return false; } #endif // DEBUG } // namespace Handle WasmModuleObject::New( Isolate* isolate, Handle compiled_module) { ModuleOrigin origin = compiled_module->module()->origin; Handle module_object; if (origin == ModuleOrigin::kWasmOrigin) { Handle module_cons( isolate->native_context()->wasm_module_constructor()); module_object = isolate->factory()->NewJSObject(module_cons); Handle module_sym(isolate->native_context()->wasm_module_sym()); Object::SetProperty(module_object, module_sym, module_object, STRICT) .Check(); } else { DCHECK(origin == ModuleOrigin::kAsmJsOrigin); Handle map = isolate->factory()->NewMap( JS_OBJECT_TYPE, JSObject::kHeaderSize + WasmModuleObject::kFieldCount * kPointerSize); module_object = isolate->factory()->NewJSObjectFromMap(map, TENURED); } module_object->SetInternalField(WasmModuleObject::kCompiledModule, *compiled_module); Handle link_to_module = isolate->factory()->NewWeakCell(module_object); compiled_module->set_weak_wasm_module(link_to_module); return Handle::cast(module_object); } WasmModuleObject* WasmModuleObject::cast(Object* object) { DCHECK(object->IsJSObject()); // TODO(titzer): brand check for WasmModuleObject. return reinterpret_cast(object); } bool WasmModuleObject::IsWasmModuleObject(Object* object) { return object->IsJSObject() && JSObject::cast(object)->GetInternalFieldCount() == kFieldCount; } DEFINE_OBJ_GETTER(WasmModuleObject, compiled_module, kCompiledModule, WasmCompiledModule) Handle WasmTableObject::New(Isolate* isolate, uint32_t initial, int64_t maximum, Handle* js_functions) { Handle table_ctor( isolate->native_context()->wasm_table_constructor()); Handle table_obj = isolate->factory()->NewJSObject(table_ctor); *js_functions = isolate->factory()->NewFixedArray(initial); Object* null = isolate->heap()->null_value(); for (int i = 0; i < static_cast(initial); ++i) { (*js_functions)->set(i, null); } table_obj->SetInternalField(kFunctions, *(*js_functions)); Handle max = isolate->factory()->NewNumber(maximum); table_obj->SetInternalField(kMaximum, *max); Handle dispatch_tables = isolate->factory()->NewFixedArray(0); table_obj->SetInternalField(kDispatchTables, *dispatch_tables); Handle table_sym(isolate->native_context()->wasm_table_sym()); Object::SetProperty(table_obj, table_sym, table_obj, STRICT).Check(); return Handle::cast(table_obj); } DEFINE_OBJ_GETTER(WasmTableObject, dispatch_tables, kDispatchTables, FixedArray) Handle WasmTableObject::AddDispatchTable( Isolate* isolate, Handle table_obj, Handle instance, int table_index, Handle function_table, Handle signature_table) { Handle dispatch_tables( FixedArray::cast(table_obj->GetInternalField(kDispatchTables)), isolate); DCHECK_EQ(0, dispatch_tables->length() % 4); if (instance.is_null()) return dispatch_tables; // TODO(titzer): use weak cells here to avoid leaking instances. // Grow the dispatch table and add a new triple at the end. Handle new_dispatch_tables = isolate->factory()->CopyFixedArrayAndGrow(dispatch_tables, 4); new_dispatch_tables->set(dispatch_tables->length() + 0, *instance); new_dispatch_tables->set(dispatch_tables->length() + 1, Smi::FromInt(table_index)); new_dispatch_tables->set(dispatch_tables->length() + 2, *function_table); new_dispatch_tables->set(dispatch_tables->length() + 3, *signature_table); table_obj->SetInternalField(WasmTableObject::kDispatchTables, *new_dispatch_tables); return new_dispatch_tables; } DEFINE_OBJ_ACCESSORS(WasmTableObject, functions, kFunctions, FixedArray) uint32_t WasmTableObject::current_length() { return functions()->length(); } bool WasmTableObject::has_maximum_length() { return GetInternalField(kMaximum)->Number() >= 0; } int64_t WasmTableObject::maximum_length() { return static_cast(GetInternalField(kMaximum)->Number()); } WasmTableObject* WasmTableObject::cast(Object* object) { DCHECK(object && object->IsJSObject()); // TODO(titzer): brand check for WasmTableObject. return reinterpret_cast(object); } void WasmTableObject::Grow(Isolate* isolate, Handle table, uint32_t count) { Handle dispatch_tables(table->dispatch_tables()); wasm::GrowDispatchTables(isolate, dispatch_tables, table->functions()->length(), count); } Handle WasmMemoryObject::New(Isolate* isolate, Handle buffer, int32_t maximum) { Handle memory_ctor( isolate->native_context()->wasm_memory_constructor()); Handle memory_obj = isolate->factory()->NewJSObject(memory_ctor, TENURED); memory_obj->SetInternalField(kArrayBuffer, *buffer); Handle max = isolate->factory()->NewNumber(maximum); memory_obj->SetInternalField(kMaximum, *max); Handle memory_sym(isolate->native_context()->wasm_memory_sym()); Object::SetProperty(memory_obj, memory_sym, memory_obj, STRICT).Check(); return Handle::cast(memory_obj); } DEFINE_OBJ_ACCESSORS(WasmMemoryObject, buffer, kArrayBuffer, JSArrayBuffer) DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmMemoryObject, instances_link, kInstancesLink, WasmInstanceWrapper) uint32_t WasmMemoryObject::current_pages() { return SafeUint32(buffer()->byte_length()) / wasm::WasmModule::kPageSize; } bool WasmMemoryObject::has_maximum_pages() { return GetInternalField(kMaximum)->Number() >= 0; } int32_t WasmMemoryObject::maximum_pages() { return static_cast(GetInternalField(kMaximum)->Number()); } WasmMemoryObject* WasmMemoryObject::cast(Object* object) { DCHECK(object && object->IsJSObject()); // TODO(titzer): brand check for WasmMemoryObject. return reinterpret_cast(object); } void WasmMemoryObject::AddInstance(Isolate* isolate, Handle instance) { Handle instance_wrapper = handle(instance->instance_wrapper()); if (has_instances_link()) { Handle current_wrapper(instances_link()); DCHECK(WasmInstanceWrapper::IsWasmInstanceWrapper(*current_wrapper)); DCHECK(!current_wrapper->has_previous()); instance_wrapper->set_next_wrapper(*current_wrapper); current_wrapper->set_previous_wrapper(*instance_wrapper); } set_instances_link(*instance_wrapper); } void WasmMemoryObject::ResetInstancesLink(Isolate* isolate) { Handle undefined = isolate->factory()->undefined_value(); SetInternalField(kInstancesLink, *undefined); } DEFINE_OBJ_ACCESSORS(WasmInstanceObject, compiled_module, kCompiledModule, WasmCompiledModule) DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, globals_buffer, kGlobalsArrayBuffer, JSArrayBuffer) DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, memory_buffer, kMemoryArrayBuffer, JSArrayBuffer) DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, memory_object, kMemoryObject, WasmMemoryObject) DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, debug_info, kDebugInfo, WasmDebugInfo) DEFINE_OPTIONAL_OBJ_ACCESSORS(WasmInstanceObject, instance_wrapper, kWasmMemInstanceWrapper, WasmInstanceWrapper) WasmModuleObject* WasmInstanceObject::module_object() { return *compiled_module()->wasm_module(); } WasmModule* WasmInstanceObject::module() { return compiled_module()->module(); } Handle WasmInstanceObject::GetOrCreateDebugInfo( Handle instance) { if (instance->has_debug_info()) return handle(instance->debug_info()); Handle new_info = WasmDebugInfo::New(instance); instance->set_debug_info(*new_info); return new_info; } WasmInstanceObject* WasmInstanceObject::cast(Object* object) { DCHECK(IsWasmInstanceObject(object)); return reinterpret_cast(object); } bool WasmInstanceObject::IsWasmInstanceObject(Object* object) { if (!object->IsJSObject()) return false; JSObject* obj = JSObject::cast(object); Isolate* isolate = obj->GetIsolate(); if (obj->GetInternalFieldCount() != kFieldCount) { return false; } Object* mem = obj->GetInternalField(kMemoryArrayBuffer); if (!(mem->IsUndefined(isolate) || mem->IsJSArrayBuffer()) || !WasmCompiledModule::IsWasmCompiledModule( obj->GetInternalField(kCompiledModule))) { return false; } // All checks passed. return true; } Handle WasmInstanceObject::New( Isolate* isolate, Handle compiled_module) { Handle instance_cons( isolate->native_context()->wasm_instance_constructor()); Handle instance_object = isolate->factory()->NewJSObject(instance_cons, TENURED); Handle instance_sym(isolate->native_context()->wasm_instance_sym()); Object::SetProperty(instance_object, instance_sym, instance_object, STRICT) .Check(); Handle instance( reinterpret_cast(*instance_object), isolate); instance->SetInternalField(kCompiledModule, *compiled_module); instance->SetInternalField(kMemoryObject, isolate->heap()->undefined_value()); Handle instance_wrapper = WasmInstanceWrapper::New(isolate, instance); instance->SetInternalField(kWasmMemInstanceWrapper, *instance_wrapper); return instance; } WasmInstanceObject* WasmExportedFunction::instance() { return WasmInstanceObject::cast(GetInternalField(kInstance)); } int WasmExportedFunction::function_index() { return SafeInt32(GetInternalField(kIndex)); } WasmExportedFunction* WasmExportedFunction::cast(Object* object) { DCHECK(object && object->IsJSFunction()); DCHECK_EQ(Code::JS_TO_WASM_FUNCTION, JSFunction::cast(object)->code()->kind()); // TODO(titzer): brand check for WasmExportedFunction. return reinterpret_cast(object); } Handle WasmExportedFunction::New( Isolate* isolate, Handle instance, MaybeHandle maybe_name, int func_index, int arity, Handle export_wrapper) { Handle name; if (maybe_name.is_null()) { EmbeddedVector buffer; int length = SNPrintF(buffer, "%d", func_index); name = isolate->factory() ->NewStringFromAscii( Vector::cast(buffer.SubVector(0, length))) .ToHandleChecked(); } else { name = maybe_name.ToHandleChecked(); } DCHECK_EQ(Code::JS_TO_WASM_FUNCTION, export_wrapper->kind()); Handle shared = isolate->factory()->NewSharedFunctionInfo(name, export_wrapper, false); shared->set_length(arity); shared->set_internal_formal_parameter_count(arity); Handle function = isolate->factory()->NewFunction( isolate->wasm_function_map(), name, export_wrapper); function->set_shared(*shared); function->SetInternalField(kInstance, *instance); function->SetInternalField(kIndex, Smi::FromInt(func_index)); return Handle::cast(function); } bool WasmSharedModuleData::IsWasmSharedModuleData(Object* object) { if (!object->IsFixedArray()) return false; FixedArray* arr = FixedArray::cast(object); if (arr->length() != kFieldCount) return false; Isolate* isolate = arr->GetIsolate(); if (!arr->get(kModuleWrapper)->IsForeign()) return false; if (!arr->get(kModuleBytes)->IsUndefined(isolate) && !arr->get(kModuleBytes)->IsSeqOneByteString()) return false; if (!arr->get(kScript)->IsScript()) return false; if (!arr->get(kAsmJsOffsetTable)->IsUndefined(isolate) && !arr->get(kAsmJsOffsetTable)->IsByteArray()) return false; if (!arr->get(kBreakPointInfos)->IsUndefined(isolate) && !arr->get(kBreakPointInfos)->IsFixedArray()) return false; return true; } WasmSharedModuleData* WasmSharedModuleData::cast(Object* object) { DCHECK(IsWasmSharedModuleData(object)); return reinterpret_cast(object); } wasm::WasmModule* WasmSharedModuleData::module() { // We populate the kModuleWrapper field with a Foreign holding the // address to the address of a WasmModule. This is because we can // handle both cases when the WasmModule's lifetime is managed through // a Managed object, as well as cases when it's managed // by the embedder. CcTests fall into the latter case. return *(reinterpret_cast( Foreign::cast(get(kModuleWrapper))->foreign_address())); } DEFINE_OPTIONAL_ARR_ACCESSORS(WasmSharedModuleData, module_bytes, kModuleBytes, SeqOneByteString); DEFINE_ARR_GETTER(WasmSharedModuleData, script, kScript, Script); DEFINE_OPTIONAL_ARR_ACCESSORS(WasmSharedModuleData, asm_js_offset_table, kAsmJsOffsetTable, ByteArray); DEFINE_OPTIONAL_ARR_GETTER(WasmSharedModuleData, breakpoint_infos, kBreakPointInfos, FixedArray); Handle WasmSharedModuleData::New( Isolate* isolate, Handle module_wrapper, Handle module_bytes, Handle