Source code

Revision control

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/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Compression.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h" // SprintfLiteral
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "mozilla/Variant.h"
#include <algorithm>
#include <new>
#include "jsmath.h"
#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
#include "frontend/ParseNode.h"
#include "frontend/Parser.h"
#include "gc/Policy.h"
#include "js/BuildId.h" // JS::BuildIdCharVector
#include "js/MemoryMetrics.h"
#include "js/Printf.h"
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "util/StringBuffer.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/SelfHosting.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "vm/Warnings.h" // js::WarnNumberASCII
#include "wasm/WasmCompile.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmSerialize.h"
#include "wasm/WasmValidate.h"
#include "frontend/SharedContext-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/JSObject-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::SourceOwnership;
using JS::SourceText;
using mozilla::Abs;
using mozilla::ArrayEqual;
using mozilla::AsVariant;
using mozilla::CeilingLog2;
using mozilla::HashGeneric;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using mozilla::IsPositiveZero;
using mozilla::IsPowerOfTwo;
using mozilla::PodZero;
using mozilla::PositiveInfinity;
using mozilla::Unused;
using mozilla::Utf8Unit;
using mozilla::Compression::LZ4;
/*****************************************************************************/
// 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;
static uint64_t RoundUpToNextValidAsmJSHeapLength(uint64_t length) {
if (length <= MinHeapLength) {
return MinHeapLength;
}
return wasm::RoundUpToNextValidARMImmediate(length);
}
/*****************************************************************************/
// 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 (UnpackTypeCodeType(valType_)) {
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_;
}
};
typedef Vector<AsmJSGlobal, 0, SystemAllocPolicy> AsmJSGlobalVector;
// 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_; }
};
typedef Vector<AsmJSImport, 0, SystemAllocPolicy> AsmJSImportVector;
// 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_; }
};
typedef Vector<AsmJSExport, 0, SystemAllocPolicy> AsmJSExportVector;
// Holds the immutable guts of an AsmJSModule.
//
// AsmJSMetadata is built incrementally by ModuleValidator and then shared
// immutably between AsmJSModules.
struct AsmJSMetadataCacheablePod {
uint32_t numFFIs = 0;
uint32_t srcLength = 0;
uint32_t srcLengthWithRightBrace = 0;
AsmJSMetadataCacheablePod() = default;
};
struct js::AsmJSMetadata : Metadata, AsmJSMetadataCacheablePod {
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;
ScriptSourceHolder scriptSource;
uint32_t srcEndBeforeCurly() const { return srcStart + srcLength; }
uint32_t srcEndAfterCurly() const {
return srcStart + srcLengthWithRightBrace;
}
AsmJSMetadata()
: Metadata(ModuleKind::AsmJS),
toStringStart(0),
srcStart(0),
strict(false) {}
~AsmJSMetadata() override = default;
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 override {
return scriptSource.get()->mutedErrors();
}
const char16_t* displayURL() const override {
return scriptSource.get()->hasDisplayURL()
? scriptSource.get()->displayURL()
: nullptr;
}
ScriptSource* maybeScriptSource() const override {
return scriptSource.get();
}
bool getFuncName(NameContext ctx, uint32_t funcIndex,
UTF8Bytes* name) const override {
const char* p = asmJSFuncNames[funcIndex].get();
if (!p) {
return true;
}
return name->append(p, strlen(p));
}
AsmJSMetadataCacheablePod& pod() { return *this; }
const AsmJSMetadataCacheablePod& pod() const { return *this; }
};
using MutableAsmJSMetadata = RefPtr<AsmJSMetadata>;
/*****************************************************************************/
// 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 PropertyName* LoopControlMaybeLabel(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::BreakStmt) ||
pn->isKind(ParseNodeKind::ContinueStmt));
return pn->as<LoopControlStatement>().label();
}
static inline PropertyName* 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 PropertyName* 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 PropertyName* FunctionName(FunctionNode* funNode) {
if (JSAtom* name = funNode->funbox()->explicitName()) {
return name->asPropertyName();
}
return nullptr;
}
static inline ParseNode* FunctionStatementList(FunctionNode* funNode) {
MOZ_ASSERT(funNode->body()->isKind(ParseNodeKind::ParamsBody));
LexicalScopeNode* last =
&funNode->body()->as<ListNode>().last()->as<LexicalScopeNode>();
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 PropertyName* ObjectNormalFieldName(ParseNode* pn) {
MOZ_ASSERT(IsNormalObjectField(pn));
MOZ_ASSERT(BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName));
return BinaryLeft(pn)->as<NameNode>().atom()->asPropertyName();
}
static inline ParseNode* ObjectNormalFieldInitializer(ParseNode* pn) {
MOZ_ASSERT(IsNormalObjectField(pn));
return BinaryRight(pn);
}
static inline bool IsUseOfName(ParseNode* pn, PropertyName* name) {
return pn->isName(name);
}
static inline bool IsIgnoredDirectiveName(JSContext* cx, JSAtom* atom) {
return atom != cx->names().useStrict;
}
static inline bool IsIgnoredDirective(JSContext* cx, ParseNode* pn) {
return pn->isKind(ParseNodeKind::ExpressionStmt) &&
UnaryKid(pn)->isKind(ParseNodeKind::StringExpr) &&
IsIgnoredDirectiveName(cx, 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;
}
*var = parser.statementListItem(YieldIsName);
if (!*var) {
return 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_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("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 JS_HAZ_ROOTED ModuleValidatorShared {
public:
class Func {
PropertyName* 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(PropertyName* 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) {}
PropertyName* 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_;
PropertyName* name_;
uint32_t firstUse_;
uint32_t mask_;
bool defined_;
Table(Table&& rhs) = delete;
public:
Table(uint32_t sigIndex, PropertyName* name, uint32_t firstUse,
uint32_t mask)
: sigIndex_(sigIndex),
name_(name),
firstUse_(firstUse),
mask_(mask),
defined_(false) {}
uint32_t sigIndex() const { return sigIndex_; }
PropertyName* 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(PropertyName* name, Scalar::Type type) : name(name), type(type) {}
PropertyName* name;
Scalar::Type type;
};
protected:
class HashableSig {
uint32_t sigIndex_;
const TypeDefVector& types_;
public:
HashableSig(uint32_t sigIndex, const TypeDefVector& 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(); }
static bool match(HashableSig lhs, Lookup rhs) {
return lhs.funcType() == rhs;
}
};
class NamedSig : public HashableSig {
PropertyName* name_;
public:
NamedSig(PropertyName* name, uint32_t sigIndex, const TypeDefVector& types)
: HashableSig(sigIndex, types), name_(name) {}
PropertyName* name() const { return name_; }
// Implement HashPolicy:
struct Lookup {
PropertyName* name;
const FuncType& funcType;
Lookup(PropertyName* name, const FuncType& funcType)
: name(name), funcType(funcType) {}
};
static HashNumber hash(Lookup l) {
return HashGeneric(l.name, l.funcType.hash());
}
static bool match(NamedSig lhs, Lookup rhs) {
return lhs.name() == rhs.name && lhs.funcType() == rhs.funcType;
}
};
using SigSet = HashSet<HashableSig, HashableSig>;
using FuncImportMap = HashMap<NamedSig, uint32_t, NamedSig>;
using GlobalMap = HashMap<PropertyName*, Global*>;
using MathNameMap = HashMap<PropertyName*, MathBuiltin>;
using ArrayViewVector = Vector<ArrayView>;
protected:
JSContext* cx_;
FunctionNode* moduleFunctionNode_;
PropertyName* moduleFunctionName_;
PropertyName* globalArgumentName_ = nullptr;
PropertyName* importArgumentName_ = nullptr;
PropertyName* bufferArgumentName_ = nullptr;
MathNameMap standardLibraryMathNames_;
// Validation-internal state:
LifoAlloc validationLifo_;
FuncVector funcDefs_;
TableVector tables_;
GlobalMap globalMap_;
SigSet sigSet_;
FuncImportMap funcImportMap_;
ArrayViewVector arrayViews_;
// State used to build the AsmJSModule in finish():
CompilerEnvironment compilerEnv_;
ModuleEnvironment env_;
MutableAsmJSMetadata asmJSMetadata_;
// Error reporting:
UniqueChars errorString_ = nullptr;
uint32_t errorOffset_ = UINT32_MAX;
bool errorOverRecursed_ = false;
protected:
ModuleValidatorShared(JSContext* cx, FunctionNode* moduleFunctionNode)
: cx_(cx),
moduleFunctionNode_(moduleFunctionNode),
moduleFunctionName_(FunctionName(moduleFunctionNode)),
standardLibraryMathNames_(cx),
validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE),
funcDefs_(cx),
tables_(cx),
globalMap_(cx),
sigSet_(cx),
funcImportMap_(cx),
arrayViews_(cx),
compilerEnv_(CompileMode::Once, Tier::Optimized, OptimizedBackend::Ion,
DebugEnabled::False, /* multi value */ false,
/* ref types */ false, /* gc types */ false,
/* huge memory */ false, /* v128 */ false),
env_(&compilerEnv_, Shareable::False, ModuleKind::AsmJS) {
compilerEnv_.computeParameters();
env_.minMemoryLength = RoundUpToNextValidAsmJSHeapLength(0);
}
protected:
MOZ_MUST_USE 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) {
JSAtom* atom = Atomize(cx_, name, strlen(name));
if (!atom) {
return false;
}
MathBuiltin builtin(func);
return this->standardLibraryMathNames_.putNew(atom->asPropertyName(),
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) {
JSAtom* atom = Atomize(cx_, name, strlen(name));
if (!atom) {
return false;
}
MathBuiltin builtin(cst);
return this->standardLibraryMathNames_.putNew(atom->asPropertyName(),
builtin);
};
for (const auto& info : constants) {
if (!AddMathConstant(info.name, info.value)) {
return false;
}
}
return true;
}
public:
JSContext* cx() const { return cx_; }
PropertyName* moduleFunctionName() const { return moduleFunctionName_; }
PropertyName* globalArgumentName() const { return globalArgumentName_; }
PropertyName* importArgumentName() const { return importArgumentName_; }
PropertyName* bufferArgumentName() const { return bufferArgumentName_; }
const ModuleEnvironment& env() { return env_; }
uint64_t minMemoryLength() const { return env_.minMemoryLength; }
void initModuleFunctionName(PropertyName* name) {
MOZ_ASSERT(!moduleFunctionName_);
moduleFunctionName_ = name;
}
MOZ_MUST_USE bool initGlobalArgumentName(PropertyName* n) {
globalArgumentName_ = n;
if (n) {
MOZ_ASSERT(n->isTenured());
asmJSMetadata_->globalArgumentName = StringToNewUTF8CharsZ(cx_, *n);
if (!asmJSMetadata_->globalArgumentName) {
return false;
}
}
return true;
}
MOZ_MUST_USE bool initImportArgumentName(PropertyName* n) {
importArgumentName_ = n;
if (n) {
MOZ_ASSERT(n->isTenured());
asmJSMetadata_->importArgumentName = StringToNewUTF8CharsZ(cx_, *n);
if (!asmJSMetadata_->importArgumentName) {
return false;
}
}
return true;
}
MOZ_MUST_USE bool initBufferArgumentName(PropertyName* n) {
bufferArgumentName_ = n;
if (n) {
MOZ_ASSERT(n->isTenured());
asmJSMetadata_->bufferArgumentName = StringToNewUTF8CharsZ(cx_, *n);
if (!asmJSMetadata_->bufferArgumentName) {
return false;
}
}
return true;
}
bool addGlobalVarInit(PropertyName* var, const NumLit& lit, Type type,
bool isConst) {
MOZ_ASSERT(type.isGlobalVarType());
MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit)));
uint32_t index = env_.globals.length();
if (!env_.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 asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addGlobalVarImport(PropertyName* var, PropertyName* field, Type type,
bool isConst) {
MOZ_ASSERT(type.isGlobalVarType());
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
if (!fieldChars) {
return false;
}
uint32_t index = env_.globals.length();
ValType valType = type.canonicalToValType();
if (!env_.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 asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addArrayView(PropertyName* var, Scalar::Type vt,
PropertyName* maybeField) {
UniqueChars fieldChars;
if (maybeField) {
fieldChars = StringToNewUTF8CharsZ(cx_, *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 asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addMathBuiltinFunction(PropertyName* var, AsmJSMathBuiltinFunction func,
PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *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 asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
private:
bool addGlobalDoubleConstant(PropertyName* 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(PropertyName* var, double constant,
PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *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 asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addGlobalConstant(PropertyName* var, double constant,
PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *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 asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addArrayViewCtor(PropertyName* var, Scalar::Type vt,
PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *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 asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addFFI(PropertyName* var, PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
if (!fieldChars) {
return false;
}
if (asmJSMetadata_->numFFIs == UINT32_MAX) {
return false;
}
uint32_t ffiIndex = asmJSMetadata_->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 asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addExportField(const Func& func, PropertyName* maybeField) {
// Record the field name of this export.
CacheableChars fieldChars;
if (maybeField) {
fieldChars = StringToNewUTF8CharsZ(cx_, *maybeField);
} else {
fieldChars = DuplicateString("");
}
if (!fieldChars) {
return false;
}
// Declare which function is exported which gives us an index into the
// module ExportVector.
uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
if (!env_.exports.emplaceBack(std::move(fieldChars), 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 asmJSMetadata_->asmJSExports.emplaceBack(
funcIndex, func.srcBegin() - asmJSMetadata_->srcStart,
func.srcEnd() - asmJSMetadata_->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();
}
MutableElemSegment seg = js_new<ElemSegment>();
if (!seg) {
return false;
}
seg->tableIndex = tableIndex;
seg->offsetIfActive = Some(InitExpr::fromConstant(LitVal(uint32_t(0))));
seg->elemFuncIndices = std::move(elems);
return env_.elemSegments.append(std::move(seg));
}
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 > env_.minMemoryLength) {
env_.minMemoryLength = 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;