blob: 3c84f7b796025d5c0bd7a1129fd1a70838f2caaf [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"
#include "Language/WebAssemblySource.h"
namespace Js
{
Var WebAssembly::EntryCompile(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
ScriptContext* scriptContext = function->GetScriptContext();
JavascriptLibrary* library = scriptContext->GetLibrary();
Assert(!(callInfo.Flags & CallFlags_New));
try
{
if (args.Info.Count < 2)
{
JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedBufferSource);
}
// Check to see if it was a response object
Var responsePromise = TryResolveResponse(function, args[0], args[1]);
if (responsePromise)
{
// Once we've resolved everything, come back here and create the module
return JavascriptPromise::CreateThenPromise((JavascriptPromise*)responsePromise, library->GetWebAssemblyCompileFunction(), library->GetThrowerFunction(), scriptContext);
}
WebAssemblySource src(args[1], true, scriptContext);
WebAssemblyModule* wasmModule = WebAssemblyModule::CreateModule(scriptContext, &src);
return JavascriptPromise::CreateResolvedPromise(wasmModule, scriptContext);
}
catch (JavascriptException & e)
{
return JavascriptPromise::CreateRejectedPromise(e.GetAndClear()->GetThrownObject(scriptContext), scriptContext);
}
}
Var WebAssembly::EntryInstantiate(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
ScriptContext* scriptContext = function->GetScriptContext();
JavascriptLibrary* library = scriptContext->GetLibrary();
Assert(!(callInfo.Flags & CallFlags_New));
Var resultObject = nullptr;
try
{
if (args.Info.Count < 2)
{
JavascriptError::ThrowTypeError(scriptContext, WASMERR_InvalidInstantiateArgument);
}
Var importObject = scriptContext->GetLibrary()->GetUndefined();
if (args.Info.Count >= 3)
{
importObject = args[2];
}
// Check to see if it was a response object
Var responsePromise = TryResolveResponse(function, args[0], args[1]);
if (responsePromise)
{
// Since instantiate takes extra arguments, we have to create a bound function to carry the importsObject until the response is resolved
// Because function::bind() binds arguments from the left first, we have to calback a different function to reverse the order of the arguments
Var boundArgs[] = { library->GetWebAssemblyInstantiateBoundFunction(), args[0], importObject };
CallInfo boundCallInfo(CallFlags_Value, 3);
ArgumentReader myargs(&boundCallInfo, boundArgs);
RecyclableObject* boundFunction = BoundFunction::New(scriptContext, myargs);
return JavascriptPromise::CreateThenPromise((JavascriptPromise*)responsePromise, boundFunction, library->GetThrowerFunction(), scriptContext);
}
if (WebAssemblyModule::Is(args[1]))
{
resultObject = WebAssemblyInstance::CreateInstance(WebAssemblyModule::FromVar(args[1]), importObject);
}
else
{
WebAssemblySource src(args[1], true, scriptContext);
WebAssemblyModule* wasmModule = WebAssemblyModule::CreateModule(scriptContext, &src);
WebAssemblyInstance* instance = WebAssemblyInstance::CreateInstance(wasmModule, importObject);
resultObject = JavascriptOperators::NewJavascriptObjectNoArg(scriptContext);
JavascriptOperators::OP_SetProperty(resultObject, PropertyIds::module, wasmModule, scriptContext);
JavascriptOperators::OP_SetProperty(resultObject, PropertyIds::instance, instance, scriptContext);
}
return JavascriptPromise::CreateResolvedPromise(resultObject, scriptContext);
}
catch (JavascriptException & e)
{
return JavascriptPromise::CreateRejectedPromise(e.GetAndClear()->GetThrownObject(scriptContext), scriptContext);
}
}
Var WebAssembly::EntryInstantiateBound(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
Assert(!(callInfo.Flags & CallFlags_New));
Var thisVar = args[0];
Var importObj = callInfo.Count > 1 ? args[1] : function->GetScriptContext()->GetLibrary()->GetUndefined();
Var bufferSrc = callInfo.Count > 2 ? args[2] : function->GetScriptContext()->GetLibrary()->GetUndefined();
return EntryInstantiate(function, CallInfo(CallFlags_Value, 3), thisVar, bufferSrc, importObj);
}
Var WebAssembly::EntryValidate(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count < 2)
{
JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedBufferSource);
}
WebAssemblySource src(args[1], false, scriptContext);
if (WebAssemblyModule::ValidateModule(scriptContext, &src))
{
return scriptContext->GetLibrary()->GetTrue();
}
else
{
return scriptContext->GetLibrary()->GetFalse();
}
}
Var WebAssembly::EntryQueryResponse(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
// Make sure this is a Response object
if (args.Info.Count < 2 || !IsResponseObject(args[1], scriptContext))
{
JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedResponse);
}
Var responseObject = args[1];
// Get the arrayBuffer method from the object
PropertyString* propStr = scriptContext->GetPropertyString(PropertyIds::arrayBuffer);
Var arrayBufferProp = JavascriptOperators::OP_GetElementI(responseObject, propStr, scriptContext);
// Call res.arrayBuffer()
if (!JavascriptConversion::IsCallable(arrayBufferProp))
{
JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedResponse);
}
RecyclableObject* arrayBufferFunc = RecyclableObject::FromVar(arrayBufferProp);
Var arrayBufferRes = CALL_FUNCTION(scriptContext->GetThreadContext(), arrayBufferFunc, Js::CallInfo(CallFlags_Value, 1), responseObject);
// Make sure res.arrayBuffer() is a Promise
if (!JavascriptPromise::Is(arrayBufferRes))
{
JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedResponse);
}
return arrayBufferRes;
}
bool WebAssembly::IsResponseObject(Var responseObject, ScriptContext* scriptContext)
{
if (!RecyclableObject::Is(responseObject))
{
return false;
}
TypeId typeId = RecyclableObject::FromVar(responseObject)->GetTypeId();
if (!CONFIG_FLAG(WasmIgnoreResponse))
{
return scriptContext->IsWellKnownHostType<WellKnownHostType_Response>(typeId) && typeId != TypeIds_Undefined;
}
// Consider all object as Response objects under -wasmIgnoreResponse
return typeId == TypeIds_Object;
}
Var WebAssembly::TryResolveResponse(RecyclableObject* function, Var thisArg, Var responseArg)
{
ScriptContext* scriptContext = function->GetScriptContext();
JavascriptLibrary* library = scriptContext->GetLibrary();
Var responsePromise = nullptr;
bool isResponse = IsResponseObject(responseArg, scriptContext);
if (isResponse)
{
CallInfo newCallInfo;
newCallInfo.Count = 2;
// We already have a response object, query it now
responsePromise = EntryQueryResponse(function, Js::CallInfo(CallFlags_Value, 2), thisArg, responseArg);
}
else if (JavascriptPromise::Is(responseArg))
{
JavascriptPromise* promise = (JavascriptPromise*)responseArg;
// Wait until this promise resolves and then try to query the response object (if it's a response object)
responsePromise = JavascriptPromise::CreateThenPromise(promise, library->GetWebAssemblyQueryResponseFunction(), library->GetThrowerFunction(), scriptContext);
}
if (responsePromise && !JavascriptPromise::Is(responsePromise))
{
AssertMsg(UNREACHED, "How did we end up with something other than a promise here ?");
JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedResponse);
}
return responsePromise;
}
uint32
WebAssembly::ToNonWrappingUint32(Var val, ScriptContext * ctx)
{
double i = JavascriptConversion::ToInteger(val, ctx);
if (i < 0 || i > (double)UINT32_MAX)
{
JavascriptError::ThrowRangeError(ctx, JSERR_ArgumentOutOfRange);
}
return (uint32)i;
}
void
WebAssembly::CheckSignature(ScriptContext * scriptContext, Wasm::WasmSignature * sig1, Wasm::WasmSignature * sig2)
{
if (!sig1->IsEquivalent(sig2))
{
JavascriptError::ThrowWebAssemblyRuntimeError(scriptContext, WASMERR_SignatureMismatch);
}
}
uint
WebAssembly::GetSignatureSize()
{
return sizeof(Wasm::WasmSignature);
}
}
#endif // ENABLE_WASM