| // 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 <functional> |
| #include <memory> |
| |
| #include "src/api.h" |
| #include "src/assembler-inl.h" |
| #include "src/code-stubs.h" |
| #include "src/debug/interface-types.h" |
| #include "src/frames-inl.h" |
| #include "src/objects.h" |
| #include "src/property-descriptor.h" |
| #include "src/simulator.h" |
| #include "src/snapshot/snapshot.h" |
| #include "src/v8.h" |
| |
| #include "src/compiler/wasm-compiler.h" |
| #include "src/wasm/compilation-manager.h" |
| #include "src/wasm/module-compiler.h" |
| #include "src/wasm/module-decoder.h" |
| #include "src/wasm/wasm-code-specialization.h" |
| #include "src/wasm/wasm-js.h" |
| #include "src/wasm/wasm-module.h" |
| #include "src/wasm/wasm-objects-inl.h" |
| #include "src/wasm/wasm-result.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace wasm { |
| |
| #define TRACE(...) \ |
| do { \ |
| if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \ |
| } while (false) |
| |
| #define TRACE_CHAIN(instance) \ |
| do { \ |
| instance->PrintInstancesChain(); \ |
| } while (false) |
| |
| #define TRACE_COMPILE(...) \ |
| do { \ |
| if (FLAG_trace_wasm_compiler) PrintF(__VA_ARGS__); \ |
| } while (false) |
| |
| // static |
| const WasmExceptionSig WasmException::empty_sig_(0, 0, nullptr); |
| |
| // static |
| constexpr const char* WasmException::kRuntimeIdStr; |
| |
| // static |
| constexpr const char* WasmException::kRuntimeValuesStr; |
| |
| void UnpackAndRegisterProtectedInstructions(Isolate* isolate, |
| Handle<FixedArray> code_table) { |
| DisallowHeapAllocation no_gc; |
| std::vector<trap_handler::ProtectedInstructionData> unpacked; |
| |
| for (int i = 0; i < code_table->length(); ++i) { |
| Object* maybe_code = code_table->get(i); |
| // This is sometimes undefined when we're called from cctests. |
| if (maybe_code->IsUndefined(isolate)) continue; |
| Code* code = Code::cast(maybe_code); |
| |
| if (code->kind() != Code::WASM_FUNCTION) { |
| continue; |
| } |
| |
| if (code->trap_handler_index()->value() != trap_handler::kInvalidIndex) { |
| // This function has already been registered. |
| continue; |
| } |
| |
| byte* base = code->entry(); |
| |
| const int mode_mask = |
| RelocInfo::ModeMask(RelocInfo::WASM_PROTECTED_INSTRUCTION_LANDING); |
| for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { |
| trap_handler::ProtectedInstructionData data; |
| data.instr_offset = static_cast<uint32_t>(it.rinfo()->data()); |
| data.landing_offset = static_cast<uint32_t>(it.rinfo()->pc() - base); |
| // Check that now over-/underflow happened. |
| DCHECK_EQ(it.rinfo()->data(), data.instr_offset); |
| DCHECK_EQ(it.rinfo()->pc() - base, data.landing_offset); |
| unpacked.emplace_back(data); |
| } |
| if (unpacked.empty()) continue; |
| |
| int size = code->CodeSize(); |
| const int index = RegisterHandlerData(reinterpret_cast<void*>(base), size, |
| unpacked.size(), &unpacked[0]); |
| unpacked.clear(); |
| // TODO(eholk): if index is negative, fail. |
| DCHECK_LE(0, index); |
| code->set_trap_handler_index(Smi::FromInt(index)); |
| } |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const WasmFunctionName& name) { |
| os << "#" << name.function_->func_index; |
| if (name.function_->name.is_set()) { |
| if (name.name_.start()) { |
| os << ":"; |
| os.write(name.name_.start(), name.name_.length()); |
| } |
| } else { |
| os << "?"; |
| } |
| return os; |
| } |
| |
| WasmModule::WasmModule(std::unique_ptr<Zone> owned) |
| : signature_zone(std::move(owned)) {} |
| |
| WasmFunction* GetWasmFunctionForExport(Isolate* isolate, |
| Handle<Object> target) { |
| if (target->IsJSFunction()) { |
| Handle<JSFunction> func = Handle<JSFunction>::cast(target); |
| if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { |
| auto exported = Handle<WasmExportedFunction>::cast(func); |
| Handle<WasmInstanceObject> other_instance(exported->instance(), isolate); |
| int func_index = exported->function_index(); |
| return &other_instance->module()->functions[func_index]; |
| } |
| } |
| return nullptr; |
| } |
| |
| Handle<Code> UnwrapExportWrapper(Handle<JSFunction> export_wrapper) { |
| Handle<Code> export_wrapper_code = handle(export_wrapper->code()); |
| DCHECK_EQ(export_wrapper_code->kind(), Code::JS_TO_WASM_FUNCTION); |
| int mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); |
| for (RelocIterator it(*export_wrapper_code, mask);; it.next()) { |
| DCHECK(!it.done()); |
| Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); |
| if (target->kind() != Code::WASM_FUNCTION && |
| target->kind() != Code::WASM_TO_JS_FUNCTION && |
| target->kind() != Code::WASM_INTERPRETER_ENTRY) |
| continue; |
| // There should only be this one call to wasm code. |
| #ifdef DEBUG |
| for (it.next(); !it.done(); it.next()) { |
| Code* code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); |
| DCHECK(code->kind() != Code::WASM_FUNCTION && |
| code->kind() != Code::WASM_TO_JS_FUNCTION && |
| code->kind() != Code::WASM_INTERPRETER_ENTRY); |
| } |
| #endif |
| return handle(target); |
| } |
| UNREACHABLE(); |
| } |
| |
| void UpdateDispatchTables(Isolate* isolate, Handle<FixedArray> dispatch_tables, |
| int index, WasmFunction* function, |
| Handle<Code> code) { |
| DCHECK_EQ(0, dispatch_tables->length() % 4); |
| for (int i = 0; i < dispatch_tables->length(); i += 4) { |
| int table_index = Smi::ToInt(dispatch_tables->get(i + 1)); |
| Handle<FixedArray> function_table( |
| FixedArray::cast(dispatch_tables->get(i + 2)), isolate); |
| Handle<FixedArray> signature_table( |
| FixedArray::cast(dispatch_tables->get(i + 3)), isolate); |
| if (function) { |
| // TODO(titzer): the signature might need to be copied to avoid |
| // a dangling pointer in the signature map. |
| Handle<WasmInstanceObject> instance( |
| WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); |
| auto& func_table = instance->module()->function_tables[table_index]; |
| uint32_t sig_index = func_table.map.FindOrInsert(function->sig); |
| signature_table->set(index, Smi::FromInt(static_cast<int>(sig_index))); |
| function_table->set(index, *code); |
| } else { |
| signature_table->set(index, Smi::FromInt(-1)); |
| function_table->set(index, Smi::kZero); |
| } |
| } |
| } |
| |
| bool IsWasmCodegenAllowed(Isolate* isolate, Handle<Context> context) { |
| // TODO(wasm): Once wasm has its own CSP policy, we should introduce a |
| // separate callback that includes information about the module about to be |
| // compiled. For the time being, pass an empty string as placeholder for the |
| // sources. |
| return isolate->allow_code_gen_callback() == nullptr || |
| isolate->allow_code_gen_callback()( |
| v8::Utils::ToLocal(context), |
| v8::Utils::ToLocal(isolate->factory()->empty_string())); |
| } |
| |
| namespace testing { |
| void ValidateInstancesChain(Isolate* isolate, |
| Handle<WasmModuleObject> module_obj, |
| int instance_count) { |
| CHECK_GE(instance_count, 0); |
| DisallowHeapAllocation no_gc; |
| WasmCompiledModule* compiled_module = module_obj->compiled_module(); |
| CHECK_EQ(JSObject::cast(compiled_module->ptr_to_weak_wasm_module()->value()), |
| *module_obj); |
| Object* prev = nullptr; |
| int found_instances = compiled_module->has_weak_owning_instance() ? 1 : 0; |
| WasmCompiledModule* current_instance = compiled_module; |
| while (current_instance->has_weak_next_instance()) { |
| CHECK((prev == nullptr && !current_instance->has_weak_prev_instance()) || |
| current_instance->ptr_to_weak_prev_instance()->value() == prev); |
| CHECK_EQ(current_instance->ptr_to_weak_wasm_module()->value(), *module_obj); |
| CHECK(current_instance->ptr_to_weak_owning_instance() |
| ->value() |
| ->IsWasmInstanceObject()); |
| prev = current_instance; |
| current_instance = WasmCompiledModule::cast( |
| current_instance->ptr_to_weak_next_instance()->value()); |
| ++found_instances; |
| CHECK_LE(found_instances, instance_count); |
| } |
| CHECK_EQ(found_instances, instance_count); |
| } |
| |
| void ValidateModuleState(Isolate* isolate, |
| Handle<WasmModuleObject> module_obj) { |
| DisallowHeapAllocation no_gc; |
| WasmCompiledModule* compiled_module = module_obj->compiled_module(); |
| CHECK(compiled_module->has_weak_wasm_module()); |
| CHECK_EQ(compiled_module->ptr_to_weak_wasm_module()->value(), *module_obj); |
| CHECK(!compiled_module->has_weak_prev_instance()); |
| CHECK(!compiled_module->has_weak_next_instance()); |
| CHECK(!compiled_module->has_weak_owning_instance()); |
| } |
| |
| void ValidateOrphanedInstance(Isolate* isolate, |
| Handle<WasmInstanceObject> instance) { |
| DisallowHeapAllocation no_gc; |
| WasmCompiledModule* compiled_module = instance->compiled_module(); |
| CHECK(compiled_module->has_weak_wasm_module()); |
| CHECK(compiled_module->ptr_to_weak_wasm_module()->cleared()); |
| } |
| |
| } // namespace testing |
| |
| Handle<JSArray> GetImports(Isolate* isolate, |
| Handle<WasmModuleObject> module_object) { |
| Handle<WasmCompiledModule> compiled_module(module_object->compiled_module(), |
| isolate); |
| Factory* factory = isolate->factory(); |
| |
| Handle<String> module_string = factory->InternalizeUtf8String("module"); |
| Handle<String> name_string = factory->InternalizeUtf8String("name"); |
| Handle<String> kind_string = factory->InternalizeUtf8String("kind"); |
| |
| Handle<String> function_string = factory->InternalizeUtf8String("function"); |
| Handle<String> table_string = factory->InternalizeUtf8String("table"); |
| Handle<String> memory_string = factory->InternalizeUtf8String("memory"); |
| Handle<String> global_string = factory->InternalizeUtf8String("global"); |
| |
| // Create the result array. |
| WasmModule* module = compiled_module->module(); |
| int num_imports = static_cast<int>(module->import_table.size()); |
| Handle<JSArray> array_object = factory->NewJSArray(PACKED_ELEMENTS, 0, 0); |
| Handle<FixedArray> storage = factory->NewFixedArray(num_imports); |
| JSArray::SetContent(array_object, storage); |
| array_object->set_length(Smi::FromInt(num_imports)); |
| |
| Handle<JSFunction> object_function = |
| Handle<JSFunction>(isolate->native_context()->object_function(), isolate); |
| |
| // Populate the result array. |
| for (int index = 0; index < num_imports; ++index) { |
| WasmImport& import = module->import_table[index]; |
| |
| Handle<JSObject> entry = factory->NewJSObject(object_function); |
| |
| Handle<String> import_kind; |
| switch (import.kind) { |
| case kExternalFunction: |
| import_kind = function_string; |
| break; |
| case kExternalTable: |
| import_kind = table_string; |
| break; |
| case kExternalMemory: |
| import_kind = memory_string; |
| break; |
| case kExternalGlobal: |
| import_kind = global_string; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| MaybeHandle<String> import_module = |
| WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate, compiled_module, import.module_name); |
| |
| MaybeHandle<String> import_name = |
| WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate, compiled_module, import.field_name); |
| |
| JSObject::AddProperty(entry, module_string, import_module.ToHandleChecked(), |
| NONE); |
| JSObject::AddProperty(entry, name_string, import_name.ToHandleChecked(), |
| NONE); |
| JSObject::AddProperty(entry, kind_string, import_kind, NONE); |
| |
| storage->set(index, *entry); |
| } |
| |
| return array_object; |
| } |
| |
| Handle<JSArray> GetExports(Isolate* isolate, |
| Handle<WasmModuleObject> module_object) { |
| Handle<WasmCompiledModule> compiled_module(module_object->compiled_module(), |
| isolate); |
| Factory* factory = isolate->factory(); |
| |
| Handle<String> name_string = factory->InternalizeUtf8String("name"); |
| Handle<String> kind_string = factory->InternalizeUtf8String("kind"); |
| |
| Handle<String> function_string = factory->InternalizeUtf8String("function"); |
| Handle<String> table_string = factory->InternalizeUtf8String("table"); |
| Handle<String> memory_string = factory->InternalizeUtf8String("memory"); |
| Handle<String> global_string = factory->InternalizeUtf8String("global"); |
| |
| // Create the result array. |
| WasmModule* module = compiled_module->module(); |
| int num_exports = static_cast<int>(module->export_table.size()); |
| Handle<JSArray> array_object = factory->NewJSArray(PACKED_ELEMENTS, 0, 0); |
| Handle<FixedArray> storage = factory->NewFixedArray(num_exports); |
| JSArray::SetContent(array_object, storage); |
| array_object->set_length(Smi::FromInt(num_exports)); |
| |
| Handle<JSFunction> object_function = |
| Handle<JSFunction>(isolate->native_context()->object_function(), isolate); |
| |
| // Populate the result array. |
| for (int index = 0; index < num_exports; ++index) { |
| WasmExport& exp = module->export_table[index]; |
| |
| Handle<String> export_kind; |
| switch (exp.kind) { |
| case kExternalFunction: |
| export_kind = function_string; |
| break; |
| case kExternalTable: |
| export_kind = table_string; |
| break; |
| case kExternalMemory: |
| export_kind = memory_string; |
| break; |
| case kExternalGlobal: |
| export_kind = global_string; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| Handle<JSObject> entry = factory->NewJSObject(object_function); |
| |
| MaybeHandle<String> export_name = |
| WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate, compiled_module, exp.name); |
| |
| JSObject::AddProperty(entry, name_string, export_name.ToHandleChecked(), |
| NONE); |
| JSObject::AddProperty(entry, kind_string, export_kind, NONE); |
| |
| storage->set(index, *entry); |
| } |
| |
| return array_object; |
| } |
| |
| Handle<JSArray> GetCustomSections(Isolate* isolate, |
| Handle<WasmModuleObject> module_object, |
| Handle<String> name, ErrorThrower* thrower) { |
| Handle<WasmCompiledModule> compiled_module(module_object->compiled_module(), |
| isolate); |
| Factory* factory = isolate->factory(); |
| |
| std::vector<CustomSectionOffset> custom_sections; |
| { |
| DisallowHeapAllocation no_gc; // for raw access to string bytes. |
| Handle<SeqOneByteString> module_bytes(compiled_module->module_bytes(), |
| isolate); |
| const byte* start = |
| reinterpret_cast<const byte*>(module_bytes->GetCharsAddress()); |
| const byte* end = start + module_bytes->length(); |
| custom_sections = DecodeCustomSections(start, end); |
| } |
| |
| std::vector<Handle<Object>> matching_sections; |
| |
| // Gather matching sections. |
| for (auto& section : custom_sections) { |
| MaybeHandle<String> section_name = |
| WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate, compiled_module, section.name); |
| |
| if (!name->Equals(*section_name.ToHandleChecked())) continue; |
| |
| // Make a copy of the payload data in the section. |
| size_t size = section.payload.length(); |
| void* memory = |
| size == 0 ? nullptr : isolate->array_buffer_allocator()->Allocate(size); |
| |
| if (size && !memory) { |
| thrower->RangeError("out of memory allocating custom section data"); |
| return Handle<JSArray>(); |
| } |
| Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer(); |
| constexpr bool is_external = false; |
| JSArrayBuffer::Setup(buffer, isolate, is_external, memory, size, memory, |
| size); |
| DisallowHeapAllocation no_gc; // for raw access to string bytes. |
| Handle<SeqOneByteString> module_bytes(compiled_module->module_bytes(), |
| isolate); |
| const byte* start = |
| reinterpret_cast<const byte*>(module_bytes->GetCharsAddress()); |
| memcpy(memory, start + section.payload.offset(), section.payload.length()); |
| |
| matching_sections.push_back(buffer); |
| } |
| |
| int num_custom_sections = static_cast<int>(matching_sections.size()); |
| Handle<JSArray> array_object = factory->NewJSArray(PACKED_ELEMENTS, 0, 0); |
| Handle<FixedArray> storage = factory->NewFixedArray(num_custom_sections); |
| JSArray::SetContent(array_object, storage); |
| array_object->set_length(Smi::FromInt(num_custom_sections)); |
| |
| for (int i = 0; i < num_custom_sections; i++) { |
| storage->set(i, *matching_sections[i]); |
| } |
| |
| return array_object; |
| } |
| |
| Handle<FixedArray> DecodeLocalNames( |
| Isolate* isolate, Handle<WasmCompiledModule> compiled_module) { |
| Handle<SeqOneByteString> wire_bytes(compiled_module->module_bytes(), isolate); |
| LocalNames decoded_locals; |
| { |
| DisallowHeapAllocation no_gc; |
| DecodeLocalNames(wire_bytes->GetChars(), |
| wire_bytes->GetChars() + wire_bytes->length(), |
| &decoded_locals); |
| } |
| Handle<FixedArray> locals_names = |
| isolate->factory()->NewFixedArray(decoded_locals.max_function_index + 1); |
| for (LocalNamesPerFunction& func : decoded_locals.names) { |
| Handle<FixedArray> func_locals_names = |
| isolate->factory()->NewFixedArray(func.max_local_index + 1); |
| locals_names->set(func.function_index, *func_locals_names); |
| for (LocalName& name : func.names) { |
| Handle<String> name_str = |
| WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate, compiled_module, name.name) |
| .ToHandleChecked(); |
| func_locals_names->set(name.local_index, *name_str); |
| } |
| } |
| return locals_names; |
| } |
| |
| const char* ExternalKindName(WasmExternalKind kind) { |
| switch (kind) { |
| case kExternalFunction: |
| return "function"; |
| case kExternalTable: |
| return "table"; |
| case kExternalMemory: |
| return "memory"; |
| case kExternalGlobal: |
| return "global"; |
| } |
| return "unknown"; |
| } |
| |
| #undef TRACE |
| #undef TRACE_CHAIN |
| #undef TRACE_COMPILE |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |