blob: 33d526b1524aa6cbe3394d0a1f432bfe61d58fcb [file] [log] [blame]
// 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/utils.h"
#include "src/base/iterator.h"
#include "src/codegen/assembler-inl.h"
#include "src/codegen/code-factory.h"
#include "src/compiler/wasm-compiler.h"
#include "src/debug/debug-interface.h"
#include "src/logging/counters.h"
#include "src/objects/debug-objects-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/shared-function-info.h"
#include "src/objects/struct-inl.h"
#include "src/trap-handler/trap-handler.h"
#include "src/utils/vector.h"
#include "src/wasm/jump-table-assembler.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/value-type.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-memory.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-text.h"
#if defined(V8_OS_STARBOARD)
#include "src/poems.h"
#endif
#define TRACE(...) \
do { \
if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \
} while (false)
#define TRACE_IFT(...) \
do { \
if (false) PrintF(__VA_ARGS__); \
} while (false)
namespace v8 {
namespace internal {
// Import a few often used types from the wasm namespace.
using WasmFunction = wasm::WasmFunction;
using WasmModule = wasm::WasmModule;
namespace {
// Manages the natively-allocated memory for a WasmInstanceObject. Since
// an instance finalizer is not guaranteed to run upon isolate shutdown,
// we must use a Managed<WasmInstanceNativeAllocations> to guarantee
// it is freed.
// Native allocations are the signature ids and targets for indirect call
// targets, as well as the call targets for imported functions.
class WasmInstanceNativeAllocations {
public:
// Helper macro to set an internal field and the corresponding field
// on an instance.
#define SET(instance, field, value) \
{ \
auto v = value; \
this->field##_ = v; \
instance->set_##field(v); \
}
// Allocates initial native storage for a given instance.
WasmInstanceNativeAllocations(Handle<WasmInstanceObject> instance,
size_t num_imported_functions,
size_t num_imported_mutable_globals,
size_t num_data_segments,
size_t num_elem_segments) {
SET(instance, imported_function_targets,
reinterpret_cast<Address*>(
calloc(num_imported_functions, sizeof(Address))));
SET(instance, imported_mutable_globals,
reinterpret_cast<Address*>(
calloc(num_imported_mutable_globals, sizeof(Address))));
SET(instance, data_segment_starts,
reinterpret_cast<Address*>(calloc(num_data_segments, sizeof(Address))));
SET(instance, data_segment_sizes,
reinterpret_cast<uint32_t*>(
calloc(num_data_segments, sizeof(uint32_t))));
SET(instance, dropped_data_segments,
reinterpret_cast<uint8_t*>(calloc(num_data_segments, sizeof(uint8_t))));
SET(instance, dropped_elem_segments,
reinterpret_cast<uint8_t*>(calloc(num_elem_segments, sizeof(uint8_t))));
}
~WasmInstanceNativeAllocations() {
::free(indirect_function_table_sig_ids_);
indirect_function_table_sig_ids_ = nullptr;
::free(indirect_function_table_targets_);
indirect_function_table_targets_ = nullptr;
::free(imported_function_targets_);
imported_function_targets_ = nullptr;
::free(imported_mutable_globals_);
imported_mutable_globals_ = nullptr;
::free(data_segment_starts_);
data_segment_starts_ = nullptr;
::free(data_segment_sizes_);
data_segment_sizes_ = nullptr;
::free(dropped_data_segments_);
dropped_data_segments_ = nullptr;
::free(dropped_elem_segments_);
dropped_elem_segments_ = nullptr;
}
// Resizes the indirect function table.
void resize_indirect_function_table(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t new_size) {
uint32_t old_size = instance->indirect_function_table_size();
void* new_sig_ids = nullptr;
void* new_targets = nullptr;
Handle<FixedArray> new_refs;
if (indirect_function_table_sig_ids_) {
// Reallocate the old storage.
new_sig_ids = realloc(indirect_function_table_sig_ids_,
new_size * sizeof(uint32_t));
new_targets =
realloc(indirect_function_table_targets_, new_size * sizeof(Address));
Handle<FixedArray> old(instance->indirect_function_table_refs(), isolate);
new_refs = isolate->factory()->CopyFixedArrayAndGrow(
old, static_cast<int>(new_size - old_size));
} else {
// Allocate new storage.
new_sig_ids = malloc(new_size * sizeof(uint32_t));
new_targets = malloc(new_size * sizeof(Address));
new_refs = isolate->factory()->NewFixedArray(static_cast<int>(new_size));
}
// Initialize new entries.
instance->set_indirect_function_table_size(new_size);
SET(instance, indirect_function_table_sig_ids,
reinterpret_cast<uint32_t*>(new_sig_ids));
SET(instance, indirect_function_table_targets,
reinterpret_cast<Address*>(new_targets));
instance->set_indirect_function_table_refs(*new_refs);
for (uint32_t j = old_size; j < new_size; j++) {
// {WasmInstanceNativeAllocations} only manages the memory of table 0.
// Therefore we pass the {table_index} as a constant here.
IndirectFunctionTableEntry(instance, 0, static_cast<int>(j)).clear();
}
}
uint32_t* indirect_function_table_sig_ids_ = nullptr;
Address* indirect_function_table_targets_ = nullptr;
Address* imported_function_targets_ = nullptr;
Address* imported_mutable_globals_ = nullptr;
Address* data_segment_starts_ = nullptr;
uint32_t* data_segment_sizes_ = nullptr;
uint8_t* dropped_data_segments_ = nullptr;
uint8_t* dropped_elem_segments_ = nullptr;
#undef SET
};
size_t EstimateNativeAllocationsSize(const WasmModule* module) {
size_t estimate =
sizeof(WasmInstanceNativeAllocations) +
(1 * kSystemPointerSize * module->num_imported_mutable_globals) +
(2 * kSystemPointerSize * module->num_imported_functions) +
((kSystemPointerSize + sizeof(uint32_t) + sizeof(uint8_t)) *
module->num_declared_data_segments);
for (auto& table : module->tables) {
estimate += 3 * kSystemPointerSize * table.initial_size;
}
return estimate;
}
WasmInstanceNativeAllocations* GetNativeAllocations(
WasmInstanceObject instance) {
return Managed<WasmInstanceNativeAllocations>::cast(
instance.managed_native_allocations())
.raw();
}
#ifdef DEBUG
bool IsBreakablePosition(wasm::NativeModule* native_module, int func_index,
int offset_in_func) {
AccountingAllocator alloc;
Zone tmp(&alloc, ZONE_NAME);
wasm::BodyLocalDecls locals(&tmp);
const byte* module_start = native_module->wire_bytes().begin();
const WasmFunction& func = native_module->module()->functions[func_index];
wasm::BytecodeIterator iterator(module_start + func.code.offset(),
module_start + func.code.end_offset(),
&locals);
DCHECK_LT(0, locals.encoded_size);
for (uint32_t offset : iterator.offsets()) {
if (offset > static_cast<uint32_t>(offset_in_func)) break;
if (offset == static_cast<uint32_t>(offset_in_func)) return true;
}
return false;
}
#endif // DEBUG
enum DispatchTableElements : int {
kDispatchTableInstanceOffset,
kDispatchTableIndexOffset,
kDispatchTableFunctionTableOffset,
// Marker:
kDispatchTableNumElements
};
} // namespace
// static
Handle<WasmModuleObject> WasmModuleObject::New(
Isolate* isolate, const wasm::WasmFeatures& enabled,
std::shared_ptr<const wasm::WasmModule> shared_module,
OwnedVector<const uint8_t> wire_bytes, Handle<Script> script,
Handle<ByteArray> asm_js_offset_table) {
// Create a new {NativeModule} first.
size_t code_size_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(shared_module.get());
auto native_module = isolate->wasm_engine()->NewNativeModule(
isolate, enabled, code_size_estimate,
wasm::NativeModule::kCanAllocateMoreMemory, std::move(shared_module));
native_module->SetWireBytes(std::move(wire_bytes));
native_module->SetRuntimeStubs(isolate);
// Delegate to the shared {WasmModuleObject::New} allocator.
Handle<WasmModuleObject> module_object =
New(isolate, std::move(native_module), script, code_size_estimate);
if (!asm_js_offset_table.is_null()) {
module_object->set_asm_js_offset_table(*asm_js_offset_table);
}
return module_object;
}
// static
Handle<WasmModuleObject> WasmModuleObject::New(
Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module,
Handle<Script> script, size_t code_size_estimate) {
const WasmModule* module = native_module->module();
int num_wrappers = MaxNumExportWrappers(module);
Handle<FixedArray> export_wrappers =
isolate->factory()->NewFixedArray(num_wrappers, AllocationType::kOld);
return New(isolate, std::move(native_module), script, export_wrappers,
code_size_estimate);
}
// static
Handle<WasmModuleObject> WasmModuleObject::New(
Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module,
Handle<Script> script, Handle<FixedArray> export_wrappers,
size_t code_size_estimate) {
const WasmModule* module = native_module->module();
// Use the given shared {NativeModule}, but increase its reference count by
// allocating a new {Managed<T>} that the {WasmModuleObject} references.
size_t memory_estimate =
code_size_estimate +
wasm::WasmCodeManager::EstimateNativeModuleNonCodeSize(module);
Handle<Managed<wasm::NativeModule>> managed_native_module =
Managed<wasm::NativeModule>::FromSharedPtr(isolate, memory_estimate,
std::move(native_module));
Handle<WasmModuleObject> module_object = Handle<WasmModuleObject>::cast(
isolate->factory()->NewJSObject(isolate->wasm_module_constructor()));
module_object->set_export_wrappers(*export_wrappers);
if (script->type() == Script::TYPE_WASM) {
script->set_wasm_module_object(*module_object);
}
module_object->set_script(*script);
module_object->set_weak_instance_list(
ReadOnlyRoots(isolate).empty_weak_array_list());
module_object->set_managed_native_module(*managed_native_module);
return module_object;
}
bool WasmModuleObject::SetBreakPoint(Handle<WasmModuleObject> module_object,
int* position,
Handle<BreakPoint> break_point) {
Isolate* isolate = module_object->GetIsolate();
// Find the function for this breakpoint.
int func_index = module_object->GetContainingFunction(*position);
if (func_index < 0) return false;
const WasmFunction& func = module_object->module()->functions[func_index];
int offset_in_func = *position - func.code.offset();
// According to the current design, we should only be called with valid
// breakable positions.
DCHECK(IsBreakablePosition(module_object->native_module(), func_index,
offset_in_func));
// Insert new break point into break_positions of module object.
WasmModuleObject::AddBreakpoint(module_object, *position, break_point);
// Iterate over all instances of this module and tell them to set this new
// breakpoint. We do this using the weak list of all instances.
Handle<WeakArrayList> weak_instance_list(module_object->weak_instance_list(),
isolate);
for (int i = 0; i < weak_instance_list->length(); ++i) {
MaybeObject maybe_instance = weak_instance_list->Get(i);
if (maybe_instance->IsWeak()) {
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(maybe_instance->GetHeapObjectAssumeWeak()),
isolate);
Handle<WasmDebugInfo> debug_info =
WasmInstanceObject::GetOrCreateDebugInfo(instance);
WasmDebugInfo::SetBreakpoint(debug_info, func_index, offset_in_func);
}
}
return true;
}
namespace {
int GetBreakpointPos(Isolate* isolate, Object break_point_info_or_undef) {
if (break_point_info_or_undef.IsUndefined(isolate)) return kMaxInt;
return BreakPointInfo::cast(break_point_info_or_undef).source_position();
}
int FindBreakpointInfoInsertPos(Isolate* isolate,
Handle<FixedArray> breakpoint_infos,
int position) {
// Find insert location via binary search, taking care of undefined values on
// the right. Position is always greater than zero.
DCHECK_LT(0, position);
int left = 0; // inclusive
int right = breakpoint_infos->length(); // exclusive
while (right - left > 1) {
int mid = left + (right - left) / 2;
Object mid_obj = breakpoint_infos->get(mid);
if (GetBreakpointPos(isolate, mid_obj) <= position) {
left = mid;
} else {
right = mid;
}
}
int left_pos = GetBreakpointPos(isolate, breakpoint_infos->get(left));
return left_pos < position ? left + 1 : left;
}
} // namespace
void WasmModuleObject::AddBreakpoint(Handle<WasmModuleObject> module_object,
int position,
Handle<BreakPoint> break_point) {
Isolate* isolate = module_object->GetIsolate();
Handle<FixedArray> breakpoint_infos;
if (module_object->has_breakpoint_infos()) {
breakpoint_infos = handle(module_object->breakpoint_infos(), isolate);
} else {
breakpoint_infos =
isolate->factory()->NewFixedArray(4, AllocationType::kOld);
module_object->set_breakpoint_infos(*breakpoint_infos);
}
int insert_pos =
FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position);
// If a BreakPointInfo object already exists for this position, add the new
// breakpoint object and return.
if (insert_pos < breakpoint_infos->length() &&
GetBreakpointPos(isolate, breakpoint_infos->get(insert_pos)) ==
position) {
Handle<BreakPointInfo> old_info(
BreakPointInfo::cast(breakpoint_infos->get(insert_pos)), isolate);
BreakPointInfo::SetBreakPoint(isolate, old_info, break_point);
return;
}
// Enlarge break positions array if necessary.
bool need_realloc = !breakpoint_infos->get(breakpoint_infos->length() - 1)
.IsUndefined(isolate);
Handle<FixedArray> new_breakpoint_infos = breakpoint_infos;
if (need_realloc) {
new_breakpoint_infos = isolate->factory()->NewFixedArray(
2 * breakpoint_infos->length(), AllocationType::kOld);
module_object->set_breakpoint_infos(*new_breakpoint_infos);
// Copy over the entries [0, insert_pos).
for (int i = 0; i < insert_pos; ++i)
new_breakpoint_infos->set(i, breakpoint_infos->get(i));
}
// Move elements [insert_pos, ...] up by one.
for (int i = breakpoint_infos->length() - 1; i >= insert_pos; --i) {
Object entry = breakpoint_infos->get(i);
if (entry.IsUndefined(isolate)) continue;
new_breakpoint_infos->set(i + 1, entry);
}
// Generate new BreakpointInfo.
Handle<BreakPointInfo> breakpoint_info =
isolate->factory()->NewBreakPointInfo(position);
BreakPointInfo::SetBreakPoint(isolate, breakpoint_info, break_point);
// Now insert new position at insert_pos.
new_breakpoint_infos->set(insert_pos, *breakpoint_info);
}
void WasmModuleObject::SetBreakpointsOnNewInstance(
Handle<WasmModuleObject> module_object,
Handle<WasmInstanceObject> instance) {
if (!module_object->has_breakpoint_infos()) return;
Isolate* isolate = module_object->GetIsolate();
Handle<WasmDebugInfo> debug_info =
WasmInstanceObject::GetOrCreateDebugInfo(instance);
Handle<FixedArray> breakpoint_infos(module_object->breakpoint_infos(),
isolate);
// If the array exists, it should not be empty.
DCHECK_LT(0, breakpoint_infos->length());
for (int i = 0, e = breakpoint_infos->length(); i < e; ++i) {
Handle<Object> obj(breakpoint_infos->get(i), isolate);
if (obj->IsUndefined(isolate)) {
for (; i < e; ++i) {
DCHECK(breakpoint_infos->get(i).IsUndefined(isolate));
}
break;
}
Handle<BreakPointInfo> breakpoint_info = Handle<BreakPointInfo>::cast(obj);
int position = breakpoint_info->source_position();
// Find the function for this breakpoint, and set the breakpoint.
int func_index = module_object->GetContainingFunction(position);
DCHECK_LE(0, func_index);
const WasmFunction& func = module_object->module()->functions[func_index];
int offset_in_func = position - func.code.offset();
WasmDebugInfo::SetBreakpoint(debug_info, func_index, offset_in_func);
}
}
namespace {
enum AsmJsOffsetTableEntryLayout {
kOTEByteOffset,
kOTECallPosition,
kOTENumberConvPosition,
kOTESize
};
Handle<ByteArray> GetDecodedAsmJsOffsetTable(
Handle<WasmModuleObject> module_object, Isolate* isolate) {
DCHECK(module_object->is_asm_js());
Handle<ByteArray> offset_table(module_object->asm_js_offset_table(), isolate);
// The last byte in the asm_js_offset_tables ByteArray tells whether it is
// still encoded (0) or decoded (1).
enum AsmJsTableType : int { Encoded = 0, Decoded = 1 };
int table_type = offset_table->get(offset_table->length() - 1);
DCHECK(table_type == Encoded || table_type == Decoded);
if (table_type == Decoded) return offset_table;
wasm::AsmJsOffsets asm_offsets;
{
DisallowHeapAllocation no_gc;
byte* bytes_start = offset_table->GetDataStartAddress();
byte* bytes_end = reinterpret_cast<byte*>(
reinterpret_cast<Address>(bytes_start) + offset_table->length() - 1);
asm_offsets = wasm::DecodeAsmJsOffsets(bytes_start, bytes_end).value();
}
// Wasm bytes must be valid and must contain asm.js offset table.
DCHECK_GE(kMaxInt, asm_offsets.size());
int num_functions = static_cast<int>(asm_offsets.size());
int num_imported_functions =
static_cast<int>(module_object->module()->num_imported_functions);
DCHECK_EQ(module_object->module()->functions.size(),
static_cast<size_t>(num_functions) + num_imported_functions);
int num_entries = 0;
for (int func = 0; func < num_functions; ++func) {
size_t new_size = asm_offsets[func].size();
DCHECK_LE(new_size, static_cast<size_t>(kMaxInt) - num_entries);
num_entries += static_cast<int>(new_size);
}
// One byte to encode that this is a decoded table.
DCHECK_GE(kMaxInt,
1 + static_cast<uint64_t>(num_entries) * kOTESize * kIntSize);
int total_size = 1 + num_entries * kOTESize * kIntSize;
Handle<ByteArray> decoded_table =
isolate->factory()->NewByteArray(total_size, AllocationType::kOld);
decoded_table->set(total_size - 1, AsmJsTableType::Decoded);
module_object->set_asm_js_offset_table(*decoded_table);
int idx = 0;
const std::vector<WasmFunction>& wasm_funs =
module_object->module()->functions;
for (int func = 0; func < num_functions; ++func) {
std::vector<wasm::AsmJsOffsetEntry>& func_asm_offsets = asm_offsets[func];
if (func_asm_offsets.empty()) continue;
int func_offset = wasm_funs[num_imported_functions + func].code.offset();
for (wasm::AsmJsOffsetEntry& e : func_asm_offsets) {
// Byte offsets must be strictly monotonously increasing:
DCHECK_IMPLIES(idx > 0, func_offset + e.byte_offset >
decoded_table->get_int(idx - kOTESize));
decoded_table->set_int(idx + kOTEByteOffset, func_offset + e.byte_offset);
decoded_table->set_int(idx + kOTECallPosition, e.source_position_call);
decoded_table->set_int(idx + kOTENumberConvPosition,
e.source_position_number_conversion);
idx += kOTESize;
}
}
DCHECK_EQ(total_size, idx * kIntSize + 1);
return decoded_table;
}
} // namespace
int WasmModuleObject::GetSourcePosition(Handle<WasmModuleObject> module_object,
uint32_t func_index,
uint32_t byte_offset,
bool is_at_number_conversion) {
Isolate* isolate = module_object->GetIsolate();
const WasmModule* module = module_object->module();
if (module->origin == wasm::kWasmOrigin) {
// for non-asm.js modules, we just add the function's start offset
// to make a module-relative position.
return byte_offset + module_object->GetFunctionOffset(func_index);
}
// asm.js modules have an additional offset table that must be searched.
Handle<ByteArray> offset_table =
GetDecodedAsmJsOffsetTable(module_object, isolate);
DCHECK_LT(func_index, module->functions.size());
uint32_t func_code_offset = module->functions[func_index].code.offset();
uint32_t total_offset = func_code_offset + byte_offset;
// Binary search for the total byte offset.
int left = 0; // inclusive
int right = offset_table->length() / kIntSize / kOTESize; // exclusive
DCHECK_LT(left, right);
while (right - left > 1) {
int mid = left + (right - left) / 2;
int mid_entry = offset_table->get_int(kOTESize * mid);
DCHECK_GE(kMaxInt, mid_entry);
if (static_cast<uint32_t>(mid_entry) <= total_offset) {
left = mid;
} else {
right = mid;
}
}
// There should be an entry for each position that could show up on the stack
// trace:
DCHECK_EQ(total_offset, offset_table->get_int(kOTESize * left));
int idx = is_at_number_conversion ? kOTENumberConvPosition : kOTECallPosition;
return offset_table->get_int(kOTESize * left + idx);
}
v8::debug::WasmDisassembly WasmModuleObject::DisassembleFunction(
int func_index) {
DisallowHeapAllocation no_gc;
if (func_index < 0 ||
static_cast<uint32_t>(func_index) >= module()->functions.size())
return {};
wasm::ModuleWireBytes wire_bytes(native_module()->wire_bytes());
std::ostringstream disassembly_os;
v8::debug::WasmDisassembly::OffsetTable offset_table;
PrintWasmText(module(), wire_bytes, static_cast<uint32_t>(func_index),
disassembly_os, &offset_table);
return {disassembly_os.str(), std::move(offset_table)};
}
bool WasmModuleObject::GetPossibleBreakpoints(
const v8::debug::Location& start, const v8::debug::Location& end,
std::vector<v8::debug::BreakLocation>* locations) {
DisallowHeapAllocation no_gc;
const std::vector<WasmFunction>& functions = module()->functions;
if (start.GetLineNumber() < 0 || start.GetColumnNumber() < 0 ||
(!end.IsEmpty() &&
(end.GetLineNumber() < 0 || end.GetColumnNumber() < 0)))
return false;
// start_func_index, start_offset and end_func_index is inclusive.
// end_offset is exclusive.
// start_offset and end_offset are module-relative byte offsets.
uint32_t start_func_index = start.GetLineNumber();
if (start_func_index >= functions.size()) return false;
int start_func_len = functions[start_func_index].code.length();
if (start.GetColumnNumber() > start_func_len) return false;
uint32_t start_offset =
functions[start_func_index].code.offset() + start.GetColumnNumber();
uint32_t end_func_index;
uint32_t end_offset;
if (end.IsEmpty()) {
// Default: everything till the end of the Script.
end_func_index = static_cast<uint32_t>(functions.size() - 1);
end_offset = functions[end_func_index].code.end_offset();
} else {
// If end is specified: Use it and check for valid input.
end_func_index = static_cast<uint32_t>(end.GetLineNumber());
// Special case: Stop before the start of the next function. Change to: Stop
// at the end of the function before, such that we don't disassemble the
// next function also.
if (end.GetColumnNumber() == 0 && end_func_index > 0) {
--end_func_index;
end_offset = functions[end_func_index].code.end_offset();
} else {
if (end_func_index >= functions.size()) return false;
end_offset =
functions[end_func_index].code.offset() + end.GetColumnNumber();
if (end_offset > functions[end_func_index].code.end_offset())
return false;
}
}
AccountingAllocator alloc;
Zone tmp(&alloc, ZONE_NAME);
const byte* module_start = native_module()->wire_bytes().begin();
for (uint32_t func_idx = start_func_index; func_idx <= end_func_index;
++func_idx) {
const WasmFunction& func = functions[func_idx];
if (func.code.length() == 0) continue;
wasm::BodyLocalDecls locals(&tmp);
wasm::BytecodeIterator iterator(module_start + func.code.offset(),
module_start + func.code.end_offset(),
&locals);
DCHECK_LT(0u, locals.encoded_size);
for (uint32_t offset : iterator.offsets()) {
uint32_t total_offset = func.code.offset() + offset;
if (total_offset >= end_offset) {
DCHECK_EQ(end_func_index, func_idx);
break;
}
if (total_offset < start_offset) continue;
locations->emplace_back(func_idx, offset, debug::kCommonBreakLocation);
}
}
return true;
}
MaybeHandle<FixedArray> WasmModuleObject::CheckBreakPoints(
Isolate* isolate, Handle<WasmModuleObject> module_object, int position) {
if (!module_object->has_breakpoint_infos()) return {};
Handle<FixedArray> breakpoint_infos(module_object->breakpoint_infos(),
isolate);
int insert_pos =
FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position);
if (insert_pos >= breakpoint_infos->length()) return {};
Handle<Object> maybe_breakpoint_info(breakpoint_infos->get(insert_pos),
isolate);
if (maybe_breakpoint_info->IsUndefined(isolate)) return {};
Handle<BreakPointInfo> breakpoint_info =
Handle<BreakPointInfo>::cast(maybe_breakpoint_info);
if (breakpoint_info->source_position() != position) return {};
// There is no support for conditional break points. Just assume that every
// break point always hits.
Handle<Object> break_points(breakpoint_info->break_points(), isolate);
if (break_points->IsFixedArray()) {
return Handle<FixedArray>::cast(break_points);
}
Handle<FixedArray> break_points_hit = isolate->factory()->NewFixedArray(1);
break_points_hit->set(0, *break_points);
return break_points_hit;
}
MaybeHandle<String> WasmModuleObject::ExtractUtf8StringFromModuleBytes(
Isolate* isolate, Handle<WasmModuleObject> module_object,
wasm::WireBytesRef ref) {
// TODO(wasm): cache strings from modules if it's a performance win.
Vector<const uint8_t> wire_bytes =
module_object->native_module()->wire_bytes();
return ExtractUtf8StringFromModuleBytes(isolate, wire_bytes, ref);
}
MaybeHandle<String> WasmModuleObject::ExtractUtf8StringFromModuleBytes(
Isolate* isolate, Vector<const uint8_t> wire_bytes,
wasm::WireBytesRef ref) {
Vector<const uint8_t> name_vec = wire_bytes + ref.offset();
name_vec.Truncate(ref.length());
// UTF8 validation happens at decode time.
DCHECK(unibrow::Utf8::ValidateEncoding(name_vec.begin(), name_vec.length()));
return isolate->factory()->NewStringFromUtf8(
Vector<const char>::cast(name_vec));
}
MaybeHandle<String> WasmModuleObject::GetModuleNameOrNull(
Isolate* isolate, Handle<WasmModuleObject> module_object) {
const WasmModule* module = module_object->module();
if (!module->name.is_set()) return {};
return ExtractUtf8StringFromModuleBytes(isolate, module_object, module->name);
}
MaybeHandle<String> WasmModuleObject::GetFunctionNameOrNull(
Isolate* isolate, Handle<WasmModuleObject> module_object,
uint32_t func_index) {
DCHECK_LT(func_index, module_object->module()->functions.size());
wasm::WireBytesRef name = module_object->module()->LookupFunctionName(
wasm::ModuleWireBytes(module_object->native_module()->wire_bytes()),
func_index);
if (!name.is_set()) return {};
return ExtractUtf8StringFromModuleBytes(isolate, module_object, name);
}
Handle<String> WasmModuleObject::GetFunctionName(
Isolate* isolate, Handle<WasmModuleObject> module_object,
uint32_t func_index) {
MaybeHandle<String> name =
GetFunctionNameOrNull(isolate, module_object, func_index);
if (!name.is_null()) return name.ToHandleChecked();
EmbeddedVector<char, 32> buffer;
int length = SNPrintF(buffer, "wasm-function[%u]", func_index);
return isolate->factory()
->NewStringFromOneByte(Vector<uint8_t>::cast(buffer.SubVector(0, length)))
.ToHandleChecked();
}
Vector<const uint8_t> WasmModuleObject::GetRawFunctionName(
uint32_t func_index) {
DCHECK_GT(module()->functions.size(), func_index);
wasm::ModuleWireBytes wire_bytes(native_module()->wire_bytes());
wasm::WireBytesRef name_ref =
module()->LookupFunctionName(wire_bytes, func_index);
wasm::WasmName name = wire_bytes.GetNameOrNull(name_ref);
return Vector<const uint8_t>::cast(name);
}
int WasmModuleObject::GetFunctionOffset(uint32_t func_index) {
const std::vector<WasmFunction>& functions = module()->functions;
if (static_cast<uint32_t>(func_index) >= functions.size()) return -1;
DCHECK_GE(kMaxInt, functions[func_index].code.offset());
return static_cast<int>(functions[func_index].code.offset());
}
int WasmModuleObject::GetContainingFunction(uint32_t byte_offset) {
const std::vector<WasmFunction>& functions = module()->functions;
// Binary search for a function containing the given position.
int left = 0; // inclusive
int right = static_cast<int>(functions.size()); // exclusive
if (right == 0) return false;
while (right - left > 1) {
int mid = left + (right - left) / 2;
if (functions[mid].code.offset() <= byte_offset) {
left = mid;
} else {
right = mid;
}
}
// If the found function does not contains the given position, return -1.
const WasmFunction& func = functions[left];
if (byte_offset < func.code.offset() ||
byte_offset >= func.code.end_offset()) {
return -1;
}
return left;
}
bool WasmModuleObject::GetPositionInfo(uint32_t position,
Script::PositionInfo* info) {
if (script().source_mapping_url().IsString()) {
if (module()->functions.size() == 0) return false;
info->line = 0;
info->column = position;
info->line_start = module()->functions[0].code.offset();
info->line_end = module()->functions.back().code.end_offset();
return true;
}
int func_index = GetContainingFunction(position);
if (func_index < 0) return false;
const WasmFunction& function = module()->functions[func_index];
info->line = func_index;
info->column = position - function.code.offset();
info->line_start = function.code.offset();
info->line_end = function.code.end_offset();
return true;
}
Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate,
wasm::ValueType type,
uint32_t initial, bool has_maximum,
uint32_t maximum,
Handle<FixedArray>* entries) {
Handle<FixedArray> backing_store = isolate->factory()->NewFixedArray(initial);
Object null = ReadOnlyRoots(isolate).null_value();
for (int i = 0; i < static_cast<int>(initial); ++i) {
backing_store->set(i, null);
}
Handle<Object> max;
if (has_maximum) {
max = isolate->factory()->NewNumberFromUint(maximum);
} else {
max = isolate->factory()->undefined_value();
}
Handle<JSFunction> table_ctor(
isolate->native_context()->wasm_table_constructor(), isolate);
auto table_obj = Handle<WasmTableObject>::cast(
isolate->factory()->NewJSObject(table_ctor));
DisallowHeapAllocation no_gc;
table_obj->set_raw_type(static_cast<int>(type));
table_obj->set_entries(*backing_store);
table_obj->set_maximum_length(*max);
table_obj->set_dispatch_tables(ReadOnlyRoots(isolate).empty_fixed_array());
if (entries != nullptr) {
*entries = backing_store;
}
return Handle<WasmTableObject>::cast(table_obj);
}
void WasmTableObject::AddDispatchTable(Isolate* isolate,
Handle<WasmTableObject> table_obj,
Handle<WasmInstanceObject> instance,
int table_index) {
Handle<FixedArray> dispatch_tables(table_obj->dispatch_tables(), isolate);
int old_length = dispatch_tables->length();
DCHECK_EQ(0, old_length % kDispatchTableNumElements);
if (instance.is_null()) return;
// TODO(titzer): use weak cells here to avoid leaking instances.
// Grow the dispatch table and add a new entry at the end.
Handle<FixedArray> new_dispatch_tables =
isolate->factory()->CopyFixedArrayAndGrow(dispatch_tables,
kDispatchTableNumElements);
new_dispatch_tables->set(old_length + kDispatchTableInstanceOffset,
*instance);
new_dispatch_tables->set(old_length + kDispatchTableIndexOffset,
Smi::FromInt(table_index));
table_obj->set_dispatch_tables(*new_dispatch_tables);
}
int WasmTableObject::Grow(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t count, Handle<Object> init_value) {
uint32_t old_size = table->current_length();
if (count == 0) return old_size; // Degenerate case: nothing to do.
// Check if growing by {count} is valid.
uint32_t max_size;
if (!table->maximum_length().ToUint32(&max_size)) {
max_size = FLAG_wasm_max_table_size;
}
DCHECK_LE(old_size, max_size);
if (max_size - old_size < count) return -1;
uint32_t new_size = old_size + count;
auto new_store = isolate->factory()->CopyFixedArrayAndGrow(
handle(table->entries(), isolate), count);
table->set_entries(*new_store, WriteBarrierMode::UPDATE_WRITE_BARRIER);
Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate);
DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements);
// Tables are stored in the instance object, no code patching is
// necessary. We simply have to grow the raw tables in each instance
// that has imported this table.
// TODO(titzer): replace the dispatch table with a weak list of all
// the instances that import a given table.
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
int table_index =
Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value();
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(dispatch_tables->get(i)), isolate);
DCHECK_EQ(old_size, WasmInstanceObject::IndirectFunctionTableSize(
isolate, instance, table_index));
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, table_index, new_size);
}
for (uint32_t entry = old_size; entry < new_size; ++entry) {
WasmTableObject::Set(isolate, table, entry, init_value);
}
return old_size;
}
bool WasmTableObject::IsInBounds(Isolate* isolate,
Handle<WasmTableObject> table,
uint32_t entry_index) {
return (entry_index <
static_cast<uint32_t>(std::numeric_limits<int>::max()) &&
static_cast<int>(entry_index) < table->entries().length());
}
bool WasmTableObject::IsValidElement(Isolate* isolate,
Handle<WasmTableObject> table,
Handle<Object> entry) {
// Anyref tables take everything.
if (table->type() == wasm::kWasmAnyRef) return true;
// FuncRef tables can store {null}, {WasmExportedFunction}, {WasmJSFunction},
// or {WasmCapiFunction} objects.
if (entry->IsNull(isolate)) return true;
return WasmExportedFunction::IsWasmExportedFunction(*entry) ||
WasmJSFunction::IsWasmJSFunction(*entry) ||
WasmCapiFunction::IsWasmCapiFunction(*entry);
}
void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t index, Handle<Object> entry) {
// Callers need to perform bounds checks, type check, and error handling.
DCHECK(IsInBounds(isolate, table, index));
DCHECK(IsValidElement(isolate, table, entry));
Handle<FixedArray> entries(table->entries(), isolate);
// The FixedArray is addressed with int's.
int entry_index = static_cast<int>(index);
if (table->type() == wasm::kWasmAnyRef) {
entries->set(entry_index, *entry);
return;
}
if (entry->IsNull(isolate)) {
ClearDispatchTables(isolate, table, entry_index); // Degenerate case.
entries->set(entry_index, ReadOnlyRoots(isolate).null_value());
return;
}
if (WasmExportedFunction::IsWasmExportedFunction(*entry)) {
auto exported_function = Handle<WasmExportedFunction>::cast(entry);
Handle<WasmInstanceObject> target_instance(exported_function->instance(),
isolate);
int func_index = exported_function->function_index();
auto* wasm_function = &target_instance->module()->functions[func_index];
DCHECK_NOT_NULL(wasm_function);
DCHECK_NOT_NULL(wasm_function->sig);
UpdateDispatchTables(isolate, table, entry_index, wasm_function->sig,
target_instance, func_index);
} else if (WasmJSFunction::IsWasmJSFunction(*entry)) {
UpdateDispatchTables(isolate, table, entry_index,
Handle<WasmJSFunction>::cast(entry));
} else {
DCHECK(WasmCapiFunction::IsWasmCapiFunction(*entry));
UpdateDispatchTables(isolate, table, entry_index,
Handle<WasmCapiFunction>::cast(entry));
}
entries->set(entry_index, *entry);
}
Handle<Object> WasmTableObject::Get(Isolate* isolate,
Handle<WasmTableObject> table,
uint32_t index) {
Handle<FixedArray> entries(table->entries(), isolate);
// Callers need to perform bounds checks and error handling.
DCHECK(IsInBounds(isolate, table, index));
// The FixedArray is addressed with int's.
int entry_index = static_cast<int>(index);
Handle<Object> entry(entries->get(entry_index), isolate);
// First we handle the easy anyref table case.
if (table->type() == wasm::kWasmAnyRef) return entry;
// Now we handle the funcref case.
if (WasmExportedFunction::IsWasmExportedFunction(*entry) ||
WasmCapiFunction::IsWasmCapiFunction(*entry)) {
return entry;
}
if (entry->IsNull(isolate)) {
return entry;
}
// {entry} is not a valid entry in the table. It has to be a placeholder
// for lazy initialization.
Handle<Tuple2> tuple = Handle<Tuple2>::cast(entry);
auto instance = handle(WasmInstanceObject::cast(tuple->value1()), isolate);
int function_index = Smi::cast(tuple->value2()).value();
// Check if we already compiled a wrapper for the function but did not store
// it in the table slot yet.
entry = WasmInstanceObject::GetOrCreateWasmExportedFunction(isolate, instance,
function_index);
entries->set(entry_index, *entry);
return entry;
}
void WasmTableObject::Fill(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t start, Handle<Object> entry,
uint32_t count) {
// Bounds checks must be done by the caller.
DCHECK_LE(start, table->entries().length());
DCHECK_LE(count, table->entries().length());
DCHECK_LE(start + count, table->entries().length());
for (uint32_t i = 0; i < count; i++) {
WasmTableObject::Set(isolate, table, start + i, entry);
}
}
void WasmTableObject::UpdateDispatchTables(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
wasm::FunctionSig* sig, Handle<WasmInstanceObject> target_instance,
int target_func_index) {
// We simply need to update the IFTs for each instance that imports
// this table.
Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate);
DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements);
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
int table_index =
Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value();
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(
dispatch_tables->get(i + kDispatchTableInstanceOffset)),
isolate);
// Note that {SignatureMap::Find} may return {-1} if the signature is
// not found; it will simply never match any check.
auto sig_id = instance->module()->signature_map.Find(*sig);
IndirectFunctionTableEntry(instance, table_index, entry_index)
.Set(sig_id, target_instance, target_func_index);
}
}
void WasmTableObject::UpdateDispatchTables(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index,
Handle<WasmJSFunction> function) {
// We simply need to update the IFTs for each instance that imports
// this table.
Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate);
DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements);
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
int table_index =
Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value();
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(
dispatch_tables->get(i + kDispatchTableInstanceOffset)),
isolate);
WasmInstanceObject::ImportWasmJSFunctionIntoTable(
isolate, instance, table_index, entry_index, function);
}
}
void WasmTableObject::UpdateDispatchTables(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
Handle<WasmCapiFunction> capi_function) {
// We simply need to update the IFTs for each instance that imports
// this table.
Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate);
DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements);
// Reconstruct signature.
// TODO(jkummerow): Unify with "SignatureHelper" in c-api.cc.
PodArray<wasm::ValueType> serialized_sig =
capi_function->GetSerializedSignature();
int total_count = serialized_sig.length() - 1;
std::unique_ptr<wasm::ValueType[]> reps(new wasm::ValueType[total_count]);
int result_count;
static const wasm::ValueType kMarker = wasm::kWasmStmt;
for (int i = 0, j = 0; i <= total_count; i++) {
if (serialized_sig.get(i) == kMarker) {
result_count = i;
continue;
}
reps[j++] = serialized_sig.get(i);
}
int param_count = total_count - result_count;
wasm::FunctionSig sig(result_count, param_count, reps.get());
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
int table_index =
Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value();
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(
dispatch_tables->get(i + kDispatchTableInstanceOffset)),
isolate);
// TODO(jkummerow): Find a way to avoid recompiling wrappers.
wasm::NativeModule* native_module =
instance->module_object().native_module();
Address host_address = capi_function->GetHostCallTarget();
wasm::WasmCodeRefScope code_ref_scope;
wasm::WasmCode* wasm_code = compiler::CompileWasmCapiCallWrapper(
isolate->wasm_engine(), native_module, &sig, host_address);
isolate->counters()->wasm_generated_code_size()->Increment(
wasm_code->instructions().length());
isolate->counters()->wasm_reloc_size()->Increment(
wasm_code->reloc_info().length());
Handle<Tuple2> tuple = isolate->factory()->NewTuple2(
instance, capi_function, AllocationType::kOld);
// Note that {SignatureMap::Find} may return {-1} if the signature is
// not found; it will simply never match any check.
auto sig_id = instance->module()->signature_map.Find(sig);
IndirectFunctionTableEntry(instance, table_index, entry_index)
.Set(sig_id, wasm_code->instruction_start(), *tuple);
}
}
void WasmTableObject::ClearDispatchTables(Isolate* isolate,
Handle<WasmTableObject> table,
int index) {
Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate);
DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements);
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
int table_index =
Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value();
Handle<WasmInstanceObject> target_instance(
WasmInstanceObject::cast(
dispatch_tables->get(i + kDispatchTableInstanceOffset)),
isolate);
DCHECK_LT(index, WasmInstanceObject::IndirectFunctionTableSize(
isolate, target_instance, table_index));
IndirectFunctionTableEntry(target_instance, table_index, index).clear();
}
}
void WasmTableObject::SetFunctionTablePlaceholder(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
Handle<WasmInstanceObject> instance, int func_index) {
// Put (instance, func_index) as a Tuple2 into the table_index.
// The {WasmExportedFunction} will be created lazily.
Handle<Tuple2> tuple = isolate->factory()->NewTuple2(
instance, Handle<Smi>(Smi::FromInt(func_index), isolate),
AllocationType::kYoung);
table->entries().set(entry_index, *tuple);
}
void WasmTableObject::GetFunctionTableEntry(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance,
int* function_index, MaybeHandle<WasmJSFunction>* maybe_js_function) {
DCHECK_EQ(table->type(), wasm::kWasmFuncRef);
DCHECK_LT(entry_index, table->entries().length());
// We initialize {is_valid} with {true}. We may change it later.
*is_valid = true;
Handle<Object> element(table->entries().get(entry_index), isolate);
*is_null = element->IsNull(isolate);
if (*is_null) return;
if (WasmExportedFunction::IsWasmExportedFunction(*element)) {
auto target_func = Handle<WasmExportedFunction>::cast(element);
*instance = handle(target_func->instance(), isolate);
*function_index = target_func->function_index();
*maybe_js_function = MaybeHandle<WasmJSFunction>();
return;
}
if (WasmJSFunction::IsWasmJSFunction(*element)) {
*instance = MaybeHandle<WasmInstanceObject>();
*maybe_js_function = Handle<WasmJSFunction>::cast(element);
return;
}
if (element->IsTuple2()) {
auto tuple = Handle<Tuple2>::cast(element);
*instance = handle(WasmInstanceObject::cast(tuple->value1()), isolate);
*function_index = Smi::cast(tuple->value2()).value();
*maybe_js_function = MaybeHandle<WasmJSFunction>();
return;
}
*is_valid = false;
}
namespace {
class IftNativeAllocations {
public:
IftNativeAllocations(Handle<WasmIndirectFunctionTable> table, uint32_t size)
: sig_ids_(size), targets_(size) {
table->set_sig_ids(sig_ids_.data());
table->set_targets(targets_.data());
}
static size_t SizeInMemory(uint32_t size) {
return size * (sizeof(Address) + sizeof(uint32_t));
}
void resize(Handle<WasmIndirectFunctionTable> table, uint32_t new_size) {
DCHECK_GE(new_size, sig_ids_.size());
DCHECK_EQ(this, Managed<IftNativeAllocations>::cast(
table->managed_native_allocations())
.raw());
sig_ids_.resize(new_size);
targets_.resize(new_size);
table->set_sig_ids(sig_ids_.data());
table->set_targets(targets_.data());
}
private:
std::vector<uint32_t> sig_ids_;
std::vector<Address> targets_;
};
} // namespace
Handle<WasmIndirectFunctionTable> WasmIndirectFunctionTable::New(
Isolate* isolate, uint32_t size) {
auto refs = isolate->factory()->NewFixedArray(static_cast<int>(size));
auto table = Handle<WasmIndirectFunctionTable>::cast(
isolate->factory()->NewStruct(WASM_INDIRECT_FUNCTION_TABLE_TYPE));
table->set_size(size);
table->set_refs(*refs);
auto native_allocations = Managed<IftNativeAllocations>::Allocate(
isolate, IftNativeAllocations::SizeInMemory(size), table, size);
table->set_managed_native_allocations(*native_allocations);
for (uint32_t i = 0; i < size; ++i) {
IndirectFunctionTableEntry(table, static_cast<int>(i)).clear();
}
return table;
}
void WasmIndirectFunctionTable::Resize(Isolate* isolate,
Handle<WasmIndirectFunctionTable> table,
uint32_t new_size) {
uint32_t old_size = table->size();
if (old_size >= new_size) return; // Nothing to do.
Managed<IftNativeAllocations>::cast(table->managed_native_allocations())
.raw()
->resize(table, new_size);
Handle<FixedArray> old_refs(table->refs(), isolate);
Handle<FixedArray> new_refs = isolate->factory()->CopyFixedArrayAndGrow(
old_refs, static_cast<int>(new_size - old_size));
table->set_refs(*new_refs);
table->set_size(new_size);
for (uint32_t i = old_size; i < new_size; ++i) {
IndirectFunctionTableEntry(table, static_cast<int>(i)).clear();
}
}
namespace {
bool AdjustBufferPermissions(Isolate* isolate, Handle<JSArrayBuffer> old_buffer,
size_t new_size) {
if (new_size > old_buffer->allocation_length()) return false;
void* old_mem_start = old_buffer->backing_store();
size_t old_size = old_buffer->byte_length();
if (old_size != new_size) {
DCHECK_NOT_NULL(old_mem_start);
DCHECK_GE(new_size, old_size);
// If adjusting permissions fails, propagate error back to return
// failure to grow.
if (!i::SetPermissions(GetPlatformPageAllocator(), old_mem_start, new_size,
PageAllocator::kReadWrite)) {
return false;
}
reinterpret_cast<v8::Isolate*>(isolate)
->AdjustAmountOfExternalAllocatedMemory(new_size - old_size);
}
return true;
}
MaybeHandle<JSArrayBuffer> MemoryGrowBuffer(Isolate* isolate,
Handle<JSArrayBuffer> old_buffer,
size_t new_size) {
CHECK_EQ(0, new_size % wasm::kWasmPageSize);
// Reusing the backing store from externalized buffers causes problems with
// Blink's array buffers. The connection between the two is lost, which can
// lead to Blink not knowing about the other reference to the buffer and
// freeing it too early.
if (old_buffer->is_external() || new_size > old_buffer->allocation_length()) {
// We couldn't reuse the old backing store, so create a new one and copy the
// old contents in.
Handle<JSArrayBuffer> new_buffer;
if (!wasm::NewArrayBuffer(isolate, new_size).ToHandle(&new_buffer)) {
return {};
}
void* old_mem_start = old_buffer->backing_store();
size_t old_size = old_buffer->byte_length();
if (old_size == 0) return new_buffer;
memcpy(new_buffer->backing_store(), old_mem_start, old_size);
DCHECK(old_buffer.is_null() || !old_buffer->is_shared());
constexpr bool free_memory = true;
i::wasm::DetachMemoryBuffer(isolate, old_buffer, free_memory);
return new_buffer;
} else {
if (!AdjustBufferPermissions(isolate, old_buffer, new_size)) return {};
// NOTE: We must allocate a new array buffer here because the spec
// assumes that ArrayBuffers do not change size.
void* backing_store = old_buffer->backing_store();
bool is_external = old_buffer->is_external();
// Disconnect buffer early so GC won't free it.
i::wasm::DetachMemoryBuffer(isolate, old_buffer, false);
Handle<JSArrayBuffer> new_buffer =
wasm::SetupArrayBuffer(isolate, backing_store, new_size, is_external);
return new_buffer;
}
}
// May GC, because SetSpecializationMemInfoFrom may GC
void SetInstanceMemory(Handle<WasmInstanceObject> instance,
Handle<JSArrayBuffer> buffer) {
instance->SetRawMemory(reinterpret_cast<byte*>(buffer->backing_store()),
buffer->byte_length());
#if DEBUG
if (!FLAG_mock_arraybuffer_allocator) {
// To flush out bugs earlier, in DEBUG mode, check that all pages of the
// memory are accessible by reading and writing one byte on each page.
// Don't do this if the mock ArrayBuffer allocator is enabled.
byte* mem_start = instance->memory_start();
size_t mem_size = instance->memory_size();
for (size_t offset = 0; offset < mem_size; offset += wasm::kWasmPageSize) {
byte val = mem_start[offset];
USE(val);
mem_start[offset] = val;
}
}
#endif
}
} // namespace
Handle<WasmMemoryObject> WasmMemoryObject::New(
Isolate* isolate, MaybeHandle<JSArrayBuffer> maybe_buffer,
uint32_t maximum) {
Handle<JSArrayBuffer> buffer;
if (!maybe_buffer.ToHandle(&buffer)) {
// If no buffer was provided, create a 0-length one.
buffer = wasm::SetupArrayBuffer(isolate, nullptr, 0, false);
}
// TODO(kschimpf): Do we need to add an argument that defines the
// style of memory the user prefers (with/without trap handling), so
// that the memory will match the style of the compiled wasm module.
// See issue v8:7143
Handle<JSFunction> memory_ctor(
isolate->native_context()->wasm_memory_constructor(), isolate);
auto memory_obj = Handle<WasmMemoryObject>::cast(
isolate->factory()->NewJSObject(memory_ctor, AllocationType::kOld));
memory_obj->set_array_buffer(*buffer);
memory_obj->set_maximum_pages(maximum);
return memory_obj;
}
MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate,
uint32_t initial,
uint32_t maximum,
bool is_shared_memory) {
Handle<JSArrayBuffer> buffer;
size_t size = static_cast<size_t>(i::wasm::kWasmPageSize) *
static_cast<size_t>(initial);
if (is_shared_memory) {
size_t max_size = static_cast<size_t>(i::wasm::kWasmPageSize) *
static_cast<size_t>(maximum);
if (!i::wasm::NewSharedArrayBuffer(isolate, size, max_size)
.ToHandle(&buffer)) {
return {};
}
} else {
if (!i::wasm::NewArrayBuffer(isolate, size).ToHandle(&buffer)) {
return {};
}
}
return New(isolate, buffer, maximum);
}
void WasmMemoryObject::AddInstance(Isolate* isolate,
Handle<WasmMemoryObject> memory,
Handle<WasmInstanceObject> instance) {
Handle<WeakArrayList> old_instances =
memory->has_instances()
? Handle<WeakArrayList>(memory->instances(), isolate)
: handle(ReadOnlyRoots(isolate->heap()).empty_weak_array_list(),
isolate);
Handle<WeakArrayList> new_instances = WeakArrayList::AddToEnd(
isolate, old_instances, MaybeObjectHandle::Weak(instance));
memory->set_instances(*new_instances);
Handle<JSArrayBuffer> buffer(memory->array_buffer(), isolate);
SetInstanceMemory(instance, buffer);
}
void WasmMemoryObject::update_instances(Isolate* isolate,
Handle<JSArrayBuffer> buffer) {
if (has_instances()) {
Handle<WeakArrayList> instances(this->instances(), isolate);
for (int i = 0; i < instances->length(); i++) {
MaybeObject elem = instances->Get(i);
HeapObject heap_object;
if (elem->GetHeapObjectIfWeak(&heap_object)) {
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(heap_object), isolate);
SetInstanceMemory(instance, buffer);
} else {
DCHECK(elem->IsCleared());
}
}
}
set_array_buffer(*buffer);
}
// static
int32_t WasmMemoryObject::Grow(Isolate* isolate,
Handle<WasmMemoryObject> memory_object,
uint32_t pages) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "GrowMemory");
Handle<JSArrayBuffer> old_buffer(memory_object->array_buffer(), isolate);
if (old_buffer->is_shared() && !FLAG_wasm_grow_shared_memory) return -1;
auto* memory_tracker = isolate->wasm_engine()->memory_tracker();
if (!memory_tracker->IsWasmMemoryGrowable(old_buffer)) return -1;
// Checks for maximum memory size, compute new size.
uint32_t maximum_pages = wasm::max_mem_pages();
if (memory_object->has_maximum_pages()) {
maximum_pages = std::min(
maximum_pages, static_cast<uint32_t>(memory_object->maximum_pages()));
}
CHECK_GE(wasm::max_mem_pages(), maximum_pages);
size_t old_size = old_buffer->byte_length();
CHECK_EQ(0, old_size % wasm::kWasmPageSize);
size_t old_pages = old_size / wasm::kWasmPageSize;
CHECK_GE(wasm::max_mem_pages(), old_pages);
if ((pages > maximum_pages - old_pages) || // exceeds remaining
(pages > wasm::max_mem_pages() - old_pages)) { // exceeds limit
return -1;
}
size_t new_size =
static_cast<size_t>(old_pages + pages) * wasm::kWasmPageSize;
// Memory is grown, but the memory objects and instances are not yet updated.
// Handle this in the interrupt handler so that it's safe for all the isolates
// that share this buffer to be updated safely.
Handle<JSArrayBuffer> new_buffer;
if (old_buffer->is_shared()) {
// Adjust protections for the buffer.
if (!AdjustBufferPermissions(isolate, old_buffer, new_size)) {
return -1;
}
void* backing_store = old_buffer->backing_store();
if (memory_tracker->IsWasmSharedMemory(backing_store)) {
// This memory is shared between different isolates.
DCHECK(old_buffer->is_shared());
// Update pending grow state, and trigger a grow interrupt on all the
// isolates that share this buffer.
memory_tracker->SetPendingUpdateOnGrow(old_buffer, new_size);
// Handle interrupts for this isolate so that the instances with this
// isolate are updated.
isolate->stack_guard()->HandleInterrupts();
// Failure to allocate, or adjust pemissions already handled here, and
// updates to instances handled in the interrupt handler safe to return.
return static_cast<uint32_t>(old_size / wasm::kWasmPageSize);
}
// SharedArrayBuffer, but not shared across isolates. Setup a new buffer
// with updated permissions and update the instances.
new_buffer =
wasm::SetupArrayBuffer(isolate, backing_store, new_size,
old_buffer->is_external(), SharedFlag::kShared);
memory_object->update_instances(isolate, new_buffer);
} else {
if (!MemoryGrowBuffer(isolate, old_buffer, new_size)
.ToHandle(&new_buffer)) {
return -1;
}
}
// Update instances if any.
memory_object->update_instances(isolate, new_buffer);
return static_cast<uint32_t>(old_size / wasm::kWasmPageSize);
}
// static
MaybeHandle<WasmGlobalObject> WasmGlobalObject::New(
Isolate* isolate, MaybeHandle<JSArrayBuffer> maybe_untagged_buffer,
MaybeHandle<FixedArray> maybe_tagged_buffer, wasm::ValueType type,
int32_t offset, bool is_mutable) {
Handle<JSFunction> global_ctor(
isolate->native_context()->wasm_global_constructor(), isolate);
auto global_obj = Handle<WasmGlobalObject>::cast(
isolate->factory()->NewJSObject(global_ctor));
{
// Disallow GC until all fields have acceptable types.
DisallowHeapAllocation no_gc;
global_obj->set_flags(0);
global_obj->set_type(type);
global_obj->set_offset(offset);
global_obj->set_is_mutable(is_mutable);
}
if (wasm::ValueTypes::IsReferenceType(type)) {
DCHECK(maybe_untagged_buffer.is_null());
Handle<FixedArray> tagged_buffer;
if (!maybe_tagged_buffer.ToHandle(&tagged_buffer)) {
// If no buffer was provided, create one.
tagged_buffer =
isolate->factory()->NewFixedArray(1, AllocationType::kOld);
CHECK_EQ(offset, 0);
}
global_obj->set_tagged_buffer(*tagged_buffer);
} else {
DCHECK(maybe_tagged_buffer.is_null());
Handle<JSArrayBuffer> untagged_buffer;
uint32_t type_size = wasm::ValueTypes::ElementSizeInBytes(type);
if (!maybe_untagged_buffer.ToHandle(&untagged_buffer)) {
// If no buffer was provided, create one long enough for the given type.
untagged_buffer = isolate->factory()->NewJSArrayBuffer(
SharedFlag::kNotShared, AllocationType::kOld);
const bool initialize = true;
if (!JSArrayBuffer::SetupAllocatingData(untagged_buffer, isolate,
type_size, initialize)) {
return {};
}
}
// Check that the offset is in bounds.
CHECK_LE(offset + type_size, untagged_buffer->byte_length());
global_obj->set_untagged_buffer(*untagged_buffer);
}
return global_obj;
}
void IndirectFunctionTableEntry::clear() {
if (!instance_.is_null()) {
instance_->indirect_function_table_sig_ids()[index_] = -1;
instance_->indirect_function_table_targets()[index_] = 0;
instance_->indirect_function_table_refs().set(
index_, ReadOnlyRoots(instance_->GetIsolate()).undefined_value());
} else {
DCHECK(!table_.is_null());
table_->sig_ids()[index_] = -1;
table_->targets()[index_] = 0;
table_->refs().set(
index_,
ReadOnlyRoots(GetIsolateFromWritableObject(*table_)).undefined_value());
}
}
void IndirectFunctionTableEntry::Set(int sig_id,
Handle<WasmInstanceObject> target_instance,
int target_func_index) {
TRACE_IFT("IFT entry 0x%" PRIxPTR
"[%d] = {sig_id=%d, target_instance=0x%" PRIxPTR
", target_func_index=%d}\n",
instance_->ptr(), index_, sig_id, target_instance->ptr(),
target_func_index);
Object ref;
Address call_target = 0;
if (target_func_index <
static_cast<int>(target_instance->module()->num_imported_functions)) {
// The function in the target instance was imported. Use its imports table,
// which contains a tuple needed by the import wrapper.
ImportedFunctionEntry entry(target_instance, target_func_index);
ref = entry.object_ref();
call_target = entry.target();
} else {
// The function in the target instance was not imported.
ref = *target_instance;
call_target = target_instance->GetCallTarget(target_func_index);
}
Set(sig_id, call_target, ref);
}
void IndirectFunctionTableEntry::Set(int sig_id, Address call_target,
Object ref) {
if (!instance_.is_null()) {
instance_->indirect_function_table_sig_ids()[index_] = sig_id;
instance_->indirect_function_table_targets()[index_] = call_target;
instance_->indirect_function_table_refs().set(index_, ref);
} else {
DCHECK(!table_.is_null());
table_->sig_ids()[index_] = sig_id;
table_->targets()[index_] = call_target;
table_->refs().set(index_, ref);
}
}
Object IndirectFunctionTableEntry::object_ref() const {
return !instance_.is_null()
? instance_->indirect_function_table_refs().get(index_)
: table_->refs().get(index_);
}
int IndirectFunctionTableEntry::sig_id() const {
return !instance_.is_null()
? instance_->indirect_function_table_sig_ids()[index_]
: table_->sig_ids()[index_];
}
Address IndirectFunctionTableEntry::target() const {
return !instance_.is_null()
? instance_->indirect_function_table_targets()[index_]
: table_->targets()[index_];
}
void ImportedFunctionEntry::SetWasmToJs(
Isolate* isolate, Handle<JSReceiver> callable,
const wasm::WasmCode* wasm_to_js_wrapper) {
TRACE_IFT("Import callable 0x%" PRIxPTR "[%d] = {callable=0x%" PRIxPTR
", target=%p}\n",
instance_->ptr(), index_, callable->ptr(),
wasm_to_js_wrapper->instructions().begin());
DCHECK(wasm_to_js_wrapper->kind() == wasm::WasmCode::kWasmToJsWrapper ||
wasm_to_js_wrapper->kind() == wasm::WasmCode::kWasmToCapiWrapper);
Handle<Tuple2> tuple =
isolate->factory()->NewTuple2(instance_, callable, AllocationType::kOld);
instance_->imported_function_refs().set(index_, *tuple);
instance_->imported_function_targets()[index_] =
wasm_to_js_wrapper->instruction_start();
}
void ImportedFunctionEntry::SetWasmToWasm(WasmInstanceObject instance,
Address call_target) {
TRACE_IFT("Import WASM 0x%" PRIxPTR "[%d] = {instance=0x%" PRIxPTR
", target=0x%" PRIxPTR "}\n",
instance_->ptr(), index_, instance.ptr(), call_target);
instance_->imported_function_refs().set(index_, instance);
instance_->imported_function_targets()[index_] = call_target;
}
WasmInstanceObject ImportedFunctionEntry::instance() {
// The imported reference entry is either a target instance or a tuple
// of this instance and the target callable.
Object value = instance_->imported_function_refs().get(index_);
if (value.IsWasmInstanceObject()) {
return WasmInstanceObject::cast(value);
}
Tuple2 tuple = Tuple2::cast(value);
return WasmInstanceObject::cast(tuple.value1());
}
JSReceiver ImportedFunctionEntry::callable() {
return JSReceiver::cast(Tuple2::cast(object_ref()).value2());
}
Object ImportedFunctionEntry::object_ref() {
return instance_->imported_function_refs().get(index_);
}
Address ImportedFunctionEntry::target() {
return instance_->imported_function_targets()[index_];
}
// static
constexpr uint16_t WasmInstanceObject::kTaggedFieldOffsets[];
// static
bool WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
Handle<WasmInstanceObject> instance, int table_index,
uint32_t minimum_size) {
Isolate* isolate = instance->GetIsolate();
if (table_index > 0) {
DCHECK_LT(table_index, instance->indirect_function_tables().length());
auto table =
handle(WasmIndirectFunctionTable::cast(
instance->indirect_function_tables().get(table_index)),
isolate);
WasmIndirectFunctionTable::Resize(isolate, table, minimum_size);
return true;
}
uint32_t old_size = instance->indirect_function_table_size();
if (old_size >= minimum_size) return false; // Nothing to do.
HandleScope scope(isolate);
auto native_allocations = GetNativeAllocations(*instance);
native_allocations->resize_indirect_function_table(isolate, instance,
minimum_size);
return true;
}
void WasmInstanceObject::SetRawMemory(byte* mem_start, size_t mem_size) {
CHECK_LE(mem_size, wasm::max_mem_bytes());
#if V8_HOST_ARCH_64_BIT
uint64_t mem_mask64 = base::bits::RoundUpToPowerOfTwo64(mem_size) - 1;
set_memory_start(mem_start);
set_memory_size(mem_size);
set_memory_mask(mem_mask64);
#else
// Must handle memory > 2GiB specially.
CHECK_LE(mem_size, size_t{kMaxUInt32});
uint32_t mem_mask32 =
(mem_size > 2 * size_t{GB})
? 0xFFFFFFFFu
: base::bits::RoundUpToPowerOfTwo32(static_cast<uint32_t>(mem_size)) -
1;
set_memory_start(mem_start);
set_memory_size(mem_size);
set_memory_mask(mem_mask32);
#endif
}
const WasmModule* WasmInstanceObject::module() {
return module_object().module();
}
Handle<WasmDebugInfo> WasmInstanceObject::GetOrCreateDebugInfo(
Handle<WasmInstanceObject> instance) {
if (instance->has_debug_info()) {
return handle(instance->debug_info(), instance->GetIsolate());
}
Handle<WasmDebugInfo> new_info = WasmDebugInfo::New(instance);
DCHECK(instance->has_debug_info());
return new_info;
}
Handle<WasmInstanceObject> WasmInstanceObject::New(
Isolate* isolate, Handle<WasmModuleObject> module_object) {
Handle<JSFunction> instance_cons(
isolate->native_context()->wasm_instance_constructor(), isolate);
Handle<JSObject> instance_object =
isolate->factory()->NewJSObject(instance_cons, AllocationType::kOld);
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(*instance_object), isolate);
instance->clear_padding();
// Initialize the imported function arrays.
auto module = module_object->module();
auto num_imported_functions = module->num_imported_functions;
auto num_imported_mutable_globals = module->num_imported_mutable_globals;
auto num_data_segments = module->num_declared_data_segments;
size_t native_allocations_size = EstimateNativeAllocationsSize(module);
auto native_allocations = Managed<WasmInstanceNativeAllocations>::Allocate(
isolate, native_allocations_size, instance, num_imported_functions,
num_imported_mutable_globals, num_data_segments,
module->elem_segments.size());
instance->set_managed_native_allocations(*native_allocations);
Handle<FixedArray> imported_function_refs =
isolate->factory()->NewFixedArray(num_imported_functions);
instance->set_imported_function_refs(*imported_function_refs);
Handle<Code> centry_stub = CodeFactory::CEntry(isolate);
instance->set_centry_stub(*centry_stub);
instance->SetRawMemory(nullptr, 0);
instance->set_isolate_root(isolate->isolate_root());
instance->set_stack_limit_address(
isolate->stack_guard()->address_of_jslimit());
instance->set_real_stack_limit_address(
isolate->stack_guard()->address_of_real_jslimit());
instance->set_globals_start(nullptr);
instance->set_indirect_function_table_size(0);
instance->set_indirect_function_table_sig_ids(nullptr);
instance->set_indirect_function_table_targets(nullptr);
instance->set_native_context(*isolate->native_context());
instance->set_module_object(*module_object);
instance->set_jump_table_start(
module_object->native_module()->jump_table_start());
// Insert the new instance into the modules weak list of instances.
// TODO(mstarzinger): Allow to reuse holes in the {WeakArrayList} below.
Handle<WeakArrayList> weak_instance_list(module_object->weak_instance_list(),
isolate);
weak_instance_list = WeakArrayList::AddToEnd(
isolate, weak_instance_list, MaybeObjectHandle::Weak(instance));
module_object->set_weak_instance_list(*weak_instance_list);
InitDataSegmentArrays(instance, module_object);
InitElemSegmentArrays(instance, module_object);
return instance;
}
// static
void WasmInstanceObject::InitDataSegmentArrays(
Handle<WasmInstanceObject> instance,
Handle<WasmModuleObject> module_object) {
auto module = module_object->module();
auto wire_bytes = module_object->native_module()->wire_bytes();
auto num_data_segments = module->num_declared_data_segments;
// The number of declared data segments will be zero if there is no DataCount
// section. These arrays will not be allocated nor initialized in that case,
// since they cannot be used (since the validator checks that number of
// declared data segments when validating the memory.init and memory.drop
// instructions).
DCHECK(num_data_segments == 0 ||
num_data_segments == module->data_segments.size());
for (size_t i = 0; i < num_data_segments; ++i) {
const wasm::WasmDataSegment& segment = module->data_segments[i];
// Set the active segments to being already dropped, since memory.init on
// a dropped passive segment and an active segment have the same
// behavior.
instance->dropped_data_segments()[i] = segment.active ? 1 : 0;
// Initialize the pointer and size of passive segments.
auto source_bytes = wire_bytes.SubVector(segment.source.offset(),
segment.source.end_offset());
instance->data_segment_starts()[i] =
reinterpret_cast<Address>(source_bytes.begin());
instance->data_segment_sizes()[i] = source_bytes.length();
}
}
void WasmInstanceObject::InitElemSegmentArrays(
Handle<WasmInstanceObject> instance,
Handle<WasmModuleObject> module_object) {
auto module = module_object->module();
auto num_elem_segments = module->elem_segments.size();
for (size_t i = 0; i < num_elem_segments; ++i) {
const wasm::WasmElemSegment& segment = module->elem_segments[i];
// Set the active segments to being already dropped, since table.init on
// a dropped passive segment and an active segment have the same
// behavior.
instance->dropped_elem_segments()[i] = segment.active ? 1 : 0;
}
}
Address WasmInstanceObject::GetCallTarget(uint32_t func_index) {
wasm::NativeModule* native_module = module_object().native_module();
if (func_index < native_module->num_imported_functions()) {
return imported_function_targets()[func_index];
}
return native_module->GetCallTargetForFunction(func_index);
}
int WasmInstanceObject::IndirectFunctionTableSize(
Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t table_index) {
if (table_index == 0) {
return instance->indirect_function_table_size();
}
auto table =
handle(WasmIndirectFunctionTable::cast(
instance->indirect_function_tables().get(table_index)),
isolate);
return table->size();
}
// static
bool WasmInstanceObject::CopyTableEntries(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t table_dst_index,
uint32_t table_src_index,
uint32_t dst, uint32_t src,
uint32_t count) {
// Copying 0 elements is a no-op.
if (count == 0) return true;
CHECK_LT(table_dst_index, instance->tables().length());
CHECK_LT(table_src_index, instance->tables().length());
auto table_dst = handle(
WasmTableObject::cast(instance->tables().get(table_dst_index)), isolate);
auto table_src = handle(
WasmTableObject::cast(instance->tables().get(table_src_index)), isolate);
uint32_t max_dst = static_cast<uint32_t>(table_dst->entries().length());
uint32_t max_src = static_cast<uint32_t>(table_src->entries().length());
bool copy_backward = src < dst;
bool ok = ClampToBounds(dst, &count, max_dst);
// Use & instead of && so the clamp is not short-circuited.
ok &= ClampToBounds(src, &count, max_src);
// If performing a partial copy when copying backward, then the first access
// will be out-of-bounds, so no entries should be copied.
if (copy_backward && !ok) return ok;
// no-op
if ((dst == src && table_dst_index == table_src_index) || count == 0) {
return ok;
}
for (uint32_t i = 0; i < count; ++i) {
uint32_t src_index = copy_backward ? (src + count - i - 1) : src + i;
uint32_t dst_index = copy_backward ? (dst + count - i - 1) : dst + i;
auto value = WasmTableObject::Get(isolate, table_src, src_index);
WasmTableObject::Set(isolate, table_dst, dst_index, value);
}
return ok;
}
// static
bool WasmInstanceObject::InitTableEntries(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t table_index,
uint32_t segment_index, uint32_t dst,
uint32_t src, uint32_t count) {
// Copying 0 elements is a no-op.
if (count == 0) return true;
// Note that this implementation just calls through to module instantiation.
// This is intentional, so that the runtime only depends on the object
// methods, and not the module instantiation logic.
return wasm::LoadElemSegment(isolate, instance, table_index, segment_index,
dst, src, count);
}
MaybeHandle<WasmExportedFunction> WasmInstanceObject::GetWasmExportedFunction(
Isolate* isolate, Handle<WasmInstanceObject> instance, int index) {
MaybeHandle<WasmExportedFunction> result;
if (instance->has_wasm_exported_functions()) {
Object val = instance->wasm_exported_functions().get(index);
if (!val.IsUndefined(isolate)) {
result = Handle<WasmExportedFunction>(WasmExportedFunction::cast(val),
isolate);
}
}
return result;
}
Handle<WasmExportedFunction>
WasmInstanceObject::GetOrCreateWasmExportedFunction(
Isolate* isolate, Handle<WasmInstanceObject> instance, int function_index) {
MaybeHandle<WasmExportedFunction> maybe_result =
WasmInstanceObject::GetWasmExportedFunction(isolate, instance,
function_index);
Handle<WasmExportedFunction> result;
if (maybe_result.ToHandle(&result)) {
return result;
}
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
const WasmModule* module = module_object->module();
const WasmFunction& function = module->functions[function_index];
int wrapper_index =
GetExportWrapperIndex(module, function.sig, function.imported);
Handle<Object> entry =
FixedArray::get(module_object->export_wrappers(), wrapper_index, isolate);
Handle<Code> wrapper;
if (entry->IsCode()) {
wrapper = Handle<Code>::cast(entry);
} else {
// The wrapper may not exist yet if no function in the exports section has
// this signature. We compile it and store the wrapper in the module for
// later use.
wrapper = wasm::JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper(
isolate, function.sig, function.imported);
module_object->export_wrappers().set(wrapper_index, *wrapper);
}
result = WasmExportedFunction::New(
isolate, instance, function_index,
static_cast<int>(function.sig->parameter_count()), wrapper);
WasmInstanceObject::SetWasmExportedFunction(isolate, instance, function_index,
result);
return result;
}
void WasmInstanceObject::SetWasmExportedFunction(
Isolate* isolate, Handle<WasmInstanceObject> instance, int index,
Handle<WasmExportedFunction> val) {
Handle<FixedArray> functions;
if (!instance->has_wasm_exported_functions()) {
// lazily-allocate the wasm exported functions.
functions = isolate->factory()->NewFixedArray(
static_cast<int>(instance->module()->functions.size()));
instance->set_wasm_exported_functions(*functions);
} else {
functions =
Handle<FixedArray>(instance->wasm_exported_functions(), isolate);
}
functions->set(index, *val);
}
// static
void WasmInstanceObject::ImportWasmJSFunctionIntoTable(
Isolate* isolate, Handle<WasmInstanceObject> instance, int table_index,
int entry_index, Handle<WasmJSFunction> js_function) {
// Deserialize the signature encapsulated with the {WasmJSFunction}.
// Note that {SignatureMap::Find} may return {-1} if the signature is
// not found; it will simply never match any check.
Zone zone(isolate->allocator(), ZONE_NAME);
wasm::FunctionSig* sig = js_function->GetSignature(&zone);
auto sig_id = instance->module()->signature_map.Find(*sig);
// Compile a wrapper for the target callable.
Handle<JSReceiver> callable(js_function->GetCallable(), isolate);
wasm::WasmCodeRefScope code_ref_scope;
Address call_target = kNullAddress;
if (sig_id >= 0) {
wasm::NativeModule* native_module =
instance->module_object().native_module();
// TODO(mstarzinger): Cache and reuse wrapper code.
const wasm::WasmFeatures enabled = native_module->enabled_features();
auto resolved =
compiler::ResolveWasmImportCall(callable, sig, enabled.bigint);
compiler::WasmImportCallKind kind = resolved.first;
callable = resolved.second; // Update to ultimate target.
DCHECK_NE(compiler::WasmImportCallKind::kLinkError, kind);
wasm::CompilationEnv env = native_module->CreateCompilationEnv();
wasm::WasmCompilationResult result = compiler::CompileWasmImportCallWrapper(
isolate->wasm_engine(), &env, kind, sig, false);
#if !defined(DISABLE_WASM_STARBOARD)
// std::move(uint8_t[]) issue
std::unique_ptr<wasm::WasmCode> wasm_code = native_module->AddCode(
result.func_index, result.code_desc, result.frame_slot_count,
result.tagged_parameter_slots, std::move(result.protected_instructions),
std::move(result.source_positions), GetCodeKind(result),
wasm::ExecutionTier::kNone);
wasm::WasmCode* published_code =
native_module->PublishCode(std::move(wasm_code));
isolate->counters()->wasm_generated_code_size()->Increment(
published_code->instructions().length());
isolate->counters()->wasm_reloc_size()->Increment(
published_code->reloc_info().length());
call_target = published_code->instruction_start();
#endif
}
// Update the dispatch table.
Handle<Tuple2> tuple =
isolate->factory()->NewTuple2(instance, callable, AllocationType::kOld);
IndirectFunctionTableEntry(instance, table_index, entry_index)
.Set(sig_id, call_target, *tuple);
}
// static
Handle<WasmExceptionObject> WasmExceptionObject::New(
Isolate* isolate, const wasm::FunctionSig* sig,
Handle<HeapObject> exception_tag) {
Handle<JSFunction> exception_cons(
isolate->native_context()->wasm_exception_constructor(), isolate);
// Serialize the signature.
DCHECK_EQ(0, sig->return_count());
DCHECK_LE(sig->parameter_count(), std::numeric_limits<int>::max());
int sig_size = static_cast<int>(sig->parameter_count());
Handle<PodArray<wasm::ValueType>> serialized_sig =
PodArray<wasm::ValueType>::New(isolate, sig_size, AllocationType::kOld);
int index = 0; // Index into the {PodArray} above.
for (wasm::ValueType param : sig->parameters()) {
serialized_sig->set(index++, param);
}
Handle<JSObject> exception_object =
isolate->factory()->NewJSObject(exception_cons, AllocationType::kOld);
Handle<WasmExceptionObject> exception =
Handle<WasmExceptionObject>::cast(exception_object);
exception->set_serialized_signature(*serialized_sig);
exception->set_exception_tag(*exception_tag);
return exception;
}
bool WasmExceptionObject::IsSignatureEqual(const wasm::FunctionSig* sig) {
DCHECK_EQ(0, sig->return_count());
DCHECK_LE(sig->parameter_count(), std::numeric_limits<int>::max());
int sig_size = static_cast<int>(sig->parameter_count());
if (sig_size != serialized_signature().length()) return false;
for (int index = 0; index < sig_size; ++index) {
if (sig->GetParam(index) != serialized_signature().get(index)) {
return false;
}
}
return true;
}
bool WasmCapiFunction::IsSignatureEqual(const wasm::FunctionSig* sig) const {
// TODO(jkummerow): Unify with "SignatureHelper" in c-api.cc.
int param_count = static_cast<int>(sig->parameter_count());
int result_count = static_cast<int>(sig->return_count());
PodArray<wasm::ValueType> serialized_sig =
shared().wasm_capi_function_data().serialized_signature();
if (param_count + result_count + 1 != serialized_sig.length()) return false;
int serialized_index = 0;
for (int i = 0; i < result_count; i++, serialized_index++) {
if (sig->GetReturn(i) != serialized_sig.get(serialized_index)) {
return false;
}
}
if (serialized_sig.get(serialized_index) != wasm::kWasmStmt) return false;
serialized_index++;
for (int i = 0; i < param_count; i++, serialized_index++) {
if (sig->GetParam(i) != serialized_sig.get(serialized_index)) return false;
}
return true;
}
// static
Handle<JSReceiver> WasmExceptionPackage::New(
Isolate* isolate, Handle<WasmExceptionTag> exception_tag, int size) {
Handle<Object> exception = isolate->factory()->NewWasmRuntimeError(
MessageTemplate::kWasmExceptionError);
CHECK(!Object::SetProperty(isolate, exception,
isolate->factory()->wasm_exception_tag_symbol(),
exception_tag, StoreOrigin::kMaybeKeyed,
Just(ShouldThrow::kThrowOnError))
.is_null());
Handle<FixedArray> values = isolate->factory()->NewFixedArray(size);
CHECK(!Object::SetProperty(isolate, exception,
isolate->factory()->wasm_exception_values_symbol(),
values, StoreOrigin::kMaybeKeyed,
Just(ShouldThrow::kThrowOnError))
.is_null());
return Handle<JSReceiver>::cast(exception);
}
// static
Handle<Object> WasmExceptionPackage::GetExceptionTag(
Isolate* isolate, Handle<Object> exception_object) {
if (exception_object->IsJSReceiver()) {
Handle<JSReceiver> exception = Handle<JSReceiver>::cast(exception_object);
Handle<Object> tag;
if (JSReceiver::GetProperty(isolate, exception,
isolate->factory()->wasm_exception_tag_symbol())
.ToHandle(&tag)) {
return tag;
}
}
return ReadOnlyRoots(isolate).undefined_value_handle();
}
// static
Handle<Object> WasmExceptionPackage::GetExceptionValues(
Isolate* isolate, Handle<Object> exception_object) {
if (exception_object->IsJSReceiver()) {
Handle<JSReceiver> exception = Handle<JSReceiver>::cast(exception_object);
Handle<Object> values;
if (JSReceiver::GetProperty(
isolate, exception,
isolate->factory()->wasm_exception_values_symbol())
.ToHandle(&values)) {
DCHECK(values->IsFixedArray());
return values;
}
}
return ReadOnlyRoots(isolate).undefined_value_handle();
}
#ifdef DEBUG
namespace {
constexpr uint32_t kBytesPerExceptionValuesArrayElement = 2;
size_t ComputeEncodedElementSize(wasm::ValueType type) {
size_t byte_size =
static_cast<size_t>(wasm::ValueTypes::ElementSizeInBytes(type));
DCHECK_EQ(byte_size % kBytesPerExceptionValuesArrayElement, 0);
DCHECK_LE(1, byte_size / kBytesPerExceptionValuesArrayElement);
return byte_size / kBytesPerExceptionValuesArrayElement;
}
} // namespace
#endif // DEBUG
// static
uint32_t WasmExceptionPackage::GetEncodedSize(
const wasm::WasmException* exception) {
const wasm::WasmExceptionSig* sig = exception->sig;
uint32_t encoded_size = 0;
for (size_t i = 0; i < sig->parameter_count(); ++i) {
switch (sig->GetParam(i)) {
case wasm::kWasmI32:
case wasm::kWasmF32:
DCHECK_EQ(2, ComputeEncodedElementSize(sig->GetParam(i)));
encoded_size += 2;
break;
case wasm::kWasmI64:
case wasm::kWasmF64:
DCHECK_EQ(4, ComputeEncodedElementSize(sig->GetParam(i)));
encoded_size += 4;
break;
case wasm::kWasmS128:
DCHECK_EQ(8, ComputeEncodedElementSize(sig->GetParam(i)));
encoded_size += 8;
break;
case wasm::kWasmAnyRef:
case wasm::kWasmFuncRef:
case wasm::kWasmExnRef:
encoded_size += 1;
break;
default:
UNREACHABLE();
}
}
return encoded_size;
}
bool WasmExportedFunction::IsWasmExportedFunction(Object object) {
if (!object.IsJSFunction()) return false;
JSFunction js_function = JSFunction::cast(object);
if (Code::JS_TO_WASM_FUNCTION != js_function.code().kind()) return false;
DCHECK(js_function.shared().HasWasmExportedFunctionData());
return true;
}
bool WasmCapiFunction::IsWasmCapiFunction(Object object) {
if (!object.IsJSFunction()) return false;
JSFunction js_function = JSFunction::cast(object);
// TODO(jkummerow): Enable this when there is a JavaScript wrapper
// able to call this function.
// if (js_function->code()->kind() != Code::WASM_TO_CAPI_FUNCTION) {
// return false;
// }
// DCHECK(js_function->shared()->HasWasmCapiFunctionData());
// return true;
return js_function.shared().HasWasmCapiFunctionData();
}
Handle<WasmCapiFunction> WasmCapiFunction::New(
Isolate* isolate, Address call_target, void* embedder_data,
Handle<PodArray<wasm::ValueType>> serialized_signature) {
Handle<WasmCapiFunctionData> fun_data =
Handle<WasmCapiFunctionData>::cast(isolate->factory()->NewStruct(
WASM_CAPI_FUNCTION_DATA_TYPE, AllocationType::kOld));
fun_data->set_call_target(call_target);
fun_data->set_embedder_data(embedder_data);
fun_data->set_serialized_signature(*serialized_signature);
// TODO(jkummerow): Install a JavaScript wrapper. For now, calling
// these functions directly is unsupported; they can only be called
// from Wasm code.
fun_data->set_wrapper_code(isolate->builtins()->builtin(Builtins::kIllegal));
Handle<SharedFunctionInfo> shared =
isolate->factory()->NewSharedFunctionInfoForWasmCapiFunction(fun_data);
return Handle<WasmCapiFunction>::cast(
isolate->factory()->NewFunctionFromSharedFunctionInfo(
shared, isolate->native_context()));
}
WasmInstanceObject WasmExportedFunction::instance() {
return shared().wasm_exported_function_data().instance();
}
int WasmExportedFunction::function_index() {
return shared().wasm_exported_function_data().function_index();
}
Handle<WasmExportedFunction> WasmExportedFunction::New(
Isolate* isolate, Handle<WasmInstanceObject> instance, int func_index,
int arity, Handle<Code> export_wrapper) {
DCHECK_EQ(Code::JS_TO_WASM_FUNCTION, export_wrapper->kind());
int num_imported_functions = instance->module()->num_imported_functions;
int jump_table_offset = -1;
if (func_index >= num_imported_functions) {
uint32_t jump_table_diff =
instance->module_object().native_module()->GetJumpTableOffset(
func_index);
DCHECK_GE(kMaxInt, jump_table_diff);
jump_table_offset = static_cast<int>(jump_table_diff);
}
Handle<WasmExportedFunctionData> function_data =
Handle<WasmExportedFunctionData>::cast(isolate->factory()->NewStruct(
WASM_EXPORTED_FUNCTION_DATA_TYPE, AllocationType::kOld));
function_data->set_wrapper_code(*export_wrapper);
function_data->set_instance(*instance);
function_data->set_jump_table_offset(jump_table_offset);
function_data->set_function_index(func_index);
function_data->set_c_wrapper_code(Smi::zero(), SKIP_WRITE_BARRIER);
function_data->set_wasm_call_target(Smi::zero(), SKIP_WRITE_BARRIER);
function_data->set_packed_args_size(0);
MaybeHandle<String> maybe_name;
bool is_asm_js_module = instance->module_object().is_asm_js();
if (is_asm_js_module) {
// We can use the function name only for asm.js. For WebAssembly, the
// function name is specified as the function_index.toString().
maybe_name = WasmModuleObject::GetFunctionNameOrNull(
isolate, handle(instance->module_object(), isolate), func_index);
}
Handle<String> name;
if (!maybe_name.ToHandle(&name)) {
EmbeddedVector<char, 16> buffer;
int length = SNPrintF(buffer, "%d", func_index);
name = isolate->factory()
->NewStringFromOneByte(
Vector<uint8_t>::cast(buffer.SubVector(0, length)))
.ToHandleChecked();
}
Handle<Map> function_map;
switch (instance->module()->origin) {
case wasm::kWasmOrigin:
function_map = isolate->wasm_exported_function_map();
break;
case wasm::kAsmJsSloppyOrigin:
function_map = isolate->sloppy_function_map();
break;
case wasm::kAsmJsStrictOrigin:
function_map = isolate->strict_function_map();
break;
}
NewFunctionArgs args =
NewFunctionArgs::ForWasm(name, function_data, function_map);
Handle<JSFunction> js_function = isolate->factory()->NewFunction(args);
// According to the spec, exported functions should not have a [[Construct]]
// method. This does not apply to functions exported from asm.js however.
DCHECK_EQ(is_asm_js_module, js_function->IsConstructor());
js_function->shared().set_length(arity);
js_function->shared().set_internal_formal_parameter_count(arity);
return Handle<WasmExportedFunction>::cast(js_function);
}
Address WasmExportedFunction::GetWasmCallTarget() {
return instance().GetCallTarget(function_index());
}
wasm::FunctionSig* WasmExportedFunction::sig() {
return instance().module()->functions[function_index()].sig;
}
// static
bool WasmJSFunction::IsWasmJSFunction(Object object) {
if (!object.IsJSFunction()) return false;
JSFunction js_function = JSFunction::cast(object);
return js_function.shared().HasWasmJSFunctionData();
}
Handle<WasmJSFunction> WasmJSFunction::New(Isolate* isolate,
wasm::FunctionSig* sig,
Handle<JSReceiver> callable) {
DCHECK_LE(sig->all().size(), kMaxInt);
int sig_size = static_cast<int>(sig->all().size());
int return_count = static_cast<int>(sig->return_count());
int parameter_count = static_cast<int>(sig->parameter_count());
Handle<PodArray<wasm::ValueType>> serialized_sig =
PodArray<wasm::ValueType>::New(isolate, sig_size, AllocationType::kOld);
if (sig_size > 0) {
serialized_sig->copy_in(0, sig->all().begin(), sig_size);
}
Handle<WasmJSFunctionData> function_data =
Handle<WasmJSFunctionData>::cast(isolate->factory()->NewStruct(
WASM_JS_FUNCTION_DATA_TYPE, AllocationType::kOld));
function_data->set_serialized_return_count(return_count);
function_data->set_serialized_parameter_count(parameter_count);
function_data->set_serialized_signature(*serialized_sig);
function_data->set_callable(*callable);
// TODO(7742): Make this callable by using a proper wrapper code.
function_data->set_wrapper_code(
isolate->builtins()->builtin(Builtins::kIllegal));
Handle<String> name = isolate->factory()->Function_string();
if (callable->IsJSFunction()) {
name = JSFunction::GetName(Handle<JSFunction>::cast(callable));
}
Handle<Map> function_map = isolate->wasm_exported_function_map();
NewFunctionArgs args =
NewFunctionArgs::ForWasm(name, function_data, function_map);
Handle<JSFunction> js_function = isolate->factory()->NewFunction(args);
return Handle<WasmJSFunction>::cast(js_function);
}
JSReceiver WasmJSFunction::GetCallable() const {
return shared().wasm_js_function_data().callable();
}
wasm::FunctionSig* WasmJSFunction::GetSignature(Zone* zone) {
WasmJSFunctionData function_data = shared().wasm_js_function_data();
int sig_size = function_data.serialized_signature().length();
wasm::ValueType* types = zone->NewArray<wasm::ValueType>(sig_size);
if (sig_size > 0) {
function_data.serialized_signature().copy_out(0, types, sig_size);
}
int return_count = function_data.serialized_return_count();
int parameter_count = function_data.serialized_parameter_count();
return new (zone) wasm::FunctionSig(return_count, parameter_count, types);
}
bool WasmJSFunction::MatchesSignature(wasm::FunctionSig* sig) {
DCHECK_LE(sig->all().size(), kMaxInt);
int sig_size = static_cast<int>(sig->all().size());
int return_count = static_cast<int>(sig->return_count());
int parameter_count = static_cast<int>(sig->parameter_count());
WasmJSFunctionData function_data = shared().wasm_js_function_data();
if (return_count != function_data.serialized_return_count() ||
parameter_count != function_data.serialized_parameter_count()) {
return false;
}
if (sig_size == 0) return true; // Prevent undefined behavior.
const wasm::ValueType* expected = sig->all().begin();
return function_data.serialized_signature().matches(expected, sig_size);
}
Address WasmCapiFunction::GetHostCallTarget() const {
return shared().wasm_capi_function_data().call_target();
}
PodArray<wasm::ValueType> WasmCapiFunction::GetSerializedSignature() const {
return shared().wasm_capi_function_data().serialized_signature();
}
Handle<WasmExceptionTag> WasmExceptionTag::New(Isolate* isolate, int index) {
Handle<WasmExceptionTag> result =
Handle<WasmExceptionTag>::cast(isolate->factory()->NewStruct(
WASM_EXCEPTION_TAG_TYPE, AllocationType::kOld));
result->set_index(index);
return result;
}
Handle<AsmWasmData> AsmWasmData::New(
Isolate* isolate, std::shared_ptr<wasm::NativeModule> native_module,
Handle<FixedArray> export_wrappers, Handle<ByteArray> asm_js_offset_table,
Handle<HeapNumber> uses_bitset) {
const WasmModule* module = native_module->module();
size_t memory_estimate =
wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module) +
wasm::WasmCodeManager::EstimateNativeModuleNonCodeSize(module);
Handle<Managed<wasm::NativeModule>> managed_native_module =
Managed<wasm::NativeModule>::FromSharedPtr(isolate, memory_estimate,
std::move(native_module));
Handle<AsmWasmData> result = Handle<AsmWasmData>::cast(
isolate->factory()->NewStruct(ASM_WASM_DATA_TYPE, AllocationType::kOld));
result->set_managed_native_module(*managed_native_module);
result->set_export_wrappers(*export_wrappers);
result->set_asm_js_offset_table(*asm_js_offset_table);
result->set_uses_bitset(*uses_bitset);
return result;
}
#undef TRACE
#undef TRACE_IFT
} // namespace internal
} // namespace v8