/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
*
* 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 "wasm/AsmJS.h"
#include "mozilla/Attributes.h"
#include "mozilla/Compression.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h" // SprintfLiteral
#include "mozilla/Try.h" // MOZ_TRY*
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "mozilla/Variant.h"
#include <algorithm>
#include <
new>
#include "jsmath.h"
#include "frontend/BytecodeCompiler.h" // CompileStandaloneFunction
#include "frontend/FrontendContext.h" // js::FrontendContext
#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
#include "frontend/ParseNode.h"
#include "frontend/Parser-macros.h" // MOZ_TRY_*
#include "frontend/Parser.h"
#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
#include "frontend/SharedContext.h" // TopLevelFunction
#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
#include "gc/GC.h"
#include "gc/Policy.h"
#include "jit/InlinableNatives.h"
#include "js/BuildId.h" // JS::BuildIdCharVector
#include "js/experimental/JitInfo.h"
#include "js/friend/ErrorMessages.h" // JSMSG_*
#include "js/MemoryMetrics.h"
#include "js/Printf.h"
#include "js/ScalarType.h" // js::Scalar::Type
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "util/DifferentialTesting.h"
#include "util/StringBuilder.h"
#include "util/Text.h"
#include "vm/ErrorReporting.h"
#include "vm/FunctionFlags.h" // js::FunctionFlags
#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
#include "vm/Interpreter.h"
#include "vm/SelfHosting.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "vm/Warnings.h" // js::WarnNumberASCII
#include "wasm/WasmCompile.h"
#include "wasm/WasmFeatures.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmModuleTypes.h"
#include "wasm/WasmSerialize.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmValidate.h"
#include "frontend/SharedContext-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/JSObject-inl.h"
#include "wasm/WasmInstance-inl.h"
using namespace js;
using namespace js::frontend;
using namespace js::jit;
using namespace js::wasm;
using JS::AsmJSOption;
using JS::AutoStableStringChars;
using JS::GenericNaN;
using JS::SourceText;
using mozilla::Abs;
using mozilla::AsVariant;
using mozilla::CeilingLog2;
using mozilla::HashGeneric;
using mozilla::IsNegativeZero;
using mozilla::IsPositiveZero;
using mozilla::IsPowerOfTwo;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::PodZero;
using mozilla::PositiveInfinity;
using mozilla::Some;
using mozilla::Utf8Unit;
using mozilla::Compression::LZ4;
using FunctionVector = JS::GCVector<JSFunction*>;
/*****************************************************************************/
// A wasm module can either use no memory, a unshared memory (ArrayBuffer) or
// shared memory (SharedArrayBuffer).
enum class MemoryUsage { None =
false, Unshared = 1, Shared = 2 };
// The asm.js valid heap lengths are precisely the WASM valid heap lengths for
// ARM greater or equal to MinHeapLength
static const size_t MinHeapLength = PageSize;
// An asm.js heap can in principle be up to INT32_MAX bytes but requirements
// on the format restrict it further to the largest pseudo-ARM-immediate.
// See IsValidAsmJSHeapLength().
static const uint64_t MaxHeapLength = 0x7f000000;
static uint64_t RoundUpToNextValidAsmJSHeapLength(uint64_t length) {
if (length <= MinHeapLength) {
return MinHeapLength;
}
return wasm::RoundUpToNextValidARMImmediate(length);
}
static uint64_t DivideRoundingUp(uint64_t a, uint64_t b) {
return (a + (b - 1)) / b;
}
/*****************************************************************************/
// asm.js module object
// The asm.js spec recognizes this set of builtin Math functions.
enum AsmJSMathBuiltinFunction {
AsmJSMathBuiltin_sin,
AsmJSMathBuiltin_cos,
AsmJSMathBuiltin_tan,
AsmJSMathBuiltin_asin,
AsmJSMathBuiltin_acos,
AsmJSMathBuiltin_atan,
AsmJSMathBuiltin_ceil,
AsmJSMathBuiltin_floor,
AsmJSMathBuiltin_exp,
AsmJSMathBuiltin_log,
AsmJSMathBuiltin_pow,
AsmJSMathBuiltin_sqrt,
AsmJSMathBuiltin_abs,
AsmJSMathBuiltin_atan2,
AsmJSMathBuiltin_imul,
AsmJSMathBuiltin_fround,
AsmJSMathBuiltin_min,
AsmJSMathBuiltin_max,
AsmJSMathBuiltin_clz32
};
// LitValPOD is a restricted version of LitVal suitable for asm.js that is
// always POD.
struct LitValPOD {
PackedTypeCode valType_;
union U {
uint32_t u32_;
uint64_t u64_;
float f32_;
double f64_;
} u;
LitValPOD() =
default;
explicit LitValPOD(uint32_t u32) : valType_(ValType(ValType::I32).packed()) {
u.u32_ = u32;
}
explicit LitValPOD(uint64_t u64) : valType_(ValType(ValType::I64).packed()) {
u.u64_ = u64;
}
explicit LitValPOD(
float f32) : valType_(ValType(ValType::F32).packed()) {
u.f32_ = f32;
}
explicit LitValPOD(
double f64) : valType_(ValType(ValType::F64).packed()) {
u.f64_ = f64;
}
LitVal asLitVal()
const {
switch (valType_.typeCode()) {
case TypeCode::I32:
return LitVal(u.u32_);
case TypeCode::I64:
return LitVal(u.u64_);
case TypeCode::F32:
return LitVal(u.f32_);
case TypeCode::F64:
return LitVal(u.f64_);
default:
MOZ_CRASH(
"Can't happen");
}
}
};
static_assert(std::is_pod_v<LitValPOD>,
"must be POD to be simply serialized/deserialized");
// An AsmJSGlobal represents a JS global variable in the asm.js module function.
class AsmJSGlobal {
public:
enum Which {
Variable,
FFI,
ArrayView,
ArrayViewCtor,
MathBuiltinFunction,
Constant
};
enum VarInitKind { InitConstant, InitImport };
enum ConstantKind { GlobalConstant, MathConstant };
private:
struct CacheablePod {
Which which_;
union V {
struct {
VarInitKind initKind_;
union U {
PackedTypeCode importValType_;
LitValPOD val_;
} u;
} var;
uint32_t ffiIndex_;
Scalar::Type viewType_;
AsmJSMathBuiltinFunction mathBuiltinFunc_;
struct {
ConstantKind kind_;
double value_;
} constant;
} u;
} pod;
CacheableChars field_;
friend class ModuleValidatorShared;
template <
typename Unit>
friend class ModuleValidator;
public:
AsmJSGlobal() =
default;
AsmJSGlobal(Which which, UniqueChars field) {
mozilla::PodZero(&pod);
// zero padding for Valgrind
pod.which_ = which;
field_ = std::move(field);
}
const char* field()
const {
return field_.get(); }
Which which()
const {
return pod.which_; }
VarInitKind varInitKind()
const {
MOZ_ASSERT(pod.which_ == Variable);
return pod.u.var.initKind_;
}
LitValPOD varInitVal()
const {
MOZ_ASSERT(pod.which_ == Variable);
MOZ_ASSERT(pod.u.var.initKind_ == InitConstant);
return pod.u.var.u.val_;
}
ValType varInitImportType()
const {
MOZ_ASSERT(pod.which_ == Variable);
MOZ_ASSERT(pod.u.var.initKind_ == InitImport);
return ValType(pod.u.var.u.importValType_);
}
uint32_t ffiIndex()
const {
MOZ_ASSERT(pod.which_ == FFI);
return pod.u.ffiIndex_;
}
// When a view is created from an imported constructor:
// var I32 = stdlib.Int32Array;
// var i32 = new I32(buffer);
// the second import has nothing to validate and thus has a null field.
Scalar::Type viewType()
const {
MOZ_ASSERT(pod.which_ == ArrayView || pod.which_ == ArrayViewCtor);
return pod.u.viewType_;
}
AsmJSMathBuiltinFunction mathBuiltinFunction()
const {
MOZ_ASSERT(pod.which_ == MathBuiltinFunction);
return pod.u.mathBuiltinFunc_;
}
ConstantKind constantKind()
const {
MOZ_ASSERT(pod.which_ == Constant);
return pod.u.constant.kind_;
}
double constantValue()
const {
MOZ_ASSERT(pod.which_ == Constant);
return pod.u.constant.value_;
}
};
using AsmJSGlobalVector = Vector<AsmJSGlobal, 0, SystemAllocPolicy>;
// An AsmJSImport is slightly different than an asm.js FFI function: a single
// asm.js FFI function can be called with many different signatures. When
// compiled to wasm, each unique FFI function paired with signature generates a
// wasm import.
class AsmJSImport {
uint32_t ffiIndex_;
public:
AsmJSImport() =
default;
explicit AsmJSImport(uint32_t ffiIndex) : ffiIndex_(ffiIndex) {}
uint32_t ffiIndex()
const {
return ffiIndex_; }
};
using AsmJSImportVector = Vector<AsmJSImport, 0, SystemAllocPolicy>;
// An AsmJSExport logically extends Export with the extra information needed for
// an asm.js exported function, viz., the offsets in module's source chars in
// case the function is toString()ed.
class AsmJSExport {
uint32_t funcIndex_ = 0;
// All fields are treated as cacheable POD:
uint32_t startOffsetInModule_ = 0;
// Store module-start-relative offsets
uint32_t endOffsetInModule_ = 0;
// so preserved by serialization.
public:
AsmJSExport() =
default;
AsmJSExport(uint32_t funcIndex, uint32_t startOffsetInModule,
uint32_t endOffsetInModule)
: funcIndex_(funcIndex),
startOffsetInModule_(startOffsetInModule),
endOffsetInModule_(endOffsetInModule) {}
uint32_t funcIndex()
const {
return funcIndex_; }
uint32_t startOffsetInModule()
const {
return startOffsetInModule_; }
uint32_t endOffsetInModule()
const {
return endOffsetInModule_; }
};
using AsmJSExportVector = Vector<AsmJSExport, 0, SystemAllocPolicy>;
// Holds the immutable guts of an AsmJSModule.
//
// CodeMetadataForAsmJSImpl is built incrementally by ModuleValidator and then
// shared immutably between AsmJSModules.
struct js::CodeMetadataForAsmJSImpl : CodeMetadataForAsmJS {
uint32_t numFFIs = 0;
uint32_t srcLength = 0;
uint32_t srcLengthWithRightBrace = 0;
AsmJSGlobalVector asmJSGlobals;
AsmJSImportVector asmJSImports;
AsmJSExportVector asmJSExports;
CacheableCharsVector asmJSFuncNames;
CacheableChars globalArgumentName;
CacheableChars importArgumentName;
CacheableChars bufferArgumentName;
// These values are not serialized since they are relative to the
// containing script which can be different between serialization and
// deserialization contexts. Thus, they must be set explicitly using the
// ambient Parser/ScriptSource after deserialization.
//
// srcStart refers to the offset in the ScriptSource to the beginning of
// the asm.js module function. If the function has been created with the
// Function constructor, this will be the first character in the function
// source. Otherwise, it will be the opening parenthesis of the arguments
// list.
uint32_t toStringStart;
uint32_t srcStart;
bool strict;
bool alwaysUseFdlibm =
false;
RefPtr<ScriptSource> source;
uint32_t srcEndBeforeCurly()
const {
return srcStart + srcLength; }
uint32_t srcEndAfterCurly()
const {
return srcStart + srcLengthWithRightBrace;
}
CodeMetadataForAsmJSImpl() : toStringStart(0), srcStart(0), strict(
false) {}
~CodeMetadataForAsmJSImpl() =
default;
const CodeMetadataForAsmJSImpl& asAsmJS()
const {
return *
this; }
const AsmJSExport& lookupAsmJSExport(uint32_t funcIndex)
const {
// The AsmJSExportVector isn't stored in sorted order so do a linear
// search. This is for the super-cold and already-expensive toString()
// path and the number of exports is generally small.
for (
const AsmJSExport& exp : asmJSExports) {
if (exp.funcIndex() == funcIndex) {
return exp;
}
}
MOZ_CRASH(
"missing asm.js func export");
}
bool mutedErrors()
const {
return source->mutedErrors(); }
const char16_t* displayURL()
const {
return source->hasDisplayURL() ? source->displayURL() : nullptr;
}
ScriptSource* maybeScriptSource()
const {
return source.get(); }
bool getFuncNameForAsmJS(uint32_t funcIndex, UTF8Bytes* name)
const {
const char* p = asmJSFuncNames[funcIndex].get();
if (!p) {
return true;
}
return name->append(p, strlen(p));
}
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
const {
return asmJSGlobals.sizeOfExcludingThis(mallocSizeOf) +
asmJSImports.sizeOfExcludingThis(mallocSizeOf) +
asmJSExports.sizeOfExcludingThis(mallocSizeOf) +
asmJSFuncNames.sizeOfExcludingThis(mallocSizeOf) +
globalArgumentName.sizeOfExcludingThis(mallocSizeOf) +
importArgumentName.sizeOfExcludingThis(mallocSizeOf) +
bufferArgumentName.sizeOfExcludingThis(mallocSizeOf);
}
};
using MutableCodeMetadataForAsmJSImpl = RefPtr<CodeMetadataForAsmJSImpl>;
/*****************************************************************************/
// ParseNode utilities
static inline ParseNode* NextNode(ParseNode* pn) {
return pn->pn_next; }
static inline ParseNode* UnaryKid(ParseNode* pn) {
return pn->as<UnaryNode>().kid();
}
static inline ParseNode* BinaryRight(ParseNode* pn) {
return pn->as<BinaryNode>().right();
}
static inline ParseNode* BinaryLeft(ParseNode* pn) {
return pn->as<BinaryNode>().left();
}
static inline ParseNode* ReturnExpr(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::ReturnStmt));
return UnaryKid(pn);
}
static inline ParseNode* TernaryKid1(ParseNode* pn) {
return pn->as<TernaryNode>().kid1();
}
static inline ParseNode* TernaryKid2(ParseNode* pn) {
return pn->as<TernaryNode>().kid2();
}
static inline ParseNode* TernaryKid3(ParseNode* pn) {
return pn->as<TernaryNode>().kid3();
}
static inline ParseNode* ListHead(ParseNode* pn) {
return pn->as<ListNode>().head();
}
static inline unsigned ListLength(ParseNode* pn) {
return pn->as<ListNode>().count();
}
static inline ParseNode* CallCallee(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return BinaryLeft(pn);
}
static inline unsigned CallArgListLength(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return ListLength(BinaryRight(pn));
}
static inline ParseNode* CallArgList(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return ListHead(BinaryRight(pn));
}
static inline ParseNode* VarListHead(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::VarStmt) ||
pn->isKind(ParseNodeKind::ConstDecl));
return ListHead(pn);
}
static inline bool IsDefaultCase(ParseNode* pn) {
return pn->as<CaseClause>().isDefault();
}
static inline ParseNode* CaseExpr(ParseNode* pn) {
return pn->as<CaseClause>().caseExpression();
}
static inline ParseNode* CaseBody(ParseNode* pn) {
return pn->as<CaseClause>().statementList();
}
static inline ParseNode* BinaryOpLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isBinaryOperation());
MOZ_ASSERT(pn->as<ListNode>().count() == 2);
return ListHead(pn);
}
static inline ParseNode* BinaryOpRight(ParseNode* pn) {
MOZ_ASSERT(pn->isBinaryOperation());
MOZ_ASSERT(pn->as<ListNode>().count() == 2);
return NextNode(ListHead(pn));
}
static inline ParseNode* BitwiseLeft(ParseNode* pn) {
return BinaryOpLeft(pn); }
static inline ParseNode* BitwiseRight(ParseNode* pn) {
return BinaryOpRight(pn);
}
static inline ParseNode* MultiplyLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* MultiplyRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* AddSubLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
pn->isKind(ParseNodeKind::SubExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* AddSubRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
pn->isKind(ParseNodeKind::SubExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* DivOrModLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
pn->isKind(ParseNodeKind::ModExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* DivOrModRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
pn->isKind(ParseNodeKind::ModExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* ComparisonLeft(ParseNode* pn) {
return BinaryOpLeft(pn);
}
static inline ParseNode* ComparisonRight(ParseNode* pn) {
return BinaryOpRight(pn);
}
static inline bool IsExpressionStatement(ParseNode* pn) {
return pn->isKind(ParseNodeKind::ExpressionStmt);
}
static inline ParseNode* ExpressionStatementExpr(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::ExpressionStmt));
return UnaryKid(pn);
}
static inline TaggedParserAtomIndex LoopControlMaybeLabel(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::BreakStmt) ||
pn->isKind(ParseNodeKind::ContinueStmt));
return pn->as<LoopControlStatement>().label();
}
static inline TaggedParserAtomIndex LabeledStatementLabel(ParseNode* pn) {
return pn->as<LabeledStatement>().label();
}
static inline ParseNode* LabeledStatementStatement(ParseNode* pn) {
return pn->as<LabeledStatement>().statement();
}
static double NumberNodeValue(ParseNode* pn) {
return pn->as<NumericLiteral>().value();
}
static bool NumberNodeHasFrac(ParseNode* pn) {
return pn->as<NumericLiteral>().decimalPoint() == HasDecimal;
}
static ParseNode* DotBase(ParseNode* pn) {
return &pn->as<PropertyAccess>().expression();
}
static TaggedParserAtomIndex DotMember(ParseNode* pn) {
return pn->as<PropertyAccess>().name();
}
static ParseNode* ElemBase(ParseNode* pn) {
return &pn->as<PropertyByValue>().expression();
}
static ParseNode* ElemIndex(ParseNode* pn) {
return &pn->as<PropertyByValue>().key();
}
static inline TaggedParserAtomIndex FunctionName(FunctionNode* funNode) {
if (
auto name = funNode->funbox()->explicitName()) {
return name;
}
return TaggedParserAtomIndex::null();
}
static inline ParseNode* FunctionFormalParametersList(FunctionNode* fn,
unsigned* numFormals) {
ParamsBodyNode* argsBody = fn->body();
// The number of formals is equal to the number of parameters (excluding the
// trailing lexical scope). There are no destructuring or rest parameters for
// asm.js functions.
*numFormals = argsBody->count();
// If the function has been fully parsed, the trailing function body node is a
// lexical scope. If we've only parsed the function parameters, the last node
// is the last parameter.
if (*numFormals > 0 && argsBody->last()->is<LexicalScopeNode>()) {
MOZ_ASSERT(argsBody->last()->as<LexicalScopeNode>().scopeBody()->isKind(
ParseNodeKind::StatementList));
(*numFormals)--;
}
return argsBody->head();
}
static inline ParseNode* FunctionStatementList(FunctionNode* funNode) {
LexicalScopeNode* last = funNode->body()->body();
MOZ_ASSERT(last->isEmptyScope());
ParseNode* body = last->scopeBody();
MOZ_ASSERT(body->isKind(ParseNodeKind::StatementList));
return body;
}
static inline bool IsNormalObjectField(ParseNode* pn) {
return pn->isKind(ParseNodeKind::PropertyDefinition) &&
pn->as<PropertyDefinition>().accessorType() == AccessorType::None &&
BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName);
}
static inline TaggedParserAtomIndex ObjectNormalFieldName(ParseNode* pn) {
MOZ_ASSERT(IsNormalObjectField(pn));
MOZ_ASSERT(BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName));
return BinaryLeft(pn)->as<NameNode>().atom();
}
static inline ParseNode* ObjectNormalFieldInitializer(ParseNode* pn) {
MOZ_ASSERT(IsNormalObjectField(pn));
return BinaryRight(pn);
}
static inline bool IsUseOfName(ParseNode* pn, TaggedParserAtomIndex name) {
return pn->isName(name);
}
static inline bool IsIgnoredDirectiveName(TaggedParserAtomIndex atom) {
return atom != TaggedParserAtomIndex::WellKnown::use_strict_();
}
static inline bool IsIgnoredDirective(ParseNode* pn) {
return pn->isKind(ParseNodeKind::ExpressionStmt) &&
UnaryKid(pn)->isKind(ParseNodeKind::StringExpr) &&
IsIgnoredDirectiveName(UnaryKid(pn)->as<NameNode>().atom());
}
static inline bool IsEmptyStatement(ParseNode* pn) {
return pn->isKind(ParseNodeKind::EmptyStmt);
}
static inline ParseNode* SkipEmptyStatements(ParseNode* pn) {
while (pn && IsEmptyStatement(pn)) {
pn = pn->pn_next;
}
return pn;
}
static inline ParseNode* NextNonEmptyStatement(ParseNode* pn) {
return SkipEmptyStatements(pn->pn_next);
}
template <
typename Unit>
static bool GetToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
auto& ts = parser.tokenStream;
TokenKind tk;
while (
true) {
if (!ts.getToken(&tk, TokenStreamShared::SlashIsRegExp)) {
return false;
}
if (tk != TokenKind::Semi) {
break;
}
}
*tkp = tk;
return true;
}
template <
typename Unit>
static bool PeekToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
auto& ts = parser.tokenStream;
TokenKind tk;
while (
true) {
if (!ts.peekToken(&tk, TokenStream::SlashIsRegExp)) {
return false;
}
if (tk != TokenKind::Semi) {
break;
}
ts.consumeKnownToken(TokenKind::Semi, TokenStreamShared::SlashIsRegExp);
}
*tkp = tk;
return true;
}
template <
typename Unit>
static bool ParseVarOrConstStatement(AsmJSParser<Unit>& parser,
ParseNode** var) {
TokenKind tk;
if (!PeekToken(parser, &tk)) {
return false;
}
if (tk != TokenKind::Var && tk != TokenKind::
Const) {
*var = nullptr;
return true;
}
MOZ_TRY_VAR_OR_RETURN(*var, parser.statementListItem(YieldIsName),
false);
MOZ_ASSERT((*var)->isKind(ParseNodeKind::VarStmt) ||
(*var)->isKind(ParseNodeKind::ConstDecl));
return true;
}
/*****************************************************************************/
// Represents the type and value of an asm.js numeric literal.
//
// A literal is a double iff the literal contains a decimal point (even if the
// fractional part is 0). Otherwise, integers may be classified:
// fixnum: [0, 2^31)
// negative int: [-2^31, 0)
// big unsigned: [2^31, 2^32)
// out of range: otherwise
// Lastly, a literal may be a float literal which is any double or integer
// literal coerced with Math.fround.
class NumLit {
public:
enum Which {
Fixnum,
NegativeInt,
BigUnsigned,
Double,
Float,
OutOfRangeInt = -1
};
private:
Which which_;
JS::Value value_;
public:
NumLit() =
default;
NumLit(Which w,
const Value& v) : which_(w), value_(v) {}
Which which()
const {
return which_; }
int32_t toInt32()
const {
MOZ_ASSERT(which_ == Fixnum || which_ == NegativeInt ||
which_ == BigUnsigned);
return value_.toInt32();
}
uint32_t toUint32()
const {
return (uint32_t)toInt32(); }
double toDouble()
const {
MOZ_ASSERT(which_ ==
Double);
return value_.toDouble();
}
float toFloat()
const {
MOZ_ASSERT(which_ ==
Float);
return float(value_.toDouble());
}
Value scalarValue()
const {
MOZ_ASSERT(which_ != OutOfRangeInt);
return value_;
}
bool valid()
const {
return which_ != OutOfRangeInt; }
bool isZeroBits()
const {
MOZ_ASSERT(valid());
switch (which()) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
case NumLit::BigUnsigned:
return toInt32() == 0;
case NumLit::
Double:
return IsPositiveZero(toDouble());
case NumLit::
Float:
return IsPositiveZero(toFloat());
case NumLit::OutOfRangeInt:
MOZ_CRASH(
"can't be here because of valid() check above");
}
return false;
}
LitValPOD value()
const {
switch (which_) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
case NumLit::BigUnsigned:
return LitValPOD(toUint32());
case NumLit::
Float:
return LitValPOD(toFloat());
case NumLit::
Double:
return LitValPOD(toDouble());
case NumLit::OutOfRangeInt:;
}
MOZ_CRASH(
"bad literal");
}
};
// Represents the type of a general asm.js expression.
//
// A canonical subset of types representing the coercion targets: Int, Float,
// Double.
//
// Void is also part of the canonical subset.
class Type {
public:
enum Which {
Fixnum = NumLit::Fixnum,
Signed = NumLit::NegativeInt,
Unsigned = NumLit::BigUnsigned,
DoubleLit = NumLit::
Double,
Float = NumLit::
Float,
Double,
MaybeDouble,
MaybeFloat,
Floatish,
Int,
Intish,
Void
};
private:
Which which_;
public:
Type() =
default;
MOZ_IMPLICIT Type(Which w) : which_(w) {}
// Map an already canonicalized Type to the return type of a function call.
static Type ret(Type t) {
MOZ_ASSERT(t.isCanonical());
// The 32-bit external type is Signed, not Int.
return t.isInt() ?
Signed : t;
}
static Type lit(
const NumLit& lit) {
MOZ_ASSERT(lit.valid());
Which which = Type::Which(lit.which());
MOZ_ASSERT(which >= Fixnum && which <=
Float);
Type t;
t.which_ = which;
return t;
}
// Map |t| to one of the canonical vartype representations of a
// wasm::ValType.
static Type canonicalize(Type t) {
switch (t.which()) {
case Fixnum:
case Signed:
case Unsigned:
case Int:
return Int;
case Float:
return Float;
case DoubleLit:
case Double:
return Double;
case Void:
return Void;
case MaybeDouble:
case MaybeFloat:
case Floatish:
case Intish:
// These types need some kind of coercion, they can't be mapped
// to an VarType.
break;
}
MOZ_CRASH(
"Invalid vartype");
}
Which which()
const {
return which_; }
bool operator==(Type rhs)
const {
return which_ == rhs.which_; }
bool operator!=(Type rhs)
const {
return which_ != rhs.which_; }
bool operator<=(Type rhs)
const {
switch (rhs.which_) {
case Signed:
return isSigned();
case Unsigned:
return isUnsigned();
case DoubleLit:
return isDoubleLit();
case Double:
return isDouble();
case Float:
return isFloat();
case MaybeDouble:
return isMaybeDouble();
case MaybeFloat:
return isMaybeFloat();
case Floatish:
return isFloatish();
case Int:
return isInt();
case Intish:
return isIntish();
case Fixnum:
return isFixnum();
case Void:
return isVoid();
}
MOZ_CRASH(
"unexpected rhs type");
}
bool isFixnum()
const {
return which_ == Fixnum; }
bool isSigned()
const {
return which_ ==
Signed || which_ == Fixnum; }
bool isUnsigned()
const {
return which_ ==
Unsigned || which_ == Fixnum; }
bool isInt()
const {
return isSigned() || isUnsigned() || which_ ==
Int; }
bool isIntish()
const {
return isInt() || which_ == Intish; }
bool isDoubleLit()
const {
return which_ == DoubleLit; }
bool isDouble()
const {
return isDoubleLit() || which_ ==
Double; }
bool isMaybeDouble()
const {
return isDouble() || which_ == MaybeDouble; }
bool isFloat()
const {
return which_ ==
Float; }
bool isMaybeFloat()
const {
return isFloat() || which_ == MaybeFloat; }
bool isFloatish()
const {
return isMaybeFloat() || which_ == Floatish; }
bool isVoid()
const {
return which_ ==
Void; }
bool isExtern()
const {
return isDouble() || isSigned(); }
// Check if this is one of the valid types for a function argument.
bool isArgType()
const {
return isInt() || isFloat() || isDouble(); }
// Check if this is one of the valid types for a function return value.
bool isReturnType()
const {
return isSigned() || isFloat() || isDouble() || isVoid();
}
// Check if this is one of the valid types for a global variable.
bool isGlobalVarType()
const {
return isArgType(); }
// Check if this is one of the canonical vartype representations of a
// wasm::ValType, or is void. See Type::canonicalize().
bool isCanonical()
const {
switch (which()) {
case Int:
case Float:
case Double:
case Void:
return true;
default:
return false;
}
}
// Check if this is a canonical representation of a wasm::ValType.
bool isCanonicalValType()
const {
return !isVoid() && isCanonical(); }
// Convert this canonical type to a wasm::ValType.
ValType canonicalToValType()
const {
switch (which()) {
case Int:
return ValType::I32;
case Float:
return ValType::F32;
case Double:
return ValType::F64;
default:
MOZ_CRASH(
"Need canonical type");
}
}
Maybe<ValType> canonicalToReturnType()
const {
return isVoid() ? Nothing() : Some(canonicalToValType());
}
// Convert this type to a wasm::TypeCode for use in a wasm
// block signature. This works for all types, including non-canonical
// ones. Consequently, the type isn't valid for subsequent asm.js
// validation; it's only valid for use in producing wasm.
TypeCode toWasmBlockSignatureType()
const {
switch (which()) {
case Fixnum:
case Signed:
case Unsigned:
case Int:
case Intish:
return TypeCode::I32;
case Float:
case MaybeFloat:
case Floatish:
return TypeCode::F32;
case DoubleLit:
case Double:
case MaybeDouble:
return TypeCode::F64;
case Void:
return TypeCode::BlockVoid;
}
MOZ_CRASH(
"Invalid Type");
}
const char* toChars()
const {
switch (which_) {
case Double:
return "double";
case DoubleLit:
return "doublelit";
case MaybeDouble:
return "double?";
case Float:
return "float";
case Floatish:
return "floatish";
case MaybeFloat:
return "float?";
case Fixnum:
return "fixnum";
case Int:
return "int";
case Signed:
return "signed";
case Unsigned:
return "unsigned";
case Intish:
return "intish";
case Void:
return "void";
}
MOZ_CRASH(
"Invalid Type");
}
};
static const unsigned VALIDATION_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
class MOZ_STACK_CLASS ModuleValidatorShared {
public:
struct Memory {
MemoryUsage usage;
uint64_t minLength;
uint64_t minPages()
const {
return DivideRoundingUp(minLength, PageSize); }
Memory() =
default;
};
class Func {
TaggedParserAtomIndex name_;
uint32_t sigIndex_;
uint32_t firstUse_;
uint32_t funcDefIndex_;
bool defined_;
// Available when defined:
uint32_t srcBegin_;
uint32_t srcEnd_;
uint32_t line_;
Bytes bytes_;
Uint32Vector callSiteLineNums_;
public:
Func(TaggedParserAtomIndex name, uint32_t sigIndex, uint32_t firstUse,
uint32_t funcDefIndex)
: name_(name),
sigIndex_(sigIndex),
firstUse_(firstUse),
funcDefIndex_(funcDefIndex),
defined_(
false),
srcBegin_(0),
srcEnd_(0),
line_(0) {}
TaggedParserAtomIndex name()
const {
return name_; }
uint32_t sigIndex()
const {
return sigIndex_; }
uint32_t firstUse()
const {
return firstUse_; }
bool defined()
const {
return defined_; }
uint32_t funcDefIndex()
const {
return funcDefIndex_; }
void define(ParseNode* fn, uint32_t line, Bytes&& bytes,
Uint32Vector&& callSiteLineNums) {
MOZ_ASSERT(!defined_);
defined_ =
true;
srcBegin_ = fn->pn_pos.begin;
srcEnd_ = fn->pn_pos.end;
line_ = line;
bytes_ = std::move(bytes);
callSiteLineNums_ = std::move(callSiteLineNums);
}
uint32_t srcBegin()
const {
MOZ_ASSERT(defined_);
return srcBegin_;
}
uint32_t srcEnd()
const {
MOZ_ASSERT(defined_);
return srcEnd_;
}
uint32_t line()
const {
MOZ_ASSERT(defined_);
return line_;
}
const Bytes& bytes()
const {
MOZ_ASSERT(defined_);
return bytes_;
}
Uint32Vector& callSiteLineNums() {
MOZ_ASSERT(defined_);
return callSiteLineNums_;
}
};
using ConstFuncVector = Vector<
const Func*>;
using FuncVector = Vector<Func>;
class Table {
uint32_t sigIndex_;
TaggedParserAtomIndex name_;
uint32_t firstUse_;
uint32_t mask_;
bool defined_;
public:
Table(uint32_t sigIndex, TaggedParserAtomIndex name, uint32_t firstUse,
uint32_t mask)
: sigIndex_(sigIndex),
name_(name),
firstUse_(firstUse),
mask_(mask),
defined_(
false) {}
Table(Table&& rhs) =
delete;
uint32_t sigIndex()
const {
return sigIndex_; }
TaggedParserAtomIndex name()
const {
return name_; }
uint32_t firstUse()
const {
return firstUse_; }
unsigned mask()
const {
return mask_; }
bool defined()
const {
return defined_; }
void define() {
MOZ_ASSERT(!defined_);
defined_ =
true;
}
};
using TableVector = Vector<Table*>;
class Global {
public:
enum Which {
Variable,
ConstantLiteral,
ConstantImport,
Function,
Table,
FFI,
ArrayView,
ArrayViewCtor,
MathBuiltinFunction
};
private:
Which which_;
union U {
struct VarOrConst {
Type::Which type_;
unsigned index_;
NumLit literalValue_;
VarOrConst(
unsigned index,
const NumLit& lit)
: type_(Type::lit(lit).which()),
index_(index),
literalValue_(lit)
// copies |lit|
{}
VarOrConst(
unsigned index, Type::Which which)
: type_(which), index_(index) {
// The |literalValue_| field remains unused and
// uninitialized for non-constant variables.
}
explicit VarOrConst(
double constant)
: type_(Type::
Double),
literalValue_(NumLit::
Double, DoubleValue(constant)) {
// The index_ field is unused and uninitialized for
// constant doubles.
}
} varOrConst;
uint32_t funcDefIndex_;
uint32_t tableIndex_;
uint32_t ffiIndex_;
Scalar::Type viewType_;
AsmJSMathBuiltinFunction mathBuiltinFunc_;
// |varOrConst|, through |varOrConst.literalValue_|, has a
// non-trivial constructor and therefore MUST be placement-new'd
// into existence.
MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
U() : funcDefIndex_(0) {}
MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
} u;
friend class ModuleValidatorShared;
template <
typename Unit>
friend class ModuleValidator;
friend class js::LifoAlloc;
explicit Global(Which which) : which_(which) {}
public:
Which which()
const {
return which_; }
Type varOrConstType()
const {
MOZ_ASSERT(which_ == Variable || which_ == ConstantLiteral ||
which_ == ConstantImport);
return u.varOrConst.type_;
}
unsigned varOrConstIndex()
const {
MOZ_ASSERT(which_ == Variable || which_ == ConstantImport);
return u.varOrConst.index_;
}
bool isConst()
const {
return which_ == ConstantLiteral || which_ == ConstantImport;
}
NumLit constLiteralValue()
const {
MOZ_ASSERT(which_ == ConstantLiteral);
return u.varOrConst.literalValue_;
}
uint32_t funcDefIndex()
const {
MOZ_ASSERT(which_ == Function);
return u.funcDefIndex_;
}
uint32_t tableIndex()
const {
MOZ_ASSERT(which_ == Table);
return u.tableIndex_;
}
unsigned ffiIndex()
const {
MOZ_ASSERT(which_ == FFI);
return u.ffiIndex_;
}
Scalar::Type viewType()
const {
MOZ_ASSERT(which_ == ArrayView || which_ == ArrayViewCtor);
return u.viewType_;
}
bool isMathFunction()
const {
return which_ == MathBuiltinFunction; }
AsmJSMathBuiltinFunction mathBuiltinFunction()
const {
MOZ_ASSERT(which_ == MathBuiltinFunction);
return u.mathBuiltinFunc_;
}
};
struct MathBuiltin {
enum Kind { Function, Constant };
Kind kind;
union {
double cst;
AsmJSMathBuiltinFunction func;
} u;
MathBuiltin() : kind(Kind(-1)), u{} {}
explicit MathBuiltin(
double cst) : kind(Constant) { u.cst = cst; }
explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) {
u.func = func;
}
};
struct ArrayView {
ArrayView(TaggedParserAtomIndex name, Scalar::Type type)
: name(name), type(type) {}
TaggedParserAtomIndex name;
Scalar::Type type;
};
protected:
class HashableSig {
uint32_t sigIndex_;
const TypeContext& types_;
public:
HashableSig(uint32_t sigIndex,
const TypeContext& types)
: sigIndex_(sigIndex), types_(types) {}
uint32_t sigIndex()
const {
return sigIndex_; }
const FuncType& funcType()
const {
return types_[sigIndex_].funcType(); }
// Implement HashPolicy:
using Lookup =
const FuncType&;
static HashNumber hash(Lookup l) {
return l.hash(nullptr); }
static bool match(HashableSig lhs, Lookup rhs) {
return FuncType::strictlyEquals(lhs.funcType(), rhs);
}
};
class NamedSig :
public HashableSig {
TaggedParserAtomIndex name_;
public:
NamedSig(TaggedParserAtomIndex name, uint32_t sigIndex,
const TypeContext& types)
: HashableSig(sigIndex, types), name_(name) {}
TaggedParserAtomIndex name()
const {
return name_; }
// Implement HashPolicy:
struct Lookup {
TaggedParserAtomIndex name;
const FuncType& funcType;
Lookup(TaggedParserAtomIndex name,
const FuncType& funcType)
: name(name), funcType(funcType) {}
};
static HashNumber hash(Lookup l) {
return HashGeneric(TaggedParserAtomIndexHasher::hash(l.name),
l.funcType.hash(nullptr));
}
static bool match(NamedSig lhs, Lookup rhs) {
return lhs.name() == rhs.name &&
FuncType::strictlyEquals(lhs.funcType(), rhs.funcType);
}
};
using SigSet = HashSet<HashableSig, HashableSig>;
using FuncImportMap = HashMap<NamedSig, uint32_t, NamedSig>;
using GlobalMap =
HashMap<TaggedParserAtomIndex, Global*, TaggedParserAtomIndexHasher>;
using MathNameMap =
HashMap<TaggedParserAtomIndex, MathBuiltin, TaggedParserAtomIndexHasher>;
using ArrayViewVector = Vector<ArrayView>;
protected:
FrontendContext* fc_;
ParserAtomsTable& parserAtoms_;
FunctionNode* moduleFunctionNode_;
TaggedParserAtomIndex moduleFunctionName_;
TaggedParserAtomIndex globalArgumentName_;
TaggedParserAtomIndex importArgumentName_;
TaggedParserAtomIndex bufferArgumentName_;
MathNameMap standardLibraryMathNames_;
// Validation-internal state:
LifoAlloc validationLifo_;
Memory memory_;
FuncVector funcDefs_;
TableVector tables_;
GlobalMap globalMap_;
SigSet sigSet_;
FuncImportMap funcImportMap_;
ArrayViewVector arrayViews_;
// State used to build the AsmJSModule in finish():
CompilerEnvironment compilerEnv_;
MutableModuleMetadata moduleMeta_;
MutableCodeMetadata codeMeta_;
MutableCodeMetadataForAsmJSImpl codeMetaForAsmJS_;
// Error reporting:
UniqueChars errorString_ = nullptr;
uint32_t errorOffset_ = UINT32_MAX;
bool errorOverRecursed_ =
false;
protected:
ModuleValidatorShared(FrontendContext* fc, ParserAtomsTable& parserAtoms,
MutableModuleMetadata moduleMeta,
MutableCodeMetadata codeMeta,
FunctionNode* moduleFunctionNode)
: fc_(fc),
parserAtoms_(parserAtoms),
moduleFunctionNode_(moduleFunctionNode),
moduleFunctionName_(FunctionName(moduleFunctionNode)),
standardLibraryMathNames_(fc),
validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE, js::MallocArena),
funcDefs_(fc),
tables_(fc),
globalMap_(fc),
sigSet_(fc),
funcImportMap_(fc),
arrayViews_(fc),
compilerEnv_(CompileMode::Once, Tier::Optimized, DebugEnabled::
False),
moduleMeta_(moduleMeta),
codeMeta_(codeMeta) {
compilerEnv_.computeParameters();
memory_.minLength = RoundUpToNextValidAsmJSHeapLength(0);
}
protected:
[[nodiscard]]
bool addStandardLibraryMathInfo() {
static constexpr
struct {
const char* name;
AsmJSMathBuiltinFunction func;
} functions[] = {
{
"sin", AsmJSMathBuiltin_sin}, {
"cos", AsmJSMathBuiltin_cos},
{
"tan", AsmJSMathBuiltin_tan}, {
"asin", AsmJSMathBuiltin_asin},
{
"acos", AsmJSMathBuiltin_acos}, {
"atan", AsmJSMathBuiltin_atan},
{
"ceil", AsmJSMathBuiltin_ceil}, {
"floor", AsmJSMathBuiltin_floor},
{
"exp", AsmJSMathBuiltin_exp}, {
"log", AsmJSMathBuiltin_log},
{
"pow", AsmJSMathBuiltin_pow}, {
"sqrt", AsmJSMathBuiltin_sqrt},
{
"abs", AsmJSMathBuiltin_abs}, {
"atan2", AsmJSMathBuiltin_atan2},
{
"imul", AsmJSMathBuiltin_imul}, {
"clz32", AsmJSMathBuiltin_clz32},
{
"fround", AsmJSMathBuiltin_fround}, {
"min", AsmJSMathBuiltin_min},
{
"max", AsmJSMathBuiltin_max},
};
auto AddMathFunction = [
this](
const char* name,
AsmJSMathBuiltinFunction func) {
auto atom = parserAtoms_.internAscii(fc_, name, strlen(name));
if (!atom) {
return false;
}
MathBuiltin builtin(func);
return this->standardLibraryMathNames_.putNew(atom, builtin);
};
for (
const auto& info : functions) {
if (!AddMathFunction(info.name, info.func)) {
return false;
}
}
static constexpr
struct {
const char* name;
double value;
} constants[] = {
{
"E", M_E},
{
"LN10", M_LN10},
{
"LN2", M_LN2},
{
"LOG2E", M_LOG2E},
{
"LOG10E", M_LOG10E},
{
"PI", M_PI},
{
"SQRT1_2", M_SQRT1_2},
{
"SQRT2", M_SQRT2},
};
auto AddMathConstant = [
this](
const char* name,
double cst) {
auto atom = parserAtoms_.internAscii(fc_, name, strlen(name));
if (!atom) {
return false;
}
MathBuiltin builtin(cst);
return this->standardLibraryMathNames_.putNew(atom, builtin);
};
for (
const auto& info : constants) {
if (!AddMathConstant(info.name, info.value)) {
return false;
}
}
return true;
}
public:
FrontendContext* fc()
const {
return fc_; }
TaggedParserAtomIndex moduleFunctionName()
const {
return moduleFunctionName_;
}
TaggedParserAtomIndex globalArgumentName()
const {
return globalArgumentName_;
}
TaggedParserAtomIndex importArgumentName()
const {
return importArgumentName_;
}
TaggedParserAtomIndex bufferArgumentName()
const {
return bufferArgumentName_;
}
const CodeMetadata* codeMeta() {
return codeMeta_; }
const ModuleMetadata* moduleMeta() {
return moduleMeta_; }
void initModuleFunctionName(TaggedParserAtomIndex name) {
MOZ_ASSERT(!moduleFunctionName_);
moduleFunctionName_ = name;
}
[[nodiscard]]
bool initGlobalArgumentName(TaggedParserAtomIndex n) {
globalArgumentName_ = n;
if (n) {
codeMetaForAsmJS_->globalArgumentName =
parserAtoms_.toNewUTF8CharsZ(fc_, n);
if (!codeMetaForAsmJS_->globalArgumentName) {
return false;
}
}
return true;
}
[[nodiscard]]
bool initImportArgumentName(TaggedParserAtomIndex n) {
importArgumentName_ = n;
if (n) {
codeMetaForAsmJS_->importArgumentName =
parserAtoms_.toNewUTF8CharsZ(fc_, n);
if (!codeMetaForAsmJS_->importArgumentName) {
return false;
}
}
return true;
}
[[nodiscard]]
bool initBufferArgumentName(TaggedParserAtomIndex n) {
bufferArgumentName_ = n;
if (n) {
codeMetaForAsmJS_->bufferArgumentName =
parserAtoms_.toNewUTF8CharsZ(fc_, n);
if (!codeMetaForAsmJS_->bufferArgumentName) {
return false;
}
}
return true;
}
bool addGlobalVarInit(TaggedParserAtomIndex var,
const NumLit& lit, Type type,
bool isConst) {
MOZ_ASSERT(type.isGlobalVarType());
MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit)));
uint32_t index = codeMeta_->globals.length();
if (!codeMeta_->globals.emplaceBack(type.canonicalToValType(), !isConst,
index, ModuleKind::AsmJS)) {
return false;
}
Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable;
Global* global = validationLifo_.new_<Global>(which);
if (!global) {
return false;
}
if (isConst) {
new (&global->u.varOrConst) Global::U::VarOrConst(index, lit);
}
else {
new (&global->u.varOrConst) Global::U::VarOrConst(index, type.which());
}
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::Variable, nullptr);
g.pod.u.var.initKind_ = AsmJSGlobal::InitConstant;
g.pod.u.var.u.val_ = lit.value();
return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
}
bool addGlobalVarImport(TaggedParserAtomIndex var,
TaggedParserAtomIndex field, Type type,
bool isConst) {
MOZ_ASSERT(type.isGlobalVarType());
UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
if (!fieldChars) {
return false;
}
uint32_t index = codeMeta_->globals.length();
ValType valType = type.canonicalToValType();
if (!codeMeta_->globals.emplaceBack(valType, !isConst, index,
ModuleKind::AsmJS)) {
return false;
}
Global::Which which = isConst ? Global::ConstantImport : Global::Variable;
Global* global = validationLifo_.new_<Global>(which);
if (!global) {
return false;
}
new (&global->u.varOrConst) Global::U::VarOrConst(index, type.which());
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::Variable, std::move(fieldChars));
g.pod.u.var.initKind_ = AsmJSGlobal::InitImport;
g.pod.u.var.u.importValType_ = valType.packed();
return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
}
bool addArrayView(TaggedParserAtomIndex var, Scalar::Type vt,
TaggedParserAtomIndex maybeField) {
UniqueChars fieldChars;
if (maybeField) {
fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField);
if (!fieldChars) {
return false;
}
}
if (!arrayViews_.append(ArrayView(var, vt))) {
return false;
}
Global* global = validationLifo_.new_<Global>(Global::ArrayView);
if (!global) {
return false;
}
new (&global->u.viewType_) Scalar::Type(vt);
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::ArrayView, std::move(fieldChars));
g.pod.u.viewType_ = vt;
return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
}
bool addMathBuiltinFunction(TaggedParserAtomIndex var,
AsmJSMathBuiltinFunction func,
TaggedParserAtomIndex field) {
UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
if (!fieldChars) {
return false;
}
Global* global = validationLifo_.new_<Global>(Global::MathBuiltinFunction);
if (!global) {
return false;
}
new (&global->u.mathBuiltinFunc_) AsmJSMathBuiltinFunction(func);
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::MathBuiltinFunction, std::move(fieldChars));
g.pod.u.mathBuiltinFunc_ = func;
return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
}
private:
bool addGlobalDoubleConstant(TaggedParserAtomIndex var,
double constant) {
Global* global = validationLifo_.new_<Global>(Global::ConstantLiteral);
if (!global) {
return false;
}
new (&global->u.varOrConst) Global::U::VarOrConst(constant);
return globalMap_.putNew(var, global);
}
public:
bool addMathBuiltinConstant(TaggedParserAtomIndex var,
double constant,
TaggedParserAtomIndex field) {
UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
if (!fieldChars) {
return false;
}
if (!addGlobalDoubleConstant(var, constant)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::Constant, std::move(fieldChars));
g.pod.u.constant.value_ = constant;
g.pod.u.constant.kind_ = AsmJSGlobal::MathConstant;
return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
}
bool addGlobalConstant(TaggedParserAtomIndex var,
double constant,
TaggedParserAtomIndex field) {
UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
if (!fieldChars) {
return false;
}
if (!addGlobalDoubleConstant(var, constant)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::Constant, std::move(fieldChars));
g.pod.u.constant.value_ = constant;
g.pod.u.constant.kind_ = AsmJSGlobal::GlobalConstant;
return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
}
bool addArrayViewCtor(TaggedParserAtomIndex var, Scalar::Type vt,
TaggedParserAtomIndex field) {
UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
if (!fieldChars) {
return false;
}
Global* global = validationLifo_.new_<Global>(Global::ArrayViewCtor);
if (!global) {
return false;
}
new (&global->u.viewType_) Scalar::Type(vt);
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::ArrayViewCtor, std::move(fieldChars));
g.pod.u.viewType_ = vt;
return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
}
bool addFFI(TaggedParserAtomIndex var, TaggedParserAtomIndex field) {
UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
if (!fieldChars) {
return false;
}
if (codeMetaForAsmJS_->numFFIs == UINT32_MAX) {
return false;
}
uint32_t ffiIndex = codeMetaForAsmJS_->numFFIs++;
Global* global = validationLifo_.new_<Global>(Global::FFI);
if (!global) {
return false;
}
new (&global->u.ffiIndex_) uint32_t(ffiIndex);
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::FFI, std::move(fieldChars));
g.pod.u.ffiIndex_ = ffiIndex;
return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
}
bool addExportField(
const Func& func, TaggedParserAtomIndex maybeField) {
// Record the field name of this export.
CacheableName fieldName;
if (maybeField) {
UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField);
if (!fieldChars) {
return false;
}
fieldName = CacheableName::fromUTF8Chars(std::move(fieldChars));
}
// Declare which function is exported which gives us an index into the
// module ExportVector.
uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
if (!moduleMeta_->exports.emplaceBack(std::move(fieldName), funcIndex,
DefinitionKind::Function)) {
return false;
}
// The exported function might have already been exported in which case
// the index will refer into the range of AsmJSExports.
return codeMetaForAsmJS_->asmJSExports.emplaceBack(
funcIndex, func.srcBegin() - codeMetaForAsmJS_->srcStart,
func.srcEnd() - codeMetaForAsmJS_->srcStart);
}
bool defineFuncPtrTable(uint32_t tableIndex, Uint32Vector&& elems) {
Table& table = *tables_[tableIndex];
if (table.
defined()) {
return false;
}
table.define();
for (uint32_t& index : elems) {
index += funcImportMap_.count();
}
ModuleElemSegment seg = ModuleElemSegment();
seg.elemType = RefType::func();
seg.tableIndex = tableIndex;
seg.offsetIfActive = Some(InitExpr(LitVal(uint32_t(0))));
seg.encoding = ModuleElemSegment::Encoding::Indices;
seg.elemIndices = std::move(elems);
bool ok = codeMeta_->elemSegmentTypes.append(seg.elemType) &&
moduleMeta_->elemSegments.append(std::move(seg));
MOZ_ASSERT_IF(ok, codeMeta_->elemSegmentTypes.length() ==
moduleMeta_->elemSegments.length());
return ok;
}
bool tryConstantAccess(uint64_t start, uint64_t width) {
MOZ_ASSERT(UINT64_MAX - start > width);
uint64_t len = start + width;
if (len > uint64_t(INT32_MAX) + 1) {
return false;
}
len = RoundUpToNextValidAsmJSHeapLength(len);
if (len > memory_.minLength) {
memory_.minLength = len;
}
return true;
}
// Error handling.
bool hasAlreadyFailed()
const {
return !!errorString_; }
bool failOffset(uint32_t offset,
const char* str) {
MOZ_ASSERT(!hasAlreadyFailed());
MOZ_ASSERT(errorOffset_ == UINT32_MAX);
MOZ_ASSERT(str);
errorOffset_ = offset;
errorString_ = DuplicateString(str);
return false;
}
bool fail(ParseNode* pn,
const char* str) {
return failOffset(pn->pn_pos.begin, str);
}
bool failfVAOffset(uint32_t offset,
const char* fmt, va_list ap)
MOZ_FORMAT_PRINTF(3, 0) {
MOZ_ASSERT(!hasAlreadyFailed());
MOZ_ASSERT(errorOffset_ == UINT32_MAX);
MOZ_ASSERT(fmt);
errorOffset_ = offset;
errorString_ = JS_vsmprintf(fmt, ap);
return false;
}
bool failfOffset(uint32_t offset,
const char* fmt, ...)
MOZ_FORMAT_PRINTF(3, 4) {
va_list ap;
va_start(ap, fmt);
failfVAOffset(offset, fmt, ap);
va_end(ap);
return false;
}
bool failf(ParseNode* pn,
const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) {
va_list ap;
va_start(ap, fmt);
failfVAOffset(pn->pn_pos.begin, fmt, ap);
va_end(ap);
return false;
}
bool failNameOffset(uint32_t offset,
const char* fmt,
TaggedParserAtomIndex name) {
// This function is invoked without the caller properly rooting its locals.
if (UniqueChars bytes = parserAtoms_.toPrintableString(name)) {
failfOffset(offset, fmt, bytes.get());
}
else {
ReportOutOfMemory(fc_);
}
return false;
}
bool failName(ParseNode* pn,
const char* fmt, TaggedParserAtomIndex name) {
return failNameOffset(pn->pn_pos.begin, fmt, name);
}
bool failOverRecursed() {
errorOverRecursed_ =
true;
return false;
}
unsigned numArrayViews()
const {
return arrayViews_.length(); }
const ArrayView& arrayView(
unsigned i)
const {
return arrayViews_[i]; }
unsigned numFuncDefs()
const {
return funcDefs_.length(); }
const Func& funcDef(
unsigned i)
const {
return funcDefs_[i]; }
unsigned numFuncPtrTables()
const {
return tables_.length(); }
Table& table(
unsigned i)
const {
return *tables_[i]; }
const Global* lookupGlobal(TaggedParserAtomIndex name)
const {
if (GlobalMap::Ptr p = globalMap_.lookup(name)) {
return p->value();
}
return nullptr;
}
Func* lookupFuncDef(TaggedParserAtomIndex name) {
if (GlobalMap::Ptr p = globalMap_.lookup(name)) {
Global* value = p->value();
if (value->which() == Global::Function) {
return &funcDefs_[value->funcDefIndex()];
}
}
return nullptr;
}
bool lookupStandardLibraryMathName(TaggedParserAtomIndex name,
MathBuiltin* mathBuiltin)
const {
if (MathNameMap::Ptr p = standardLibraryMathNames_.lookup(name)) {
*mathBuiltin = p->value();
return true;
}
return false;
}
bool startFunctionBodies() {
if (!arrayViews_.empty()) {
memory_.usage = MemoryUsage::Unshared;
}
else {
memory_.usage = MemoryUsage::None;
}
return true;
}
};
// The ModuleValidator encapsulates the entire validation of an asm.js module.
// Its lifetime goes from the validation of the top components of an asm.js
// module (all the globals), the emission of bytecode for all the functions in
// the module and the validation of function's pointer tables. It also finishes
// the compilation of all the module's stubs.
template <
typename Unit>
class MOZ_STACK_CLASS ModuleValidator :
public ModuleValidatorShared {
private:
AsmJSParser<Unit>& parser_;
public:
ModuleValidator(FrontendContext* fc, ParserAtomsTable& parserAtoms,
MutableModuleMetadata moduleMeta,
MutableCodeMetadata codeMeta, AsmJSParser<Unit>& parser,
FunctionNode* moduleFunctionNode)
: ModuleValidatorShared(fc, parserAtoms, moduleMeta, codeMeta,
moduleFunctionNode),
parser_(parser) {}
~ModuleValidator() {
if (errorString_) {
MOZ_ASSERT(errorOffset_ != UINT32_MAX);
typeFailure(errorOffset_, errorString_.get());
}
if (errorOverRecursed_) {
ReportOverRecursed(fc_);
}
}
private:
// Helpers:
bool newSig(FuncType&& sig, uint32_t* sigIndex) {
if (codeMeta_->types->length() >= MaxTypes) {
return failCurrentOffset(
"too many signatures");
}
*sigIndex = codeMeta_->types->length();
return codeMeta_->types->addType(std::move(sig));
}
bool declareSig(FuncType&& sig, uint32_t* sigIndex) {
SigSet::AddPtr p = sigSet_.lookupForAdd(sig);
if (p) {
*sigIndex = p->sigIndex();
MOZ_ASSERT(FuncType::strictlyEquals(
codeMeta_->types->type(*sigIndex).funcType(), sig));
return true;
}
return newSig(std::move(sig), sigIndex) &&
sigSet_.add(p, HashableSig(*sigIndex, *codeMeta_->types));
}
private:
void typeFailure(uint32_t offset, ...) {
va_list args;
va_start(args, offset);
auto& ts = tokenStream();
ErrorMetadata metadata;
if (ts.computeErrorMetadata(&metadata, AsVariant(offset))) {
if (ts.anyCharsAccess().options().throwOnAsmJSValidationFailure()) {
ReportCompileErrorLatin1VA(fc_, std::move(metadata), nullptr,
JSMSG_USE_ASM_TYPE_FAIL, &args);
}
else {
// asm.js type failure is indicated by calling one of the fail*
// functions below. These functions always return false to
// halt asm.js parsing. Whether normal parsing is attempted as
// fallback, depends whether an exception is also set.
//
// If warning succeeds, no exception is set. If warning fails,
// an exception is set and execution will halt. Thus it's safe
// and correct to ignore the return value here.
(
void)ts.compileWarning(std::move(metadata), nullptr,
JSMSG_USE_ASM_TYPE_FAIL, &args);
}
}
va_end(args);
}
public:
bool init() {
codeMetaForAsmJS_ = js_new<CodeMetadataForAsmJSImpl>();
if (!codeMetaForAsmJS_) {
ReportOutOfMemory(fc_);
return false;
}
codeMetaForAsmJS_->toStringStart =
moduleFunctionNode_->funbox()->extent().toStringStart;
codeMetaForAsmJS_->srcStart = moduleFunctionNode_->body()->pn_pos.begin;
codeMetaForAsmJS_->strict = parser_.pc_->sc()->strict() &&
!parser_.pc_->sc()->hasExplicitUseStrict();
codeMetaForAsmJS_->alwaysUseFdlibm = parser_.options().alwaysUseFdlibm();
codeMetaForAsmJS_->source = do_AddRef(parser_.ss);
return addStandardLibraryMathInfo();
}
AsmJSParser<Unit>& parser()
const {
return parser_; }
auto& tokenStream()
const {
return parser_.tokenStream; }
bool alwaysUseFdlibm()
const {
return codeMetaForAsmJS_->alwaysUseFdlibm; }
public:
bool addFuncDef(TaggedParserAtomIndex name, uint32_t firstUse, FuncType&& sig,
Func** func) {
uint32_t sigIndex;
if (!declareSig(std::move(sig), &sigIndex)) {
return false;
}
uint32_t funcDefIndex = funcDefs_.length();
if (funcDefIndex >= MaxFuncs) {
return failCurrentOffset(
"too many functions");
}
Global* global = validationLifo_.new_<Global>(Global::Function);
if (!global) {
return false;
}
new (&global->u.funcDefIndex_) uint32_t(funcDefIndex);
if (!globalMap_.putNew(name, global)) {
return false;
}
if (!funcDefs_.emplaceBack(name, sigIndex, firstUse, funcDefIndex)) {
return false;
}
*func = &funcDefs_.back();
return true;
}
bool declareFuncPtrTable(FuncType&& sig, TaggedParserAtomIndex name,
uint32_t firstUse, uint32_t mask,
uint32_t* tableIndex) {
if (mask > MaxTableElemsRuntime) {
return failCurrentOffset(
"function pointer table too big");
}
MOZ_ASSERT(codeMeta_->tables.length() == tables_.length());
*tableIndex = codeMeta_->tables.length();
uint32_t sigIndex;
if (!newSig(std::move(sig), &sigIndex)) {
return false;
}
MOZ_ASSERT(sigIndex >= codeMeta_->asmJSSigToTableIndex.length());
if (!codeMeta_->asmJSSigToTableIndex.resize(sigIndex + 1)) {
return false;
}
Limits limits = Limits(mask + 1, Nothing(), Shareable::
False);
codeMeta_->asmJSSigToTableIndex[sigIndex] = codeMeta_->tables.length();
if (!codeMeta_->tables.emplaceBack(limits, RefType::func(),
/* initExpr */ Nothing(),
/*isAsmJS*/ true)) {
return false;
}
Global* global = validationLifo_.new_<Global>(Global::Table);
if (!global) {
return false;
}
new (&global->u.tableIndex_) uint32_t(*tableIndex);
if (!globalMap_.putNew(name, global)) {
return false;
}
Table* t = validationLifo_.new_<Table>(sigIndex, name, firstUse, mask);
return t && tables_.append(t);
}
bool declareImport(TaggedParserAtomIndex name, FuncType&& sig,
unsigned ffiIndex, uint32_t* importIndex) {
if (sig.args().length() > MaxParams) {
return failCurrentOffset(
"too many parameters");
}
FuncImportMap::AddPtr p =
funcImportMap_.lookupForAdd(NamedSig::Lookup(name, sig));
if (p) {
*importIndex = p->value();
return true;
}
*importIndex = funcImportMap_.count();
MOZ_ASSERT(*importIndex == codeMetaForAsmJS_->asmJSImports.length());
if (*importIndex >= MaxImports) {
return failCurrentOffset(
"too many imports");
}
if (!codeMetaForAsmJS_->asmJSImports.emplaceBack(ffiIndex)) {
return false;
}
uint32_t sigIndex;
if (!declareSig(std::move(sig), &sigIndex)) {
return false;
}
return funcImportMap_.add(p, NamedSig(name, sigIndex, *codeMeta_->types),
*importIndex);
}
// Error handling.
bool failCurrentOffset(
const char* str) {
return failOffset(tokenStream().anyCharsAccess().currentToken().pos.begin,
str);
}
SharedModule finish() {
MOZ_ASSERT(codeMeta_->numMemories() == 0);
if (memory_.usage != MemoryUsage::None) {
Limits limits;
limits.shared = memory_.usage == MemoryUsage::Shared ? Shareable::
True
: Shareable::
False;
limits.initial = memory_.minPages();
limits.maximum = Nothing();
limits.addressType = AddressType::I32;
if (!codeMeta_->memories.append(MemoryDesc(limits))) {
return nullptr;
}
}
MOZ_ASSERT(codeMeta_->funcs.empty());
if (!codeMeta_->funcs.resize(funcImportMap_.count() + funcDefs_.length())) {
return nullptr;
}
for (FuncImportMap::Range r = funcImportMap_.all(); !r.empty();
r.popFront()) {
uint32_t funcIndex = r.front().value();
uint32_t funcTypeIndex = r.front().key().sigIndex();
codeMeta_->funcs[funcIndex] = FuncDesc(funcTypeIndex);
}
for (
const Func& func : funcDefs_) {
uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
uint32_t funcTypeIndex = func.sigIndex();
codeMeta_->funcs[funcIndex] = FuncDesc(funcTypeIndex);
}
for (
const Export& exp : moduleMeta_->exports) {
if (exp.kind() != DefinitionKind::Function) {
continue;
}
uint32_t funcIndex = exp.funcIndex();
codeMeta_->funcs[funcIndex].declareFuncExported(
/* eager */ true,
/* canRefFunc */ false);
}
codeMeta_->numFuncImports = funcImportMap_.count();
// All globals (inits and imports) are imports from Wasm point of view.
codeMeta_->numGlobalImports = codeMeta_->globals.length();
MOZ_ASSERT(codeMetaForAsmJS_->asmJSFuncNames.empty());
if (!codeMetaForAsmJS_->asmJSFuncNames.resize(funcImportMap_.count())) {
return nullptr;
}
--> --------------------
--> maximum size reached
--> --------------------