blob: 67b41be3b54638e9070190db99ade78b678df472 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
// Portions of this file are copyright 2014 Mozilla Foundation, available under the Apache 2.0 license.
//-------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------
// Copyright 2014 Mozilla Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http ://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLanguagePch.h"
#ifdef ASMJS_PLAT
#include "ByteCode/Symbol.h"
#include "ByteCode/FuncInfo.h"
#include "ByteCode/ByteCodeWriter.h"
#include "ByteCode/ByteCodeGenerator.h"
namespace Js
{
bool
AsmJSCompiler::CheckIdentifier(AsmJsModuleCompiler &m, ParseNode *usepn, PropertyName name)
{
if (name == m.GetParser()->names()->arguments || name == m.GetParser()->names()->eval)
{
return m.FailName(usepn, _u("'%s' is not an allowed identifier"), name);
}
return true;
}
bool
AsmJSCompiler::CheckModuleLevelName(AsmJsModuleCompiler &m, ParseNode *usepn, PropertyName name)
{
if (!CheckIdentifier(m, usepn, name))
{
return false;
}
if (name == m.GetModuleFunctionName())
{
return m.FailName(usepn, _u("duplicate name '%s' not allowed"), name);
}
//Check for all the duplicates here.
return true;
}
bool
AsmJSCompiler::CheckFunctionHead(AsmJsModuleCompiler &m, ParseNode *fn, bool isGlobal /*= true*/)
{
PnFnc fnc = fn->sxFnc;
if (fnc.HasNonSimpleParameterList())
{
return m.Fail(fn, _u("default & rest args not allowed"));
}
if (fnc.IsStaticMember())
{
return m.Fail(fn, _u("static functions are not allowed"));
}
if (fnc.IsGenerator())
{
return m.Fail(fn, _u("generator functions are not allowed"));
}
if (fnc.IsAsync())
{
return m.Fail(fn, _u("async functions are not allowed"));
}
if (fnc.IsLambda())
{
return m.Fail(fn, _u("lambda functions are not allowed"));
}
if (!isGlobal && fnc.nestedCount != 0)
{
return m.Fail(fn, _u("closure functions are not allowed"));
}
return true;
}
bool AsmJSCompiler::CheckTypeAnnotation( AsmJsModuleCompiler &m, ParseNode *coercionNode, AsmJSCoercion *coercion,
ParseNode **coercedExpr /*= nullptr */)
{
switch( coercionNode->nop )
{
case knopRsh:
case knopLsh:
case knopXor:
case knopAnd:
case knopOr: {
ParseNode *rhs = ParserWrapper::GetBinaryRight( coercionNode );
*coercion = AsmJS_ToInt32;
if( coercedExpr )
{
if( rhs->nop == knopInt && rhs->sxInt.lw == 0 )
{
if( rhs->nop == knopAnd )
{
// X & 0 == 0;
*coercedExpr = rhs;
}
else
{
// (X|0) == (X^0) == (X<<0) == (X>>0) == X
*coercedExpr = ParserWrapper::GetBinaryLeft( coercionNode );
}
}
else
{
*coercedExpr = coercionNode;
}
}
return true;
}
case knopPos: {
*coercion = AsmJS_ToNumber;
if( coercedExpr )
{
*coercedExpr = ParserWrapper::GetUnaryNode( coercionNode );
}
return true;
}
case knopCall: {
ParseNode* target;
AsmJsFunctionDeclaration* sym;
AsmJsSIMDFunction* simdSym;
target = coercionNode->sxCall.pnodeTarget;
if (!target || target->nop != knopName)
{
return m.Fail(coercionNode, _u("Call must be of the form id(...)"));
}
simdSym = m.LookupSimdTypeCheck(target->name());
// var x = f4.check(ffi.field)
if (simdSym)
{
if (coercionNode->sxCall.argCount == simdSym->GetArgCount())
{
switch (simdSym->GetSimdBuiltInFunction())
{
case AsmJsSIMDBuiltin_int32x4_check:
*coercion = AsmJS_Int32x4;
break;
case AsmJsSIMDBuiltin_bool32x4_check:
*coercion = AsmJS_Bool32x4;
break;
case AsmJsSIMDBuiltin_bool16x8_check:
*coercion = AsmJS_Bool16x8;
break;
case AsmJsSIMDBuiltin_bool8x16_check:
*coercion = AsmJS_Bool8x16;
break;
case AsmJsSIMDBuiltin_float32x4_check:
*coercion = AsmJS_Float32x4;
break;
case AsmJsSIMDBuiltin_float64x2_check:
*coercion = AsmJS_Float64x2;
break;
case AsmJsSIMDBuiltin_int16x8_check:
*coercion = AsmJS_Int16x8;
break;
case AsmJsSIMDBuiltin_int8x16_check:
*coercion = AsmJS_Int8x16;
break;
case AsmJsSIMDBuiltin_uint32x4_check:
*coercion = AsmJS_Uint32x4;
break;
case AsmJsSIMDBuiltin_uint16x8_check:
*coercion = AsmJS_Uint16x8;
break;
case AsmJsSIMDBuiltin_uint8x16_check:
*coercion = AsmJS_Uint8x16;
break;
default:
Assert(UNREACHED);
}
if (coercedExpr)
{
*coercedExpr = coercionNode->sxCall.pnodeArgs;
}
return true;
}
else
{
return m.Fail(coercionNode, _u("Invalid SIMD coercion"));
}
}
// not a SIMD coercion, fall through
*coercion = AsmJS_FRound;
sym = m.LookupFunction(target->name());
if (!AsmJsMathFunction::IsFround(sym))
{
return m.Fail( coercionNode, _u("call must be to fround coercion") );
}
if( coercedExpr )
{
*coercedExpr = coercionNode->sxCall.pnodeArgs;
}
return true;
}
case knopInt:{
*coercion = AsmJS_ToInt32;
if( coercedExpr )
{
*coercedExpr = coercionNode;
}
return true;
}
case knopFlt:{
if (ParserWrapper::IsMinInt(coercionNode))
{
*coercion = AsmJS_ToInt32;
}
else if (coercionNode->sxFlt.maybeInt)
{
return m.Fail(coercionNode, _u("Integer literal in return must be in range [-2^31, 2^31)"));
}
else
{
*coercion = AsmJS_ToNumber;
}
if( coercedExpr )
{
*coercedExpr = coercionNode ;
}
return true;
}
case knopName:{
// in this case we are returning a constant var from the global scope
AsmJsSymbol * constSymSource = m.LookupIdentifier(coercionNode->name());
if (!constSymSource)
{
return m.Fail(coercionNode, _u("Identifier not globally declared"));
}
AsmJsVar * constSrc = constSymSource->Cast<AsmJsVar>();
if (constSymSource->GetSymbolType() != AsmJsSymbol::Variable || constSrc->isMutable())
{
return m.Fail(coercionNode, _u("Unannotated variables must be constant"));
}
if (constSrc->GetType().isSigned())
{
*coercion = AsmJS_ToInt32;
}
else if (constSrc->GetType().isDouble())
{
*coercion = AsmJS_ToNumber;
}
else
{
Assert(constSrc->GetType().isFloat());
*coercion = AsmJS_FRound;
}
if (coercedExpr)
{
*coercedExpr = coercionNode;
}
return true;
}
default:;
}
return m.Fail( coercionNode, _u("must be of the form +x, fround(x) or x|0") );
}
bool
AsmJSCompiler::CheckModuleArgument(AsmJsModuleCompiler &m, ParseNode *arg, PropertyName *name, AsmJsModuleArg::ArgType type)
{
if (!ParserWrapper::IsDefinition(arg))
{
return m.Fail(arg, _u("duplicate argument name not allowed"));
}
if (!CheckIdentifier(m, arg, arg->name()))
{
return false;
}
*name = arg->name();
m.GetByteCodeGenerator()->AssignPropertyId(*name);
AsmJsModuleArg * moduleArg = Anew(m.GetAllocator(), AsmJsModuleArg, *name, type);
if (!m.DefineIdentifier(*name, moduleArg))
{
return m.Fail(arg, _u("duplicate argument name not allowed"));
}
if (!CheckModuleLevelName(m, arg, *name))
{
return false;
}
return true;
}
bool
AsmJSCompiler::CheckModuleArguments(AsmJsModuleCompiler &m, ParseNode *fn)
{
ArgSlot numFormals = 0;
ParseNode *arg1 = ParserWrapper::FunctionArgsList( fn, numFormals );
ParseNode *arg2 = arg1 ? ParserWrapper::NextVar( arg1 ) : nullptr;
ParseNode *arg3 = arg2 ? ParserWrapper::NextVar( arg2 ) : nullptr;
if (numFormals > 3)
{
return m.Fail(fn, _u("asm.js modules takes at most 3 argument"));
}
PropertyName arg1Name = nullptr;
if (numFormals >= 1 && !CheckModuleArgument(m, arg1, &arg1Name, AsmJsModuleArg::ArgType::StdLib))
{
return false;
}
m.InitStdLibArgName(arg1Name);
PropertyName arg2Name = nullptr;
if (numFormals >= 2 && !CheckModuleArgument(m, arg2, &arg2Name, AsmJsModuleArg::ArgType::Import))
{
return false;
}
m.InitForeignArgName(arg2Name);
PropertyName arg3Name = nullptr;
if (numFormals >= 3 && !CheckModuleArgument(m, arg3, &arg3Name, AsmJsModuleArg::ArgType::Heap))
{
return false;
}
m.InitBufferArgName(arg3Name);
return true;
}
bool AsmJSCompiler::CheckGlobalVariableImportExpr( AsmJsModuleCompiler &m, PropertyName varName, AsmJSCoercion coercion, ParseNode *coercedExpr )
{
if( !ParserWrapper::IsDotMember(coercedExpr) )
{
return m.FailName( coercedExpr, _u("invalid import expression for global '%s'"), varName );
}
ParseNode *base = ParserWrapper::DotBase(coercedExpr);
PropertyName field = ParserWrapper::DotMember(coercedExpr);
PropertyName importName = m.GetForeignArgName();
if (!importName || !field)
{
return m.Fail(coercedExpr, _u("cannot import without an asm.js foreign parameter"));
}
m.GetByteCodeGenerator()->AssignPropertyId(field);
if ((base->name() != importName))
{
return m.FailName(coercedExpr, _u("base of import expression must be '%s'"), importName);
}
return m.AddGlobalVarImport(varName, field, coercion);
}
bool AsmJSCompiler::CheckGlobalVariableInitImport( AsmJsModuleCompiler &m, PropertyName varName, ParseNode *initNode, bool isMutable /*= true*/)
{
AsmJSCoercion coercion;
ParseNode *coercedExpr;
if( !CheckTypeAnnotation( m, initNode, &coercion, &coercedExpr ) )
{
return false;
}
if ((ParserWrapper::IsFroundNumericLiteral(coercedExpr)) && coercion == AsmJSCoercion::AsmJS_FRound)
{
return m.AddNumericVar(varName, coercedExpr, true, isMutable);
}
return CheckGlobalVariableImportExpr( m, varName, coercion, coercedExpr );
}
bool AsmJSCompiler::CheckNewArrayView( AsmJsModuleCompiler &m, PropertyName varName, ParseNode *newExpr )
{
Assert( newExpr->nop == knopNew );
ParseNode *ctorExpr = newExpr->sxCall.pnodeTarget;
ArrayBufferView::ViewType type;
if( ParserWrapper::IsDotMember(ctorExpr) )
{
ParseNode *base = ParserWrapper::DotBase(ctorExpr);
PropertyName globalName = m.GetStdLibArgName();
if (!globalName)
{
return m.Fail(base, _u("cannot create array view without an asm.js global parameter"));
}
if (!ParserWrapper::IsNameDeclaration(base) || base->name() != globalName)
{
return m.FailName(base, _u("expecting '%s.*Array"), globalName);
}
PropertyName fieldName = ParserWrapper::DotMember(ctorExpr);
if (!fieldName)
{
return m.FailName(ctorExpr, _u("Failed to define array view to var %s"), varName);
}
PropertyId field = fieldName->GetPropertyId();
switch (field)
{
case PropertyIds::Int8Array:
type = ArrayBufferView::TYPE_INT8;
m.AddArrayBuiltinUse(AsmJSTypedArrayBuiltin_Int8Array);
break;
case PropertyIds::Uint8Array:
type = ArrayBufferView::TYPE_UINT8;
m.AddArrayBuiltinUse(AsmJSTypedArrayBuiltin_Uint8Array);
break;
case PropertyIds::Int16Array:
type = ArrayBufferView::TYPE_INT16;
m.AddArrayBuiltinUse(AsmJSTypedArrayBuiltin_Int16Array);
break;
case PropertyIds::Uint16Array:
type = ArrayBufferView::TYPE_UINT16;
m.AddArrayBuiltinUse(AsmJSTypedArrayBuiltin_Uint16Array);
break;
case PropertyIds::Int32Array:
type = ArrayBufferView::TYPE_INT32;
m.AddArrayBuiltinUse(AsmJSTypedArrayBuiltin_Int32Array);
break;
case PropertyIds::Uint32Array:
type = ArrayBufferView::TYPE_UINT32;
m.AddArrayBuiltinUse(AsmJSTypedArrayBuiltin_Uint32Array);
break;
case PropertyIds::Float32Array:
type = ArrayBufferView::TYPE_FLOAT32;
m.AddArrayBuiltinUse(AsmJSTypedArrayBuiltin_Float32Array);
break;
case PropertyIds::Float64Array:
type = ArrayBufferView::TYPE_FLOAT64;
m.AddArrayBuiltinUse(AsmJSTypedArrayBuiltin_Float64Array);
break;
default:
return m.Fail(ctorExpr, _u("could not match typed array name"));
break;
}
}
else if (ctorExpr->nop == knopName)
{
AsmJsSymbol * buffFunc = m.LookupIdentifier(ctorExpr->name());
if (!buffFunc || buffFunc->GetSymbolType() != AsmJsSymbol::TypedArrayBuiltinFunction)
{
return m.Fail(ctorExpr, _u("invalid 'new' import"));
}
type = buffFunc->Cast<AsmJsTypedArrayFunction>()->GetViewType();
if (type == ArrayBufferView::TYPE_COUNT)
{
return m.Fail(ctorExpr, _u("could not match typed array name"));
}
}
else
{
return m.Fail(newExpr, _u("invalid 'new' import"));
}
ParseNode *bufArg = newExpr->sxCall.pnodeArgs;
if( !bufArg || !ParserWrapper::IsNameDeclaration( bufArg ) )
{
return m.Fail( ctorExpr, _u("array view constructor takes exactly one argument") );
}
PropertyName bufferName = m.GetBufferArgName();
if( !bufferName )
{
return m.Fail( bufArg, _u("cannot create array view without an asm.js heap parameter") );
}
if( bufferName != bufArg->name() )
{
return m.FailName( bufArg, _u("argument to array view constructor must be '%s'"), bufferName );
}
if( !m.AddArrayView( varName, type ) )
{
return m.FailName( ctorExpr, _u("Failed to define array view to var %s"), varName );
}
return true;
}
bool
AsmJSCompiler::CheckGlobalDotImport(AsmJsModuleCompiler &m, PropertyName varName, ParseNode *initNode)
{
ParseNode *base = ParserWrapper::DotBase(initNode);
PropertyName field = ParserWrapper::DotMember(initNode);
if( !field )
{
return m.Fail( initNode, _u("Global import must be in the form c.x where c is stdlib or foreign and x is a string literal") );
}
m.GetByteCodeGenerator()->AssignPropertyId(field);
PropertyName lib = nullptr;
if (ParserWrapper::IsDotMember(base))
{
lib = ParserWrapper::DotMember(base);
base = ParserWrapper::DotBase(base);
if (m.GetScriptContext()->GetConfig()->IsSimdjsEnabled())
{
if (!lib || (lib->GetPropertyId() != PropertyIds::Math && lib->GetPropertyId() != PropertyIds::SIMD))
{
return m.FailName(initNode, _u("'%s' should be Math or SIMD, as in global.Math.xxxx"), field);
}
}
else
{
if (!lib || lib->GetPropertyId() != PropertyIds::Math)
{
return m.FailName(initNode, _u("'%s' should be Math, as in global.Math.xxxx"), field);
}
}
}
if( ParserWrapper::IsNameDeclaration(base) && base->name() == m.GetStdLibArgName() )
{
if (m.GetScriptContext()->GetConfig()->IsSimdjsEnabled())
{
if (lib && lib->GetPropertyId() == PropertyIds::SIMD)
{
// global.SIMD.xxx
AsmJsSIMDFunction *simdFunc;
if (!m.LookupStdLibSIMDName(field->GetPropertyId(), field, &simdFunc))
{
return m.FailName(initNode, _u("'%s' is not standard SIMD builtin"), varName);
}
if (simdFunc->GetName() != nullptr)
{
OutputMessage(m.GetScriptContext(), DEIT_ASMJS_FAILED, _u("Warning: SIMD Builtin already defined for var %s"), simdFunc->GetName()->Psz());
}
simdFunc->SetName(varName);
if (!m.DefineIdentifier(varName, simdFunc))
{
return m.FailName(initNode, _u("Failed to define SIMD builtin function to var %s"), varName);
}
m.AddSimdBuiltinUse(simdFunc->GetSimdBuiltInFunction());
return true;
}
}
// global.Math.xxx
MathBuiltin mathBuiltin;
if (m.LookupStandardLibraryMathName(field, &mathBuiltin))
{
switch (mathBuiltin.kind)
{
case MathBuiltin::Function:{
auto func = mathBuiltin.u.func;
if (func->GetName() != nullptr)
{
OutputMessage(m.GetScriptContext(), DEIT_ASMJS_FAILED, _u("Warning: Math Builtin already defined for var %s"), func->GetName()->Psz());
}
func->SetName(varName);
if (!m.DefineIdentifier(varName, func))
{
return m.FailName(initNode, _u("Failed to define math builtin function to var %s"), varName);
}
m.AddMathBuiltinUse(func->GetMathBuiltInFunction());
}
break;
case MathBuiltin::Constant:
if (!m.AddNumericConst(varName, mathBuiltin.u.cst))
{
return m.FailName(initNode, _u("Failed to define math constant to var %s"), varName);
}
m.AddMathBuiltinUse(mathBuiltin.mathLibFunctionName);
break;
default:
Assume(UNREACHED);
}
return true;
}
TypedArrayBuiltin arrayBuiltin;
if (m.LookupStandardLibraryArrayName(field, &arrayBuiltin))
{
if (arrayBuiltin.mFunc->GetName() != nullptr)
{
OutputMessage(m.GetScriptContext(), DEIT_ASMJS_FAILED, _u("Warning: Typed array builtin already defined for var %s"), arrayBuiltin.mFunc->GetName()->Psz());
}
arrayBuiltin.mFunc->SetName(varName);
if (!m.DefineIdentifier(varName, arrayBuiltin.mFunc))
{
return m.FailName(initNode, _u("Failed to define typed array builtin function to var %s"), varName);
}
m.AddArrayBuiltinUse(arrayBuiltin.mFunc->GetArrayBuiltInFunction());
return true;
}
return m.FailName(initNode, _u("'%s' is not a standard Math builtin"), field);
}
else if( ParserWrapper::IsNameDeclaration(base) && base->name() == m.GetForeignArgName() )
{
// foreign import
return m.AddModuleFunctionImport( varName, field );
}
else if (ParserWrapper::IsNameDeclaration(base))
{
// Check if SIMD function import
// e.g. var x = f4.add
AsmJsSIMDFunction *simdFunc, *operation;
simdFunc = m.LookupSimdConstructor(base->name());
if (simdFunc == nullptr || !m.LookupStdLibSIMDName(simdFunc->GetSimdBuiltInFunction(), field, &operation))
{
return m.FailName(initNode, _u("Invalid dot expression import. %s is not a standard SIMD operation"), varName);
}
if (operation->GetName() != nullptr)
{
OutputMessage(m.GetScriptContext(), DEIT_ASMJS_FAILED, _u("Warning: SIMD Builtin already defined for var %s"), operation->GetName()->Psz());
}
// bind operation to var
operation->SetName(varName);
if (!m.DefineIdentifier(varName, operation))
{
return m.FailName(initNode, _u("Failed to define SIMD builtin function to var %s"), varName);
}
m.AddSimdBuiltinUse(operation->GetSimdBuiltInFunction());
return true;
}
return m.Fail(initNode, _u("expecting c.y where c is either the global or foreign parameter"));
}
bool
AsmJSCompiler::CheckModuleGlobal(AsmJsModuleCompiler &m, ParseNode *var)
{
Assert(var->nop == knopVarDecl || var->nop == knopConstDecl);
bool isMutable = var->nop != knopConstDecl;
PropertyName name = var->name();
m.GetByteCodeGenerator()->AssignPropertyId(name);
if (m.LookupIdentifier(name))
{
return m.FailName(var, _u("import variable %s names must be unique"), name);
}
if (!CheckModuleLevelName(m, var, name))
{
return false;
}
if (!var->sxVar.pnodeInit)
{
return m.Fail(var, _u("module import needs initializer"));
}
ParseNode *initNode = var->sxVar.pnodeInit;
if( ParserWrapper::IsNumericLiteral( initNode ) )
{
if (m.AddNumericVar(name, initNode, false, isMutable))
{
return true;
}
else
{
return m.FailName(var, _u("Failed to declare numeric var %s"), name);
}
}
if (initNode->nop == knopOr || initNode->nop == knopPos || initNode->nop == knopCall)
{
// SIMD_JS
// e.g. var x = f4(1.0, 2.0, 3.0, 4.0)
if (initNode->nop == knopCall)
{
AsmJsSIMDFunction* simdSym;
// also checks if simd constructor
simdSym = m.LookupSimdConstructor(initNode->sxCall.pnodeTarget->name());
// call to simd constructor
if (simdSym)
{
// validate args and define a SIMD symbol
return m.AddSimdValueVar(name, initNode, simdSym);
}
// else it is FFI import: var x = f4check(FFI.field), handled in CheckGlobalVariableInitImport
}
return CheckGlobalVariableInitImport(m, name, initNode, isMutable );
}
if( initNode->nop == knopNew )
{
return CheckNewArrayView(m, var->name(), initNode);
}
if (ParserWrapper::IsDotMember(initNode))
{
return CheckGlobalDotImport(m, name, initNode);
}
return m.Fail( initNode, _u("Failed to recognize global variable") );
}
bool
AsmJSCompiler::CheckModuleGlobals(AsmJsModuleCompiler &m)
{
ParseNode *varStmts;
if( !ParserWrapper::ParseVarOrConstStatement( m.GetCurrentParserNode(), &varStmts ) )
{
return false;
}
if (!varStmts)
{
return true;
}
while (varStmts->nop == knopList)
{
ParseNode * pnode = ParserWrapper::GetBinaryLeft(varStmts);
while (pnode && pnode->nop != knopEndCode)
{
ParseNode * decl;
if (pnode->nop == knopList)
{
decl = ParserWrapper::GetBinaryLeft(pnode);
pnode = ParserWrapper::GetBinaryRight(pnode);
}
else
{
decl = pnode;
pnode = nullptr;
}
if (decl->nop == knopFncDecl)
{
goto varDeclEnd;
}
else if (decl->nop != knopConstDecl && decl->nop != knopVarDecl)
{
break;
}
if (decl->sxVar.pnodeInit && decl->sxVar.pnodeInit->nop == knopArray)
{
// Assume we reached func tables
goto varDeclEnd;
}
if (!CheckModuleGlobal(m, decl))
{
return false;
}
}
if (ParserWrapper::GetBinaryRight(varStmts)->nop == knopEndCode)
{
// this is an error condition, but CheckFunctionsSequential will figure it out
goto varDeclEnd;
}
varStmts = ParserWrapper::GetBinaryRight(varStmts);
}
varDeclEnd:
// we will collect information on the function tables now and come back to the functions themselves afterwards,
// because function table information is used when processing function bodies
ParseNode * fnNodes = varStmts;
while (fnNodes->nop != knopEndCode && ParserWrapper::GetBinaryLeft(fnNodes)->nop == knopFncDecl)
{
fnNodes = ParserWrapper::GetBinaryRight(fnNodes);
}
if (fnNodes->nop == knopEndCode)
{
// if there are no function tables, we can just initialize count to 0
m.SetFuncPtrTableCount(0);
}
else
{
m.SetCurrentParseNode(fnNodes);
if (!CheckFunctionTables(m))
{
return false;
}
}
// this will move us back to the beginning of the function declarations
m.SetCurrentParseNode(varStmts);
return true;
}
bool AsmJSCompiler::CheckFunction( AsmJsModuleCompiler &m, ParseNode* fncNode )
{
Assert( fncNode->nop == knopFncDecl );
if( PHASE_TRACE1( Js::ByteCodePhase ) )
{
Output::Print( _u(" Checking Asm function: %s\n"), fncNode->sxFnc.funcInfo->name);
}
if( !CheckFunctionHead( m, fncNode, false ) )
{
return false;
}
AsmJsFunc* func = m.CreateNewFunctionEntry(fncNode);
if (!func)
{
return m.Fail(fncNode, _u(" Error creating function entry"));
}
return true;
}
bool AsmJSCompiler::CheckFunctionsSequential( AsmJsModuleCompiler &m )
{
AsmJSParser& list = m.GetCurrentParserNode();
Assert( list->nop == knopList );
ParseNode* pnode = ParserWrapper::GetBinaryLeft(list);
while (pnode->nop == knopFncDecl)
{
if( !CheckFunction( m, pnode ) )
{
return false;
}
if(ParserWrapper::GetBinaryRight(list)->nop == knopEndCode)
{
break;
}
list = ParserWrapper::GetBinaryRight(list);
pnode = ParserWrapper::GetBinaryLeft(list);
}
m.SetCurrentParseNode( list );
return true;
}
bool AsmJSCompiler::CheckFunctionTables(AsmJsModuleCompiler &m)
{
AsmJSParser& list = m.GetCurrentParserNode();
Assert(list->nop == knopList);
int32 funcPtrTableCount = 0;
while (list->nop != knopEndCode)
{
ParseNode * varStmt = ParserWrapper::GetBinaryLeft(list);
if (varStmt->nop != knopConstDecl && varStmt->nop != knopVarDecl)
{
break;
}
if (!varStmt->sxVar.pnodeInit || varStmt->sxVar.pnodeInit->nop != knopArray)
{
break;
}
const uint tableSize = varStmt->sxVar.pnodeInit->sxArrLit.count;
if (!::Math::IsPow2(tableSize))
{
return m.FailName(varStmt, _u("Function table [%s] size must be a power of 2"), varStmt->name());
}
if (!m.AddFunctionTable(varStmt->name(), tableSize))
{
return m.FailName(varStmt, _u("Unable to create new function table %s"), varStmt->name());
}
AsmJsFunctionTable* ftable = (AsmJsFunctionTable*)m.LookupIdentifier(varStmt->name());
Assert(ftable);
ParseNode* pnode = varStmt->sxVar.pnodeInit->sxArrLit.pnode1;
if (pnode->nop == knopList)
{
pnode = ParserWrapper::GetBinaryLeft(pnode);
}
if (!ParserWrapper::IsNameDeclaration(pnode))
{
return m.FailName(pnode, _u("Invalid element in function table %s"), varStmt->name());
}
++funcPtrTableCount;
list = ParserWrapper::GetBinaryRight(list);
}
m.SetFuncPtrTableCount(funcPtrTableCount);
m.SetCurrentParseNode(list);
return true;
}
bool AsmJSCompiler::CheckModuleReturn( AsmJsModuleCompiler& m )
{
ParseNode* endStmt = m.GetCurrentParserNode();
if (endStmt->nop != knopList)
{
return m.Fail(endStmt, _u("Module must have a return"));
}
ParseNode* node = ParserWrapper::GetBinaryLeft( endStmt );
ParseNode* endNode = ParserWrapper::GetBinaryRight( endStmt );
if( node->nop != knopReturn || endNode->nop != knopEndCode )
{
return m.Fail( node, _u("Only expression after table functions must be a return") );
}
ParseNode* objNode = node->sxReturn.pnodeExpr;
if ( !objNode )
{
return m.Fail( node, _u( "Module return must be an object or 1 function" ) );
}
if( objNode->nop != knopObject )
{
if( ParserWrapper::IsNameDeclaration( objNode ) )
{
PropertyName name = objNode->name();
AsmJsSymbol* sym = m.LookupIdentifier( name );
if( !sym )
{
return m.FailName( node, _u("Symbol %s not recognized inside module"), name );
}
if( sym->GetSymbolType() != AsmJsSymbol::ModuleFunction )
{
return m.FailName( node, _u("Symbol %s can only be a function of the module"), name );
}
AsmJsFunc* func = sym->Cast<AsmJsFunc>();
if( !m.SetExportFunc( func ) )
{
return m.FailName( node, _u("Error adding return Symbol %s"), name );
}
return true;
}
return m.Fail( node, _u("Module return must be an object or 1 function") );
}
ParseNode* objectElement = ParserWrapper::GetUnaryNode(objNode);
if (!objectElement)
{
return m.Fail(node, _u("Return object must not be empty"));
}
while( objectElement )
{
ParseNode* member = nullptr;
if( objectElement->nop == knopList )
{
member = ParserWrapper::GetBinaryLeft( objectElement );
objectElement = ParserWrapper::GetBinaryRight( objectElement );
}
else if( objectElement->nop == knopMember )
{
member = objectElement;
objectElement = nullptr;
}
else
{
return m.Fail( node, _u("Return object must only contain members") );
}
if( member )
{
ParseNode* field = ParserWrapper::GetBinaryLeft( member );
ParseNode* value = ParserWrapper::GetBinaryRight( member );
if( !ParserWrapper::IsNameDeclaration( field ) || !ParserWrapper::IsNameDeclaration( value ) )
{
return m.Fail( node, _u("Return object member must be fields") );
}
AsmJsSymbol* sym = m.LookupIdentifier( value->name() );
if( !sym )
{
return m.FailName( node, _u("Symbol %s not recognized inside module"), value->name() );
}
if( sym->GetSymbolType() != AsmJsSymbol::ModuleFunction )
{
return m.FailName( node, _u("Symbol %s can only be a function of the module"), value->name() );
}
AsmJsFunc* func = sym->Cast<AsmJsFunc>();
if( !m.AddExport( field->name(), func->GetFunctionIndex() ) )
{
return m.FailName( node, _u("Error adding return Symbol %s"), value->name() );
}
}
}
return true;
}
bool AsmJSCompiler::CheckFuncPtrTables( AsmJsModuleCompiler &m )
{
ParseNode *list = m.GetCurrentParserNode();
if (!list)
{
return true;
}
while (list->nop != knopEndCode)
{
ParseNode * varStmt = ParserWrapper::GetBinaryLeft(list);
if (varStmt->nop != knopConstDecl && varStmt->nop != knopVarDecl)
{
break;
}
ParseNode* nodeInit = varStmt->sxVar.pnodeInit;
if( !nodeInit || nodeInit->nop != knopArray )
{
return m.Fail( varStmt, _u("Invalid variable after function declaration") );
}
PropertyName tableName = varStmt->name();
AsmJsSymbol* symFunctionTable = m.LookupIdentifier(tableName);
if( !symFunctionTable)
{
// func table not used in functions disregard it
}
else
{
//Check name
if(symFunctionTable->GetSymbolType() != AsmJsSymbol::FuncPtrTable )
{
return m.FailName( varStmt, _u("Variable %s is already defined"), tableName );
}
AsmJsFunctionTable* table = symFunctionTable->Cast<AsmJsFunctionTable>();
if( table->IsDefined() )
{
return m.FailName( varStmt, _u("Multiple declaration of function table %s"), tableName );
}
// Check content of the array
uint count = nodeInit->sxArrLit.count;
if( table->GetSize() != count )
{
return m.FailName( varStmt, _u("Invalid size of function table %s"), tableName );
}
// Set the content of the array in the table
ParseNode* node = nodeInit->sxArrLit.pnode1;
uint i = 0;
while( node )
{
ParseNode* funcNameNode = nullptr;
if( node->nop == knopList )
{
funcNameNode = ParserWrapper::GetBinaryLeft( node );
node = ParserWrapper::GetBinaryRight( node );
}
else
{
Assert( i + 1 == count );
funcNameNode = node;
node = nullptr;
}
if( ParserWrapper::IsNameDeclaration( funcNameNode ) )
{
AsmJsSymbol* sym = m.LookupIdentifier( funcNameNode->name() );
if( !sym || sym->GetSymbolType() != AsmJsSymbol::ModuleFunction )
{
return m.FailName( varStmt, _u("Element in function table %s is not a function"), tableName );
}
AsmJsFunc* func = sym->Cast<AsmJsFunc>();
AsmJsRetType retType;
if (!table->SupportsArgCall(func->GetArgCount(), func->GetArgTypeArray(), retType))
{
return m.FailName(funcNameNode, _u("Function signatures in table %s do not match"), tableName);
}
if (!table->CheckAndSetReturnType(func->GetReturnType()))
{
return m.FailName(funcNameNode, _u("Function return types in table %s do not match"), tableName);
}
table->SetModuleFunctionIndex( func->GetFunctionIndex(), i );
++i;
}
else
{
return m.FailName(funcNameNode, _u("Element in function table %s is not a function name"), tableName);
}
}
table->Define();
}
list = ParserWrapper::GetBinaryRight(list);
}
if( !m.AreAllFuncTableDefined() )
{
return m.Fail(list, _u("Some function table were used but not defined"));
}
m.SetCurrentParseNode(list);
return true;
}
bool AsmJSCompiler::CheckModule( ExclusiveContext *cx, AsmJSParser &parser, ParseNode *stmtList )
{
AsmJsModuleCompiler m( cx, parser );
if( !m.Init() )
{
return false;
}
if( PropertyName moduleFunctionName = ParserWrapper::FunctionName( m.GetModuleFunctionNode() ) )
{
if( !CheckModuleLevelName( m, m.GetModuleFunctionNode(), moduleFunctionName ) )
{
return false;
}
m.InitModuleName( moduleFunctionName );
if( PHASE_TRACE1( Js::ByteCodePhase ) )
{
Output::Print( _u("Asm.Js Module [%s] detected, trying to compile\n"), moduleFunctionName->Psz() );
}
}
m.AccumulateCompileTime(AsmJsCompilation::Module);
if( !CheckFunctionHead( m, m.GetModuleFunctionNode() ) )
{
goto AsmJsCompilationError;
}
if (!CheckModuleArguments(m, m.GetModuleFunctionNode()))
{
goto AsmJsCompilationError;
}
if (!CheckModuleGlobals(m))
{
goto AsmJsCompilationError;
}
m.AccumulateCompileTime(AsmJsCompilation::Module);
if (!CheckFunctionsSequential(m))
{
goto AsmJsCompilationError;
}
m.AccumulateCompileTime();
m.InitMemoryOffsets();
if( !m.CompileAllFunctions() )
{
return false;
}
m.AccumulateCompileTime(AsmJsCompilation::ByteCode);
if (!CheckFuncPtrTables(m))
{
m.RevertAllFunctions();
return false;
}
m.AccumulateCompileTime();
if (!CheckModuleReturn(m))
{
m.RevertAllFunctions();
return false;
}
m.CommitFunctions();
m.CommitModule();
m.AccumulateCompileTime(AsmJsCompilation::Module);
m.PrintCompileTrace();
return true;
AsmJsCompilationError:
ParseNode * moduleNode = m.GetModuleFunctionNode();
if( moduleNode )
{
FunctionBody* body = moduleNode->sxFnc.funcInfo->GetParsedFunctionBody();
body->ResetByteCodeGenState();
}
cx->byteCodeGenerator->Writer()->Reset();
return false;
}
bool AsmJSCompiler::Compile(ExclusiveContext *cx, AsmJSParser parser, ParseNode *stmtList)
{
if (!CheckModule(cx, parser, stmtList))
{
OutputError(cx->scriptContext, _u("Asm.js compilation failed."));
return false;
}
return true;
}
void AsmJSCompiler::OutputError(ScriptContext * scriptContext, const wchar * message, ...)
{
va_list argptr;
va_start(argptr, message);
VOutputMessage(scriptContext, DEIT_ASMJS_FAILED, message, argptr);
}
void AsmJSCompiler::OutputMessage(ScriptContext * scriptContext, const DEBUG_EVENT_INFO_TYPE messageType, const wchar * message, ...)
{
va_list argptr;
va_start(argptr, message);
VOutputMessage(scriptContext, messageType, message, argptr);
}
void AsmJSCompiler::VOutputMessage(ScriptContext * scriptContext, const DEBUG_EVENT_INFO_TYPE messageType, const wchar * message, va_list argptr)
{
char16 buf[2048];
size_t size;
size = _vsnwprintf_s(buf, _countof(buf), _TRUNCATE, message, argptr);
if (size == -1)
{
size = 2048;
}
scriptContext->RaiseMessageToDebugger(messageType, buf, scriptContext->GetUrl());
if (PHASE_TRACE1(AsmjsPhase) || PHASE_TESTTRACE1(AsmjsPhase))
{
Output::PrintBuffer(buf, size);
Output::Print(_u("\n"));
Output::Flush();
}
}
}
#endif