blob: fde8a6958de3503ab27c90898400b246956953bb [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
#ifdef ENABLE_WASM
#include "../WasmReader/WasmReaderPch.h"
namespace Js
{
Var GetImportVariable(Wasm::WasmImport* wi, ScriptContext* ctx, Var ffi)
{
PropertyRecord const * modPropertyRecord = nullptr;
const char16* modName = wi->modName;
uint32 modNameLen = wi->modNameLen;
ctx->GetOrAddPropertyRecord(modName, modNameLen, &modPropertyRecord);
Var modProp = JavascriptOperators::OP_GetProperty(ffi, modPropertyRecord->GetPropertyId(), ctx);
const char16* name = wi->importName;
uint32 nameLen = wi->importNameLen;
PropertyRecord const * propertyRecord = nullptr;
ctx->GetOrAddPropertyRecord(name, nameLen, &propertyRecord);
if (!RecyclableObject::Is(modProp))
{
JavascriptError::ThrowTypeError(ctx, WASMERR_InvalidImport);
}
return JavascriptOperators::OP_GetProperty(modProp, propertyRecord->GetPropertyId(), ctx);
}
WebAssemblyInstance::WebAssemblyInstance(WebAssemblyModule * wasmModule, DynamicType * type) :
DynamicObject(type),
m_module(wasmModule),
m_exports(nullptr)
{
}
/* static */
bool
WebAssemblyInstance::Is(Var value)
{
return JavascriptOperators::GetTypeId(value) == TypeIds_WebAssemblyInstance;
}
/* static */
WebAssemblyInstance *
WebAssemblyInstance::FromVar(Var value)
{
Assert(WebAssemblyInstance::Is(value));
return static_cast<WebAssemblyInstance*>(value);
}
// Implements "new WebAssembly.Instance(moduleObject [, importObject])" as described here:
// https://github.com/WebAssembly/design/blob/master/JS.md#webassemblyinstance-constructor
Var
WebAssemblyInstance::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0];
bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget);
Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr);
if (!(callInfo.Flags & CallFlags_New) || (newTarget && JavascriptOperators::IsUndefinedObject(newTarget)))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_ClassConstructorCannotBeCalledWithoutNew, _u("WebAssembly.Instance"));
}
if (args.Info.Count < 2 || !WebAssemblyModule::Is(args[1]))
{
JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedModule);
}
WebAssemblyModule * module = WebAssemblyModule::FromVar(args[1]);
Var importObject = scriptContext->GetLibrary()->GetUndefined();
if (args.Info.Count >= 3)
{
importObject = args[2];
}
return CreateInstance(module, importObject);
}
Var
WebAssemblyInstance::GetterExports(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0 || !WebAssemblyInstance::Is(args[0]))
{
JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedInstanceObject);
}
WebAssemblyInstance* instance = WebAssemblyInstance::FromVar(args[0]);
Js::Var exports = instance->m_exports;
if (!exports || !DynamicObject::Is(exports))
{
Assert(UNREACHED);
exports = scriptContext->GetLibrary()->GetUndefined();
}
return exports;
}
WebAssemblyInstance *
WebAssemblyInstance::CreateInstance(WebAssemblyModule * module, Var importObject)
{
if (!JavascriptOperators::IsUndefined(importObject) && !JavascriptOperators::IsObject(importObject))
{
JavascriptError::ThrowTypeError(module->GetScriptContext(), JSERR_NeedObject);
}
if (module->GetImportCount() > 0 && !JavascriptOperators::IsObject(importObject))
{
JavascriptError::ThrowTypeError(module->GetScriptContext(), JSERR_NeedObject);
}
ScriptContext * scriptContext = module->GetScriptContext();
WebAssemblyEnvironment environment(module);
WebAssemblyInstance * newInstance = RecyclerNewZ(scriptContext->GetRecycler(), WebAssemblyInstance, module, scriptContext->GetLibrary()->GetWebAssemblyInstanceType());
try
{
LoadImports(module, scriptContext, importObject, &environment);
LoadGlobals(module, scriptContext, &environment);
LoadFunctions(module, scriptContext, &environment);
ValidateTableAndMemory(module, scriptContext, &environment);
try
{
LoadDataSegs(module, scriptContext, &environment);
LoadIndirectFunctionTable(module, scriptContext, &environment);
}
catch (...)
{
AssertMsg(UNREACHED, "By spec, we should not have any exceptions possible here");
throw;
}
newInstance->m_exports = BuildObject(module, scriptContext, &environment);
}
catch (Wasm::WasmCompilationException& e)
{
JavascriptError::ThrowWebAssemblyLinkErrorVar(scriptContext, WASMERR_WasmLinkError, e.GetTempErrorMessageRef());
}
uint32 startFuncIdx = module->GetStartFunction();
if (startFuncIdx != Js::Constants::UninitializedValue)
{
AsmJsScriptFunction* start = environment.GetWasmFunction(startFuncIdx);
Js::CallInfo info(Js::CallFlags_New, 1);
Js::Arguments startArg(info, (Var*)&start);
Js::JavascriptFunction::CallFunction<true>(start, start->GetEntryPoint(), startArg);
}
return newInstance;
}
void WebAssemblyInstance::LoadFunctions(WebAssemblyModule * wasmModule, ScriptContext* ctx, WebAssemblyEnvironment* env)
{
FrameDisplay * frameDisplay = RecyclerNewPlus(ctx->GetRecycler(), sizeof(void*), FrameDisplay, 1);
frameDisplay->SetItem(0, env->GetStartPtr());
for (uint i = 0; i < wasmModule->GetWasmFunctionCount(); ++i)
{
if (i < wasmModule->GetImportedFunctionCount() && env->GetWasmFunction(i) != nullptr)
{
continue;
}
Wasm::WasmFunctionInfo* wasmFuncInfo = wasmModule->GetWasmFunctionInfo(i);
FunctionBody* body = wasmFuncInfo->GetBody();
AsmJsScriptFunction * funcObj = ctx->GetLibrary()->CreateAsmJsScriptFunction(body);
funcObj->SetModuleMemory((Field(Var)*)env->GetStartPtr());
funcObj->SetSignature(body->GetAsmJsFunctionInfo()->GetWasmSignature());
funcObj->SetEnvironment(frameDisplay);
// Todo:: need to fix issue #2452 before we can do this,
// otherwise we'll change the type of the functions and cause multiple instance to not share jitted code
//Wasm::WasmSignature* sig = wasmFuncInfo->GetSignature();
//funcObj->SetPropertyWithAttributes(PropertyIds::length, JavascriptNumber::ToVar(sig->GetParamCount(), ctx), PropertyNone, nullptr);
//funcObj->SetPropertyWithAttributes(PropertyIds::name, JavascriptConversion::ToString(JavascriptNumber::ToVar(i, ctx), ctx), PropertyNone, nullptr);
env->SetWasmFunction(i, funcObj);
if (!PHASE_OFF(WasmDeferredPhase, body))
{
// if we still have WasmReaderInfo we haven't yet parsed
if (body->GetAsmJsFunctionInfo()->GetWasmReaderInfo())
{
WasmLibrary::SetWasmEntryPointToInterpreter(funcObj, true);
}
}
else
{
AsmJsFunctionInfo* info = body->GetAsmJsFunctionInfo();
if (info->GetWasmReaderInfo())
{
WasmLibrary::SetWasmEntryPointToInterpreter(funcObj, false);
WAsmJs::JitFunctionIfReady(funcObj);
info->SetWasmReaderInfo(nullptr);
}
}
}
}
void WebAssemblyInstance::LoadDataSegs(WebAssemblyModule * wasmModule, ScriptContext* ctx, WebAssemblyEnvironment* env)
{
WebAssemblyMemory* mem = env->GetMemory(0);
Assert(mem);
ArrayBuffer* buffer = mem->GetBuffer();
for (uint32 iSeg = 0; iSeg < wasmModule->GetDataSegCount(); ++iSeg)
{
Wasm::WasmDataSegment* segment = wasmModule->GetDataSeg(iSeg);
Assert(segment != nullptr);
const uint32 offset = env->GetDataSegmentOffset(iSeg);
const uint32 size = segment->GetSourceSize();
if (size > 0)
{
js_memcpy_s(buffer->GetBuffer() + offset, (uint32)buffer->GetByteLength() - offset, segment->GetData(), size);
}
}
}
Var WebAssemblyInstance::BuildObject(WebAssemblyModule * wasmModule, ScriptContext* scriptContext, WebAssemblyEnvironment* env)
{
Js::Var exportsNamespace = scriptContext->GetLibrary()->CreateObject(scriptContext->GetLibrary()->GetNull());
for (uint32 iExport = 0; iExport < wasmModule->GetExportCount(); ++iExport)
{
Wasm::WasmExport* wasmExport = wasmModule->GetExport(iExport);
Assert(wasmExport);
if (wasmExport)
{
PropertyRecord const * propertyRecord = nullptr;
scriptContext->GetOrAddPropertyRecord(wasmExport->name, wasmExport->nameLength, &propertyRecord);
Var obj = scriptContext->GetLibrary()->GetUndefined();
switch (wasmExport->kind)
{
case Wasm::ExternalKinds::Table:
obj = env->GetTable(wasmExport->index);
break;
case Wasm::ExternalKinds::Memory:
obj = env->GetMemory(wasmExport->index);
break;
case Wasm::ExternalKinds::Function:
obj = env->GetWasmFunction(wasmExport->index);
break;
case Wasm::ExternalKinds::Global:
Wasm::WasmGlobal* global = wasmModule->GetGlobal(wasmExport->index);
if (global->IsMutable())
{
JavascriptError::ThrowTypeError(wasmModule->GetScriptContext(), WASMERR_MutableGlobal);
}
Wasm::WasmConstLitNode cnst = env->GetGlobalValue(global);
switch (global->GetType())
{
case Wasm::WasmTypes::I32:
obj = JavascriptNumber::ToVar(cnst.i32, scriptContext);
break;
case Wasm::WasmTypes::F32:
obj = JavascriptNumber::New(cnst.f32, scriptContext);
break;
case Wasm::WasmTypes::F64:
obj = JavascriptNumber::New(cnst.f64, scriptContext);
break;
case Wasm::WasmTypes::I64:
JavascriptError::ThrowTypeError(wasmModule->GetScriptContext(), WASMERR_InvalidTypeConversion);
default:
Assert(UNREACHED);
break;
}
}
JavascriptOperators::OP_SetProperty(exportsNamespace, propertyRecord->GetPropertyId(), obj, scriptContext);
}
}
DynamicObject::FromVar(exportsNamespace)->PreventExtensions();
return exportsNamespace;
}
void WebAssemblyInstance::LoadImports(
WebAssemblyModule * wasmModule,
ScriptContext* ctx,
Var ffi,
WebAssemblyEnvironment* env)
{
const uint32 importCount = wasmModule->GetImportCount();
if (importCount > 0 && (!ffi || !RecyclableObject::Is(ffi)))
{
JavascriptError::ThrowTypeError(ctx, WASMERR_InvalidImport);
}
uint32 counters[Wasm::ExternalKinds::Limit];
memset(counters, 0, sizeof(counters));
for (uint32 i = 0; i < importCount; ++i)
{
Wasm::WasmImport* import = wasmModule->GetImport(i);
Var prop = GetImportVariable(import, ctx, ffi);
uint32& counter = counters[import->kind];
switch (import->kind)
{
case Wasm::ExternalKinds::Function:
{
if (!JavascriptFunction::Is(prop))
{
JavascriptError::ThrowWebAssemblyLinkError(ctx, JSERR_Property_NeedFunction);
}
Assert(counter < wasmModule->GetImportedFunctionCount());
Assert(wasmModule->GetFunctionIndexType(counter) == Wasm::FunctionIndexTypes::ImportThunk);
env->SetImportedFunction(counter, prop);
if (AsmJsScriptFunction::IsWasmScriptFunction(prop))
{
Assert(env->GetWasmFunction(counter) == nullptr);
AsmJsScriptFunction* func = AsmJsScriptFunction::FromVar(prop);
if (!wasmModule->GetWasmFunctionInfo(counter)->GetSignature()->IsEquivalent(func->GetSignature()))
{
char16 temp[2048] = { 0 };
char16 importargs[512] = { 0 };
wasmModule->GetWasmFunctionInfo(counter)->GetSignature()->WriteSignatureToString(importargs, 512);
char16 exportargs[512] = { 0 };
func->GetSignature()->WriteSignatureToString(exportargs, 512);
_snwprintf_s(temp, 2048, _TRUNCATE, _u("%ls%ls to %ls%ls"), func->GetDisplayName()->GetString(), exportargs, import->importName, importargs);
// this makes a copy of the error message buffer, so it's fine to not worry about clean-up
JavascriptError::ThrowWebAssemblyLinkError(ctx, WASMERR_LinkSignatureMismatch, temp);
}
// Imported Wasm functions can be called directly
env->SetWasmFunction(counter, func);
}
break;
}
case Wasm::ExternalKinds::Memory:
{
Assert(wasmModule->HasMemoryImport());
if (wasmModule->HasMemoryImport())
{
if (!WebAssemblyMemory::Is(prop))
{
JavascriptError::ThrowWebAssemblyLinkError(ctx, WASMERR_NeedMemoryObject);
}
WebAssemblyMemory * mem = WebAssemblyMemory::FromVar(prop);
if (mem->GetInitialLength() < wasmModule->GetMemoryInitSize())
{
throw Wasm::WasmCompilationException(_u("Imported memory initial size (%u) is smaller than declared (%u)"), mem->GetInitialLength(), wasmModule->GetMemoryInitSize());
}
if (mem->GetMaximumLength() > wasmModule->GetMemoryMaxSize())
{
throw Wasm::WasmCompilationException(_u("Imported memory maximum size (%u) is larger than declared (%u)"), mem->GetMaximumLength(), wasmModule->GetMemoryMaxSize());
}
env->SetMemory(counter, mem);
}
break;
}
case Wasm::ExternalKinds::Table:
{
Assert(wasmModule->HasTableImport());
if (wasmModule->HasTableImport())
{
if (!WebAssemblyTable::Is(prop))
{
JavascriptError::ThrowWebAssemblyLinkError(ctx, WASMERR_NeedTableObject);
}
WebAssemblyTable * table = WebAssemblyTable::FromVar(prop);
if (!wasmModule->IsValidTableImport(table))
{
JavascriptError::ThrowWebAssemblyLinkError(ctx, WASMERR_NeedTableObject);
}
env->SetTable(counter, table);
}
break;
}
case Wasm::ExternalKinds::Global:
{
Wasm::WasmGlobal* global = wasmModule->GetGlobal(counter);
if (global->IsMutable() || (!JavascriptNumber::Is(prop) && !TaggedInt::Is(prop)))
{
JavascriptError::ThrowWebAssemblyLinkError(ctx, WASMERR_InvalidImport);
}
Assert(global->GetReferenceType() == Wasm::GlobalReferenceTypes::ImportedReference);
Wasm::WasmConstLitNode cnst = {0};
switch (global->GetType())
{
case Wasm::WasmTypes::I32: cnst.i32 = JavascriptConversion::ToInt32(prop, ctx); break;
case Wasm::WasmTypes::F32: cnst.f32 = (float)JavascriptConversion::ToNumber(prop, ctx); break;
case Wasm::WasmTypes::F64: cnst.f64 = JavascriptConversion::ToNumber(prop, ctx); break;
case Wasm::WasmTypes::I64: Js::JavascriptError::ThrowTypeError(ctx, WASMERR_InvalidTypeConversion);
default:
Js::Throw::InternalError();
}
env->SetGlobalValue(global, cnst);
break;
}
default:
Js::Throw::InternalError();
}
++counter;
}
}
void WebAssemblyInstance::LoadGlobals(WebAssemblyModule * wasmModule, ScriptContext* ctx, WebAssemblyEnvironment* env)
{
uint count = wasmModule->GetGlobalCount();
for (uint i = 0; i < count; i++)
{
Wasm::WasmGlobal* global = wasmModule->GetGlobal(i);
Wasm::WasmConstLitNode cnst = {};
if (global->GetReferenceType() == Wasm::GlobalReferenceTypes::ImportedReference)
{
// the value should already be resolved
continue;
}
if (global->GetReferenceType() == Wasm::GlobalReferenceTypes::LocalReference)
{
Wasm::WasmGlobal* sourceGlobal = wasmModule->GetGlobal(global->GetGlobalIndexInit());
if (sourceGlobal->GetReferenceType() != Wasm::GlobalReferenceTypes::Const &&
sourceGlobal->GetReferenceType() != Wasm::GlobalReferenceTypes::ImportedReference)
{
JavascriptError::ThrowTypeError(ctx, WASMERR_InvalidGlobalRef);
}
if (sourceGlobal->GetType() != global->GetType())
{
JavascriptError::ThrowTypeError(ctx, WASMERR_InvalidTypeConversion);
}
cnst = env->GetGlobalValue(sourceGlobal);
}
else
{
cnst = global->GetConstInit();
}
env->SetGlobalValue(global, cnst);
}
}
void WebAssemblyInstance::LoadIndirectFunctionTable(WebAssemblyModule * wasmModule, ScriptContext* ctx, WebAssemblyEnvironment* env)
{
WebAssemblyTable* table = env->GetTable(0);
Assert(table != nullptr);
for (uint elementsIndex = 0; elementsIndex < wasmModule->GetElementSegCount(); ++elementsIndex)
{
Wasm::WasmElementSegment* eSeg = wasmModule->GetElementSeg(elementsIndex);
if (eSeg->GetNumElements() > 0)
{
uint offset = env->GetElementSegmentOffset(elementsIndex);
for (uint segIndex = 0; segIndex < eSeg->GetNumElements(); ++segIndex)
{
uint funcIndex = eSeg->GetElement(segIndex);
Var funcObj = env->GetWasmFunction(funcIndex);
table->DirectSetValue(segIndex + offset, funcObj);
}
}
}
}
void WebAssemblyInstance::ValidateTableAndMemory(WebAssemblyModule * wasmModule, ScriptContext* ctx, WebAssemblyEnvironment* env)
{
WebAssemblyTable* table = env->GetTable(0);
if (wasmModule->HasTableImport())
{
if (table == nullptr)
{
JavascriptError::ThrowWebAssemblyLinkError(ctx, WASMERR_NeedTableObject);
}
}
else
{
table = wasmModule->CreateTable();
env->SetTable(0, table);
}
WebAssemblyMemory* mem = env->GetMemory(0);
if (wasmModule->HasMemoryImport())
{
if (mem == nullptr)
{
JavascriptError::ThrowWebAssemblyLinkError(ctx, WASMERR_NeedMemoryObject);
}
}
else
{
mem = wasmModule->CreateMemory();
if (mem == nullptr)
{
JavascriptError::ThrowWebAssemblyLinkError(ctx, WASMERR_MemoryCreateFailed);
}
env->SetMemory(0, mem);
}
ArrayBuffer * buffer = mem->GetBuffer();
if (buffer->IsDetached())
{
JavascriptError::ThrowTypeError(wasmModule->GetScriptContext(), JSERR_DetachedTypedArray);
}
env->CalculateOffsets(table, mem);
}
} // namespace Js
#endif // ENABLE_WASM