WebAssemblyModuleRecord.cpp [plain text]
#include "config.h"
#include "WebAssemblyModuleRecord.h"
#if ENABLE(WEBASSEMBLY)
#include "Error.h"
#include "JSCInlines.h"
#include "JSLexicalEnvironment.h"
#include "JSModuleEnvironment.h"
#include "JSWebAssemblyHelpers.h"
#include "JSWebAssemblyInstance.h"
#include "JSWebAssemblyLinkError.h"
#include "JSWebAssemblyModule.h"
#include "ProtoCallFrame.h"
#include "WasmSignature.h"
#include "WebAssemblyFunction.h"
#include <limits>
namespace JSC {
const ClassInfo WebAssemblyModuleRecord::s_info = { "WebAssemblyModuleRecord", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(WebAssemblyModuleRecord) };
Structure* WebAssemblyModuleRecord::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
}
WebAssemblyModuleRecord* WebAssemblyModuleRecord::create(ExecState* exec, VM& vm, Structure* structure, const Identifier& moduleKey, const Wasm::ModuleInformation& moduleInformation)
{
WebAssemblyModuleRecord* instance = new (NotNull, allocateCell<WebAssemblyModuleRecord>(vm.heap)) WebAssemblyModuleRecord(vm, structure, moduleKey);
instance->finishCreation(exec, vm, moduleInformation);
return instance;
}
WebAssemblyModuleRecord::WebAssemblyModuleRecord(VM& vm, Structure* structure, const Identifier& moduleKey)
: Base(vm, structure, moduleKey)
{
}
void WebAssemblyModuleRecord::destroy(JSCell* cell)
{
WebAssemblyModuleRecord* thisObject = static_cast<WebAssemblyModuleRecord*>(cell);
thisObject->WebAssemblyModuleRecord::~WebAssemblyModuleRecord();
}
void WebAssemblyModuleRecord::finishCreation(ExecState* exec, VM& vm, const Wasm::ModuleInformation& moduleInformation)
{
Base::finishCreation(exec, vm);
ASSERT(inherits(vm, info()));
for (const auto& exp : moduleInformation.exports) {
Identifier field = Identifier::fromString(&vm, String::fromUTF8(exp.field));
addExportEntry(ExportEntry::createLocal(field, field));
}
}
void WebAssemblyModuleRecord::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
WebAssemblyModuleRecord* thisObject = jsCast<WebAssemblyModuleRecord*>(cell);
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_instance);
visitor.append(thisObject->m_startFunction);
}
void WebAssemblyModuleRecord::prepareLink(VM& vm, JSWebAssemblyInstance* instance)
{
RELEASE_ASSERT(!m_instance);
m_instance.set(vm, this, instance);
}
void WebAssemblyModuleRecord::link(ExecState* exec, JSValue, JSObject* importObject, Wasm::CreationMode creationMode)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(scope);
auto* globalObject = exec->lexicalGlobalObject();
RELEASE_ASSERT(m_instance);
Wasm::CodeBlock* codeBlock = m_instance->instance().codeBlock();
JSWebAssemblyModule* module = m_instance->module();
const Wasm::ModuleInformation& moduleInformation = module->moduleInformation();
auto exception = [&] (JSObject* error) {
throwException(exec, scope, error);
};
auto importFailMessage = [&] (const Wasm::Import& import, const char* before, const char* after) {
return makeString(before, " ", String::fromUTF8(import.module), ":", String::fromUTF8(import.field), " ", after);
};
bool hasTableImport = false;
for (const auto& import : moduleInformation.imports) {
switch (import.kind) {
case Wasm::ExternalKind::Function:
case Wasm::ExternalKind::Global:
case Wasm::ExternalKind::Table:
break;
case Wasm::ExternalKind::Memory:
continue;
}
Identifier moduleName = Identifier::fromString(&vm, String::fromUTF8(import.module));
Identifier fieldName = Identifier::fromString(&vm, String::fromUTF8(import.field));
JSValue value;
if (creationMode == Wasm::CreationMode::FromJS) {
JSValue importModuleValue = importObject->get(exec, moduleName);
RETURN_IF_EXCEPTION(scope, void());
if (!importModuleValue.isObject())
return exception(createTypeError(exec, importFailMessage(import, "import", "must be an object"), defaultSourceAppender, runtimeTypeForValue(vm, importModuleValue)));
JSObject* object = jsCast<JSObject*>(importModuleValue);
value = object->get(exec, fieldName);
RETURN_IF_EXCEPTION(scope, void());
} else {
AbstractModuleRecord* importedModule = hostResolveImportedModule(exec, moduleName);
RETURN_IF_EXCEPTION(scope, void());
Resolution resolution = importedModule->resolveExport(exec, fieldName);
RETURN_IF_EXCEPTION(scope, void());
switch (resolution.type) {
case Resolution::Type::NotFound:
throwSyntaxError(exec, scope, makeString("Importing binding name '", String(fieldName.impl()), "' is not found."));
return;
case Resolution::Type::Ambiguous:
throwSyntaxError(exec, scope, makeString("Importing binding name '", String(fieldName.impl()), "' cannot be resolved due to ambiguous multiple bindings."));
return;
case Resolution::Type::Error:
throwSyntaxError(exec, scope, makeString("Importing binding name 'default' cannot be resolved by star export entries."));
return;
case Resolution::Type::Resolved:
break;
}
AbstractModuleRecord* importedRecord = resolution.moduleRecord;
JSModuleEnvironment* importedEnvironment = importedRecord->moduleEnvironmentMayBeNull();
if (importedEnvironment) {
SymbolTable* symbolTable = importedEnvironment->symbolTable();
ConcurrentJSLocker locker(symbolTable->m_lock);
auto iter = symbolTable->find(locker, resolution.localName.impl());
ASSERT(iter != symbolTable->end(locker));
SymbolTableEntry& entry = iter->value;
ASSERT(!entry.isNull());
ASSERT(importedEnvironment->isValidScopeOffset(entry.scopeOffset()));
value = importedEnvironment->variableAt(entry.scopeOffset()).get();
}
}
if (!value)
value = jsUndefined();
switch (import.kind) {
case Wasm::ExternalKind::Function: {
if (!value.isFunction(vm))
return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "import function", "must be callable")));
Wasm::Instance* calleeInstance = nullptr;
WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = nullptr;
JSObject* function = jsCast<JSObject*>(value);
WebAssemblyFunction* wasmFunction;
WebAssemblyWrapperFunction* wasmWrapperFunction;
if (isWebAssemblyHostFunction(vm, function, wasmFunction, wasmWrapperFunction)) {
Wasm::SignatureIndex importedSignatureIndex;
if (wasmFunction) {
importedSignatureIndex = wasmFunction->signatureIndex();
calleeInstance = &wasmFunction->instance()->instance();
entrypointLoadLocation = wasmFunction->entrypointLoadLocation();
} else {
importedSignatureIndex = wasmWrapperFunction->signatureIndex();
function = wasmWrapperFunction->function();
}
Wasm::SignatureIndex expectedSignatureIndex = moduleInformation.importFunctionSignatureIndices[import.kindIndex];
if (importedSignatureIndex != expectedSignatureIndex)
return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported function", "signature doesn't match the provided WebAssembly function's signature")));
}
auto* info = m_instance->instance().importFunctionInfo(import.kindIndex);
info->targetInstance = calleeInstance;
info->wasmEntrypointLoadLocation = entrypointLoadLocation;
m_instance->instance().importFunction<JSWebAssemblyInstance::PoisonedBarrier<JSObject>>(import.kindIndex)->set(vm, m_instance.get(), function);
break;
}
case Wasm::ExternalKind::Global: {
ASSERT(moduleInformation.globals[import.kindIndex].mutability == Wasm::Global::Immutable);
if (moduleInformation.globals[import.kindIndex].type == Wasm::I64)
return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported global", "cannot be an i64")));
if (!value.isNumber())
return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported global", "must be a number")));
switch (moduleInformation.globals[import.kindIndex].type) {
case Wasm::I32:
m_instance->instance().setGlobal(import.kindIndex, value.toInt32(exec));
break;
case Wasm::F32:
m_instance->instance().setGlobal(import.kindIndex, bitwise_cast<uint32_t>(value.toFloat(exec)));
break;
case Wasm::F64:
m_instance->instance().setGlobal(import.kindIndex, bitwise_cast<uint64_t>(value.asNumber()));
break;
default:
RELEASE_ASSERT_NOT_REACHED();
}
scope.assertNoException();
break;
}
case Wasm::ExternalKind::Table: {
RELEASE_ASSERT(!hasTableImport); hasTableImport = true;
JSWebAssemblyTable* table = jsDynamicCast<JSWebAssemblyTable*>(vm, value);
if (!table)
return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Table import", "is not an instance of WebAssembly.Table")));
uint32_t expectedInitial = moduleInformation.tableInformation.initial();
uint32_t actualInitial = table->length();
if (actualInitial < expectedInitial)
return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Table import", "provided an 'initial' that is too small")));
if (std::optional<uint32_t> expectedMaximum = moduleInformation.tableInformation.maximum()) {
std::optional<uint32_t> actualMaximum = table->maximum();
if (!actualMaximum)
return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Table import", "does not have a 'maximum' but the module requires that it does")));
if (*actualMaximum > *expectedMaximum)
return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Imported Table", "'maximum' is larger than the module's expected 'maximum'")));
}
m_instance->setTable(vm, table);
RETURN_IF_EXCEPTION(scope, void());
break;
}
case Wasm::ExternalKind::Memory:
break;
}
}
{
if (!!moduleInformation.tableInformation && moduleInformation.tableInformation.isImport()) {
RELEASE_ASSERT(hasTableImport);
}
if (!!moduleInformation.tableInformation && !hasTableImport) {
RELEASE_ASSERT(!moduleInformation.tableInformation.isImport());
RefPtr<Wasm::Table> wasmTable = Wasm::Table::create(moduleInformation.tableInformation.initial(), moduleInformation.tableInformation.maximum());
if (!wasmTable)
return exception(createJSWebAssemblyLinkError(exec, vm, "couldn't create Table"));
JSWebAssemblyTable* table = JSWebAssemblyTable::create(exec, vm, globalObject->WebAssemblyTableStructure(), wasmTable.releaseNonNull());
scope.assertNoException();
ASSERT(table);
m_instance->setTable(vm, table);
RETURN_IF_EXCEPTION(scope, void());
}
}
{
for (size_t globalIndex = moduleInformation.firstInternalGlobal; globalIndex < moduleInformation.globals.size(); ++globalIndex) {
const auto& global = moduleInformation.globals[globalIndex];
ASSERT(global.initializationType != Wasm::Global::IsImport);
if (global.initializationType == Wasm::Global::FromGlobalImport) {
ASSERT(global.initialBitsOrImportNumber < moduleInformation.firstInternalGlobal);
m_instance->instance().setGlobal(globalIndex, m_instance->instance().loadI64Global(global.initialBitsOrImportNumber));
} else
m_instance->instance().setGlobal(globalIndex, global.initialBitsOrImportNumber);
}
}
SymbolTable* exportSymbolTable = module->exportSymbolTable();
unsigned functionImportCount = codeBlock->functionImportCount();
JSModuleEnvironment* moduleEnvironment = JSModuleEnvironment::create(vm, globalObject, nullptr, exportSymbolTable, JSValue(), this);
for (const auto& exp : moduleInformation.exports) {
JSValue exportedValue;
switch (exp.kind) {
case Wasm::ExternalKind::Function: {
if (exp.kindIndex < functionImportCount) {
unsigned functionIndex = exp.kindIndex;
JSObject* functionImport = m_instance->instance().importFunction<JSWebAssemblyInstance::PoisonedBarrier<JSObject>>(functionIndex)->get();
if (isWebAssemblyHostFunction(vm, functionImport))
exportedValue = functionImport;
else {
Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(functionIndex);
exportedValue = WebAssemblyWrapperFunction::create(vm, globalObject, functionImport, functionIndex, m_instance.get(), signatureIndex);
}
} else {
Wasm::Callee& embedderEntrypointCallee = codeBlock->embedderEntrypointCalleeFromFunctionIndexSpace(exp.kindIndex);
Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = codeBlock->entrypointLoadLocationFromFunctionIndexSpace(exp.kindIndex);
Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(exp.kindIndex);
const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature.argumentCount(), String::fromUTF8(exp.field), m_instance.get(), embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
exportedValue = function;
}
break;
}
case Wasm::ExternalKind::Table: {
RELEASE_ASSERT(m_instance->table());
ASSERT(exp.kindIndex == 0);
exportedValue = m_instance->table();
break;
}
case Wasm::ExternalKind::Memory: {
ASSERT(exp.kindIndex == 0);
exportedValue = m_instance->memory();
break;
}
case Wasm::ExternalKind::Global: {
const Wasm::Global& global = moduleInformation.globals[exp.kindIndex];
ASSERT(global.mutability == Wasm::Global::Immutable);
switch (global.type) {
case Wasm::I32:
exportedValue = JSValue(m_instance->instance().loadI32Global(exp.kindIndex));
break;
case Wasm::I64:
throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, "exported global cannot be an i64"_s));
return;
case Wasm::F32:
exportedValue = JSValue(m_instance->instance().loadF32Global(exp.kindIndex));
break;
case Wasm::F64:
exportedValue = JSValue(m_instance->instance().loadF64Global(exp.kindIndex));
break;
default:
RELEASE_ASSERT_NOT_REACHED();
}
break;
}
}
bool shouldThrowReadOnlyError = false;
bool ignoreReadOnlyErrors = true;
bool putResult = false;
symbolTablePutTouchWatchpointSet(moduleEnvironment, exec, Identifier::fromString(&vm, String::fromUTF8(exp.field)), exportedValue, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
scope.assertNoException();
RELEASE_ASSERT(putResult);
}
bool hasStart = !!moduleInformation.startFunctionIndexSpace;
if (hasStart) {
auto startFunctionIndexSpace = moduleInformation.startFunctionIndexSpace.value_or(0);
Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(startFunctionIndexSpace);
const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
ASSERT(!signature.argumentCount());
ASSERT(signature.returnType() == Wasm::Void);
if (startFunctionIndexSpace < codeBlock->functionImportCount()) {
JSObject* startFunction = m_instance->instance().importFunction<JSWebAssemblyInstance::PoisonedBarrier<JSObject>>(startFunctionIndexSpace)->get();
m_startFunction.set(vm, this, startFunction);
} else {
Wasm::Callee& embedderEntrypointCallee = codeBlock->embedderEntrypointCalleeFromFunctionIndexSpace(startFunctionIndexSpace);
Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = codeBlock->entrypointLoadLocationFromFunctionIndexSpace(startFunctionIndexSpace);
WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature.argumentCount(), "start", m_instance.get(), embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
m_startFunction.set(vm, this, function);
}
}
m_moduleEnvironment.set(vm, this, moduleEnvironment);
}
template <typename Scope, typename M, typename N, typename ...Args>
NEVER_INLINE static JSValue dataSegmentFail(ExecState* exec, VM& vm, Scope& scope, M memorySize, N segmentSize, N offset, Args... args)
{
return throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, makeString("Invalid data segment initialization: segment of "_s, String::number(segmentSize), " bytes memory of "_s, String::number(memorySize), " bytes, at offset "_s, String::number(offset), args...)));
}
JSValue WebAssemblyModuleRecord::evaluate(ExecState* exec)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Wasm::Module& module = m_instance->instance().module();
Wasm::CodeBlock* codeBlock = m_instance->instance().codeBlock();
const Wasm::ModuleInformation& moduleInformation = module.moduleInformation();
JSWebAssemblyTable* table = m_instance->table();
const Vector<Wasm::Segment::Ptr>& data = moduleInformation.data;
std::optional<JSValue> exception;
auto forEachElement = [&] (auto fn) {
for (const Wasm::Element& element : moduleInformation.elements) {
ASSERT(!!table);
if (!element.functionIndices.size())
continue;
uint32_t tableIndex = element.offset.isGlobalImport()
? static_cast<uint32_t>(m_instance->instance().loadI32Global(element.offset.globalImportIndex()))
: element.offset.constValue();
fn(element, tableIndex);
if (exception)
break;
}
};
auto forEachSegment = [&] (auto fn) {
uint8_t* memory = reinterpret_cast<uint8_t*>(m_instance->instance().cachedMemory());
uint64_t sizeInBytes = m_instance->instance().cachedMemorySize();
for (const Wasm::Segment::Ptr& segment : data) {
uint32_t offset = segment->offset.isGlobalImport()
? static_cast<uint32_t>(m_instance->instance().loadI32Global(segment->offset.globalImportIndex()))
: segment->offset.constValue();
fn(memory, sizeInBytes, segment, offset);
if (exception)
break;
}
};
forEachElement([&] (const Wasm::Element& element, uint32_t tableIndex) {
uint64_t lastWrittenIndex = static_cast<uint64_t>(tableIndex) + static_cast<uint64_t>(element.functionIndices.size()) - 1;
if (UNLIKELY(lastWrittenIndex >= table->length()))
exception = JSValue(throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, "Element is trying to set an out of bounds table index"_s)));
});
if (UNLIKELY(exception))
return exception.value();
forEachSegment([&] (uint8_t*, uint64_t sizeInBytes, const Wasm::Segment::Ptr& segment, uint32_t offset) {
if (UNLIKELY(sizeInBytes < segment->sizeInBytes))
exception = dataSegmentFail(exec, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ", segment is too big"_s);
else if (UNLIKELY(offset > sizeInBytes - segment->sizeInBytes))
exception = dataSegmentFail(exec, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ", segment writes outside of memory"_s);
});
if (UNLIKELY(exception))
return exception.value();
JSGlobalObject* globalObject = m_instance->globalObject(vm);
forEachElement([&] (const Wasm::Element& element, uint32_t tableIndex) {
for (uint32_t i = 0; i < element.functionIndices.size(); ++i) {
uint32_t functionIndex = element.functionIndices[i];
Wasm::SignatureIndex signatureIndex = module.signatureIndexFromFunctionIndexSpace(functionIndex);
if (functionIndex < codeBlock->functionImportCount()) {
JSObject* functionImport = m_instance->instance().importFunction<JSWebAssemblyInstance::PoisonedBarrier<JSObject>>(functionIndex)->get();
if (isWebAssemblyHostFunction(vm, functionImport)) {
WebAssemblyFunction* wasmFunction = jsDynamicCast<WebAssemblyFunction*>(vm, functionImport);
RELEASE_ASSERT(wasmFunction);
table->setFunction(vm, tableIndex, wasmFunction);
++tableIndex;
continue;
}
table->setFunction(vm, tableIndex,
WebAssemblyWrapperFunction::create(vm, globalObject, functionImport, functionIndex, m_instance.get(), signatureIndex));
++tableIndex;
continue;
}
Wasm::Callee& embedderEntrypointCallee = codeBlock->embedderEntrypointCalleeFromFunctionIndexSpace(functionIndex);
Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = codeBlock->entrypointLoadLocationFromFunctionIndexSpace(functionIndex);
const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
WebAssemblyFunction* function = WebAssemblyFunction::create(
vm, globalObject, signature.argumentCount(), String(), m_instance.get(), embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
table->setFunction(vm, tableIndex, function);
++tableIndex;
}
});
ASSERT(!exception);
forEachSegment([&] (uint8_t* memory, uint64_t, const Wasm::Segment::Ptr& segment, uint32_t offset) {
if (segment->sizeInBytes) {
RELEASE_ASSERT(memory);
memcpy(memory + offset, &segment->byte(0), segment->sizeInBytes);
}
});
ASSERT(!exception);
if (JSObject* startFunction = m_startFunction.get()) {
CallData callData;
CallType callType = JSC::getCallData(vm, startFunction, callData);
call(exec, startFunction, callType, callData, jsUndefined(), *vm.emptyList);
RETURN_IF_EXCEPTION(scope, { });
}
return jsUndefined();
}
}
#endif // ENABLE(WEBASSEMBLY)