// 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 #include #include "src/api/api-inl.h" #include "src/codegen/assembler-inl.h" #include "src/compiler/wasm-compiler.h" #include "src/debug/interface-types.h" #include "src/execution/frames-inl.h" #include "src/execution/simulator.h" #include "src/init/v8.h" #include "src/objects/js-array-inl.h" #include "src/objects/objects.h" #include "src/objects/property-descriptor.h" #include "src/snapshot/snapshot.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-code-manager.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 { // static const uint32_t WasmElemSegment::kNullIndex; WireBytesRef LazilyGeneratedNames::LookupFunctionName( const ModuleWireBytes& wire_bytes, uint32_t function_index, Vector export_table) const { base::MutexGuard lock(&mutex_); if (!function_names_) { function_names_.reset(new std::unordered_map()); DecodeFunctionNames(wire_bytes.start(), wire_bytes.end(), function_names_.get(), export_table); } auto it = function_names_->find(function_index); if (it == function_names_->end()) return WireBytesRef(); return it->second; } std::pair LazilyGeneratedNames::LookupNameFromImportsAndExports( ImportExportKindCode kind, uint32_t index, Vector import_table, Vector export_table) const { base::MutexGuard lock(&mutex_); DCHECK(kind == kExternalGlobal || kind == kExternalMemory || kind == kExternalTable); auto& names = kind == kExternalGlobal ? global_names_ : kind == kExternalMemory ? memory_names_ : table_names_; if (!names) { names.reset( new std::unordered_map>()); GenerateNamesFromImportsAndExports(kind, import_table, export_table, names.get()); } auto it = names->find(index); if (it == names->end()) return {}; return it->second; } // static int MaxNumExportWrappers(const WasmModule* module) { // For each signature there may exist a wrapper, both for imported and // internal functions. return static_cast(module->signature_map.size()) * 2; } // static int GetExportWrapperIndex(const WasmModule* module, const FunctionSig* sig, bool is_import) { int result = module->signature_map.Find(*sig); CHECK_GE(result, 0); result += is_import ? module->signature_map.size() : 0; return result; } // static int GetWasmFunctionOffset(const WasmModule* module, uint32_t func_index) { const std::vector& functions = module->functions; if (static_cast(func_index) >= functions.size()) return -1; DCHECK_GE(kMaxInt, functions[func_index].code.offset()); return static_cast(functions[func_index].code.offset()); } // static int GetNearestWasmFunction(const WasmModule* module, uint32_t byte_offset) { const std::vector& functions = module->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return -1; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code.offset() <= byte_offset) { left = mid; } else { right = mid; } } return left; } // static int GetContainingWasmFunction(const WasmModule* module, uint32_t byte_offset) { int func_index = GetNearestWasmFunction(module, byte_offset); if (func_index >= 0) { // If the found function does not contain the given position, return -1. const WasmFunction& func = module->functions[func_index]; if (byte_offset < func.code.offset() || byte_offset >= func.code.end_offset()) { return -1; } } return func_index; } void LazilyGeneratedNames::AddForTesting(int function_index, WireBytesRef name) { base::MutexGuard lock(&mutex_); if (!function_names_) { function_names_.reset(new std::unordered_map()); } function_names_->insert(std::make_pair(function_index, name)); } AsmJsOffsetInformation::AsmJsOffsetInformation( Vector encoded_offsets) : encoded_offsets_(OwnedVector::Of(encoded_offsets)) {} AsmJsOffsetInformation::~AsmJsOffsetInformation() = default; int AsmJsOffsetInformation::GetSourcePosition(int declared_func_index, int byte_offset, bool is_at_number_conversion) { EnsureDecodedOffsets(); DCHECK_LE(0, declared_func_index); DCHECK_GT(decoded_offsets_->functions.size(), declared_func_index); std::vector& function_offsets = decoded_offsets_->functions[declared_func_index].entries; auto byte_offset_less = [](const AsmJsOffsetEntry& a, const AsmJsOffsetEntry& b) { return a.byte_offset < b.byte_offset; }; SLOW_DCHECK(std::is_sorted(function_offsets.begin(), function_offsets.end(), byte_offset_less)); auto it = std::lower_bound(function_offsets.begin(), function_offsets.end(), AsmJsOffsetEntry{byte_offset, 0, 0}, byte_offset_less); DCHECK_NE(function_offsets.end(), it); DCHECK_EQ(byte_offset, it->byte_offset); return is_at_number_conversion ? it->source_position_number_conversion : it->source_position_call; } std::pair AsmJsOffsetInformation::GetFunctionOffsets( int declared_func_index) { EnsureDecodedOffsets(); DCHECK_LE(0, declared_func_index); DCHECK_GT(decoded_offsets_->functions.size(), declared_func_index); AsmJsOffsetFunctionEntries& function_info = decoded_offsets_->functions[declared_func_index]; return {function_info.start_offset, function_info.end_offset}; } void AsmJsOffsetInformation::EnsureDecodedOffsets() { base::MutexGuard mutex_guard(&mutex_); DCHECK_EQ(encoded_offsets_ == nullptr, decoded_offsets_ != nullptr); if (decoded_offsets_) return; AsmJsOffsetsResult result = wasm::DecodeAsmJsOffsets(encoded_offsets_.as_vector()); decoded_offsets_ = std::make_unique(std::move(result).value()); encoded_offsets_.ReleaseData(); } // Get a string stored in the module bytes representing a name. WasmName ModuleWireBytes::GetNameOrNull(WireBytesRef ref) const { if (!ref.is_set()) return {nullptr, 0}; // no name. DCHECK(BoundsCheck(ref)); return WasmName::cast( module_bytes_.SubVector(ref.offset(), ref.end_offset())); } // Get a string stored in the module bytes representing a function name. WasmName ModuleWireBytes::GetNameOrNull(const WasmFunction* function, const WasmModule* module) const { return GetNameOrNull(module->lazily_generated_names.LookupFunctionName( *this, function->func_index, VectorOf(module->export_table))); } std::ostream& operator<<(std::ostream& os, const WasmFunctionName& name) { os << "#" << name.function_->func_index; if (!name.name_.empty()) { if (name.name_.begin()) { os << ":"; os.write(name.name_.begin(), name.name_.length()); } } else { os << "?"; } return os; } WasmModule::WasmModule(std::unique_ptr signature_zone) : signature_zone(std::move(signature_zone)), subtyping_cache(this->signature_zone.get() == nullptr ? nullptr : new ZoneUnorderedSet>( this->signature_zone.get())), type_equivalence_cache( this->signature_zone.get() == nullptr ? nullptr : new ZoneUnorderedSet>( this->signature_zone.get())) {} bool IsWasmCodegenAllowed(Isolate* isolate, Handle 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. if (auto wasm_codegen_callback = isolate->allow_wasm_code_gen_callback()) { return wasm_codegen_callback( v8::Utils::ToLocal(context), v8::Utils::ToLocal(isolate->factory()->empty_string())); } auto codegen_callback = isolate->allow_code_gen_callback(); return codegen_callback == nullptr || codegen_callback( v8::Utils::ToLocal(context), v8::Utils::ToLocal(isolate->factory()->empty_string())); } namespace { // Converts the given {type} into a string representation that can be used in // reflective functions. Should be kept in sync with the {GetValueType} helper. Handle ToValueTypeString(Isolate* isolate, ValueType type) { return isolate->factory()->InternalizeUtf8String( type == kWasmFuncRef ? CStrVector("anyfunc") : VectorOf(type.name())); } } // namespace Handle GetTypeForFunction(Isolate* isolate, const FunctionSig* sig) { Factory* factory = isolate->factory(); // Extract values for the {ValueType[]} arrays. int param_index = 0; int param_count = static_cast(sig->parameter_count()); Handle param_values = factory->NewFixedArray(param_count); for (ValueType type : sig->parameters()) { Handle type_value = ToValueTypeString(isolate, type); param_values->set(param_index++, *type_value); } int result_index = 0; int result_count = static_cast(sig->return_count()); Handle result_values = factory->NewFixedArray(result_count); for (ValueType type : sig->returns()) { Handle type_value = ToValueTypeString(isolate, type); result_values->set(result_index++, *type_value); } // Create the resulting {FunctionType} object. Handle object_function = isolate->object_function(); Handle object = factory->NewJSObject(object_function); Handle params = factory->NewJSArrayWithElements(param_values); Handle results = factory->NewJSArrayWithElements(result_values); Handle params_string = factory->InternalizeUtf8String("parameters"); Handle results_string = factory->InternalizeUtf8String("results"); JSObject::AddProperty(isolate, object, params_string, params, NONE); JSObject::AddProperty(isolate, object, results_string, results, NONE); return object; } Handle GetTypeForGlobal(Isolate* isolate, bool is_mutable, ValueType type) { Factory* factory = isolate->factory(); Handle object_function = isolate->object_function(); Handle object = factory->NewJSObject(object_function); Handle mutable_string = factory->InternalizeUtf8String("mutable"); Handle value_string = factory->InternalizeUtf8String("value"); JSObject::AddProperty(isolate, object, mutable_string, factory->ToBoolean(is_mutable), NONE); JSObject::AddProperty(isolate, object, value_string, ToValueTypeString(isolate, type), NONE); return object; } Handle GetTypeForMemory(Isolate* isolate, uint32_t min_size, base::Optional max_size) { Factory* factory = isolate->factory(); Handle object_function = isolate->object_function(); Handle object = factory->NewJSObject(object_function); Handle minimum_string = factory->InternalizeUtf8String("minimum"); Handle maximum_string = factory->InternalizeUtf8String("maximum"); JSObject::AddProperty(isolate, object, minimum_string, factory->NewNumberFromUint(min_size), NONE); if (max_size.has_value()) { JSObject::AddProperty(isolate, object, maximum_string, factory->NewNumberFromUint(max_size.value()), NONE); } return object; } Handle GetTypeForTable(Isolate* isolate, ValueType type, uint32_t min_size, base::Optional max_size) { Factory* factory = isolate->factory(); Handle element; if (type.is_reference_to(HeapType::kFunc)) { // TODO(wasm): We should define the "anyfunc" string in one central // place and then use that constant everywhere. element = factory->InternalizeUtf8String("anyfunc"); } else { DCHECK(WasmFeatures::FromFlags().has_reftypes() && type.is_reference_to(HeapType::kExtern)); element = factory->InternalizeUtf8String("externref"); } Handle object_function = isolate->object_function(); Handle object = factory->NewJSObject(object_function); Handle element_string = factory->InternalizeUtf8String("element"); Handle minimum_string = factory->InternalizeUtf8String("minimum"); Handle maximum_string = factory->InternalizeUtf8String("maximum"); JSObject::AddProperty(isolate, object, element_string, element, NONE); JSObject::AddProperty(isolate, object, minimum_string, factory->NewNumberFromUint(min_size), NONE); if (max_size.has_value()) { JSObject::AddProperty(isolate, object, maximum_string, factory->NewNumberFromUint(max_size.value()), NONE); } return object; } Handle GetImports(Isolate* isolate, Handle module_object) { auto enabled_features = i::wasm::WasmFeatures::FromIsolate(isolate); Factory* factory = isolate->factory(); Handle module_string = factory->InternalizeUtf8String("module"); Handle name_string = factory->InternalizeUtf8String("name"); Handle kind_string = factory->InternalizeUtf8String("kind"); Handle type_string = factory->InternalizeUtf8String("type"); Handle function_string = factory->InternalizeUtf8String("function"); Handle table_string = factory->InternalizeUtf8String("table"); Handle memory_string = factory->InternalizeUtf8String("memory"); Handle global_string = factory->InternalizeUtf8String("global"); Handle exception_string = factory->InternalizeUtf8String("exception"); // Create the result array. const WasmModule* module = module_object->module(); int num_imports = static_cast(module->import_table.size()); Handle array_object = factory->NewJSArray(PACKED_ELEMENTS, 0, 0); Handle storage = factory->NewFixedArray(num_imports); JSArray::SetContent(array_object, storage); array_object->set_length(Smi::FromInt(num_imports)); Handle object_function = Handle(isolate->native_context()->object_function(), isolate); // Populate the result array. for (int index = 0; index < num_imports; ++index) { const WasmImport& import = module->import_table[index]; Handle entry = factory->NewJSObject(object_function); Handle import_kind; Handle type_value; switch (import.kind) { case kExternalFunction: if (enabled_features.has_type_reflection()) { auto& func = module->functions[import.index]; type_value = GetTypeForFunction(isolate, func.sig); } import_kind = function_string; break; case kExternalTable: if (enabled_features.has_type_reflection()) { auto& table = module->tables[import.index]; base::Optional maximum_size; if (table.has_maximum_size) maximum_size.emplace(table.maximum_size); type_value = GetTypeForTable(isolate, table.type, table.initial_size, maximum_size); } import_kind = table_string; break; case kExternalMemory: if (enabled_features.has_type_reflection()) { DCHECK_EQ(0, import.index); // Only one memory supported. base::Optional maximum_size; if (module->has_maximum_pages) { maximum_size.emplace(module->maximum_pages); } type_value = GetTypeForMemory(isolate, module->initial_pages, maximum_size); } import_kind = memory_string; break; case kExternalGlobal: if (enabled_features.has_type_reflection()) { auto& global = module->globals[import.index]; type_value = GetTypeForGlobal(isolate, global.mutability, global.type); } import_kind = global_string; break; case kExternalException: import_kind = exception_string; break; } DCHECK(!import_kind->is_null()); Handle import_module = WasmModuleObject::ExtractUtf8StringFromModuleBytes( isolate, module_object, import.module_name, kInternalize); Handle import_name = WasmModuleObject::ExtractUtf8StringFromModuleBytes( isolate, module_object, import.field_name, kInternalize); JSObject::AddProperty(isolate, entry, module_string, import_module, NONE); JSObject::AddProperty(isolate, entry, name_string, import_name, NONE); JSObject::AddProperty(isolate, entry, kind_string, import_kind, NONE); if (!type_value.is_null()) { JSObject::AddProperty(isolate, entry, type_string, type_value, NONE); } storage->set(index, *entry); } return array_object; } Handle GetExports(Isolate* isolate, Handle module_object) { auto enabled_features = i::wasm::WasmFeatures::FromIsolate(isolate); Factory* factory = isolate->factory(); Handle name_string = factory->InternalizeUtf8String("name"); Handle kind_string = factory->InternalizeUtf8String("kind"); Handle type_string = factory->InternalizeUtf8String("type"); Handle function_string = factory->InternalizeUtf8String("function"); Handle table_string = factory->InternalizeUtf8String("table"); Handle memory_string = factory->InternalizeUtf8String("memory"); Handle global_string = factory->InternalizeUtf8String("global"); Handle exception_string = factory->InternalizeUtf8String("exception"); // Create the result array. const WasmModule* module = module_object->module(); int num_exports = static_cast(module->export_table.size()); Handle array_object = factory->NewJSArray(PACKED_ELEMENTS, 0, 0); Handle storage = factory->NewFixedArray(num_exports); JSArray::SetContent(array_object, storage); array_object->set_length(Smi::FromInt(num_exports)); Handle object_function = Handle(isolate->native_context()->object_function(), isolate); // Populate the result array. for (int index = 0; index < num_exports; ++index) { const WasmExport& exp = module->export_table[index]; Handle export_kind; Handle type_value; switch (exp.kind) { case kExternalFunction: if (enabled_features.has_type_reflection()) { auto& func = module->functions[exp.index]; type_value = GetTypeForFunction(isolate, func.sig); } export_kind = function_string; break; case kExternalTable: if (enabled_features.has_type_reflection()) { auto& table = module->tables[exp.index]; base::Optional maximum_size; if (table.has_maximum_size) maximum_size.emplace(table.maximum_size); type_value = GetTypeForTable(isolate, table.type, table.initial_size, maximum_size); } export_kind = table_string; break; case kExternalMemory: if (enabled_features.has_type_reflection()) { DCHECK_EQ(0, exp.index); // Only one memory supported. base::Optional maximum_size; if (module->has_maximum_pages) { maximum_size.emplace(module->maximum_pages); } type_value = GetTypeForMemory(isolate, module->initial_pages, maximum_size); } export_kind = memory_string; break; case kExternalGlobal: if (enabled_features.has_type_reflection()) { auto& global = module->globals[exp.index]; type_value = GetTypeForGlobal(isolate, global.mutability, global.type); } export_kind = global_string; break; case kExternalException: export_kind = exception_string; break; default: UNREACHABLE(); } Handle entry = factory->NewJSObject(object_function); Handle export_name = WasmModuleObject::ExtractUtf8StringFromModuleBytes( isolate, module_object, exp.name, kNoInternalize); JSObject::AddProperty(isolate, entry, name_string, export_name, NONE); JSObject::AddProperty(isolate, entry, kind_string, export_kind, NONE); if (!type_value.is_null()) { JSObject::AddProperty(isolate, entry, type_string, type_value, NONE); } storage->set(index, *entry); } return array_object; } Handle GetCustomSections(Isolate* isolate, Handle module_object, Handle name, ErrorThrower* thrower) { Factory* factory = isolate->factory(); Vector wire_bytes = module_object->native_module()->wire_bytes(); std::vector custom_sections = DecodeCustomSections(wire_bytes.begin(), wire_bytes.end()); std::vector> matching_sections; // Gather matching sections. for (auto& section : custom_sections) { Handle section_name = WasmModuleObject::ExtractUtf8StringFromModuleBytes( isolate, module_object, section.name, kNoInternalize); if (!name->Equals(*section_name)) continue; // Make a copy of the payload data in the section. size_t size = section.payload.length(); MaybeHandle result = isolate->factory()->NewJSArrayBufferAndBackingStore( size, InitializedFlag::kUninitialized); Handle array_buffer; if (!result.ToHandle(&array_buffer)) { thrower->RangeError("out of memory allocating custom section data"); return Handle(); } memcpy(array_buffer->backing_store(), wire_bytes.begin() + section.payload.offset(), section.payload.length()); matching_sections.push_back(array_buffer); } int num_custom_sections = static_cast(matching_sections.size()); Handle array_object = factory->NewJSArray(PACKED_ELEMENTS, 0, 0); Handle 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; } // Get the source position from a given function index and byte offset, // for either asm.js or pure Wasm modules. int GetSourcePosition(const WasmModule* module, uint32_t func_index, uint32_t byte_offset, bool is_at_number_conversion) { DCHECK_EQ(is_asmjs_module(module), module->asm_js_offset_information != nullptr); if (!is_asmjs_module(module)) { // For non-asm.js modules, we just add the function's start offset // to make a module-relative position. return byte_offset + GetWasmFunctionOffset(module, func_index); } // asm.js modules have an additional offset table that must be searched. return module->asm_js_offset_information->GetSourcePosition( declared_function_index(module, func_index), byte_offset, is_at_number_conversion); } namespace { template inline size_t VectorSize(const std::vector& vector) { return sizeof(T) * vector.size(); } } // namespace size_t EstimateStoredSize(const WasmModule* module) { return sizeof(WasmModule) + VectorSize(module->globals) + (module->signature_zone ? module->signature_zone->allocation_size() : 0) + VectorSize(module->types) + VectorSize(module->type_kinds) + VectorSize(module->signature_ids) + VectorSize(module->functions) + VectorSize(module->data_segments) + VectorSize(module->tables) + VectorSize(module->import_table) + VectorSize(module->export_table) + VectorSize(module->exceptions) + VectorSize(module->elem_segments); } size_t PrintSignature(Vector buffer, const wasm::FunctionSig* sig, char delimiter) { if (buffer.empty()) return 0; size_t old_size = buffer.size(); auto append_char = [&buffer](char c) { if (buffer.size() == 1) return; // Keep last character for '\0'. buffer[0] = c; buffer += 1; }; for (wasm::ValueType t : sig->parameters()) { append_char(t.short_name()); } append_char(delimiter); for (wasm::ValueType t : sig->returns()) { append_char(t.short_name()); } buffer[0] = '\0'; return old_size - buffer.size(); } } // namespace wasm } // namespace internal } // namespace v8