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::link(ExecState* exec, JSWebAssemblyModule* module, JSWebAssemblyInstance* instance)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(scope);
auto* globalObject = exec->lexicalGlobalObject();
JSWebAssemblyCodeBlock* codeBlock = instance->codeBlock();
const Wasm::ModuleInformation& moduleInformation = module->moduleInformation();
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 = instance->importFunction(functionIndex);
if (isWebAssemblyHostFunction(vm, functionImport))
exportedValue = functionImport;
else {
Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(functionIndex);
exportedValue = WebAssemblyWrapperFunction::create(vm, globalObject, functionImport, functionIndex, instance, signatureIndex);
}
} else {
Wasm::Callee& jsEntrypointCallee = codeBlock->jsEntrypointCalleeFromFunctionIndexSpace(exp.kindIndex);
Wasm::WasmEntrypointLoadLocation wasmEntrypointLoadLocation = codeBlock->wasmEntrypointLoadLocationFromFunctionIndexSpace(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), instance, jsEntrypointCallee, wasmEntrypointLoadLocation, signatureIndex);
exportedValue = function;
}
break;
}
case Wasm::ExternalKind::Table: {
RELEASE_ASSERT(instance->table());
ASSERT(exp.kindIndex == 0);
exportedValue = instance->table();
break;
}
case Wasm::ExternalKind::Memory: {
ASSERT(exp.kindIndex == 0);
exportedValue = 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(instance->loadI32Global(exp.kindIndex));
break;
case Wasm::I64:
throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("exported global cannot be an i64")));
return;
case Wasm::F32:
exportedValue = JSValue(instance->loadF32Global(exp.kindIndex));
break;
case Wasm::F64:
exportedValue = JSValue(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);
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 = instance->importFunction(startFunctionIndexSpace);
m_startFunction.set(vm, this, startFunction);
} else {
Wasm::Callee& jsEntrypointCallee = codeBlock->jsEntrypointCalleeFromFunctionIndexSpace(startFunctionIndexSpace);
Wasm::WasmEntrypointLoadLocation wasmEntrypointLoadLocation = codeBlock->wasmEntrypointLoadLocationFromFunctionIndexSpace(startFunctionIndexSpace);
WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature.argumentCount(), "start", instance, jsEntrypointCallee, wasmEntrypointLoadLocation, signatureIndex);
m_startFunction.set(vm, this, function);
}
}
RELEASE_ASSERT(!m_instance);
m_instance.set(vm, this, instance);
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(ASCIILiteral("Invalid data segment initialization: segment of "), String::number(segmentSize), ASCIILiteral(" bytes memory of "), String::number(memorySize), ASCIILiteral(" bytes, at offset "), String::number(offset), args...)));
}
JSValue WebAssemblyModuleRecord::evaluate(ExecState* exec)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSWebAssemblyModule* module = m_instance->module();
JSWebAssemblyCodeBlock* codeBlock = m_instance->codeBlock();
const Wasm::ModuleInformation& moduleInformation = module->moduleInformation();
JSWebAssemblyTable* table = m_instance->table();
const Vector<Wasm::Segment::Ptr>& data = m_instance->module()->moduleInformation().data;
JSWebAssemblyMemory* jsMemory = m_instance->memory();
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->loadI32Global(element.offset.globalImportIndex()))
: element.offset.constValue();
fn(element, tableIndex);
if (exception)
break;
}
};
auto forEachSegment = [&] (auto fn) {
uint8_t* memory = reinterpret_cast<uint8_t*>(jsMemory->memory().memory());
uint64_t sizeInBytes = jsMemory->memory().size();
for (const Wasm::Segment::Ptr& segment : data) {
uint32_t offset = segment->offset.isGlobalImport()
? static_cast<uint32_t>(m_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->size()))
exception = JSValue(throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Element is trying to set an out of bounds table index"))));
});
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, ASCIILiteral(", segment is too big"));
else if (UNLIKELY(offset > sizeInBytes - segment->sizeInBytes))
exception = dataSegmentFail(exec, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ASCIILiteral(", segment writes outside of memory"));
});
if (UNLIKELY(exception))
return exception.value();
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 = jsCast<JSObject*>(m_instance->importFunction(functionIndex));
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, m_instance->globalObject(), functionImport, functionIndex, m_instance.get(), signatureIndex));
++tableIndex;
continue;
}
Wasm::Callee& jsEntrypointCallee = codeBlock->jsEntrypointCalleeFromFunctionIndexSpace(functionIndex);
Wasm::WasmEntrypointLoadLocation wasmEntrypointLoadLocation = codeBlock->wasmEntrypointLoadLocationFromFunctionIndexSpace(functionIndex);
const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
WebAssemblyFunction* function = WebAssemblyFunction::create(
vm, m_instance->globalObject(), signature.argumentCount(), String(), m_instance.get(), jsEntrypointCallee, wasmEntrypointLoadLocation, 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(startFunction, callData);
call(exec, startFunction, callType, callData, jsUndefined(), exec->emptyList());
RETURN_IF_EXCEPTION(scope, { });
}
return jsUndefined();
}
}
#endif // ENABLE(WEBASSEMBLY)