Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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
*
*
* 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 {