Bug 1842773 - Part 2: Add "resizable" flag to ArrayBuffer objects. r=sfink
[gecko.git] / js / src / wasm / AsmJS.cpp
blob11a0c2b23d245258b1af0ed03f8f68a3749c533f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
4 * Copyright 2014 Mozilla Foundation
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 #include "wasm/AsmJS.h"
21 #include "mozilla/Attributes.h"
22 #include "mozilla/Compression.h"
23 #include "mozilla/MathAlgorithms.h"
24 #include "mozilla/Maybe.h"
25 #include "mozilla/ScopeExit.h"
26 #include "mozilla/Sprintf.h" // SprintfLiteral
27 #include "mozilla/Try.h" // MOZ_TRY*
28 #include "mozilla/Utf8.h" // mozilla::Utf8Unit
29 #include "mozilla/Variant.h"
31 #include <algorithm>
32 #include <new>
34 #include "jsmath.h"
36 #include "frontend/BytecodeCompiler.h" // CompileStandaloneFunction
37 #include "frontend/FrontendContext.h" // js::FrontendContext
38 #include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
39 #include "frontend/ParseNode.h"
40 #include "frontend/Parser-macros.h" // MOZ_TRY_*
41 #include "frontend/Parser.h"
42 #include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
43 #include "frontend/SharedContext.h" // TopLevelFunction
44 #include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
45 #include "gc/GC.h"
46 #include "gc/Policy.h"
47 #include "jit/InlinableNatives.h"
48 #include "js/BuildId.h" // JS::BuildIdCharVector
49 #include "js/experimental/JitInfo.h"
50 #include "js/friend/ErrorMessages.h" // JSMSG_*
51 #include "js/MemoryMetrics.h"
52 #include "js/Printf.h"
53 #include "js/ScalarType.h" // js::Scalar::Type
54 #include "js/SourceText.h"
55 #include "js/StableStringChars.h"
56 #include "js/Wrapper.h"
57 #include "util/DifferentialTesting.h"
58 #include "util/StringBuffer.h"
59 #include "util/Text.h"
60 #include "vm/ErrorReporting.h"
61 #include "vm/FunctionFlags.h" // js::FunctionFlags
62 #include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
63 #include "vm/Interpreter.h"
64 #include "vm/SelfHosting.h"
65 #include "vm/Time.h"
66 #include "vm/TypedArrayObject.h"
67 #include "vm/Warnings.h" // js::WarnNumberASCII
68 #include "wasm/WasmCompile.h"
69 #include "wasm/WasmFeatures.h"
70 #include "wasm/WasmGenerator.h"
71 #include "wasm/WasmInstance.h"
72 #include "wasm/WasmIonCompile.h"
73 #include "wasm/WasmJS.h"
74 #include "wasm/WasmModuleTypes.h"
75 #include "wasm/WasmSerialize.h"
76 #include "wasm/WasmSignalHandlers.h"
77 #include "wasm/WasmValidate.h"
79 #include "frontend/SharedContext-inl.h"
80 #include "vm/ArrayBufferObject-inl.h"
81 #include "vm/JSObject-inl.h"
82 #include "wasm/WasmInstance-inl.h"
84 using namespace js;
85 using namespace js::frontend;
86 using namespace js::jit;
87 using namespace js::wasm;
89 using JS::AsmJSOption;
90 using JS::AutoStableStringChars;
91 using JS::GenericNaN;
92 using JS::SourceOwnership;
93 using JS::SourceText;
94 using mozilla::Abs;
95 using mozilla::AsVariant;
96 using mozilla::CeilingLog2;
97 using mozilla::HashGeneric;
98 using mozilla::IsNegativeZero;
99 using mozilla::IsPositiveZero;
100 using mozilla::IsPowerOfTwo;
101 using mozilla::Nothing;
102 using mozilla::PodZero;
103 using mozilla::PositiveInfinity;
104 using mozilla::Some;
105 using mozilla::Utf8Unit;
106 using mozilla::Compression::LZ4;
108 using FunctionVector = JS::GCVector<JSFunction*>;
110 /*****************************************************************************/
112 // A wasm module can either use no memory, a unshared memory (ArrayBuffer) or
113 // shared memory (SharedArrayBuffer).
115 enum class MemoryUsage { None = false, Unshared = 1, Shared = 2 };
117 // The asm.js valid heap lengths are precisely the WASM valid heap lengths for
118 // ARM greater or equal to MinHeapLength
119 static const size_t MinHeapLength = PageSize;
120 // An asm.js heap can in principle be up to INT32_MAX bytes but requirements
121 // on the format restrict it further to the largest pseudo-ARM-immediate.
122 // See IsValidAsmJSHeapLength().
123 static const uint64_t MaxHeapLength = 0x7f000000;
125 static uint64_t RoundUpToNextValidAsmJSHeapLength(uint64_t length) {
126 if (length <= MinHeapLength) {
127 return MinHeapLength;
130 return wasm::RoundUpToNextValidARMImmediate(length);
133 static uint64_t DivideRoundingUp(uint64_t a, uint64_t b) {
134 return (a + (b - 1)) / b;
137 /*****************************************************************************/
138 // asm.js module object
140 // The asm.js spec recognizes this set of builtin Math functions.
141 enum AsmJSMathBuiltinFunction {
142 AsmJSMathBuiltin_sin,
143 AsmJSMathBuiltin_cos,
144 AsmJSMathBuiltin_tan,
145 AsmJSMathBuiltin_asin,
146 AsmJSMathBuiltin_acos,
147 AsmJSMathBuiltin_atan,
148 AsmJSMathBuiltin_ceil,
149 AsmJSMathBuiltin_floor,
150 AsmJSMathBuiltin_exp,
151 AsmJSMathBuiltin_log,
152 AsmJSMathBuiltin_pow,
153 AsmJSMathBuiltin_sqrt,
154 AsmJSMathBuiltin_abs,
155 AsmJSMathBuiltin_atan2,
156 AsmJSMathBuiltin_imul,
157 AsmJSMathBuiltin_fround,
158 AsmJSMathBuiltin_min,
159 AsmJSMathBuiltin_max,
160 AsmJSMathBuiltin_clz32
163 // LitValPOD is a restricted version of LitVal suitable for asm.js that is
164 // always POD.
166 struct LitValPOD {
167 PackedTypeCode valType_;
168 union U {
169 uint32_t u32_;
170 uint64_t u64_;
171 float f32_;
172 double f64_;
173 } u;
175 LitValPOD() = default;
177 explicit LitValPOD(uint32_t u32) : valType_(ValType(ValType::I32).packed()) {
178 u.u32_ = u32;
180 explicit LitValPOD(uint64_t u64) : valType_(ValType(ValType::I64).packed()) {
181 u.u64_ = u64;
184 explicit LitValPOD(float f32) : valType_(ValType(ValType::F32).packed()) {
185 u.f32_ = f32;
187 explicit LitValPOD(double f64) : valType_(ValType(ValType::F64).packed()) {
188 u.f64_ = f64;
191 LitVal asLitVal() const {
192 switch (valType_.typeCode()) {
193 case TypeCode::I32:
194 return LitVal(u.u32_);
195 case TypeCode::I64:
196 return LitVal(u.u64_);
197 case TypeCode::F32:
198 return LitVal(u.f32_);
199 case TypeCode::F64:
200 return LitVal(u.f64_);
201 default:
202 MOZ_CRASH("Can't happen");
207 static_assert(std::is_pod_v<LitValPOD>,
208 "must be POD to be simply serialized/deserialized");
210 // An AsmJSGlobal represents a JS global variable in the asm.js module function.
211 class AsmJSGlobal {
212 public:
213 enum Which {
214 Variable,
215 FFI,
216 ArrayView,
217 ArrayViewCtor,
218 MathBuiltinFunction,
219 Constant
221 enum VarInitKind { InitConstant, InitImport };
222 enum ConstantKind { GlobalConstant, MathConstant };
224 private:
225 struct CacheablePod {
226 Which which_;
227 union V {
228 struct {
229 VarInitKind initKind_;
230 union U {
231 PackedTypeCode importValType_;
232 LitValPOD val_;
233 } u;
234 } var;
235 uint32_t ffiIndex_;
236 Scalar::Type viewType_;
237 AsmJSMathBuiltinFunction mathBuiltinFunc_;
238 struct {
239 ConstantKind kind_;
240 double value_;
241 } constant;
242 } u;
243 } pod;
244 CacheableChars field_;
246 friend class ModuleValidatorShared;
247 template <typename Unit>
248 friend class ModuleValidator;
250 public:
251 AsmJSGlobal() = default;
252 AsmJSGlobal(Which which, UniqueChars field) {
253 mozilla::PodZero(&pod); // zero padding for Valgrind
254 pod.which_ = which;
255 field_ = std::move(field);
257 const char* field() const { return field_.get(); }
258 Which which() const { return pod.which_; }
259 VarInitKind varInitKind() const {
260 MOZ_ASSERT(pod.which_ == Variable);
261 return pod.u.var.initKind_;
263 LitValPOD varInitVal() const {
264 MOZ_ASSERT(pod.which_ == Variable);
265 MOZ_ASSERT(pod.u.var.initKind_ == InitConstant);
266 return pod.u.var.u.val_;
268 ValType varInitImportType() const {
269 MOZ_ASSERT(pod.which_ == Variable);
270 MOZ_ASSERT(pod.u.var.initKind_ == InitImport);
271 return ValType(pod.u.var.u.importValType_);
273 uint32_t ffiIndex() const {
274 MOZ_ASSERT(pod.which_ == FFI);
275 return pod.u.ffiIndex_;
277 // When a view is created from an imported constructor:
278 // var I32 = stdlib.Int32Array;
279 // var i32 = new I32(buffer);
280 // the second import has nothing to validate and thus has a null field.
281 Scalar::Type viewType() const {
282 MOZ_ASSERT(pod.which_ == ArrayView || pod.which_ == ArrayViewCtor);
283 return pod.u.viewType_;
285 AsmJSMathBuiltinFunction mathBuiltinFunction() const {
286 MOZ_ASSERT(pod.which_ == MathBuiltinFunction);
287 return pod.u.mathBuiltinFunc_;
289 ConstantKind constantKind() const {
290 MOZ_ASSERT(pod.which_ == Constant);
291 return pod.u.constant.kind_;
293 double constantValue() const {
294 MOZ_ASSERT(pod.which_ == Constant);
295 return pod.u.constant.value_;
299 using AsmJSGlobalVector = Vector<AsmJSGlobal, 0, SystemAllocPolicy>;
301 // An AsmJSImport is slightly different than an asm.js FFI function: a single
302 // asm.js FFI function can be called with many different signatures. When
303 // compiled to wasm, each unique FFI function paired with signature generates a
304 // wasm import.
305 class AsmJSImport {
306 uint32_t ffiIndex_;
308 public:
309 AsmJSImport() = default;
310 explicit AsmJSImport(uint32_t ffiIndex) : ffiIndex_(ffiIndex) {}
311 uint32_t ffiIndex() const { return ffiIndex_; }
314 using AsmJSImportVector = Vector<AsmJSImport, 0, SystemAllocPolicy>;
316 // An AsmJSExport logically extends Export with the extra information needed for
317 // an asm.js exported function, viz., the offsets in module's source chars in
318 // case the function is toString()ed.
319 class AsmJSExport {
320 uint32_t funcIndex_ = 0;
322 // All fields are treated as cacheable POD:
323 uint32_t startOffsetInModule_ = 0; // Store module-start-relative offsets
324 uint32_t endOffsetInModule_ = 0; // so preserved by serialization.
326 public:
327 AsmJSExport() = default;
328 AsmJSExport(uint32_t funcIndex, uint32_t startOffsetInModule,
329 uint32_t endOffsetInModule)
330 : funcIndex_(funcIndex),
331 startOffsetInModule_(startOffsetInModule),
332 endOffsetInModule_(endOffsetInModule) {}
333 uint32_t funcIndex() const { return funcIndex_; }
334 uint32_t startOffsetInModule() const { return startOffsetInModule_; }
335 uint32_t endOffsetInModule() const { return endOffsetInModule_; }
338 using AsmJSExportVector = Vector<AsmJSExport, 0, SystemAllocPolicy>;
340 // Holds the immutable guts of an AsmJSModule.
342 // AsmJSMetadata is built incrementally by ModuleValidator and then shared
343 // immutably between AsmJSModules.
345 struct AsmJSMetadataCacheablePod {
346 uint32_t numFFIs = 0;
347 uint32_t srcLength = 0;
348 uint32_t srcLengthWithRightBrace = 0;
350 AsmJSMetadataCacheablePod() = default;
353 struct js::AsmJSMetadata : Metadata, AsmJSMetadataCacheablePod {
354 AsmJSGlobalVector asmJSGlobals;
355 AsmJSImportVector asmJSImports;
356 AsmJSExportVector asmJSExports;
357 CacheableCharsVector asmJSFuncNames;
358 CacheableChars globalArgumentName;
359 CacheableChars importArgumentName;
360 CacheableChars bufferArgumentName;
362 // These values are not serialized since they are relative to the
363 // containing script which can be different between serialization and
364 // deserialization contexts. Thus, they must be set explicitly using the
365 // ambient Parser/ScriptSource after deserialization.
367 // srcStart refers to the offset in the ScriptSource to the beginning of
368 // the asm.js module function. If the function has been created with the
369 // Function constructor, this will be the first character in the function
370 // source. Otherwise, it will be the opening parenthesis of the arguments
371 // list.
372 uint32_t toStringStart;
373 uint32_t srcStart;
374 bool strict;
375 bool alwaysUseFdlibm = false;
376 RefPtr<ScriptSource> source;
378 uint32_t srcEndBeforeCurly() const { return srcStart + srcLength; }
379 uint32_t srcEndAfterCurly() const {
380 return srcStart + srcLengthWithRightBrace;
383 AsmJSMetadata()
384 : Metadata(ModuleKind::AsmJS),
385 toStringStart(0),
386 srcStart(0),
387 strict(false) {}
388 ~AsmJSMetadata() override = default;
390 const AsmJSExport& lookupAsmJSExport(uint32_t funcIndex) const {
391 // The AsmJSExportVector isn't stored in sorted order so do a linear
392 // search. This is for the super-cold and already-expensive toString()
393 // path and the number of exports is generally small.
394 for (const AsmJSExport& exp : asmJSExports) {
395 if (exp.funcIndex() == funcIndex) {
396 return exp;
399 MOZ_CRASH("missing asm.js func export");
402 bool mutedErrors() const override { return source->mutedErrors(); }
403 const char16_t* displayURL() const override {
404 return source->hasDisplayURL() ? source->displayURL() : nullptr;
406 ScriptSource* maybeScriptSource() const override { return source.get(); }
407 bool getFuncName(NameContext ctx, uint32_t funcIndex,
408 UTF8Bytes* name) const override {
409 const char* p = asmJSFuncNames[funcIndex].get();
410 if (!p) {
411 return true;
413 return name->append(p, strlen(p));
416 AsmJSMetadataCacheablePod& pod() { return *this; }
417 const AsmJSMetadataCacheablePod& pod() const { return *this; }
420 using MutableAsmJSMetadata = RefPtr<AsmJSMetadata>;
422 /*****************************************************************************/
423 // ParseNode utilities
425 static inline ParseNode* NextNode(ParseNode* pn) { return pn->pn_next; }
427 static inline ParseNode* UnaryKid(ParseNode* pn) {
428 return pn->as<UnaryNode>().kid();
431 static inline ParseNode* BinaryRight(ParseNode* pn) {
432 return pn->as<BinaryNode>().right();
435 static inline ParseNode* BinaryLeft(ParseNode* pn) {
436 return pn->as<BinaryNode>().left();
439 static inline ParseNode* ReturnExpr(ParseNode* pn) {
440 MOZ_ASSERT(pn->isKind(ParseNodeKind::ReturnStmt));
441 return UnaryKid(pn);
444 static inline ParseNode* TernaryKid1(ParseNode* pn) {
445 return pn->as<TernaryNode>().kid1();
448 static inline ParseNode* TernaryKid2(ParseNode* pn) {
449 return pn->as<TernaryNode>().kid2();
452 static inline ParseNode* TernaryKid3(ParseNode* pn) {
453 return pn->as<TernaryNode>().kid3();
456 static inline ParseNode* ListHead(ParseNode* pn) {
457 return pn->as<ListNode>().head();
460 static inline unsigned ListLength(ParseNode* pn) {
461 return pn->as<ListNode>().count();
464 static inline ParseNode* CallCallee(ParseNode* pn) {
465 MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
466 return BinaryLeft(pn);
469 static inline unsigned CallArgListLength(ParseNode* pn) {
470 MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
471 return ListLength(BinaryRight(pn));
474 static inline ParseNode* CallArgList(ParseNode* pn) {
475 MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
476 return ListHead(BinaryRight(pn));
479 static inline ParseNode* VarListHead(ParseNode* pn) {
480 MOZ_ASSERT(pn->isKind(ParseNodeKind::VarStmt) ||
481 pn->isKind(ParseNodeKind::ConstDecl));
482 return ListHead(pn);
485 static inline bool IsDefaultCase(ParseNode* pn) {
486 return pn->as<CaseClause>().isDefault();
489 static inline ParseNode* CaseExpr(ParseNode* pn) {
490 return pn->as<CaseClause>().caseExpression();
493 static inline ParseNode* CaseBody(ParseNode* pn) {
494 return pn->as<CaseClause>().statementList();
497 static inline ParseNode* BinaryOpLeft(ParseNode* pn) {
498 MOZ_ASSERT(pn->isBinaryOperation());
499 MOZ_ASSERT(pn->as<ListNode>().count() == 2);
500 return ListHead(pn);
503 static inline ParseNode* BinaryOpRight(ParseNode* pn) {
504 MOZ_ASSERT(pn->isBinaryOperation());
505 MOZ_ASSERT(pn->as<ListNode>().count() == 2);
506 return NextNode(ListHead(pn));
509 static inline ParseNode* BitwiseLeft(ParseNode* pn) { return BinaryOpLeft(pn); }
511 static inline ParseNode* BitwiseRight(ParseNode* pn) {
512 return BinaryOpRight(pn);
515 static inline ParseNode* MultiplyLeft(ParseNode* pn) {
516 MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
517 return BinaryOpLeft(pn);
520 static inline ParseNode* MultiplyRight(ParseNode* pn) {
521 MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
522 return BinaryOpRight(pn);
525 static inline ParseNode* AddSubLeft(ParseNode* pn) {
526 MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
527 pn->isKind(ParseNodeKind::SubExpr));
528 return BinaryOpLeft(pn);
531 static inline ParseNode* AddSubRight(ParseNode* pn) {
532 MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
533 pn->isKind(ParseNodeKind::SubExpr));
534 return BinaryOpRight(pn);
537 static inline ParseNode* DivOrModLeft(ParseNode* pn) {
538 MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
539 pn->isKind(ParseNodeKind::ModExpr));
540 return BinaryOpLeft(pn);
543 static inline ParseNode* DivOrModRight(ParseNode* pn) {
544 MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
545 pn->isKind(ParseNodeKind::ModExpr));
546 return BinaryOpRight(pn);
549 static inline ParseNode* ComparisonLeft(ParseNode* pn) {
550 return BinaryOpLeft(pn);
553 static inline ParseNode* ComparisonRight(ParseNode* pn) {
554 return BinaryOpRight(pn);
557 static inline bool IsExpressionStatement(ParseNode* pn) {
558 return pn->isKind(ParseNodeKind::ExpressionStmt);
561 static inline ParseNode* ExpressionStatementExpr(ParseNode* pn) {
562 MOZ_ASSERT(pn->isKind(ParseNodeKind::ExpressionStmt));
563 return UnaryKid(pn);
566 static inline TaggedParserAtomIndex LoopControlMaybeLabel(ParseNode* pn) {
567 MOZ_ASSERT(pn->isKind(ParseNodeKind::BreakStmt) ||
568 pn->isKind(ParseNodeKind::ContinueStmt));
569 return pn->as<LoopControlStatement>().label();
572 static inline TaggedParserAtomIndex LabeledStatementLabel(ParseNode* pn) {
573 return pn->as<LabeledStatement>().label();
576 static inline ParseNode* LabeledStatementStatement(ParseNode* pn) {
577 return pn->as<LabeledStatement>().statement();
580 static double NumberNodeValue(ParseNode* pn) {
581 return pn->as<NumericLiteral>().value();
584 static bool NumberNodeHasFrac(ParseNode* pn) {
585 return pn->as<NumericLiteral>().decimalPoint() == HasDecimal;
588 static ParseNode* DotBase(ParseNode* pn) {
589 return &pn->as<PropertyAccess>().expression();
592 static TaggedParserAtomIndex DotMember(ParseNode* pn) {
593 return pn->as<PropertyAccess>().name();
596 static ParseNode* ElemBase(ParseNode* pn) {
597 return &pn->as<PropertyByValue>().expression();
600 static ParseNode* ElemIndex(ParseNode* pn) {
601 return &pn->as<PropertyByValue>().key();
604 static inline TaggedParserAtomIndex FunctionName(FunctionNode* funNode) {
605 if (auto name = funNode->funbox()->explicitName()) {
606 return name;
608 return TaggedParserAtomIndex::null();
611 static inline ParseNode* FunctionFormalParametersList(FunctionNode* fn,
612 unsigned* numFormals) {
613 ParamsBodyNode* argsBody = fn->body();
615 // The number of formals is equal to the number of parameters (excluding the
616 // trailing lexical scope). There are no destructuring or rest parameters for
617 // asm.js functions.
618 *numFormals = argsBody->count();
620 // If the function has been fully parsed, the trailing function body node is a
621 // lexical scope. If we've only parsed the function parameters, the last node
622 // is the last parameter.
623 if (*numFormals > 0 && argsBody->last()->is<LexicalScopeNode>()) {
624 MOZ_ASSERT(argsBody->last()->as<LexicalScopeNode>().scopeBody()->isKind(
625 ParseNodeKind::StatementList));
626 (*numFormals)--;
629 return argsBody->head();
632 static inline ParseNode* FunctionStatementList(FunctionNode* funNode) {
633 LexicalScopeNode* last = funNode->body()->body();
634 MOZ_ASSERT(last->isEmptyScope());
635 ParseNode* body = last->scopeBody();
636 MOZ_ASSERT(body->isKind(ParseNodeKind::StatementList));
637 return body;
640 static inline bool IsNormalObjectField(ParseNode* pn) {
641 return pn->isKind(ParseNodeKind::PropertyDefinition) &&
642 pn->as<PropertyDefinition>().accessorType() == AccessorType::None &&
643 BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName);
646 static inline TaggedParserAtomIndex ObjectNormalFieldName(ParseNode* pn) {
647 MOZ_ASSERT(IsNormalObjectField(pn));
648 MOZ_ASSERT(BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName));
649 return BinaryLeft(pn)->as<NameNode>().atom();
652 static inline ParseNode* ObjectNormalFieldInitializer(ParseNode* pn) {
653 MOZ_ASSERT(IsNormalObjectField(pn));
654 return BinaryRight(pn);
657 static inline bool IsUseOfName(ParseNode* pn, TaggedParserAtomIndex name) {
658 return pn->isName(name);
661 static inline bool IsIgnoredDirectiveName(TaggedParserAtomIndex atom) {
662 return atom != TaggedParserAtomIndex::WellKnown::use_strict_();
665 static inline bool IsIgnoredDirective(ParseNode* pn) {
666 return pn->isKind(ParseNodeKind::ExpressionStmt) &&
667 UnaryKid(pn)->isKind(ParseNodeKind::StringExpr) &&
668 IsIgnoredDirectiveName(UnaryKid(pn)->as<NameNode>().atom());
671 static inline bool IsEmptyStatement(ParseNode* pn) {
672 return pn->isKind(ParseNodeKind::EmptyStmt);
675 static inline ParseNode* SkipEmptyStatements(ParseNode* pn) {
676 while (pn && IsEmptyStatement(pn)) {
677 pn = pn->pn_next;
679 return pn;
682 static inline ParseNode* NextNonEmptyStatement(ParseNode* pn) {
683 return SkipEmptyStatements(pn->pn_next);
686 template <typename Unit>
687 static bool GetToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
688 auto& ts = parser.tokenStream;
689 TokenKind tk;
690 while (true) {
691 if (!ts.getToken(&tk, TokenStreamShared::SlashIsRegExp)) {
692 return false;
694 if (tk != TokenKind::Semi) {
695 break;
698 *tkp = tk;
699 return true;
702 template <typename Unit>
703 static bool PeekToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
704 auto& ts = parser.tokenStream;
705 TokenKind tk;
706 while (true) {
707 if (!ts.peekToken(&tk, TokenStream::SlashIsRegExp)) {
708 return false;
710 if (tk != TokenKind::Semi) {
711 break;
713 ts.consumeKnownToken(TokenKind::Semi, TokenStreamShared::SlashIsRegExp);
715 *tkp = tk;
716 return true;
719 template <typename Unit>
720 static bool ParseVarOrConstStatement(AsmJSParser<Unit>& parser,
721 ParseNode** var) {
722 TokenKind tk;
723 if (!PeekToken(parser, &tk)) {
724 return false;
726 if (tk != TokenKind::Var && tk != TokenKind::Const) {
727 *var = nullptr;
728 return true;
731 MOZ_TRY_VAR_OR_RETURN(*var, parser.statementListItem(YieldIsName), false);
733 MOZ_ASSERT((*var)->isKind(ParseNodeKind::VarStmt) ||
734 (*var)->isKind(ParseNodeKind::ConstDecl));
735 return true;
738 /*****************************************************************************/
740 // Represents the type and value of an asm.js numeric literal.
742 // A literal is a double iff the literal contains a decimal point (even if the
743 // fractional part is 0). Otherwise, integers may be classified:
744 // fixnum: [0, 2^31)
745 // negative int: [-2^31, 0)
746 // big unsigned: [2^31, 2^32)
747 // out of range: otherwise
748 // Lastly, a literal may be a float literal which is any double or integer
749 // literal coerced with Math.fround.
750 class NumLit {
751 public:
752 enum Which {
753 Fixnum,
754 NegativeInt,
755 BigUnsigned,
756 Double,
757 Float,
758 OutOfRangeInt = -1
761 private:
762 Which which_;
763 JS::Value value_;
765 public:
766 NumLit() = default;
768 NumLit(Which w, const Value& v) : which_(w), value_(v) {}
770 Which which() const { return which_; }
772 int32_t toInt32() const {
773 MOZ_ASSERT(which_ == Fixnum || which_ == NegativeInt ||
774 which_ == BigUnsigned);
775 return value_.toInt32();
778 uint32_t toUint32() const { return (uint32_t)toInt32(); }
780 double toDouble() const {
781 MOZ_ASSERT(which_ == Double);
782 return value_.toDouble();
785 float toFloat() const {
786 MOZ_ASSERT(which_ == Float);
787 return float(value_.toDouble());
790 Value scalarValue() const {
791 MOZ_ASSERT(which_ != OutOfRangeInt);
792 return value_;
795 bool valid() const { return which_ != OutOfRangeInt; }
797 bool isZeroBits() const {
798 MOZ_ASSERT(valid());
799 switch (which()) {
800 case NumLit::Fixnum:
801 case NumLit::NegativeInt:
802 case NumLit::BigUnsigned:
803 return toInt32() == 0;
804 case NumLit::Double:
805 return IsPositiveZero(toDouble());
806 case NumLit::Float:
807 return IsPositiveZero(toFloat());
808 case NumLit::OutOfRangeInt:
809 MOZ_CRASH("can't be here because of valid() check above");
811 return false;
814 LitValPOD value() const {
815 switch (which_) {
816 case NumLit::Fixnum:
817 case NumLit::NegativeInt:
818 case NumLit::BigUnsigned:
819 return LitValPOD(toUint32());
820 case NumLit::Float:
821 return LitValPOD(toFloat());
822 case NumLit::Double:
823 return LitValPOD(toDouble());
824 case NumLit::OutOfRangeInt:;
826 MOZ_CRASH("bad literal");
830 // Represents the type of a general asm.js expression.
832 // A canonical subset of types representing the coercion targets: Int, Float,
833 // Double.
835 // Void is also part of the canonical subset.
837 class Type {
838 public:
839 enum Which {
840 Fixnum = NumLit::Fixnum,
841 Signed = NumLit::NegativeInt,
842 Unsigned = NumLit::BigUnsigned,
843 DoubleLit = NumLit::Double,
844 Float = NumLit::Float,
845 Double,
846 MaybeDouble,
847 MaybeFloat,
848 Floatish,
849 Int,
850 Intish,
851 Void
854 private:
855 Which which_;
857 public:
858 Type() = default;
859 MOZ_IMPLICIT Type(Which w) : which_(w) {}
861 // Map an already canonicalized Type to the return type of a function call.
862 static Type ret(Type t) {
863 MOZ_ASSERT(t.isCanonical());
864 // The 32-bit external type is Signed, not Int.
865 return t.isInt() ? Signed : t;
868 static Type lit(const NumLit& lit) {
869 MOZ_ASSERT(lit.valid());
870 Which which = Type::Which(lit.which());
871 MOZ_ASSERT(which >= Fixnum && which <= Float);
872 Type t;
873 t.which_ = which;
874 return t;
877 // Map |t| to one of the canonical vartype representations of a
878 // wasm::ValType.
879 static Type canonicalize(Type t) {
880 switch (t.which()) {
881 case Fixnum:
882 case Signed:
883 case Unsigned:
884 case Int:
885 return Int;
887 case Float:
888 return Float;
890 case DoubleLit:
891 case Double:
892 return Double;
894 case Void:
895 return Void;
897 case MaybeDouble:
898 case MaybeFloat:
899 case Floatish:
900 case Intish:
901 // These types need some kind of coercion, they can't be mapped
902 // to an VarType.
903 break;
905 MOZ_CRASH("Invalid vartype");
908 Which which() const { return which_; }
910 bool operator==(Type rhs) const { return which_ == rhs.which_; }
911 bool operator!=(Type rhs) const { return which_ != rhs.which_; }
913 bool operator<=(Type rhs) const {
914 switch (rhs.which_) {
915 case Signed:
916 return isSigned();
917 case Unsigned:
918 return isUnsigned();
919 case DoubleLit:
920 return isDoubleLit();
921 case Double:
922 return isDouble();
923 case Float:
924 return isFloat();
925 case MaybeDouble:
926 return isMaybeDouble();
927 case MaybeFloat:
928 return isMaybeFloat();
929 case Floatish:
930 return isFloatish();
931 case Int:
932 return isInt();
933 case Intish:
934 return isIntish();
935 case Fixnum:
936 return isFixnum();
937 case Void:
938 return isVoid();
940 MOZ_CRASH("unexpected rhs type");
943 bool isFixnum() const { return which_ == Fixnum; }
945 bool isSigned() const { return which_ == Signed || which_ == Fixnum; }
947 bool isUnsigned() const { return which_ == Unsigned || which_ == Fixnum; }
949 bool isInt() const { return isSigned() || isUnsigned() || which_ == Int; }
951 bool isIntish() const { return isInt() || which_ == Intish; }
953 bool isDoubleLit() const { return which_ == DoubleLit; }
955 bool isDouble() const { return isDoubleLit() || which_ == Double; }
957 bool isMaybeDouble() const { return isDouble() || which_ == MaybeDouble; }
959 bool isFloat() const { return which_ == Float; }
961 bool isMaybeFloat() const { return isFloat() || which_ == MaybeFloat; }
963 bool isFloatish() const { return isMaybeFloat() || which_ == Floatish; }
965 bool isVoid() const { return which_ == Void; }
967 bool isExtern() const { return isDouble() || isSigned(); }
969 // Check if this is one of the valid types for a function argument.
970 bool isArgType() const { return isInt() || isFloat() || isDouble(); }
972 // Check if this is one of the valid types for a function return value.
973 bool isReturnType() const {
974 return isSigned() || isFloat() || isDouble() || isVoid();
977 // Check if this is one of the valid types for a global variable.
978 bool isGlobalVarType() const { return isArgType(); }
980 // Check if this is one of the canonical vartype representations of a
981 // wasm::ValType, or is void. See Type::canonicalize().
982 bool isCanonical() const {
983 switch (which()) {
984 case Int:
985 case Float:
986 case Double:
987 case Void:
988 return true;
989 default:
990 return false;
994 // Check if this is a canonical representation of a wasm::ValType.
995 bool isCanonicalValType() const { return !isVoid() && isCanonical(); }
997 // Convert this canonical type to a wasm::ValType.
998 ValType canonicalToValType() const {
999 switch (which()) {
1000 case Int:
1001 return ValType::I32;
1002 case Float:
1003 return ValType::F32;
1004 case Double:
1005 return ValType::F64;
1006 default:
1007 MOZ_CRASH("Need canonical type");
1011 Maybe<ValType> canonicalToReturnType() const {
1012 return isVoid() ? Nothing() : Some(canonicalToValType());
1015 // Convert this type to a wasm::TypeCode for use in a wasm
1016 // block signature. This works for all types, including non-canonical
1017 // ones. Consequently, the type isn't valid for subsequent asm.js
1018 // validation; it's only valid for use in producing wasm.
1019 TypeCode toWasmBlockSignatureType() const {
1020 switch (which()) {
1021 case Fixnum:
1022 case Signed:
1023 case Unsigned:
1024 case Int:
1025 case Intish:
1026 return TypeCode::I32;
1028 case Float:
1029 case MaybeFloat:
1030 case Floatish:
1031 return TypeCode::F32;
1033 case DoubleLit:
1034 case Double:
1035 case MaybeDouble:
1036 return TypeCode::F64;
1038 case Void:
1039 return TypeCode::BlockVoid;
1041 MOZ_CRASH("Invalid Type");
1044 const char* toChars() const {
1045 switch (which_) {
1046 case Double:
1047 return "double";
1048 case DoubleLit:
1049 return "doublelit";
1050 case MaybeDouble:
1051 return "double?";
1052 case Float:
1053 return "float";
1054 case Floatish:
1055 return "floatish";
1056 case MaybeFloat:
1057 return "float?";
1058 case Fixnum:
1059 return "fixnum";
1060 case Int:
1061 return "int";
1062 case Signed:
1063 return "signed";
1064 case Unsigned:
1065 return "unsigned";
1066 case Intish:
1067 return "intish";
1068 case Void:
1069 return "void";
1071 MOZ_CRASH("Invalid Type");
1075 static const unsigned VALIDATION_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
1077 class MOZ_STACK_CLASS ModuleValidatorShared {
1078 public:
1079 struct Memory {
1080 MemoryUsage usage;
1081 uint64_t minLength;
1083 uint64_t minPages() const { return DivideRoundingUp(minLength, PageSize); }
1085 Memory() = default;
1088 class Func {
1089 TaggedParserAtomIndex name_;
1090 uint32_t sigIndex_;
1091 uint32_t firstUse_;
1092 uint32_t funcDefIndex_;
1094 bool defined_;
1096 // Available when defined:
1097 uint32_t srcBegin_;
1098 uint32_t srcEnd_;
1099 uint32_t line_;
1100 Bytes bytes_;
1101 Uint32Vector callSiteLineNums_;
1103 public:
1104 Func(TaggedParserAtomIndex name, uint32_t sigIndex, uint32_t firstUse,
1105 uint32_t funcDefIndex)
1106 : name_(name),
1107 sigIndex_(sigIndex),
1108 firstUse_(firstUse),
1109 funcDefIndex_(funcDefIndex),
1110 defined_(false),
1111 srcBegin_(0),
1112 srcEnd_(0),
1113 line_(0) {}
1115 TaggedParserAtomIndex name() const { return name_; }
1116 uint32_t sigIndex() const { return sigIndex_; }
1117 uint32_t firstUse() const { return firstUse_; }
1118 bool defined() const { return defined_; }
1119 uint32_t funcDefIndex() const { return funcDefIndex_; }
1121 void define(ParseNode* fn, uint32_t line, Bytes&& bytes,
1122 Uint32Vector&& callSiteLineNums) {
1123 MOZ_ASSERT(!defined_);
1124 defined_ = true;
1125 srcBegin_ = fn->pn_pos.begin;
1126 srcEnd_ = fn->pn_pos.end;
1127 line_ = line;
1128 bytes_ = std::move(bytes);
1129 callSiteLineNums_ = std::move(callSiteLineNums);
1132 uint32_t srcBegin() const {
1133 MOZ_ASSERT(defined_);
1134 return srcBegin_;
1136 uint32_t srcEnd() const {
1137 MOZ_ASSERT(defined_);
1138 return srcEnd_;
1140 uint32_t line() const {
1141 MOZ_ASSERT(defined_);
1142 return line_;
1144 const Bytes& bytes() const {
1145 MOZ_ASSERT(defined_);
1146 return bytes_;
1148 Uint32Vector& callSiteLineNums() {
1149 MOZ_ASSERT(defined_);
1150 return callSiteLineNums_;
1154 using ConstFuncVector = Vector<const Func*>;
1155 using FuncVector = Vector<Func>;
1157 class Table {
1158 uint32_t sigIndex_;
1159 TaggedParserAtomIndex name_;
1160 uint32_t firstUse_;
1161 uint32_t mask_;
1162 bool defined_;
1164 public:
1165 Table(uint32_t sigIndex, TaggedParserAtomIndex name, uint32_t firstUse,
1166 uint32_t mask)
1167 : sigIndex_(sigIndex),
1168 name_(name),
1169 firstUse_(firstUse),
1170 mask_(mask),
1171 defined_(false) {}
1173 Table(Table&& rhs) = delete;
1175 uint32_t sigIndex() const { return sigIndex_; }
1176 TaggedParserAtomIndex name() const { return name_; }
1177 uint32_t firstUse() const { return firstUse_; }
1178 unsigned mask() const { return mask_; }
1179 bool defined() const { return defined_; }
1180 void define() {
1181 MOZ_ASSERT(!defined_);
1182 defined_ = true;
1186 using TableVector = Vector<Table*>;
1188 class Global {
1189 public:
1190 enum Which {
1191 Variable,
1192 ConstantLiteral,
1193 ConstantImport,
1194 Function,
1195 Table,
1196 FFI,
1197 ArrayView,
1198 ArrayViewCtor,
1199 MathBuiltinFunction
1202 private:
1203 Which which_;
1204 union U {
1205 struct VarOrConst {
1206 Type::Which type_;
1207 unsigned index_;
1208 NumLit literalValue_;
1210 VarOrConst(unsigned index, const NumLit& lit)
1211 : type_(Type::lit(lit).which()),
1212 index_(index),
1213 literalValue_(lit) // copies |lit|
1216 VarOrConst(unsigned index, Type::Which which)
1217 : type_(which), index_(index) {
1218 // The |literalValue_| field remains unused and
1219 // uninitialized for non-constant variables.
1222 explicit VarOrConst(double constant)
1223 : type_(Type::Double),
1224 literalValue_(NumLit::Double, DoubleValue(constant)) {
1225 // The index_ field is unused and uninitialized for
1226 // constant doubles.
1228 } varOrConst;
1229 uint32_t funcDefIndex_;
1230 uint32_t tableIndex_;
1231 uint32_t ffiIndex_;
1232 Scalar::Type viewType_;
1233 AsmJSMathBuiltinFunction mathBuiltinFunc_;
1235 // |varOrConst|, through |varOrConst.literalValue_|, has a
1236 // non-trivial constructor and therefore MUST be placement-new'd
1237 // into existence.
1238 MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
1239 U() : funcDefIndex_(0) {}
1240 MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
1241 } u;
1243 friend class ModuleValidatorShared;
1244 template <typename Unit>
1245 friend class ModuleValidator;
1246 friend class js::LifoAlloc;
1248 explicit Global(Which which) : which_(which) {}
1250 public:
1251 Which which() const { return which_; }
1252 Type varOrConstType() const {
1253 MOZ_ASSERT(which_ == Variable || which_ == ConstantLiteral ||
1254 which_ == ConstantImport);
1255 return u.varOrConst.type_;
1257 unsigned varOrConstIndex() const {
1258 MOZ_ASSERT(which_ == Variable || which_ == ConstantImport);
1259 return u.varOrConst.index_;
1261 bool isConst() const {
1262 return which_ == ConstantLiteral || which_ == ConstantImport;
1264 NumLit constLiteralValue() const {
1265 MOZ_ASSERT(which_ == ConstantLiteral);
1266 return u.varOrConst.literalValue_;
1268 uint32_t funcDefIndex() const {
1269 MOZ_ASSERT(which_ == Function);
1270 return u.funcDefIndex_;
1272 uint32_t tableIndex() const {
1273 MOZ_ASSERT(which_ == Table);
1274 return u.tableIndex_;
1276 unsigned ffiIndex() const {
1277 MOZ_ASSERT(which_ == FFI);
1278 return u.ffiIndex_;
1280 Scalar::Type viewType() const {
1281 MOZ_ASSERT(which_ == ArrayView || which_ == ArrayViewCtor);
1282 return u.viewType_;
1284 bool isMathFunction() const { return which_ == MathBuiltinFunction; }
1285 AsmJSMathBuiltinFunction mathBuiltinFunction() const {
1286 MOZ_ASSERT(which_ == MathBuiltinFunction);
1287 return u.mathBuiltinFunc_;
1291 struct MathBuiltin {
1292 enum Kind { Function, Constant };
1293 Kind kind;
1295 union {
1296 double cst;
1297 AsmJSMathBuiltinFunction func;
1298 } u;
1300 MathBuiltin() : kind(Kind(-1)), u{} {}
1301 explicit MathBuiltin(double cst) : kind(Constant) { u.cst = cst; }
1302 explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) {
1303 u.func = func;
1307 struct ArrayView {
1308 ArrayView(TaggedParserAtomIndex name, Scalar::Type type)
1309 : name(name), type(type) {}
1311 TaggedParserAtomIndex name;
1312 Scalar::Type type;
1315 protected:
1316 class HashableSig {
1317 uint32_t sigIndex_;
1318 const TypeContext& types_;
1320 public:
1321 HashableSig(uint32_t sigIndex, const TypeContext& types)
1322 : sigIndex_(sigIndex), types_(types) {}
1323 uint32_t sigIndex() const { return sigIndex_; }
1324 const FuncType& funcType() const { return types_[sigIndex_].funcType(); }
1326 // Implement HashPolicy:
1327 using Lookup = const FuncType&;
1328 static HashNumber hash(Lookup l) { return l.hash(nullptr); }
1329 static bool match(HashableSig lhs, Lookup rhs) {
1330 return FuncType::strictlyEquals(lhs.funcType(), rhs);
1334 class NamedSig : public HashableSig {
1335 TaggedParserAtomIndex name_;
1337 public:
1338 NamedSig(TaggedParserAtomIndex name, uint32_t sigIndex,
1339 const TypeContext& types)
1340 : HashableSig(sigIndex, types), name_(name) {}
1341 TaggedParserAtomIndex name() const { return name_; }
1343 // Implement HashPolicy:
1344 struct Lookup {
1345 TaggedParserAtomIndex name;
1346 const FuncType& funcType;
1347 Lookup(TaggedParserAtomIndex name, const FuncType& funcType)
1348 : name(name), funcType(funcType) {}
1350 static HashNumber hash(Lookup l) {
1351 return HashGeneric(TaggedParserAtomIndexHasher::hash(l.name),
1352 l.funcType.hash(nullptr));
1354 static bool match(NamedSig lhs, Lookup rhs) {
1355 return lhs.name() == rhs.name &&
1356 FuncType::strictlyEquals(lhs.funcType(), rhs.funcType);
1360 using SigSet = HashSet<HashableSig, HashableSig>;
1361 using FuncImportMap = HashMap<NamedSig, uint32_t, NamedSig>;
1362 using GlobalMap =
1363 HashMap<TaggedParserAtomIndex, Global*, TaggedParserAtomIndexHasher>;
1364 using MathNameMap =
1365 HashMap<TaggedParserAtomIndex, MathBuiltin, TaggedParserAtomIndexHasher>;
1366 using ArrayViewVector = Vector<ArrayView>;
1368 protected:
1369 FrontendContext* fc_;
1370 ParserAtomsTable& parserAtoms_;
1371 FunctionNode* moduleFunctionNode_;
1372 TaggedParserAtomIndex moduleFunctionName_;
1373 TaggedParserAtomIndex globalArgumentName_;
1374 TaggedParserAtomIndex importArgumentName_;
1375 TaggedParserAtomIndex bufferArgumentName_;
1376 MathNameMap standardLibraryMathNames_;
1378 // Validation-internal state:
1379 LifoAlloc validationLifo_;
1380 Memory memory_;
1381 FuncVector funcDefs_;
1382 TableVector tables_;
1383 GlobalMap globalMap_;
1384 SigSet sigSet_;
1385 FuncImportMap funcImportMap_;
1386 ArrayViewVector arrayViews_;
1388 // State used to build the AsmJSModule in finish():
1389 CompilerEnvironment compilerEnv_;
1390 ModuleEnvironment moduleEnv_;
1391 MutableAsmJSMetadata asmJSMetadata_;
1393 // Error reporting:
1394 UniqueChars errorString_ = nullptr;
1395 uint32_t errorOffset_ = UINT32_MAX;
1396 bool errorOverRecursed_ = false;
1398 protected:
1399 ModuleValidatorShared(FrontendContext* fc, ParserAtomsTable& parserAtoms,
1400 FunctionNode* moduleFunctionNode)
1401 : fc_(fc),
1402 parserAtoms_(parserAtoms),
1403 moduleFunctionNode_(moduleFunctionNode),
1404 moduleFunctionName_(FunctionName(moduleFunctionNode)),
1405 standardLibraryMathNames_(fc),
1406 validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE),
1407 funcDefs_(fc),
1408 tables_(fc),
1409 globalMap_(fc),
1410 sigSet_(fc),
1411 funcImportMap_(fc),
1412 arrayViews_(fc),
1413 compilerEnv_(CompileMode::Once, Tier::Optimized, DebugEnabled::False),
1414 moduleEnv_(FeatureArgs(), ModuleKind::AsmJS) {
1415 compilerEnv_.computeParameters();
1416 memory_.minLength = RoundUpToNextValidAsmJSHeapLength(0);
1419 protected:
1420 [[nodiscard]] bool initModuleEnvironment() { return moduleEnv_.init(); }
1422 [[nodiscard]] bool addStandardLibraryMathInfo() {
1423 static constexpr struct {
1424 const char* name;
1425 AsmJSMathBuiltinFunction func;
1426 } functions[] = {
1427 {"sin", AsmJSMathBuiltin_sin}, {"cos", AsmJSMathBuiltin_cos},
1428 {"tan", AsmJSMathBuiltin_tan}, {"asin", AsmJSMathBuiltin_asin},
1429 {"acos", AsmJSMathBuiltin_acos}, {"atan", AsmJSMathBuiltin_atan},
1430 {"ceil", AsmJSMathBuiltin_ceil}, {"floor", AsmJSMathBuiltin_floor},
1431 {"exp", AsmJSMathBuiltin_exp}, {"log", AsmJSMathBuiltin_log},
1432 {"pow", AsmJSMathBuiltin_pow}, {"sqrt", AsmJSMathBuiltin_sqrt},
1433 {"abs", AsmJSMathBuiltin_abs}, {"atan2", AsmJSMathBuiltin_atan2},
1434 {"imul", AsmJSMathBuiltin_imul}, {"clz32", AsmJSMathBuiltin_clz32},
1435 {"fround", AsmJSMathBuiltin_fround}, {"min", AsmJSMathBuiltin_min},
1436 {"max", AsmJSMathBuiltin_max},
1439 auto AddMathFunction = [this](const char* name,
1440 AsmJSMathBuiltinFunction func) {
1441 auto atom = parserAtoms_.internAscii(fc_, name, strlen(name));
1442 if (!atom) {
1443 return false;
1445 MathBuiltin builtin(func);
1446 return this->standardLibraryMathNames_.putNew(atom, builtin);
1449 for (const auto& info : functions) {
1450 if (!AddMathFunction(info.name, info.func)) {
1451 return false;
1455 static constexpr struct {
1456 const char* name;
1457 double value;
1458 } constants[] = {
1459 {"E", M_E},
1460 {"LN10", M_LN10},
1461 {"LN2", M_LN2},
1462 {"LOG2E", M_LOG2E},
1463 {"LOG10E", M_LOG10E},
1464 {"PI", M_PI},
1465 {"SQRT1_2", M_SQRT1_2},
1466 {"SQRT2", M_SQRT2},
1469 auto AddMathConstant = [this](const char* name, double cst) {
1470 auto atom = parserAtoms_.internAscii(fc_, name, strlen(name));
1471 if (!atom) {
1472 return false;
1474 MathBuiltin builtin(cst);
1475 return this->standardLibraryMathNames_.putNew(atom, builtin);
1478 for (const auto& info : constants) {
1479 if (!AddMathConstant(info.name, info.value)) {
1480 return false;
1484 return true;
1487 public:
1488 FrontendContext* fc() const { return fc_; }
1489 TaggedParserAtomIndex moduleFunctionName() const {
1490 return moduleFunctionName_;
1492 TaggedParserAtomIndex globalArgumentName() const {
1493 return globalArgumentName_;
1495 TaggedParserAtomIndex importArgumentName() const {
1496 return importArgumentName_;
1498 TaggedParserAtomIndex bufferArgumentName() const {
1499 return bufferArgumentName_;
1501 const ModuleEnvironment& env() { return moduleEnv_; }
1503 void initModuleFunctionName(TaggedParserAtomIndex name) {
1504 MOZ_ASSERT(!moduleFunctionName_);
1505 moduleFunctionName_ = name;
1507 [[nodiscard]] bool initGlobalArgumentName(TaggedParserAtomIndex n) {
1508 globalArgumentName_ = n;
1509 if (n) {
1510 asmJSMetadata_->globalArgumentName = parserAtoms_.toNewUTF8CharsZ(fc_, n);
1511 if (!asmJSMetadata_->globalArgumentName) {
1512 return false;
1515 return true;
1517 [[nodiscard]] bool initImportArgumentName(TaggedParserAtomIndex n) {
1518 importArgumentName_ = n;
1519 if (n) {
1520 asmJSMetadata_->importArgumentName = parserAtoms_.toNewUTF8CharsZ(fc_, n);
1521 if (!asmJSMetadata_->importArgumentName) {
1522 return false;
1525 return true;
1527 [[nodiscard]] bool initBufferArgumentName(TaggedParserAtomIndex n) {
1528 bufferArgumentName_ = n;
1529 if (n) {
1530 asmJSMetadata_->bufferArgumentName = parserAtoms_.toNewUTF8CharsZ(fc_, n);
1531 if (!asmJSMetadata_->bufferArgumentName) {
1532 return false;
1535 return true;
1537 bool addGlobalVarInit(TaggedParserAtomIndex var, const NumLit& lit, Type type,
1538 bool isConst) {
1539 MOZ_ASSERT(type.isGlobalVarType());
1540 MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit)));
1542 uint32_t index = moduleEnv_.globals.length();
1543 if (!moduleEnv_.globals.emplaceBack(type.canonicalToValType(), !isConst,
1544 index, ModuleKind::AsmJS)) {
1545 return false;
1548 Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable;
1549 Global* global = validationLifo_.new_<Global>(which);
1550 if (!global) {
1551 return false;
1553 if (isConst) {
1554 new (&global->u.varOrConst) Global::U::VarOrConst(index, lit);
1555 } else {
1556 new (&global->u.varOrConst) Global::U::VarOrConst(index, type.which());
1558 if (!globalMap_.putNew(var, global)) {
1559 return false;
1562 AsmJSGlobal g(AsmJSGlobal::Variable, nullptr);
1563 g.pod.u.var.initKind_ = AsmJSGlobal::InitConstant;
1564 g.pod.u.var.u.val_ = lit.value();
1565 return asmJSMetadata_->asmJSGlobals.append(std::move(g));
1567 bool addGlobalVarImport(TaggedParserAtomIndex var,
1568 TaggedParserAtomIndex field, Type type,
1569 bool isConst) {
1570 MOZ_ASSERT(type.isGlobalVarType());
1572 UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
1573 if (!fieldChars) {
1574 return false;
1577 uint32_t index = moduleEnv_.globals.length();
1578 ValType valType = type.canonicalToValType();
1579 if (!moduleEnv_.globals.emplaceBack(valType, !isConst, index,
1580 ModuleKind::AsmJS)) {
1581 return false;
1584 Global::Which which = isConst ? Global::ConstantImport : Global::Variable;
1585 Global* global = validationLifo_.new_<Global>(which);
1586 if (!global) {
1587 return false;
1589 new (&global->u.varOrConst) Global::U::VarOrConst(index, type.which());
1590 if (!globalMap_.putNew(var, global)) {
1591 return false;
1594 AsmJSGlobal g(AsmJSGlobal::Variable, std::move(fieldChars));
1595 g.pod.u.var.initKind_ = AsmJSGlobal::InitImport;
1596 g.pod.u.var.u.importValType_ = valType.packed();
1597 return asmJSMetadata_->asmJSGlobals.append(std::move(g));
1599 bool addArrayView(TaggedParserAtomIndex var, Scalar::Type vt,
1600 TaggedParserAtomIndex maybeField) {
1601 UniqueChars fieldChars;
1602 if (maybeField) {
1603 fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField);
1604 if (!fieldChars) {
1605 return false;
1609 if (!arrayViews_.append(ArrayView(var, vt))) {
1610 return false;
1613 Global* global = validationLifo_.new_<Global>(Global::ArrayView);
1614 if (!global) {
1615 return false;
1617 new (&global->u.viewType_) Scalar::Type(vt);
1618 if (!globalMap_.putNew(var, global)) {
1619 return false;
1622 AsmJSGlobal g(AsmJSGlobal::ArrayView, std::move(fieldChars));
1623 g.pod.u.viewType_ = vt;
1624 return asmJSMetadata_->asmJSGlobals.append(std::move(g));
1626 bool addMathBuiltinFunction(TaggedParserAtomIndex var,
1627 AsmJSMathBuiltinFunction func,
1628 TaggedParserAtomIndex field) {
1629 UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
1630 if (!fieldChars) {
1631 return false;
1634 Global* global = validationLifo_.new_<Global>(Global::MathBuiltinFunction);
1635 if (!global) {
1636 return false;
1638 new (&global->u.mathBuiltinFunc_) AsmJSMathBuiltinFunction(func);
1639 if (!globalMap_.putNew(var, global)) {
1640 return false;
1643 AsmJSGlobal g(AsmJSGlobal::MathBuiltinFunction, std::move(fieldChars));
1644 g.pod.u.mathBuiltinFunc_ = func;
1645 return asmJSMetadata_->asmJSGlobals.append(std::move(g));
1648 private:
1649 bool addGlobalDoubleConstant(TaggedParserAtomIndex var, double constant) {
1650 Global* global = validationLifo_.new_<Global>(Global::ConstantLiteral);
1651 if (!global) {
1652 return false;
1654 new (&global->u.varOrConst) Global::U::VarOrConst(constant);
1655 return globalMap_.putNew(var, global);
1658 public:
1659 bool addMathBuiltinConstant(TaggedParserAtomIndex var, double constant,
1660 TaggedParserAtomIndex field) {
1661 UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
1662 if (!fieldChars) {
1663 return false;
1666 if (!addGlobalDoubleConstant(var, constant)) {
1667 return false;
1670 AsmJSGlobal g(AsmJSGlobal::Constant, std::move(fieldChars));
1671 g.pod.u.constant.value_ = constant;
1672 g.pod.u.constant.kind_ = AsmJSGlobal::MathConstant;
1673 return asmJSMetadata_->asmJSGlobals.append(std::move(g));
1675 bool addGlobalConstant(TaggedParserAtomIndex var, double constant,
1676 TaggedParserAtomIndex field) {
1677 UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
1678 if (!fieldChars) {
1679 return false;
1682 if (!addGlobalDoubleConstant(var, constant)) {
1683 return false;
1686 AsmJSGlobal g(AsmJSGlobal::Constant, std::move(fieldChars));
1687 g.pod.u.constant.value_ = constant;
1688 g.pod.u.constant.kind_ = AsmJSGlobal::GlobalConstant;
1689 return asmJSMetadata_->asmJSGlobals.append(std::move(g));
1691 bool addArrayViewCtor(TaggedParserAtomIndex var, Scalar::Type vt,
1692 TaggedParserAtomIndex field) {
1693 UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
1694 if (!fieldChars) {
1695 return false;
1698 Global* global = validationLifo_.new_<Global>(Global::ArrayViewCtor);
1699 if (!global) {
1700 return false;
1702 new (&global->u.viewType_) Scalar::Type(vt);
1703 if (!globalMap_.putNew(var, global)) {
1704 return false;
1707 AsmJSGlobal g(AsmJSGlobal::ArrayViewCtor, std::move(fieldChars));
1708 g.pod.u.viewType_ = vt;
1709 return asmJSMetadata_->asmJSGlobals.append(std::move(g));
1711 bool addFFI(TaggedParserAtomIndex var, TaggedParserAtomIndex field) {
1712 UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
1713 if (!fieldChars) {
1714 return false;
1717 if (asmJSMetadata_->numFFIs == UINT32_MAX) {
1718 return false;
1720 uint32_t ffiIndex = asmJSMetadata_->numFFIs++;
1722 Global* global = validationLifo_.new_<Global>(Global::FFI);
1723 if (!global) {
1724 return false;
1726 new (&global->u.ffiIndex_) uint32_t(ffiIndex);
1727 if (!globalMap_.putNew(var, global)) {
1728 return false;
1731 AsmJSGlobal g(AsmJSGlobal::FFI, std::move(fieldChars));
1732 g.pod.u.ffiIndex_ = ffiIndex;
1733 return asmJSMetadata_->asmJSGlobals.append(std::move(g));
1735 bool addExportField(const Func& func, TaggedParserAtomIndex maybeField) {
1736 // Record the field name of this export.
1737 CacheableName fieldName;
1738 if (maybeField) {
1739 UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField);
1740 if (!fieldChars) {
1741 return false;
1743 fieldName = CacheableName::fromUTF8Chars(std::move(fieldChars));
1746 // Declare which function is exported which gives us an index into the
1747 // module ExportVector.
1748 uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
1749 if (!moduleEnv_.exports.emplaceBack(std::move(fieldName), funcIndex,
1750 DefinitionKind::Function)) {
1751 return false;
1754 // The exported function might have already been exported in which case
1755 // the index will refer into the range of AsmJSExports.
1756 return asmJSMetadata_->asmJSExports.emplaceBack(
1757 funcIndex, func.srcBegin() - asmJSMetadata_->srcStart,
1758 func.srcEnd() - asmJSMetadata_->srcStart);
1761 bool defineFuncPtrTable(uint32_t tableIndex, Uint32Vector&& elems) {
1762 Table& table = *tables_[tableIndex];
1763 if (table.defined()) {
1764 return false;
1767 table.define();
1769 for (uint32_t& index : elems) {
1770 index += funcImportMap_.count();
1773 ModuleElemSegment seg = ModuleElemSegment();
1774 seg.elemType = RefType::func();
1775 seg.tableIndex = tableIndex;
1776 seg.offsetIfActive = Some(InitExpr(LitVal(uint32_t(0))));
1777 seg.encoding = ModuleElemSegment::Encoding::Indices;
1778 seg.elemIndices = std::move(elems);
1779 return moduleEnv_.elemSegments.append(std::move(seg));
1782 bool tryConstantAccess(uint64_t start, uint64_t width) {
1783 MOZ_ASSERT(UINT64_MAX - start > width);
1784 uint64_t len = start + width;
1785 if (len > uint64_t(INT32_MAX) + 1) {
1786 return false;
1788 len = RoundUpToNextValidAsmJSHeapLength(len);
1789 if (len > memory_.minLength) {
1790 memory_.minLength = len;
1792 return true;
1795 // Error handling.
1796 bool hasAlreadyFailed() const { return !!errorString_; }
1798 bool failOffset(uint32_t offset, const char* str) {
1799 MOZ_ASSERT(!hasAlreadyFailed());
1800 MOZ_ASSERT(errorOffset_ == UINT32_MAX);
1801 MOZ_ASSERT(str);
1802 errorOffset_ = offset;
1803 errorString_ = DuplicateString(str);
1804 return false;
1807 bool fail(ParseNode* pn, const char* str) {
1808 return failOffset(pn->pn_pos.begin, str);
1811 bool failfVAOffset(uint32_t offset, const char* fmt, va_list ap)
1812 MOZ_FORMAT_PRINTF(3, 0) {
1813 MOZ_ASSERT(!hasAlreadyFailed());
1814 MOZ_ASSERT(errorOffset_ == UINT32_MAX);
1815 MOZ_ASSERT(fmt);
1816 errorOffset_ = offset;
1817 errorString_ = JS_vsmprintf(fmt, ap);
1818 return false;
1821 bool failfOffset(uint32_t offset, const char* fmt, ...)
1822 MOZ_FORMAT_PRINTF(3, 4) {
1823 va_list ap;
1824 va_start(ap, fmt);
1825 failfVAOffset(offset, fmt, ap);
1826 va_end(ap);
1827 return false;
1830 bool failf(ParseNode* pn, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) {
1831 va_list ap;
1832 va_start(ap, fmt);
1833 failfVAOffset(pn->pn_pos.begin, fmt, ap);
1834 va_end(ap);
1835 return false;
1838 bool failNameOffset(uint32_t offset, const char* fmt,
1839 TaggedParserAtomIndex name) {
1840 // This function is invoked without the caller properly rooting its locals.
1841 if (UniqueChars bytes = parserAtoms_.toPrintableString(name)) {
1842 failfOffset(offset, fmt, bytes.get());
1843 } else {
1844 ReportOutOfMemory(fc_);
1846 return false;
1849 bool failName(ParseNode* pn, const char* fmt, TaggedParserAtomIndex name) {
1850 return failNameOffset(pn->pn_pos.begin, fmt, name);
1853 bool failOverRecursed() {
1854 errorOverRecursed_ = true;
1855 return false;
1858 unsigned numArrayViews() const { return arrayViews_.length(); }
1859 const ArrayView& arrayView(unsigned i) const { return arrayViews_[i]; }
1860 unsigned numFuncDefs() const { return funcDefs_.length(); }
1861 const Func& funcDef(unsigned i) const { return funcDefs_[i]; }
1862 unsigned numFuncPtrTables() const { return tables_.length(); }
1863 Table& table(unsigned i) const { return *tables_[i]; }
1865 const Global* lookupGlobal(TaggedParserAtomIndex name) const {
1866 if (GlobalMap::Ptr p = globalMap_.lookup(name)) {
1867 return p->value();
1869 return nullptr;
1872 Func* lookupFuncDef(TaggedParserAtomIndex name) {
1873 if (GlobalMap::Ptr p = globalMap_.lookup(name)) {
1874 Global* value = p->value();
1875 if (value->which() == Global::Function) {
1876 return &funcDefs_[value->funcDefIndex()];
1879 return nullptr;
1882 bool lookupStandardLibraryMathName(TaggedParserAtomIndex name,
1883 MathBuiltin* mathBuiltin) const {
1884 if (MathNameMap::Ptr p = standardLibraryMathNames_.lookup(name)) {
1885 *mathBuiltin = p->value();
1886 return true;
1888 return false;
1891 bool startFunctionBodies() {
1892 if (!arrayViews_.empty()) {
1893 memory_.usage = MemoryUsage::Unshared;
1894 } else {
1895 memory_.usage = MemoryUsage::None;
1897 return true;
1901 // The ModuleValidator encapsulates the entire validation of an asm.js module.
1902 // Its lifetime goes from the validation of the top components of an asm.js
1903 // module (all the globals), the emission of bytecode for all the functions in
1904 // the module and the validation of function's pointer tables. It also finishes
1905 // the compilation of all the module's stubs.
1906 template <typename Unit>
1907 class MOZ_STACK_CLASS ModuleValidator : public ModuleValidatorShared {
1908 private:
1909 AsmJSParser<Unit>& parser_;
1911 public:
1912 ModuleValidator(FrontendContext* fc, ParserAtomsTable& parserAtoms,
1913 AsmJSParser<Unit>& parser, FunctionNode* moduleFunctionNode)
1914 : ModuleValidatorShared(fc, parserAtoms, moduleFunctionNode),
1915 parser_(parser) {}
1917 ~ModuleValidator() {
1918 if (errorString_) {
1919 MOZ_ASSERT(errorOffset_ != UINT32_MAX);
1920 typeFailure(errorOffset_, errorString_.get());
1922 if (errorOverRecursed_) {
1923 ReportOverRecursed(fc_);
1927 private:
1928 // Helpers:
1929 bool newSig(FuncType&& sig, uint32_t* sigIndex) {
1930 if (moduleEnv_.types->length() >= MaxTypes) {
1931 return failCurrentOffset("too many signatures");
1934 *sigIndex = moduleEnv_.types->length();
1935 return moduleEnv_.types->addType(std::move(sig));
1937 bool declareSig(FuncType&& sig, uint32_t* sigIndex) {
1938 SigSet::AddPtr p = sigSet_.lookupForAdd(sig);
1939 if (p) {
1940 *sigIndex = p->sigIndex();
1941 MOZ_ASSERT(FuncType::strictlyEquals(
1942 moduleEnv_.types->type(*sigIndex).funcType(), sig));
1943 return true;
1946 return newSig(std::move(sig), sigIndex) &&
1947 sigSet_.add(p, HashableSig(*sigIndex, *moduleEnv_.types));
1950 private:
1951 void typeFailure(uint32_t offset, ...) {
1952 va_list args;
1953 va_start(args, offset);
1955 auto& ts = tokenStream();
1956 ErrorMetadata metadata;
1957 if (ts.computeErrorMetadata(&metadata, AsVariant(offset))) {
1958 if (ts.anyCharsAccess().options().throwOnAsmJSValidationFailure()) {
1959 ReportCompileErrorLatin1VA(fc_, std::move(metadata), nullptr,
1960 JSMSG_USE_ASM_TYPE_FAIL, &args);
1961 } else {
1962 // asm.js type failure is indicated by calling one of the fail*
1963 // functions below. These functions always return false to
1964 // halt asm.js parsing. Whether normal parsing is attempted as
1965 // fallback, depends whether an exception is also set.
1967 // If warning succeeds, no exception is set. If warning fails,
1968 // an exception is set and execution will halt. Thus it's safe
1969 // and correct to ignore the return value here.
1970 (void)ts.compileWarning(std::move(metadata), nullptr,
1971 JSMSG_USE_ASM_TYPE_FAIL, &args);
1975 va_end(args);
1978 public:
1979 bool init() {
1980 asmJSMetadata_ = js_new<AsmJSMetadata>();
1981 if (!asmJSMetadata_) {
1982 ReportOutOfMemory(fc_);
1983 return false;
1986 asmJSMetadata_->toStringStart =
1987 moduleFunctionNode_->funbox()->extent().toStringStart;
1988 asmJSMetadata_->srcStart = moduleFunctionNode_->body()->pn_pos.begin;
1989 asmJSMetadata_->strict = parser_.pc_->sc()->strict() &&
1990 !parser_.pc_->sc()->hasExplicitUseStrict();
1991 asmJSMetadata_->alwaysUseFdlibm = parser_.options().alwaysUseFdlibm();
1992 asmJSMetadata_->source = do_AddRef(parser_.ss);
1994 if (!initModuleEnvironment()) {
1995 return false;
1997 return addStandardLibraryMathInfo();
2000 AsmJSParser<Unit>& parser() const { return parser_; }
2002 auto& tokenStream() const { return parser_.tokenStream; }
2004 bool alwaysUseFdlibm() const { return asmJSMetadata_->alwaysUseFdlibm; }
2006 public:
2007 bool addFuncDef(TaggedParserAtomIndex name, uint32_t firstUse, FuncType&& sig,
2008 Func** func) {
2009 uint32_t sigIndex;
2010 if (!declareSig(std::move(sig), &sigIndex)) {
2011 return false;
2014 uint32_t funcDefIndex = funcDefs_.length();
2015 if (funcDefIndex >= MaxFuncs) {
2016 return failCurrentOffset("too many functions");
2019 Global* global = validationLifo_.new_<Global>(Global::Function);
2020 if (!global) {
2021 return false;
2023 new (&global->u.funcDefIndex_) uint32_t(funcDefIndex);
2024 if (!globalMap_.putNew(name, global)) {
2025 return false;
2027 if (!funcDefs_.emplaceBack(name, sigIndex, firstUse, funcDefIndex)) {
2028 return false;
2030 *func = &funcDefs_.back();
2031 return true;
2033 bool declareFuncPtrTable(FuncType&& sig, TaggedParserAtomIndex name,
2034 uint32_t firstUse, uint32_t mask,
2035 uint32_t* tableIndex) {
2036 if (mask > MaxTableLength) {
2037 return failCurrentOffset("function pointer table too big");
2040 MOZ_ASSERT(moduleEnv_.tables.length() == tables_.length());
2041 *tableIndex = moduleEnv_.tables.length();
2043 uint32_t sigIndex;
2044 if (!newSig(std::move(sig), &sigIndex)) {
2045 return false;
2048 MOZ_ASSERT(sigIndex >= moduleEnv_.asmJSSigToTableIndex.length());
2049 if (!moduleEnv_.asmJSSigToTableIndex.resize(sigIndex + 1)) {
2050 return false;
2053 moduleEnv_.asmJSSigToTableIndex[sigIndex] = moduleEnv_.tables.length();
2054 if (!moduleEnv_.tables.emplaceBack(RefType::func(), mask + 1, Nothing(),
2055 /* initExpr */ Nothing(),
2056 /*isAsmJS*/ true)) {
2057 return false;
2060 Global* global = validationLifo_.new_<Global>(Global::Table);
2061 if (!global) {
2062 return false;
2065 new (&global->u.tableIndex_) uint32_t(*tableIndex);
2066 if (!globalMap_.putNew(name, global)) {
2067 return false;
2070 Table* t = validationLifo_.new_<Table>(sigIndex, name, firstUse, mask);
2071 return t && tables_.append(t);
2073 bool declareImport(TaggedParserAtomIndex name, FuncType&& sig,
2074 unsigned ffiIndex, uint32_t* importIndex) {
2075 FuncImportMap::AddPtr p =
2076 funcImportMap_.lookupForAdd(NamedSig::Lookup(name, sig));
2077 if (p) {
2078 *importIndex = p->value();
2079 return true;
2082 *importIndex = funcImportMap_.count();
2083 MOZ_ASSERT(*importIndex == asmJSMetadata_->asmJSImports.length());
2085 if (*importIndex >= MaxImports) {
2086 return failCurrentOffset("too many imports");
2089 if (!asmJSMetadata_->asmJSImports.emplaceBack(ffiIndex)) {
2090 return false;
2093 uint32_t sigIndex;
2094 if (!declareSig(std::move(sig), &sigIndex)) {
2095 return false;
2098 return funcImportMap_.add(p, NamedSig(name, sigIndex, *moduleEnv_.types),
2099 *importIndex);
2102 // Error handling.
2103 bool failCurrentOffset(const char* str) {
2104 return failOffset(tokenStream().anyCharsAccess().currentToken().pos.begin,
2105 str);
2108 SharedModule finish() {
2109 MOZ_ASSERT(moduleEnv_.numMemories() == 0);
2110 if (memory_.usage != MemoryUsage::None) {
2111 Limits limits;
2112 limits.shared = memory_.usage == MemoryUsage::Shared ? Shareable::True
2113 : Shareable::False;
2114 limits.initial = memory_.minPages();
2115 limits.maximum = Nothing();
2116 limits.indexType = IndexType::I32;
2117 if (!moduleEnv_.memories.append(MemoryDesc(limits))) {
2118 return nullptr;
2121 MOZ_ASSERT(moduleEnv_.funcs.empty());
2122 if (!moduleEnv_.funcs.resize(funcImportMap_.count() + funcDefs_.length())) {
2123 return nullptr;
2125 for (FuncImportMap::Range r = funcImportMap_.all(); !r.empty();
2126 r.popFront()) {
2127 uint32_t funcIndex = r.front().value();
2128 uint32_t funcTypeIndex = r.front().key().sigIndex();
2129 MOZ_ASSERT(!moduleEnv_.funcs[funcIndex].type);
2130 moduleEnv_.funcs[funcIndex] = FuncDesc(
2131 &moduleEnv_.types->type(funcTypeIndex).funcType(), funcTypeIndex);
2133 for (const Func& func : funcDefs_) {
2134 uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
2135 uint32_t funcTypeIndex = func.sigIndex();
2136 MOZ_ASSERT(!moduleEnv_.funcs[funcIndex].type);
2137 moduleEnv_.funcs[funcIndex] = FuncDesc(
2138 &moduleEnv_.types->type(funcTypeIndex).funcType(), funcTypeIndex);
2140 for (const Export& exp : moduleEnv_.exports) {
2141 if (exp.kind() != DefinitionKind::Function) {
2142 continue;
2144 uint32_t funcIndex = exp.funcIndex();
2145 moduleEnv_.declareFuncExported(funcIndex, /* eager */ true,
2146 /* canRefFunc */ false);
2149 moduleEnv_.numFuncImports = funcImportMap_.count();
2151 // All globals (inits and imports) are imports from Wasm point of view.
2152 moduleEnv_.numGlobalImports = moduleEnv_.globals.length();
2154 MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty());
2155 if (!asmJSMetadata_->asmJSFuncNames.resize(funcImportMap_.count())) {
2156 return nullptr;
2158 for (const Func& func : funcDefs_) {
2159 CacheableChars funcName = parserAtoms_.toNewUTF8CharsZ(fc_, func.name());
2160 if (!funcName ||
2161 !asmJSMetadata_->asmJSFuncNames.emplaceBack(std::move(funcName))) {
2162 return nullptr;
2166 uint32_t endBeforeCurly =
2167 tokenStream().anyCharsAccess().currentToken().pos.end;
2168 asmJSMetadata_->srcLength = endBeforeCurly - asmJSMetadata_->srcStart;
2170 TokenPos pos;
2171 MOZ_ALWAYS_TRUE(
2172 tokenStream().peekTokenPos(&pos, TokenStreamShared::SlashIsRegExp));
2173 uint32_t endAfterCurly = pos.end;
2174 asmJSMetadata_->srcLengthWithRightBrace =
2175 endAfterCurly - asmJSMetadata_->srcStart;
2177 ScriptedCaller scriptedCaller;
2178 if (parser_.ss->filename()) {
2179 scriptedCaller.line = 0; // unused
2180 scriptedCaller.filename = DuplicateString(parser_.ss->filename());
2181 if (!scriptedCaller.filename) {
2182 return nullptr;
2186 // The default options are fine for asm.js
2187 SharedCompileArgs args =
2188 CompileArgs::buildForAsmJS(std::move(scriptedCaller));
2189 if (!args) {
2190 ReportOutOfMemory(fc_);
2191 return nullptr;
2194 uint32_t codeSectionSize = 0;
2195 for (const Func& func : funcDefs_) {
2196 codeSectionSize += func.bytes().length();
2199 moduleEnv_.codeSection.emplace();
2200 moduleEnv_.codeSection->start = 0;
2201 moduleEnv_.codeSection->size = codeSectionSize;
2203 // asm.js does not have any wasm bytecode to save; view-source is
2204 // provided through the ScriptSource.
2205 SharedBytes bytes = js_new<ShareableBytes>();
2206 if (!bytes) {
2207 ReportOutOfMemory(fc_);
2208 return nullptr;
2211 ModuleGenerator mg(*args, &moduleEnv_, &compilerEnv_, nullptr, nullptr,
2212 nullptr);
2213 if (!mg.init(asmJSMetadata_.get())) {
2214 return nullptr;
2217 for (Func& func : funcDefs_) {
2218 if (!mg.compileFuncDef(funcImportMap_.count() + func.funcDefIndex(),
2219 func.line(), func.bytes().begin(),
2220 func.bytes().end(),
2221 std::move(func.callSiteLineNums()))) {
2222 return nullptr;
2226 if (!mg.finishFuncDefs()) {
2227 return nullptr;
2230 return mg.finishModule(*bytes);
2234 /*****************************************************************************/
2235 // Numeric literal utilities
2237 static bool IsNumericNonFloatLiteral(ParseNode* pn) {
2238 // Note: '-' is never rolled into the number; numbers are always positive
2239 // and negations must be applied manually.
2240 return pn->isKind(ParseNodeKind::NumberExpr) ||
2241 (pn->isKind(ParseNodeKind::NegExpr) &&
2242 UnaryKid(pn)->isKind(ParseNodeKind::NumberExpr));
2245 static bool IsCallToGlobal(ModuleValidatorShared& m, ParseNode* pn,
2246 const ModuleValidatorShared::Global** global) {
2247 if (!pn->isKind(ParseNodeKind::CallExpr)) {
2248 return false;
2251 ParseNode* callee = CallCallee(pn);
2252 if (!callee->isKind(ParseNodeKind::Name)) {
2253 return false;
2256 *global = m.lookupGlobal(callee->as<NameNode>().name());
2257 return !!*global;
2260 static bool IsCoercionCall(ModuleValidatorShared& m, ParseNode* pn,
2261 Type* coerceTo, ParseNode** coercedExpr) {
2262 const ModuleValidatorShared::Global* global;
2263 if (!IsCallToGlobal(m, pn, &global)) {
2264 return false;
2267 if (CallArgListLength(pn) != 1) {
2268 return false;
2271 if (coercedExpr) {
2272 *coercedExpr = CallArgList(pn);
2275 if (global->isMathFunction() &&
2276 global->mathBuiltinFunction() == AsmJSMathBuiltin_fround) {
2277 *coerceTo = Type::Float;
2278 return true;
2281 return false;
2284 static bool IsFloatLiteral(ModuleValidatorShared& m, ParseNode* pn) {
2285 ParseNode* coercedExpr;
2286 Type coerceTo;
2287 if (!IsCoercionCall(m, pn, &coerceTo, &coercedExpr)) {
2288 return false;
2290 // Don't fold into || to avoid clang/memcheck bug (bug 1077031).
2291 if (!coerceTo.isFloat()) {
2292 return false;
2294 return IsNumericNonFloatLiteral(coercedExpr);
2297 static bool IsNumericLiteral(ModuleValidatorShared& m, ParseNode* pn) {
2298 return IsNumericNonFloatLiteral(pn) || IsFloatLiteral(m, pn);
2301 // The JS grammar treats -42 as -(42) (i.e., with separate grammar
2302 // productions) for the unary - and literal 42). However, the asm.js spec
2303 // recognizes -42 (modulo parens, so -(42) and -((42))) as a single literal
2304 // so fold the two potential parse nodes into a single double value.
2305 static double ExtractNumericNonFloatValue(ParseNode* pn,
2306 ParseNode** out = nullptr) {
2307 MOZ_ASSERT(IsNumericNonFloatLiteral(pn));
2309 if (pn->isKind(ParseNodeKind::NegExpr)) {
2310 pn = UnaryKid(pn);
2311 if (out) {
2312 *out = pn;
2314 return -NumberNodeValue(pn);
2317 return NumberNodeValue(pn);
2320 static NumLit ExtractNumericLiteral(ModuleValidatorShared& m, ParseNode* pn) {
2321 MOZ_ASSERT(IsNumericLiteral(m, pn));
2323 if (pn->isKind(ParseNodeKind::CallExpr)) {
2324 // Float literals are explicitly coerced and thus the coerced literal may be
2325 // any valid (non-float) numeric literal.
2326 MOZ_ASSERT(CallArgListLength(pn) == 1);
2327 pn = CallArgList(pn);
2328 double d = ExtractNumericNonFloatValue(pn);
2329 return NumLit(NumLit::Float, DoubleValue(d));
2332 double d = ExtractNumericNonFloatValue(pn, &pn);
2334 // The asm.js spec syntactically distinguishes any literal containing a
2335 // decimal point or the literal -0 as having double type.
2336 if (NumberNodeHasFrac(pn) || IsNegativeZero(d)) {
2337 return NumLit(NumLit::Double, DoubleValue(d));
2340 // The syntactic checks above rule out these double values.
2341 MOZ_ASSERT(!IsNegativeZero(d));
2342 MOZ_ASSERT(!std::isnan(d));
2344 // Although doubles can only *precisely* represent 53-bit integers, they
2345 // can *imprecisely* represent integers much bigger than an int64_t.
2346 // Furthermore, d may be inf or -inf. In both cases, casting to an int64_t
2347 // is undefined, so test against the integer bounds using doubles.
2348 if (d < double(INT32_MIN) || d > double(UINT32_MAX)) {
2349 return NumLit(NumLit::OutOfRangeInt, UndefinedValue());
2352 // With the above syntactic and range limitations, d is definitely an
2353 // integer in the range [INT32_MIN, UINT32_MAX] range.
2354 int64_t i64 = int64_t(d);
2355 if (i64 >= 0) {
2356 if (i64 <= INT32_MAX) {
2357 return NumLit(NumLit::Fixnum, Int32Value(i64));
2359 MOZ_ASSERT(i64 <= UINT32_MAX);
2360 return NumLit(NumLit::BigUnsigned, Int32Value(uint32_t(i64)));
2362 MOZ_ASSERT(i64 >= INT32_MIN);
2363 return NumLit(NumLit::NegativeInt, Int32Value(i64));
2366 static inline bool IsLiteralInt(const NumLit& lit, uint32_t* u32) {
2367 switch (lit.which()) {
2368 case NumLit::Fixnum:
2369 case NumLit::BigUnsigned:
2370 case NumLit::NegativeInt:
2371 *u32 = lit.toUint32();
2372 return true;
2373 case NumLit::Double:
2374 case NumLit::Float:
2375 case NumLit::OutOfRangeInt:
2376 return false;
2378 MOZ_CRASH("Bad literal type");
2381 static inline bool IsLiteralInt(ModuleValidatorShared& m, ParseNode* pn,
2382 uint32_t* u32) {
2383 return IsNumericLiteral(m, pn) &&
2384 IsLiteralInt(ExtractNumericLiteral(m, pn), u32);
2387 /*****************************************************************************/
2389 namespace {
2391 using LabelVector = Vector<TaggedParserAtomIndex, 4, SystemAllocPolicy>;
2393 class MOZ_STACK_CLASS FunctionValidatorShared {
2394 public:
2395 struct Local {
2396 Type type;
2397 unsigned slot;
2398 Local(Type t, unsigned slot) : type(t), slot(slot) {
2399 MOZ_ASSERT(type.isCanonicalValType());
2403 protected:
2404 using LocalMap =
2405 HashMap<TaggedParserAtomIndex, Local, TaggedParserAtomIndexHasher>;
2406 using LabelMap =
2407 HashMap<TaggedParserAtomIndex, uint32_t, TaggedParserAtomIndexHasher>;
2409 // This is also a ModuleValidator<Unit>& after the appropriate static_cast<>.
2410 ModuleValidatorShared& m_;
2412 FunctionNode* fn_;
2413 Bytes bytes_;
2414 Encoder encoder_;
2415 Uint32Vector callSiteLineNums_;
2416 LocalMap locals_;
2418 // Labels
2419 LabelMap breakLabels_;
2420 LabelMap continueLabels_;
2421 Uint32Vector breakableStack_;
2422 Uint32Vector continuableStack_;
2423 uint32_t blockDepth_;
2425 bool hasAlreadyReturned_;
2426 Maybe<ValType> ret_;
2428 private:
2429 FunctionValidatorShared(ModuleValidatorShared& m, FunctionNode* fn,
2430 FrontendContext* fc)
2431 : m_(m),
2432 fn_(fn),
2433 encoder_(bytes_),
2434 locals_(fc),
2435 breakLabels_(fc),
2436 continueLabels_(fc),
2437 blockDepth_(0),
2438 hasAlreadyReturned_(false) {}
2440 protected:
2441 template <typename Unit>
2442 FunctionValidatorShared(ModuleValidator<Unit>& m, FunctionNode* fn,
2443 FrontendContext* fc)
2444 : FunctionValidatorShared(static_cast<ModuleValidatorShared&>(m), fn,
2445 fc) {}
2447 public:
2448 ModuleValidatorShared& m() const { return m_; }
2450 FrontendContext* fc() const { return m_.fc(); }
2451 FunctionNode* fn() const { return fn_; }
2453 void define(ModuleValidatorShared::Func* func, unsigned line) {
2454 MOZ_ASSERT(!blockDepth_);
2455 MOZ_ASSERT(breakableStack_.empty());
2456 MOZ_ASSERT(continuableStack_.empty());
2457 MOZ_ASSERT(breakLabels_.empty());
2458 MOZ_ASSERT(continueLabels_.empty());
2459 func->define(fn_, line, std::move(bytes_), std::move(callSiteLineNums_));
2462 bool fail(ParseNode* pn, const char* str) { return m_.fail(pn, str); }
2464 bool failf(ParseNode* pn, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) {
2465 va_list ap;
2466 va_start(ap, fmt);
2467 m_.failfVAOffset(pn->pn_pos.begin, fmt, ap);
2468 va_end(ap);
2469 return false;
2472 bool failName(ParseNode* pn, const char* fmt, TaggedParserAtomIndex name) {
2473 return m_.failName(pn, fmt, name);
2476 /***************************************************** Local scope setup */
2478 bool addLocal(ParseNode* pn, TaggedParserAtomIndex name, Type type) {
2479 LocalMap::AddPtr p = locals_.lookupForAdd(name);
2480 if (p) {
2481 return failName(pn, "duplicate local name '%s' not allowed", name);
2483 return locals_.add(p, name, Local(type, locals_.count()));
2486 /****************************** For consistency of returns in a function */
2488 bool hasAlreadyReturned() const { return hasAlreadyReturned_; }
2490 Maybe<ValType> returnedType() const { return ret_; }
2492 void setReturnedType(const Maybe<ValType>& ret) {
2493 MOZ_ASSERT(!hasAlreadyReturned_);
2494 ret_ = ret;
2495 hasAlreadyReturned_ = true;
2498 /**************************************************************** Labels */
2499 private:
2500 bool writeBr(uint32_t absolute, Op op = Op::Br) {
2501 MOZ_ASSERT(op == Op::Br || op == Op::BrIf);
2502 MOZ_ASSERT(absolute < blockDepth_);
2503 return encoder().writeOp(op) &&
2504 encoder().writeVarU32(blockDepth_ - 1 - absolute);
2506 void removeLabel(TaggedParserAtomIndex label, LabelMap* map) {
2507 LabelMap::Ptr p = map->lookup(label);
2508 MOZ_ASSERT(p);
2509 map->remove(p);
2512 public:
2513 bool pushBreakableBlock() {
2514 return encoder().writeOp(Op::Block) &&
2515 encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) &&
2516 breakableStack_.append(blockDepth_++);
2518 bool popBreakableBlock() {
2519 MOZ_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
2520 return encoder().writeOp(Op::End);
2523 bool pushUnbreakableBlock(const LabelVector* labels = nullptr) {
2524 if (labels) {
2525 for (TaggedParserAtomIndex label : *labels) {
2526 if (!breakLabels_.putNew(label, blockDepth_)) {
2527 return false;
2531 blockDepth_++;
2532 return encoder().writeOp(Op::Block) &&
2533 encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid));
2535 bool popUnbreakableBlock(const LabelVector* labels = nullptr) {
2536 if (labels) {
2537 for (TaggedParserAtomIndex label : *labels) {
2538 removeLabel(label, &breakLabels_);
2541 --blockDepth_;
2542 return encoder().writeOp(Op::End);
2545 bool pushContinuableBlock() {
2546 return encoder().writeOp(Op::Block) &&
2547 encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) &&
2548 continuableStack_.append(blockDepth_++);
2550 bool popContinuableBlock() {
2551 MOZ_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
2552 return encoder().writeOp(Op::End);
2555 bool pushLoop() {
2556 return encoder().writeOp(Op::Block) &&
2557 encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) &&
2558 encoder().writeOp(Op::Loop) &&
2559 encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) &&
2560 breakableStack_.append(blockDepth_++) &&
2561 continuableStack_.append(blockDepth_++);
2563 bool popLoop() {
2564 MOZ_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
2565 MOZ_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
2566 return encoder().writeOp(Op::End) && encoder().writeOp(Op::End);
2569 bool pushIf(size_t* typeAt) {
2570 ++blockDepth_;
2571 return encoder().writeOp(Op::If) && encoder().writePatchableFixedU7(typeAt);
2573 bool switchToElse() {
2574 MOZ_ASSERT(blockDepth_ > 0);
2575 return encoder().writeOp(Op::Else);
2577 void setIfType(size_t typeAt, TypeCode type) {
2578 encoder().patchFixedU7(typeAt, uint8_t(type));
2580 bool popIf() {
2581 MOZ_ASSERT(blockDepth_ > 0);
2582 --blockDepth_;
2583 return encoder().writeOp(Op::End);
2585 bool popIf(size_t typeAt, TypeCode type) {
2586 MOZ_ASSERT(blockDepth_ > 0);
2587 --blockDepth_;
2588 if (!encoder().writeOp(Op::End)) {
2589 return false;
2592 setIfType(typeAt, type);
2593 return true;
2596 bool writeBreakIf() { return writeBr(breakableStack_.back(), Op::BrIf); }
2597 bool writeContinueIf() { return writeBr(continuableStack_.back(), Op::BrIf); }
2598 bool writeUnlabeledBreakOrContinue(bool isBreak) {
2599 return writeBr(isBreak ? breakableStack_.back() : continuableStack_.back());
2601 bool writeContinue() { return writeBr(continuableStack_.back()); }
2603 bool addLabels(const LabelVector& labels, uint32_t relativeBreakDepth,
2604 uint32_t relativeContinueDepth) {
2605 for (TaggedParserAtomIndex label : labels) {
2606 if (!breakLabels_.putNew(label, blockDepth_ + relativeBreakDepth)) {
2607 return false;
2609 if (!continueLabels_.putNew(label, blockDepth_ + relativeContinueDepth)) {
2610 return false;
2613 return true;
2615 void removeLabels(const LabelVector& labels) {
2616 for (TaggedParserAtomIndex label : labels) {
2617 removeLabel(label, &breakLabels_);
2618 removeLabel(label, &continueLabels_);
2621 bool writeLabeledBreakOrContinue(TaggedParserAtomIndex label, bool isBreak) {
2622 LabelMap& map = isBreak ? breakLabels_ : continueLabels_;
2623 if (LabelMap::Ptr p = map.lookup(label)) {
2624 return writeBr(p->value());
2626 MOZ_CRASH("nonexistent label");
2629 /*************************************************** Read-only interface */
2631 const Local* lookupLocal(TaggedParserAtomIndex name) const {
2632 if (auto p = locals_.lookup(name)) {
2633 return &p->value();
2635 return nullptr;
2638 const ModuleValidatorShared::Global* lookupGlobal(
2639 TaggedParserAtomIndex name) const {
2640 if (locals_.has(name)) {
2641 return nullptr;
2643 return m_.lookupGlobal(name);
2646 size_t numLocals() const { return locals_.count(); }
2648 /**************************************************** Encoding interface */
2650 Encoder& encoder() { return encoder_; }
2652 [[nodiscard]] bool writeInt32Lit(int32_t i32) {
2653 return encoder().writeOp(Op::I32Const) && encoder().writeVarS32(i32);
2655 [[nodiscard]] bool writeConstExpr(const NumLit& lit) {
2656 switch (lit.which()) {
2657 case NumLit::Fixnum:
2658 case NumLit::NegativeInt:
2659 case NumLit::BigUnsigned:
2660 return writeInt32Lit(lit.toInt32());
2661 case NumLit::Float:
2662 return encoder().writeOp(Op::F32Const) &&
2663 encoder().writeFixedF32(lit.toFloat());
2664 case NumLit::Double:
2665 return encoder().writeOp(Op::F64Const) &&
2666 encoder().writeFixedF64(lit.toDouble());
2667 case NumLit::OutOfRangeInt:
2668 break;
2670 MOZ_CRASH("unexpected literal type");
2674 // Encapsulates the building of an asm bytecode function from an asm.js function
2675 // source code, packing the asm.js code into the asm bytecode form that can
2676 // be decoded and compiled with a FunctionCompiler.
2677 template <typename Unit>
2678 class MOZ_STACK_CLASS FunctionValidator : public FunctionValidatorShared {
2679 public:
2680 FunctionValidator(ModuleValidator<Unit>& m, FunctionNode* fn)
2681 : FunctionValidatorShared(m, fn, m.fc()) {}
2683 public:
2684 ModuleValidator<Unit>& m() const {
2685 return static_cast<ModuleValidator<Unit>&>(FunctionValidatorShared::m());
2688 [[nodiscard]] bool writeCall(ParseNode* pn, Op op) {
2689 MOZ_ASSERT(op == Op::Call);
2690 if (!encoder().writeOp(op)) {
2691 return false;
2694 return appendCallSiteLineNumber(pn);
2696 [[nodiscard]] bool writeCall(ParseNode* pn, MozOp op) {
2697 MOZ_ASSERT(op == MozOp::OldCallDirect || op == MozOp::OldCallIndirect);
2698 if (!encoder().writeOp(op)) {
2699 return false;
2702 return appendCallSiteLineNumber(pn);
2704 [[nodiscard]] bool prepareCall(ParseNode* pn) {
2705 return appendCallSiteLineNumber(pn);
2708 private:
2709 [[nodiscard]] bool appendCallSiteLineNumber(ParseNode* node) {
2710 const TokenStreamAnyChars& anyChars = m().tokenStream().anyCharsAccess();
2711 auto lineToken = anyChars.lineToken(node->pn_pos.begin);
2712 uint32_t lineNumber = anyChars.lineNumber(lineToken);
2713 if (lineNumber > CallSiteDesc::MAX_LINE_OR_BYTECODE_VALUE) {
2714 return fail(node, "line number exceeding implementation limits");
2716 return callSiteLineNums_.append(lineNumber);
2720 } /* anonymous namespace */
2722 /*****************************************************************************/
2723 // asm.js type-checking and code-generation algorithm
2725 static bool CheckIdentifier(ModuleValidatorShared& m, ParseNode* usepn,
2726 TaggedParserAtomIndex name) {
2727 if (name == TaggedParserAtomIndex::WellKnown::arguments() ||
2728 name == TaggedParserAtomIndex::WellKnown::eval()) {
2729 return m.failName(usepn, "'%s' is not an allowed identifier", name);
2731 return true;
2734 static bool CheckModuleLevelName(ModuleValidatorShared& m, ParseNode* usepn,
2735 TaggedParserAtomIndex name) {
2736 if (!CheckIdentifier(m, usepn, name)) {
2737 return false;
2740 if (name == m.moduleFunctionName() || name == m.globalArgumentName() ||
2741 name == m.importArgumentName() || name == m.bufferArgumentName() ||
2742 m.lookupGlobal(name)) {
2743 return m.failName(usepn, "duplicate name '%s' not allowed", name);
2746 return true;
2749 static bool CheckFunctionHead(ModuleValidatorShared& m, FunctionNode* funNode) {
2750 FunctionBox* funbox = funNode->funbox();
2751 MOZ_ASSERT(!funbox->hasExprBody());
2753 if (funbox->hasRest()) {
2754 return m.fail(funNode, "rest args not allowed");
2756 if (funbox->hasDestructuringArgs) {
2757 return m.fail(funNode, "destructuring args not allowed");
2759 return true;
2762 static bool CheckArgument(ModuleValidatorShared& m, ParseNode* arg,
2763 TaggedParserAtomIndex* name) {
2764 *name = TaggedParserAtomIndex::null();
2766 if (!arg->isKind(ParseNodeKind::Name)) {
2767 return m.fail(arg, "argument is not a plain name");
2770 TaggedParserAtomIndex argName = arg->as<NameNode>().name();
2771 if (!CheckIdentifier(m, arg, argName)) {
2772 return false;
2775 *name = argName;
2776 return true;
2779 static bool CheckModuleArgument(ModuleValidatorShared& m, ParseNode* arg,
2780 TaggedParserAtomIndex* name) {
2781 if (!CheckArgument(m, arg, name)) {
2782 return false;
2785 return CheckModuleLevelName(m, arg, *name);
2788 static bool CheckModuleArguments(ModuleValidatorShared& m,
2789 FunctionNode* funNode) {
2790 unsigned numFormals;
2791 ParseNode* arg1 = FunctionFormalParametersList(funNode, &numFormals);
2792 ParseNode* arg2 = arg1 ? NextNode(arg1) : nullptr;
2793 ParseNode* arg3 = arg2 ? NextNode(arg2) : nullptr;
2795 if (numFormals > 3) {
2796 return m.fail(funNode, "asm.js modules takes at most 3 argument");
2799 TaggedParserAtomIndex arg1Name;
2800 if (arg1 && !CheckModuleArgument(m, arg1, &arg1Name)) {
2801 return false;
2803 if (!m.initGlobalArgumentName(arg1Name)) {
2804 return false;
2807 TaggedParserAtomIndex arg2Name;
2808 if (arg2 && !CheckModuleArgument(m, arg2, &arg2Name)) {
2809 return false;
2811 if (!m.initImportArgumentName(arg2Name)) {
2812 return false;
2815 TaggedParserAtomIndex arg3Name;
2816 if (arg3 && !CheckModuleArgument(m, arg3, &arg3Name)) {
2817 return false;
2819 return m.initBufferArgumentName(arg3Name);
2822 static bool CheckPrecedingStatements(ModuleValidatorShared& m,
2823 ParseNode* stmtList) {
2824 MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
2826 ParseNode* stmt = ListHead(stmtList);
2827 for (unsigned i = 0, n = ListLength(stmtList); i < n; i++) {
2828 if (!IsIgnoredDirective(stmt)) {
2829 return m.fail(stmt, "invalid asm.js statement");
2833 return true;
2836 static bool CheckGlobalVariableInitConstant(ModuleValidatorShared& m,
2837 TaggedParserAtomIndex varName,
2838 ParseNode* initNode, bool isConst) {
2839 NumLit lit = ExtractNumericLiteral(m, initNode);
2840 if (!lit.valid()) {
2841 return m.fail(initNode,
2842 "global initializer is out of representable integer range");
2845 Type canonicalType = Type::canonicalize(Type::lit(lit));
2846 if (!canonicalType.isGlobalVarType()) {
2847 return m.fail(initNode, "global variable type not allowed");
2850 return m.addGlobalVarInit(varName, lit, canonicalType, isConst);
2853 static bool CheckTypeAnnotation(ModuleValidatorShared& m,
2854 ParseNode* coercionNode, Type* coerceTo,
2855 ParseNode** coercedExpr = nullptr) {
2856 switch (coercionNode->getKind()) {
2857 case ParseNodeKind::BitOrExpr: {
2858 ParseNode* rhs = BitwiseRight(coercionNode);
2859 uint32_t i;
2860 if (!IsLiteralInt(m, rhs, &i) || i != 0) {
2861 return m.fail(rhs, "must use |0 for argument/return coercion");
2863 *coerceTo = Type::Int;
2864 if (coercedExpr) {
2865 *coercedExpr = BitwiseLeft(coercionNode);
2867 return true;
2869 case ParseNodeKind::PosExpr: {
2870 *coerceTo = Type::Double;
2871 if (coercedExpr) {
2872 *coercedExpr = UnaryKid(coercionNode);
2874 return true;
2876 case ParseNodeKind::CallExpr: {
2877 if (IsCoercionCall(m, coercionNode, coerceTo, coercedExpr)) {
2878 return true;
2880 break;
2882 default:;
2885 return m.fail(coercionNode, "must be of the form +x, x|0 or fround(x)");
2888 static bool CheckGlobalVariableInitImport(ModuleValidatorShared& m,
2889 TaggedParserAtomIndex varName,
2890 ParseNode* initNode, bool isConst) {
2891 Type coerceTo;
2892 ParseNode* coercedExpr;
2893 if (!CheckTypeAnnotation(m, initNode, &coerceTo, &coercedExpr)) {
2894 return false;
2897 if (!coercedExpr->isKind(ParseNodeKind::DotExpr)) {
2898 return m.failName(coercedExpr, "invalid import expression for global '%s'",
2899 varName);
2902 if (!coerceTo.isGlobalVarType()) {
2903 return m.fail(initNode, "global variable type not allowed");
2906 ParseNode* base = DotBase(coercedExpr);
2907 TaggedParserAtomIndex field = DotMember(coercedExpr);
2909 TaggedParserAtomIndex importName = m.importArgumentName();
2910 if (!importName) {
2911 return m.fail(coercedExpr,
2912 "cannot import without an asm.js foreign parameter");
2914 if (!IsUseOfName(base, importName)) {
2915 return m.failName(coercedExpr, "base of import expression must be '%s'",
2916 importName);
2919 return m.addGlobalVarImport(varName, field, coerceTo, isConst);
2922 static bool IsArrayViewCtorName(ModuleValidatorShared& m,
2923 TaggedParserAtomIndex name,
2924 Scalar::Type* type) {
2925 if (name == TaggedParserAtomIndex::WellKnown::Int8Array()) {
2926 *type = Scalar::Int8;
2927 } else if (name == TaggedParserAtomIndex::WellKnown::Uint8Array()) {
2928 *type = Scalar::Uint8;
2929 } else if (name == TaggedParserAtomIndex::WellKnown::Int16Array()) {
2930 *type = Scalar::Int16;
2931 } else if (name == TaggedParserAtomIndex::WellKnown::Uint16Array()) {
2932 *type = Scalar::Uint16;
2933 } else if (name == TaggedParserAtomIndex::WellKnown::Int32Array()) {
2934 *type = Scalar::Int32;
2935 } else if (name == TaggedParserAtomIndex::WellKnown::Uint32Array()) {
2936 *type = Scalar::Uint32;
2937 } else if (name == TaggedParserAtomIndex::WellKnown::Float32Array()) {
2938 *type = Scalar::Float32;
2939 } else if (name == TaggedParserAtomIndex::WellKnown::Float64Array()) {
2940 *type = Scalar::Float64;
2941 } else {
2942 return false;
2944 return true;
2947 static bool CheckNewArrayViewArgs(ModuleValidatorShared& m, ParseNode* newExpr,
2948 TaggedParserAtomIndex bufferName) {
2949 ParseNode* ctorExpr = BinaryLeft(newExpr);
2950 ParseNode* ctorArgs = BinaryRight(newExpr);
2951 ParseNode* bufArg = ListHead(ctorArgs);
2952 if (!bufArg || NextNode(bufArg) != nullptr) {
2953 return m.fail(ctorExpr,
2954 "array view constructor takes exactly one argument");
2957 if (!IsUseOfName(bufArg, bufferName)) {
2958 return m.failName(bufArg, "argument to array view constructor must be '%s'",
2959 bufferName);
2962 return true;
2965 static bool CheckNewArrayView(ModuleValidatorShared& m,
2966 TaggedParserAtomIndex varName,
2967 ParseNode* newExpr) {
2968 TaggedParserAtomIndex globalName = m.globalArgumentName();
2969 if (!globalName) {
2970 return m.fail(
2971 newExpr, "cannot create array view without an asm.js global parameter");
2974 TaggedParserAtomIndex bufferName = m.bufferArgumentName();
2975 if (!bufferName) {
2976 return m.fail(newExpr,
2977 "cannot create array view without an asm.js heap parameter");
2980 ParseNode* ctorExpr = BinaryLeft(newExpr);
2982 TaggedParserAtomIndex field;
2983 Scalar::Type type;
2984 if (ctorExpr->isKind(ParseNodeKind::DotExpr)) {
2985 ParseNode* base = DotBase(ctorExpr);
2987 if (!IsUseOfName(base, globalName)) {
2988 return m.failName(base, "expecting '%s.*Array", globalName);
2991 field = DotMember(ctorExpr);
2992 if (!IsArrayViewCtorName(m, field, &type)) {
2993 return m.fail(ctorExpr, "could not match typed array name");
2995 } else {
2996 if (!ctorExpr->isKind(ParseNodeKind::Name)) {
2997 return m.fail(ctorExpr,
2998 "expecting name of imported array view constructor");
3001 TaggedParserAtomIndex globalName = ctorExpr->as<NameNode>().name();
3002 const ModuleValidatorShared::Global* global = m.lookupGlobal(globalName);
3003 if (!global) {
3004 return m.failName(ctorExpr, "%s not found in module global scope",
3005 globalName);
3008 if (global->which() != ModuleValidatorShared::Global::ArrayViewCtor) {
3009 return m.failName(ctorExpr,
3010 "%s must be an imported array view constructor",
3011 globalName);
3014 type = global->viewType();
3017 if (!CheckNewArrayViewArgs(m, newExpr, bufferName)) {
3018 return false;
3021 return m.addArrayView(varName, type, field);
3024 static bool CheckGlobalMathImport(ModuleValidatorShared& m, ParseNode* initNode,
3025 TaggedParserAtomIndex varName,
3026 TaggedParserAtomIndex field) {
3027 // Math builtin, with the form glob.Math.[[builtin]]
3028 ModuleValidatorShared::MathBuiltin mathBuiltin;
3029 if (!m.lookupStandardLibraryMathName(field, &mathBuiltin)) {
3030 return m.failName(initNode, "'%s' is not a standard Math builtin", field);
3033 switch (mathBuiltin.kind) {
3034 case ModuleValidatorShared::MathBuiltin::Function:
3035 return m.addMathBuiltinFunction(varName, mathBuiltin.u.func, field);
3036 case ModuleValidatorShared::MathBuiltin::Constant:
3037 return m.addMathBuiltinConstant(varName, mathBuiltin.u.cst, field);
3038 default:
3039 break;
3041 MOZ_CRASH("unexpected or uninitialized math builtin type");
3044 static bool CheckGlobalDotImport(ModuleValidatorShared& m,
3045 TaggedParserAtomIndex varName,
3046 ParseNode* initNode) {
3047 ParseNode* base = DotBase(initNode);
3048 TaggedParserAtomIndex field = DotMember(initNode);
3050 if (base->isKind(ParseNodeKind::DotExpr)) {
3051 ParseNode* global = DotBase(base);
3052 TaggedParserAtomIndex math = DotMember(base);
3054 TaggedParserAtomIndex globalName = m.globalArgumentName();
3055 if (!globalName) {
3056 return m.fail(
3057 base, "import statement requires the module have a stdlib parameter");
3060 if (!IsUseOfName(global, globalName)) {
3061 if (global->isKind(ParseNodeKind::DotExpr)) {
3062 return m.failName(base,
3063 "imports can have at most two dot accesses "
3064 "(e.g. %s.Math.sin)",
3065 globalName);
3067 return m.failName(base, "expecting %s.*", globalName);
3070 if (math == TaggedParserAtomIndex::WellKnown::Math()) {
3071 return CheckGlobalMathImport(m, initNode, varName, field);
3073 return m.failName(base, "expecting %s.Math", globalName);
3076 if (!base->isKind(ParseNodeKind::Name)) {
3077 return m.fail(base, "expected name of variable or parameter");
3080 auto baseName = base->as<NameNode>().name();
3081 if (baseName == m.globalArgumentName()) {
3082 if (field == TaggedParserAtomIndex::WellKnown::NaN()) {
3083 return m.addGlobalConstant(varName, GenericNaN(), field);
3085 if (field == TaggedParserAtomIndex::WellKnown::Infinity()) {
3086 return m.addGlobalConstant(varName, PositiveInfinity<double>(), field);
3089 Scalar::Type type;
3090 if (IsArrayViewCtorName(m, field, &type)) {
3091 return m.addArrayViewCtor(varName, type, field);
3094 return m.failName(
3095 initNode, "'%s' is not a standard constant or typed array name", field);
3098 if (baseName != m.importArgumentName()) {
3099 return m.fail(base, "expected global or import name");
3102 return m.addFFI(varName, field);
3105 static bool CheckModuleGlobal(ModuleValidatorShared& m, ParseNode* decl,
3106 bool isConst) {
3107 if (!decl->isKind(ParseNodeKind::AssignExpr)) {
3108 return m.fail(decl, "module import needs initializer");
3110 AssignmentNode* assignNode = &decl->as<AssignmentNode>();
3112 ParseNode* var = assignNode->left();
3114 if (!var->isKind(ParseNodeKind::Name)) {
3115 return m.fail(var, "import variable is not a plain name");
3118 TaggedParserAtomIndex varName = var->as<NameNode>().name();
3119 if (!CheckModuleLevelName(m, var, varName)) {
3120 return false;
3123 ParseNode* initNode = assignNode->right();
3125 if (IsNumericLiteral(m, initNode)) {
3126 return CheckGlobalVariableInitConstant(m, varName, initNode, isConst);
3129 if (initNode->isKind(ParseNodeKind::BitOrExpr) ||
3130 initNode->isKind(ParseNodeKind::PosExpr) ||
3131 initNode->isKind(ParseNodeKind::CallExpr)) {
3132 return CheckGlobalVariableInitImport(m, varName, initNode, isConst);
3135 if (initNode->isKind(ParseNodeKind::NewExpr)) {
3136 return CheckNewArrayView(m, varName, initNode);
3139 if (initNode->isKind(ParseNodeKind::DotExpr)) {
3140 return CheckGlobalDotImport(m, varName, initNode);
3143 return m.fail(initNode, "unsupported import expression");
3146 template <typename Unit>
3147 static bool CheckModuleProcessingDirectives(ModuleValidator<Unit>& m) {
3148 auto& ts = m.parser().tokenStream;
3149 while (true) {
3150 bool matched;
3151 if (!ts.matchToken(&matched, TokenKind::String,
3152 TokenStreamShared::SlashIsRegExp)) {
3153 return false;
3155 if (!matched) {
3156 return true;
3159 if (!IsIgnoredDirectiveName(ts.anyCharsAccess().currentToken().atom())) {
3160 return m.failCurrentOffset("unsupported processing directive");
3163 TokenKind tt;
3164 if (!ts.getToken(&tt)) {
3165 return false;
3167 if (tt != TokenKind::Semi) {
3168 return m.failCurrentOffset("expected semicolon after string literal");
3173 template <typename Unit>
3174 static bool CheckModuleGlobals(ModuleValidator<Unit>& m) {
3175 while (true) {
3176 ParseNode* varStmt;
3177 if (!ParseVarOrConstStatement(m.parser(), &varStmt)) {
3178 return false;
3180 if (!varStmt) {
3181 break;
3183 for (ParseNode* var = VarListHead(varStmt); var; var = NextNode(var)) {
3184 if (!CheckModuleGlobal(m, var,
3185 varStmt->isKind(ParseNodeKind::ConstDecl))) {
3186 return false;
3191 return true;
3194 static bool ArgFail(FunctionValidatorShared& f, TaggedParserAtomIndex argName,
3195 ParseNode* stmt) {
3196 return f.failName(stmt,
3197 "expecting argument type declaration for '%s' of the "
3198 "form 'arg = arg|0' or 'arg = +arg' or 'arg = fround(arg)'",
3199 argName);
3202 static bool CheckArgumentType(FunctionValidatorShared& f, ParseNode* stmt,
3203 TaggedParserAtomIndex name, Type* type) {
3204 if (!stmt || !IsExpressionStatement(stmt)) {
3205 return ArgFail(f, name, stmt ? stmt : f.fn());
3208 ParseNode* initNode = ExpressionStatementExpr(stmt);
3209 if (!initNode->isKind(ParseNodeKind::AssignExpr)) {
3210 return ArgFail(f, name, stmt);
3213 ParseNode* argNode = BinaryLeft(initNode);
3214 ParseNode* coercionNode = BinaryRight(initNode);
3216 if (!IsUseOfName(argNode, name)) {
3217 return ArgFail(f, name, stmt);
3220 ParseNode* coercedExpr;
3221 if (!CheckTypeAnnotation(f.m(), coercionNode, type, &coercedExpr)) {
3222 return false;
3225 if (!type->isArgType()) {
3226 return f.failName(stmt, "invalid type for argument '%s'", name);
3229 if (!IsUseOfName(coercedExpr, name)) {
3230 return ArgFail(f, name, stmt);
3233 return true;
3236 static bool CheckProcessingDirectives(ModuleValidatorShared& m,
3237 ParseNode** stmtIter) {
3238 ParseNode* stmt = *stmtIter;
3240 while (stmt && IsIgnoredDirective(stmt)) {
3241 stmt = NextNode(stmt);
3244 *stmtIter = stmt;
3245 return true;
3248 static bool CheckArguments(FunctionValidatorShared& f, ParseNode** stmtIter,
3249 ValTypeVector* argTypes) {
3250 ParseNode* stmt = *stmtIter;
3252 unsigned numFormals;
3253 ParseNode* argpn = FunctionFormalParametersList(f.fn(), &numFormals);
3255 for (unsigned i = 0; i < numFormals;
3256 i++, argpn = NextNode(argpn), stmt = NextNode(stmt)) {
3257 TaggedParserAtomIndex name;
3258 if (!CheckArgument(f.m(), argpn, &name)) {
3259 return false;
3262 Type type;
3263 if (!CheckArgumentType(f, stmt, name, &type)) {
3264 return false;
3267 if (!argTypes->append(type.canonicalToValType())) {
3268 return false;
3271 if (!f.addLocal(argpn, name, type)) {
3272 return false;
3276 *stmtIter = stmt;
3277 return true;
3280 static bool IsLiteralOrConst(FunctionValidatorShared& f, ParseNode* pn,
3281 NumLit* lit) {
3282 if (pn->isKind(ParseNodeKind::Name)) {
3283 const ModuleValidatorShared::Global* global =
3284 f.lookupGlobal(pn->as<NameNode>().name());
3285 if (!global ||
3286 global->which() != ModuleValidatorShared::Global::ConstantLiteral) {
3287 return false;
3290 *lit = global->constLiteralValue();
3291 return true;
3294 if (!IsNumericLiteral(f.m(), pn)) {
3295 return false;
3298 *lit = ExtractNumericLiteral(f.m(), pn);
3299 return true;
3302 static bool CheckFinalReturn(FunctionValidatorShared& f,
3303 ParseNode* lastNonEmptyStmt) {
3304 if (!f.encoder().writeOp(Op::End)) {
3305 return false;
3308 if (!f.hasAlreadyReturned()) {
3309 f.setReturnedType(Nothing());
3310 return true;
3313 if (!lastNonEmptyStmt->isKind(ParseNodeKind::ReturnStmt) &&
3314 f.returnedType()) {
3315 return f.fail(lastNonEmptyStmt,
3316 "void incompatible with previous return type");
3319 return true;
3322 static bool CheckVariable(FunctionValidatorShared& f, ParseNode* decl,
3323 ValTypeVector* types, Vector<NumLit>* inits) {
3324 if (!decl->isKind(ParseNodeKind::AssignExpr)) {
3325 return f.failName(
3326 decl, "var '%s' needs explicit type declaration via an initial value",
3327 decl->as<NameNode>().name());
3329 AssignmentNode* assignNode = &decl->as<AssignmentNode>();
3331 ParseNode* var = assignNode->left();
3333 if (!var->isKind(ParseNodeKind::Name)) {
3334 return f.fail(var, "local variable is not a plain name");
3337 TaggedParserAtomIndex name = var->as<NameNode>().name();
3339 if (!CheckIdentifier(f.m(), var, name)) {
3340 return false;
3343 ParseNode* initNode = assignNode->right();
3345 NumLit lit;
3346 if (!IsLiteralOrConst(f, initNode, &lit)) {
3347 return f.failName(
3348 var, "var '%s' initializer must be literal or const literal", name);
3351 if (!lit.valid()) {
3352 return f.failName(var, "var '%s' initializer out of range", name);
3355 Type type = Type::canonicalize(Type::lit(lit));
3357 return f.addLocal(var, name, type) &&
3358 types->append(type.canonicalToValType()) && inits->append(lit);
3361 static bool CheckVariables(FunctionValidatorShared& f, ParseNode** stmtIter) {
3362 ParseNode* stmt = *stmtIter;
3364 uint32_t firstVar = f.numLocals();
3366 ValTypeVector types;
3367 Vector<NumLit> inits(f.fc());
3369 for (; stmt && stmt->isKind(ParseNodeKind::VarStmt);
3370 stmt = NextNonEmptyStatement(stmt)) {
3371 for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) {
3372 if (!CheckVariable(f, var, &types, &inits)) {
3373 return false;
3378 MOZ_ASSERT(f.encoder().empty());
3380 if (!EncodeLocalEntries(f.encoder(), types)) {
3381 return false;
3384 for (uint32_t i = 0; i < inits.length(); i++) {
3385 NumLit lit = inits[i];
3386 if (lit.isZeroBits()) {
3387 continue;
3389 if (!f.writeConstExpr(lit)) {
3390 return false;
3392 if (!f.encoder().writeOp(Op::LocalSet)) {
3393 return false;
3395 if (!f.encoder().writeVarU32(firstVar + i)) {
3396 return false;
3400 *stmtIter = stmt;
3401 return true;
3404 template <typename Unit>
3405 static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* expr, Type* type);
3407 template <typename Unit>
3408 static bool CheckNumericLiteral(FunctionValidator<Unit>& f, ParseNode* num,
3409 Type* type) {
3410 NumLit lit = ExtractNumericLiteral(f.m(), num);
3411 if (!lit.valid()) {
3412 return f.fail(num, "numeric literal out of representable integer range");
3414 *type = Type::lit(lit);
3415 return f.writeConstExpr(lit);
3418 static bool CheckVarRef(FunctionValidatorShared& f, ParseNode* varRef,
3419 Type* type) {
3420 TaggedParserAtomIndex name = varRef->as<NameNode>().name();
3422 if (const FunctionValidatorShared::Local* local = f.lookupLocal(name)) {
3423 if (!f.encoder().writeOp(Op::LocalGet)) {
3424 return false;
3426 if (!f.encoder().writeVarU32(local->slot)) {
3427 return false;
3429 *type = local->type;
3430 return true;
3433 if (const ModuleValidatorShared::Global* global = f.lookupGlobal(name)) {
3434 switch (global->which()) {
3435 case ModuleValidatorShared::Global::ConstantLiteral:
3436 *type = global->varOrConstType();
3437 return f.writeConstExpr(global->constLiteralValue());
3438 case ModuleValidatorShared::Global::ConstantImport:
3439 case ModuleValidatorShared::Global::Variable: {
3440 *type = global->varOrConstType();
3441 return f.encoder().writeOp(Op::GlobalGet) &&
3442 f.encoder().writeVarU32(global->varOrConstIndex());
3444 case ModuleValidatorShared::Global::Function:
3445 case ModuleValidatorShared::Global::FFI:
3446 case ModuleValidatorShared::Global::MathBuiltinFunction:
3447 case ModuleValidatorShared::Global::Table:
3448 case ModuleValidatorShared::Global::ArrayView:
3449 case ModuleValidatorShared::Global::ArrayViewCtor:
3450 break;
3452 return f.failName(varRef,
3453 "'%s' may not be accessed by ordinary expressions", name);
3456 return f.failName(varRef, "'%s' not found in local or asm.js module scope",
3457 name);
3460 static inline bool IsLiteralOrConstInt(FunctionValidatorShared& f,
3461 ParseNode* pn, uint32_t* u32) {
3462 NumLit lit;
3463 if (!IsLiteralOrConst(f, pn, &lit)) {
3464 return false;
3467 return IsLiteralInt(lit, u32);
3470 static const int32_t NoMask = -1;
3472 template <typename Unit>
3473 static bool CheckArrayAccess(FunctionValidator<Unit>& f, ParseNode* viewName,
3474 ParseNode* indexExpr, Scalar::Type* viewType) {
3475 if (!viewName->isKind(ParseNodeKind::Name)) {
3476 return f.fail(viewName,
3477 "base of array access must be a typed array view name");
3480 const ModuleValidatorShared::Global* global =
3481 f.lookupGlobal(viewName->as<NameNode>().name());
3482 if (!global || global->which() != ModuleValidatorShared::Global::ArrayView) {
3483 return f.fail(viewName,
3484 "base of array access must be a typed array view name");
3487 *viewType = global->viewType();
3489 uint32_t index;
3490 if (IsLiteralOrConstInt(f, indexExpr, &index)) {
3491 uint64_t byteOffset = uint64_t(index) << TypedArrayShift(*viewType);
3492 uint64_t width = TypedArrayElemSize(*viewType);
3493 if (!f.m().tryConstantAccess(byteOffset, width)) {
3494 return f.fail(indexExpr, "constant index out of range");
3497 return f.writeInt32Lit(byteOffset);
3500 // Mask off the low bits to account for the clearing effect of a right shift
3501 // followed by the left shift implicit in the array access. E.g., H32[i>>2]
3502 // loses the low two bits.
3503 int32_t mask = ~(TypedArrayElemSize(*viewType) - 1);
3505 if (indexExpr->isKind(ParseNodeKind::RshExpr)) {
3506 ParseNode* shiftAmountNode = BitwiseRight(indexExpr);
3508 uint32_t shift;
3509 if (!IsLiteralInt(f.m(), shiftAmountNode, &shift)) {
3510 return f.failf(shiftAmountNode, "shift amount must be constant");
3513 unsigned requiredShift = TypedArrayShift(*viewType);
3514 if (shift != requiredShift) {
3515 return f.failf(shiftAmountNode, "shift amount must be %u", requiredShift);
3518 ParseNode* pointerNode = BitwiseLeft(indexExpr);
3520 Type pointerType;
3521 if (!CheckExpr(f, pointerNode, &pointerType)) {
3522 return false;
3525 if (!pointerType.isIntish()) {
3526 return f.failf(pointerNode, "%s is not a subtype of int",
3527 pointerType.toChars());
3529 } else {
3530 // For legacy scalar access compatibility, accept Int8/Uint8 accesses
3531 // with no shift.
3532 if (TypedArrayShift(*viewType) != 0) {
3533 return f.fail(
3534 indexExpr,
3535 "index expression isn't shifted; must be an Int8/Uint8 access");
3538 MOZ_ASSERT(mask == NoMask);
3540 ParseNode* pointerNode = indexExpr;
3542 Type pointerType;
3543 if (!CheckExpr(f, pointerNode, &pointerType)) {
3544 return false;
3546 if (!pointerType.isInt()) {
3547 return f.failf(pointerNode, "%s is not a subtype of int",
3548 pointerType.toChars());
3552 // Don't generate the mask op if there is no need for it which could happen
3553 // for a shift of zero.
3554 if (mask != NoMask) {
3555 return f.writeInt32Lit(mask) && f.encoder().writeOp(Op::I32And);
3558 return true;
3561 static bool WriteArrayAccessFlags(FunctionValidatorShared& f,
3562 Scalar::Type viewType) {
3563 // asm.js only has naturally-aligned accesses.
3564 size_t align = TypedArrayElemSize(viewType);
3565 MOZ_ASSERT(IsPowerOfTwo(align));
3566 if (!f.encoder().writeFixedU8(CeilingLog2(align))) {
3567 return false;
3570 // asm.js doesn't have constant offsets, so just encode a 0.
3571 return f.encoder().writeVarU32(0);
3574 template <typename Unit>
3575 static bool CheckLoadArray(FunctionValidator<Unit>& f, ParseNode* elem,
3576 Type* type) {
3577 Scalar::Type viewType;
3579 if (!CheckArrayAccess(f, ElemBase(elem), ElemIndex(elem), &viewType)) {
3580 return false;
3583 switch (viewType) {
3584 case Scalar::Int8:
3585 if (!f.encoder().writeOp(Op::I32Load8S)) return false;
3586 break;
3587 case Scalar::Uint8:
3588 if (!f.encoder().writeOp(Op::I32Load8U)) return false;
3589 break;
3590 case Scalar::Int16:
3591 if (!f.encoder().writeOp(Op::I32Load16S)) return false;
3592 break;
3593 case Scalar::Uint16:
3594 if (!f.encoder().writeOp(Op::I32Load16U)) return false;
3595 break;
3596 case Scalar::Uint32:
3597 case Scalar::Int32:
3598 if (!f.encoder().writeOp(Op::I32Load)) return false;
3599 break;
3600 case Scalar::Float32:
3601 if (!f.encoder().writeOp(Op::F32Load)) return false;
3602 break;
3603 case Scalar::Float64:
3604 if (!f.encoder().writeOp(Op::F64Load)) return false;
3605 break;
3606 default:
3607 MOZ_CRASH("unexpected scalar type");
3610 switch (viewType) {
3611 case Scalar::Int8:
3612 case Scalar::Int16:
3613 case Scalar::Int32:
3614 case Scalar::Uint8:
3615 case Scalar::Uint16:
3616 case Scalar::Uint32:
3617 *type = Type::Intish;
3618 break;
3619 case Scalar::Float32:
3620 *type = Type::MaybeFloat;
3621 break;
3622 case Scalar::Float64:
3623 *type = Type::MaybeDouble;
3624 break;
3625 default:
3626 MOZ_CRASH("Unexpected array type");
3629 return WriteArrayAccessFlags(f, viewType);
3632 template <typename Unit>
3633 static bool CheckStoreArray(FunctionValidator<Unit>& f, ParseNode* lhs,
3634 ParseNode* rhs, Type* type) {
3635 Scalar::Type viewType;
3636 if (!CheckArrayAccess(f, ElemBase(lhs), ElemIndex(lhs), &viewType)) {
3637 return false;
3640 Type rhsType;
3641 if (!CheckExpr(f, rhs, &rhsType)) {
3642 return false;
3645 switch (viewType) {
3646 case Scalar::Int8:
3647 case Scalar::Int16:
3648 case Scalar::Int32:
3649 case Scalar::Uint8:
3650 case Scalar::Uint16:
3651 case Scalar::Uint32:
3652 if (!rhsType.isIntish()) {
3653 return f.failf(lhs, "%s is not a subtype of intish", rhsType.toChars());
3655 break;
3656 case Scalar::Float32:
3657 if (!rhsType.isMaybeDouble() && !rhsType.isFloatish()) {
3658 return f.failf(lhs, "%s is not a subtype of double? or floatish",
3659 rhsType.toChars());
3661 break;
3662 case Scalar::Float64:
3663 if (!rhsType.isMaybeFloat() && !rhsType.isMaybeDouble()) {
3664 return f.failf(lhs, "%s is not a subtype of float? or double?",
3665 rhsType.toChars());
3667 break;
3668 default:
3669 MOZ_CRASH("Unexpected view type");
3672 switch (viewType) {
3673 case Scalar::Int8:
3674 case Scalar::Uint8:
3675 if (!f.encoder().writeOp(MozOp::I32TeeStore8)) {
3676 return false;
3678 break;
3679 case Scalar::Int16:
3680 case Scalar::Uint16:
3681 if (!f.encoder().writeOp(MozOp::I32TeeStore16)) {
3682 return false;
3684 break;
3685 case Scalar::Int32:
3686 case Scalar::Uint32:
3687 if (!f.encoder().writeOp(MozOp::I32TeeStore)) {
3688 return false;
3690 break;
3691 case Scalar::Float32:
3692 if (rhsType.isFloatish()) {
3693 if (!f.encoder().writeOp(MozOp::F32TeeStore)) {
3694 return false;
3696 } else {
3697 if (!f.encoder().writeOp(MozOp::F64TeeStoreF32)) {
3698 return false;
3701 break;
3702 case Scalar::Float64:
3703 if (rhsType.isFloatish()) {
3704 if (!f.encoder().writeOp(MozOp::F32TeeStoreF64)) {
3705 return false;
3707 } else {
3708 if (!f.encoder().writeOp(MozOp::F64TeeStore)) {
3709 return false;
3712 break;
3713 default:
3714 MOZ_CRASH("unexpected scalar type");
3717 if (!WriteArrayAccessFlags(f, viewType)) {
3718 return false;
3721 *type = rhsType;
3722 return true;
3725 template <typename Unit>
3726 static bool CheckAssignName(FunctionValidator<Unit>& f, ParseNode* lhs,
3727 ParseNode* rhs, Type* type) {
3728 TaggedParserAtomIndex name = lhs->as<NameNode>().name();
3730 if (const FunctionValidatorShared::Local* lhsVar = f.lookupLocal(name)) {
3731 Type rhsType;
3732 if (!CheckExpr(f, rhs, &rhsType)) {
3733 return false;
3736 if (!f.encoder().writeOp(Op::LocalTee)) {
3737 return false;
3739 if (!f.encoder().writeVarU32(lhsVar->slot)) {
3740 return false;
3743 if (!(rhsType <= lhsVar->type)) {
3744 return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(),
3745 lhsVar->type.toChars());
3747 *type = rhsType;
3748 return true;
3751 if (const ModuleValidatorShared::Global* global = f.lookupGlobal(name)) {
3752 if (global->which() != ModuleValidatorShared::Global::Variable) {
3753 return f.failName(lhs, "'%s' is not a mutable variable", name);
3756 Type rhsType;
3757 if (!CheckExpr(f, rhs, &rhsType)) {
3758 return false;
3761 Type globType = global->varOrConstType();
3762 if (!(rhsType <= globType)) {
3763 return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(),
3764 globType.toChars());
3766 if (!f.encoder().writeOp(MozOp::TeeGlobal)) {
3767 return false;
3769 if (!f.encoder().writeVarU32(global->varOrConstIndex())) {
3770 return false;
3773 *type = rhsType;
3774 return true;
3777 return f.failName(lhs, "'%s' not found in local or asm.js module scope",
3778 name);
3781 template <typename Unit>
3782 static bool CheckAssign(FunctionValidator<Unit>& f, ParseNode* assign,
3783 Type* type) {
3784 MOZ_ASSERT(assign->isKind(ParseNodeKind::AssignExpr));
3786 ParseNode* lhs = BinaryLeft(assign);
3787 ParseNode* rhs = BinaryRight(assign);
3789 if (lhs->getKind() == ParseNodeKind::ElemExpr) {
3790 return CheckStoreArray(f, lhs, rhs, type);
3793 if (lhs->getKind() == ParseNodeKind::Name) {
3794 return CheckAssignName(f, lhs, rhs, type);
3797 return f.fail(
3798 assign,
3799 "left-hand side of assignment must be a variable or array access");
3802 template <typename Unit>
3803 static bool CheckMathIMul(FunctionValidator<Unit>& f, ParseNode* call,
3804 Type* type) {
3805 if (CallArgListLength(call) != 2) {
3806 return f.fail(call, "Math.imul must be passed 2 arguments");
3809 ParseNode* lhs = CallArgList(call);
3810 ParseNode* rhs = NextNode(lhs);
3812 Type lhsType;
3813 if (!CheckExpr(f, lhs, &lhsType)) {
3814 return false;
3817 Type rhsType;
3818 if (!CheckExpr(f, rhs, &rhsType)) {
3819 return false;
3822 if (!lhsType.isIntish()) {
3823 return f.failf(lhs, "%s is not a subtype of intish", lhsType.toChars());
3825 if (!rhsType.isIntish()) {
3826 return f.failf(rhs, "%s is not a subtype of intish", rhsType.toChars());
3829 *type = Type::Signed;
3830 return f.encoder().writeOp(Op::I32Mul);
3833 template <typename Unit>
3834 static bool CheckMathClz32(FunctionValidator<Unit>& f, ParseNode* call,
3835 Type* type) {
3836 if (CallArgListLength(call) != 1) {
3837 return f.fail(call, "Math.clz32 must be passed 1 argument");
3840 ParseNode* arg = CallArgList(call);
3842 Type argType;
3843 if (!CheckExpr(f, arg, &argType)) {
3844 return false;
3847 if (!argType.isIntish()) {
3848 return f.failf(arg, "%s is not a subtype of intish", argType.toChars());
3851 *type = Type::Fixnum;
3852 return f.encoder().writeOp(Op::I32Clz);
3855 template <typename Unit>
3856 static bool CheckMathAbs(FunctionValidator<Unit>& f, ParseNode* call,
3857 Type* type) {
3858 if (CallArgListLength(call) != 1) {
3859 return f.fail(call, "Math.abs must be passed 1 argument");
3862 ParseNode* arg = CallArgList(call);
3864 Type argType;
3865 if (!CheckExpr(f, arg, &argType)) {
3866 return false;
3869 if (argType.isSigned()) {
3870 *type = Type::Unsigned;
3871 return f.encoder().writeOp(MozOp::I32Abs);
3874 if (argType.isMaybeDouble()) {
3875 *type = Type::Double;
3876 return f.encoder().writeOp(Op::F64Abs);
3879 if (argType.isMaybeFloat()) {
3880 *type = Type::Floatish;
3881 return f.encoder().writeOp(Op::F32Abs);
3884 return f.failf(call, "%s is not a subtype of signed, float? or double?",
3885 argType.toChars());
3888 template <typename Unit>
3889 static bool CheckMathSqrt(FunctionValidator<Unit>& f, ParseNode* call,
3890 Type* type) {
3891 if (CallArgListLength(call) != 1) {
3892 return f.fail(call, "Math.sqrt must be passed 1 argument");
3895 ParseNode* arg = CallArgList(call);
3897 Type argType;
3898 if (!CheckExpr(f, arg, &argType)) {
3899 return false;
3902 if (argType.isMaybeDouble()) {
3903 *type = Type::Double;
3904 return f.encoder().writeOp(Op::F64Sqrt);
3907 if (argType.isMaybeFloat()) {
3908 *type = Type::Floatish;
3909 return f.encoder().writeOp(Op::F32Sqrt);
3912 return f.failf(call, "%s is neither a subtype of double? nor float?",
3913 argType.toChars());
3916 template <typename Unit>
3917 static bool CheckMathMinMax(FunctionValidator<Unit>& f, ParseNode* callNode,
3918 bool isMax, Type* type) {
3919 if (CallArgListLength(callNode) < 2) {
3920 return f.fail(callNode, "Math.min/max must be passed at least 2 arguments");
3923 ParseNode* firstArg = CallArgList(callNode);
3924 Type firstType;
3925 if (!CheckExpr(f, firstArg, &firstType)) {
3926 return false;
3929 Op op = Op::Limit;
3930 MozOp mozOp = MozOp::Limit;
3931 if (firstType.isMaybeDouble()) {
3932 *type = Type::Double;
3933 firstType = Type::MaybeDouble;
3934 op = isMax ? Op::F64Max : Op::F64Min;
3935 } else if (firstType.isMaybeFloat()) {
3936 *type = Type::Float;
3937 firstType = Type::MaybeFloat;
3938 op = isMax ? Op::F32Max : Op::F32Min;
3939 } else if (firstType.isSigned()) {
3940 *type = Type::Signed;
3941 firstType = Type::Signed;
3942 mozOp = isMax ? MozOp::I32Max : MozOp::I32Min;
3943 } else {
3944 return f.failf(firstArg, "%s is not a subtype of double?, float? or signed",
3945 firstType.toChars());
3948 unsigned numArgs = CallArgListLength(callNode);
3949 ParseNode* nextArg = NextNode(firstArg);
3950 for (unsigned i = 1; i < numArgs; i++, nextArg = NextNode(nextArg)) {
3951 Type nextType;
3952 if (!CheckExpr(f, nextArg, &nextType)) {
3953 return false;
3955 if (!(nextType <= firstType)) {
3956 return f.failf(nextArg, "%s is not a subtype of %s", nextType.toChars(),
3957 firstType.toChars());
3960 if (op != Op::Limit) {
3961 if (!f.encoder().writeOp(op)) {
3962 return false;
3964 } else {
3965 if (!f.encoder().writeOp(mozOp)) {
3966 return false;
3971 return true;
3974 using CheckArgType = bool (*)(FunctionValidatorShared& f, ParseNode* argNode,
3975 Type type);
3977 template <CheckArgType checkArg, typename Unit>
3978 static bool CheckCallArgs(FunctionValidator<Unit>& f, ParseNode* callNode,
3979 ValTypeVector* args) {
3980 ParseNode* argNode = CallArgList(callNode);
3981 for (unsigned i = 0; i < CallArgListLength(callNode);
3982 i++, argNode = NextNode(argNode)) {
3983 Type type;
3984 if (!CheckExpr(f, argNode, &type)) {
3985 return false;
3988 if (!checkArg(f, argNode, type)) {
3989 return false;
3992 if (!args->append(Type::canonicalize(type).canonicalToValType())) {
3993 return false;
3996 return true;
3999 static bool CheckSignatureAgainstExisting(ModuleValidatorShared& m,
4000 ParseNode* usepn, const FuncType& sig,
4001 const FuncType& existing) {
4002 if (!FuncType::strictlyEquals(sig, existing)) {
4003 return m.failf(usepn, "incompatible argument types to function");
4005 return true;
4008 template <typename Unit>
4009 static bool CheckFunctionSignature(ModuleValidator<Unit>& m, ParseNode* usepn,
4010 FuncType&& sig, TaggedParserAtomIndex name,
4011 ModuleValidatorShared::Func** func) {
4012 if (sig.args().length() > MaxParams) {
4013 return m.failf(usepn, "too many parameters");
4016 ModuleValidatorShared::Func* existing = m.lookupFuncDef(name);
4017 if (!existing) {
4018 if (!CheckModuleLevelName(m, usepn, name)) {
4019 return false;
4021 return m.addFuncDef(name, usepn->pn_pos.begin, std::move(sig), func);
4024 const FuncType& existingSig =
4025 m.env().types->type(existing->sigIndex()).funcType();
4027 if (!CheckSignatureAgainstExisting(m, usepn, sig, existingSig)) {
4028 return false;
4031 *func = existing;
4032 return true;
4035 static bool CheckIsArgType(FunctionValidatorShared& f, ParseNode* argNode,
4036 Type type) {
4037 if (!type.isArgType()) {
4038 return f.failf(argNode, "%s is not a subtype of int, float, or double",
4039 type.toChars());
4041 return true;
4044 template <typename Unit>
4045 static bool CheckInternalCall(FunctionValidator<Unit>& f, ParseNode* callNode,
4046 TaggedParserAtomIndex calleeName, Type ret,
4047 Type* type) {
4048 MOZ_ASSERT(ret.isCanonical());
4050 ValTypeVector args;
4051 if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) {
4052 return false;
4055 ValTypeVector results;
4056 Maybe<ValType> retType = ret.canonicalToReturnType();
4057 if (retType && !results.append(retType.ref())) {
4058 return false;
4061 FuncType sig(std::move(args), std::move(results));
4063 ModuleValidatorShared::Func* callee;
4064 if (!CheckFunctionSignature(f.m(), callNode, std::move(sig), calleeName,
4065 &callee)) {
4066 return false;
4069 if (!f.writeCall(callNode, MozOp::OldCallDirect)) {
4070 return false;
4073 if (!f.encoder().writeVarU32(callee->funcDefIndex())) {
4074 return false;
4077 *type = Type::ret(ret);
4078 return true;
4081 template <typename Unit>
4082 static bool CheckFuncPtrTableAgainstExisting(ModuleValidator<Unit>& m,
4083 ParseNode* usepn,
4084 TaggedParserAtomIndex name,
4085 FuncType&& sig, unsigned mask,
4086 uint32_t* tableIndex) {
4087 if (const ModuleValidatorShared::Global* existing = m.lookupGlobal(name)) {
4088 if (existing->which() != ModuleValidatorShared::Global::Table) {
4089 return m.failName(usepn, "'%s' is not a function-pointer table", name);
4092 ModuleValidatorShared::Table& table = m.table(existing->tableIndex());
4093 if (mask != table.mask()) {
4094 return m.failf(usepn, "mask does not match previous value (%u)",
4095 table.mask());
4098 if (!CheckSignatureAgainstExisting(
4099 m, usepn, sig, m.env().types->type(table.sigIndex()).funcType())) {
4100 return false;
4103 *tableIndex = existing->tableIndex();
4104 return true;
4107 if (!CheckModuleLevelName(m, usepn, name)) {
4108 return false;
4111 return m.declareFuncPtrTable(std::move(sig), name, usepn->pn_pos.begin, mask,
4112 tableIndex);
4115 template <typename Unit>
4116 static bool CheckFuncPtrCall(FunctionValidator<Unit>& f, ParseNode* callNode,
4117 Type ret, Type* type) {
4118 MOZ_ASSERT(ret.isCanonical());
4120 ParseNode* callee = CallCallee(callNode);
4121 ParseNode* tableNode = ElemBase(callee);
4122 ParseNode* indexExpr = ElemIndex(callee);
4124 if (!tableNode->isKind(ParseNodeKind::Name)) {
4125 return f.fail(tableNode, "expecting name of function-pointer array");
4128 TaggedParserAtomIndex name = tableNode->as<NameNode>().name();
4129 if (const ModuleValidatorShared::Global* existing = f.lookupGlobal(name)) {
4130 if (existing->which() != ModuleValidatorShared::Global::Table) {
4131 return f.failName(
4132 tableNode, "'%s' is not the name of a function-pointer array", name);
4136 if (!indexExpr->isKind(ParseNodeKind::BitAndExpr)) {
4137 return f.fail(indexExpr,
4138 "function-pointer table index expression needs & mask");
4141 ParseNode* indexNode = BitwiseLeft(indexExpr);
4142 ParseNode* maskNode = BitwiseRight(indexExpr);
4144 uint32_t mask;
4145 if (!IsLiteralInt(f.m(), maskNode, &mask) || mask == UINT32_MAX ||
4146 !IsPowerOfTwo(mask + 1)) {
4147 return f.fail(maskNode,
4148 "function-pointer table index mask value must be a power of "
4149 "two minus 1");
4152 Type indexType;
4153 if (!CheckExpr(f, indexNode, &indexType)) {
4154 return false;
4157 if (!indexType.isIntish()) {
4158 return f.failf(indexNode, "%s is not a subtype of intish",
4159 indexType.toChars());
4162 ValTypeVector args;
4163 if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) {
4164 return false;
4167 ValTypeVector results;
4168 Maybe<ValType> retType = ret.canonicalToReturnType();
4169 if (retType && !results.append(retType.ref())) {
4170 return false;
4173 FuncType sig(std::move(args), std::move(results));
4175 uint32_t tableIndex;
4176 if (!CheckFuncPtrTableAgainstExisting(f.m(), tableNode, name, std::move(sig),
4177 mask, &tableIndex)) {
4178 return false;
4181 if (!f.writeCall(callNode, MozOp::OldCallIndirect)) {
4182 return false;
4185 // Call signature
4186 if (!f.encoder().writeVarU32(f.m().table(tableIndex).sigIndex())) {
4187 return false;
4190 *type = Type::ret(ret);
4191 return true;
4194 static bool CheckIsExternType(FunctionValidatorShared& f, ParseNode* argNode,
4195 Type type) {
4196 if (!type.isExtern()) {
4197 return f.failf(argNode, "%s is not a subtype of extern", type.toChars());
4199 return true;
4202 template <typename Unit>
4203 static bool CheckFFICall(FunctionValidator<Unit>& f, ParseNode* callNode,
4204 unsigned ffiIndex, Type ret, Type* type) {
4205 MOZ_ASSERT(ret.isCanonical());
4207 TaggedParserAtomIndex calleeName =
4208 CallCallee(callNode)->as<NameNode>().name();
4210 if (ret.isFloat()) {
4211 return f.fail(callNode, "FFI calls can't return float");
4214 ValTypeVector args;
4215 if (!CheckCallArgs<CheckIsExternType>(f, callNode, &args)) {
4216 return false;
4219 ValTypeVector results;
4220 Maybe<ValType> retType = ret.canonicalToReturnType();
4221 if (retType && !results.append(retType.ref())) {
4222 return false;
4225 FuncType sig(std::move(args), std::move(results));
4227 uint32_t importIndex;
4228 if (!f.m().declareImport(calleeName, std::move(sig), ffiIndex,
4229 &importIndex)) {
4230 return false;
4233 if (!f.writeCall(callNode, Op::Call)) {
4234 return false;
4237 if (!f.encoder().writeVarU32(importIndex)) {
4238 return false;
4241 *type = Type::ret(ret);
4242 return true;
4245 static bool CheckFloatCoercionArg(FunctionValidatorShared& f,
4246 ParseNode* inputNode, Type inputType) {
4247 if (inputType.isMaybeDouble()) {
4248 return f.encoder().writeOp(Op::F32DemoteF64);
4250 if (inputType.isSigned()) {
4251 return f.encoder().writeOp(Op::F32ConvertI32S);
4253 if (inputType.isUnsigned()) {
4254 return f.encoder().writeOp(Op::F32ConvertI32U);
4256 if (inputType.isFloatish()) {
4257 return true;
4260 return f.failf(inputNode,
4261 "%s is not a subtype of signed, unsigned, double? or floatish",
4262 inputType.toChars());
4265 template <typename Unit>
4266 static bool CheckCoercedCall(FunctionValidator<Unit>& f, ParseNode* call,
4267 Type ret, Type* type);
4269 template <typename Unit>
4270 static bool CheckCoercionArg(FunctionValidator<Unit>& f, ParseNode* arg,
4271 Type expected, Type* type) {
4272 MOZ_ASSERT(expected.isCanonicalValType());
4274 if (arg->isKind(ParseNodeKind::CallExpr)) {
4275 return CheckCoercedCall(f, arg, expected, type);
4278 Type argType;
4279 if (!CheckExpr(f, arg, &argType)) {
4280 return false;
4283 if (expected.isFloat()) {
4284 if (!CheckFloatCoercionArg(f, arg, argType)) {
4285 return false;
4287 } else {
4288 MOZ_CRASH("not call coercions");
4291 *type = Type::ret(expected);
4292 return true;
4295 template <typename Unit>
4296 static bool CheckMathFRound(FunctionValidator<Unit>& f, ParseNode* callNode,
4297 Type* type) {
4298 if (CallArgListLength(callNode) != 1) {
4299 return f.fail(callNode, "Math.fround must be passed 1 argument");
4302 ParseNode* argNode = CallArgList(callNode);
4303 Type argType;
4304 if (!CheckCoercionArg(f, argNode, Type::Float, &argType)) {
4305 return false;
4308 MOZ_ASSERT(argType == Type::Float);
4309 *type = Type::Float;
4310 return true;
4313 template <typename Unit>
4314 static bool CheckMathBuiltinCall(FunctionValidator<Unit>& f,
4315 ParseNode* callNode,
4316 AsmJSMathBuiltinFunction func, Type* type) {
4317 unsigned arity = 0;
4318 Op f32 = Op::Limit;
4319 Op f64 = Op::Limit;
4320 MozOp mozf64 = MozOp::Limit;
4321 switch (func) {
4322 case AsmJSMathBuiltin_imul:
4323 return CheckMathIMul(f, callNode, type);
4324 case AsmJSMathBuiltin_clz32:
4325 return CheckMathClz32(f, callNode, type);
4326 case AsmJSMathBuiltin_abs:
4327 return CheckMathAbs(f, callNode, type);
4328 case AsmJSMathBuiltin_sqrt:
4329 return CheckMathSqrt(f, callNode, type);
4330 case AsmJSMathBuiltin_fround:
4331 return CheckMathFRound(f, callNode, type);
4332 case AsmJSMathBuiltin_min:
4333 return CheckMathMinMax(f, callNode, /* isMax = */ false, type);
4334 case AsmJSMathBuiltin_max:
4335 return CheckMathMinMax(f, callNode, /* isMax = */ true, type);
4336 case AsmJSMathBuiltin_ceil:
4337 arity = 1;
4338 f64 = Op::F64Ceil;
4339 f32 = Op::F32Ceil;
4340 break;
4341 case AsmJSMathBuiltin_floor:
4342 arity = 1;
4343 f64 = Op::F64Floor;
4344 f32 = Op::F32Floor;
4345 break;
4346 case AsmJSMathBuiltin_sin:
4347 arity = 1;
4348 if (!f.m().alwaysUseFdlibm()) {
4349 mozf64 = MozOp::F64SinNative;
4350 } else {
4351 mozf64 = MozOp::F64SinFdlibm;
4353 f32 = Op::Unreachable;
4354 break;
4355 case AsmJSMathBuiltin_cos:
4356 arity = 1;
4357 if (!f.m().alwaysUseFdlibm()) {
4358 mozf64 = MozOp::F64CosNative;
4359 } else {
4360 mozf64 = MozOp::F64CosFdlibm;
4362 f32 = Op::Unreachable;
4363 break;
4364 case AsmJSMathBuiltin_tan:
4365 arity = 1;
4366 if (!f.m().alwaysUseFdlibm()) {
4367 mozf64 = MozOp::F64TanNative;
4368 } else {
4369 mozf64 = MozOp::F64TanFdlibm;
4371 f32 = Op::Unreachable;
4372 break;
4373 case AsmJSMathBuiltin_asin:
4374 arity = 1;
4375 mozf64 = MozOp::F64Asin;
4376 f32 = Op::Unreachable;
4377 break;
4378 case AsmJSMathBuiltin_acos:
4379 arity = 1;
4380 mozf64 = MozOp::F64Acos;
4381 f32 = Op::Unreachable;
4382 break;
4383 case AsmJSMathBuiltin_atan:
4384 arity = 1;
4385 mozf64 = MozOp::F64Atan;
4386 f32 = Op::Unreachable;
4387 break;
4388 case AsmJSMathBuiltin_exp:
4389 arity = 1;
4390 mozf64 = MozOp::F64Exp;
4391 f32 = Op::Unreachable;
4392 break;
4393 case AsmJSMathBuiltin_log:
4394 arity = 1;
4395 mozf64 = MozOp::F64Log;
4396 f32 = Op::Unreachable;
4397 break;
4398 case AsmJSMathBuiltin_pow:
4399 arity = 2;
4400 mozf64 = MozOp::F64Pow;
4401 f32 = Op::Unreachable;
4402 break;
4403 case AsmJSMathBuiltin_atan2:
4404 arity = 2;
4405 mozf64 = MozOp::F64Atan2;
4406 f32 = Op::Unreachable;
4407 break;
4408 default:
4409 MOZ_CRASH("unexpected mathBuiltin function");
4412 unsigned actualArity = CallArgListLength(callNode);
4413 if (actualArity != arity) {
4414 return f.failf(callNode, "call passed %u arguments, expected %u",
4415 actualArity, arity);
4418 if (!f.prepareCall(callNode)) {
4419 return false;
4422 Type firstType;
4423 ParseNode* argNode = CallArgList(callNode);
4424 if (!CheckExpr(f, argNode, &firstType)) {
4425 return false;
4428 if (!firstType.isMaybeFloat() && !firstType.isMaybeDouble()) {
4429 return f.fail(
4430 argNode,
4431 "arguments to math call should be a subtype of double? or float?");
4434 bool opIsDouble = firstType.isMaybeDouble();
4435 if (!opIsDouble && f32 == Op::Unreachable) {
4436 return f.fail(callNode, "math builtin cannot be used as float");
4439 if (arity == 2) {
4440 Type secondType;
4441 argNode = NextNode(argNode);
4442 if (!CheckExpr(f, argNode, &secondType)) {
4443 return false;
4446 if (firstType.isMaybeDouble() && !secondType.isMaybeDouble()) {
4447 return f.fail(
4448 argNode,
4449 "both arguments to math builtin call should be the same type");
4451 if (firstType.isMaybeFloat() && !secondType.isMaybeFloat()) {
4452 return f.fail(
4453 argNode,
4454 "both arguments to math builtin call should be the same type");
4458 if (opIsDouble) {
4459 if (f64 != Op::Limit) {
4460 if (!f.encoder().writeOp(f64)) {
4461 return false;
4463 } else {
4464 if (!f.encoder().writeOp(mozf64)) {
4465 return false;
4468 } else {
4469 if (!f.encoder().writeOp(f32)) {
4470 return false;
4474 *type = opIsDouble ? Type::Double : Type::Floatish;
4475 return true;
4478 template <typename Unit>
4479 static bool CheckUncoercedCall(FunctionValidator<Unit>& f, ParseNode* expr,
4480 Type* type) {
4481 MOZ_ASSERT(expr->isKind(ParseNodeKind::CallExpr));
4483 const ModuleValidatorShared::Global* global;
4484 if (IsCallToGlobal(f.m(), expr, &global) && global->isMathFunction()) {
4485 return CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), type);
4488 return f.fail(
4489 expr,
4490 "all function calls must be calls to standard lib math functions,"
4491 " ignored (via f(); or comma-expression), coerced to signed (via f()|0),"
4492 " coerced to float (via fround(f())), or coerced to double (via +f())");
4495 static bool CoerceResult(FunctionValidatorShared& f, ParseNode* expr,
4496 Type expected, Type actual, Type* type) {
4497 MOZ_ASSERT(expected.isCanonical());
4499 // At this point, the bytecode resembles this:
4500 // | the thing we wanted to coerce | current position |>
4501 switch (expected.which()) {
4502 case Type::Void:
4503 if (!actual.isVoid()) {
4504 if (!f.encoder().writeOp(Op::Drop)) {
4505 return false;
4508 break;
4509 case Type::Int:
4510 if (!actual.isIntish()) {
4511 return f.failf(expr, "%s is not a subtype of intish", actual.toChars());
4513 break;
4514 case Type::Float:
4515 if (!CheckFloatCoercionArg(f, expr, actual)) {
4516 return false;
4518 break;
4519 case Type::Double:
4520 if (actual.isMaybeDouble()) {
4521 // No conversion necessary.
4522 } else if (actual.isMaybeFloat()) {
4523 if (!f.encoder().writeOp(Op::F64PromoteF32)) {
4524 return false;
4526 } else if (actual.isSigned()) {
4527 if (!f.encoder().writeOp(Op::F64ConvertI32S)) {
4528 return false;
4530 } else if (actual.isUnsigned()) {
4531 if (!f.encoder().writeOp(Op::F64ConvertI32U)) {
4532 return false;
4534 } else {
4535 return f.failf(
4536 expr, "%s is not a subtype of double?, float?, signed or unsigned",
4537 actual.toChars());
4539 break;
4540 default:
4541 MOZ_CRASH("unexpected uncoerced result type");
4544 *type = Type::ret(expected);
4545 return true;
4548 template <typename Unit>
4549 static bool CheckCoercedMathBuiltinCall(FunctionValidator<Unit>& f,
4550 ParseNode* callNode,
4551 AsmJSMathBuiltinFunction func, Type ret,
4552 Type* type) {
4553 Type actual;
4554 if (!CheckMathBuiltinCall(f, callNode, func, &actual)) {
4555 return false;
4557 return CoerceResult(f, callNode, ret, actual, type);
4560 template <typename Unit>
4561 static bool CheckCoercedCall(FunctionValidator<Unit>& f, ParseNode* call,
4562 Type ret, Type* type) {
4563 MOZ_ASSERT(ret.isCanonical());
4565 AutoCheckRecursionLimit recursion(f.fc());
4566 if (!recursion.checkDontReport(f.fc())) {
4567 return f.m().failOverRecursed();
4570 if (IsNumericLiteral(f.m(), call)) {
4571 NumLit lit = ExtractNumericLiteral(f.m(), call);
4572 if (!f.writeConstExpr(lit)) {
4573 return false;
4575 return CoerceResult(f, call, ret, Type::lit(lit), type);
4578 ParseNode* callee = CallCallee(call);
4580 if (callee->isKind(ParseNodeKind::ElemExpr)) {
4581 return CheckFuncPtrCall(f, call, ret, type);
4584 if (!callee->isKind(ParseNodeKind::Name)) {
4585 return f.fail(callee, "unexpected callee expression type");
4588 TaggedParserAtomIndex calleeName = callee->as<NameNode>().name();
4590 if (const ModuleValidatorShared::Global* global =
4591 f.lookupGlobal(calleeName)) {
4592 switch (global->which()) {
4593 case ModuleValidatorShared::Global::FFI:
4594 return CheckFFICall(f, call, global->ffiIndex(), ret, type);
4595 case ModuleValidatorShared::Global::MathBuiltinFunction:
4596 return CheckCoercedMathBuiltinCall(
4597 f, call, global->mathBuiltinFunction(), ret, type);
4598 case ModuleValidatorShared::Global::ConstantLiteral:
4599 case ModuleValidatorShared::Global::ConstantImport:
4600 case ModuleValidatorShared::Global::Variable:
4601 case ModuleValidatorShared::Global::Table:
4602 case ModuleValidatorShared::Global::ArrayView:
4603 case ModuleValidatorShared::Global::ArrayViewCtor:
4604 return f.failName(callee, "'%s' is not callable function", calleeName);
4605 case ModuleValidatorShared::Global::Function:
4606 break;
4610 return CheckInternalCall(f, call, calleeName, ret, type);
4613 template <typename Unit>
4614 static bool CheckPos(FunctionValidator<Unit>& f, ParseNode* pos, Type* type) {
4615 MOZ_ASSERT(pos->isKind(ParseNodeKind::PosExpr));
4616 ParseNode* operand = UnaryKid(pos);
4618 if (operand->isKind(ParseNodeKind::CallExpr)) {
4619 return CheckCoercedCall(f, operand, Type::Double, type);
4622 Type actual;
4623 if (!CheckExpr(f, operand, &actual)) {
4624 return false;
4627 return CoerceResult(f, operand, Type::Double, actual, type);
4630 template <typename Unit>
4631 static bool CheckNot(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
4632 MOZ_ASSERT(expr->isKind(ParseNodeKind::NotExpr));
4633 ParseNode* operand = UnaryKid(expr);
4635 Type operandType;
4636 if (!CheckExpr(f, operand, &operandType)) {
4637 return false;
4640 if (!operandType.isInt()) {
4641 return f.failf(operand, "%s is not a subtype of int",
4642 operandType.toChars());
4645 *type = Type::Int;
4646 return f.encoder().writeOp(Op::I32Eqz);
4649 template <typename Unit>
4650 static bool CheckNeg(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
4651 MOZ_ASSERT(expr->isKind(ParseNodeKind::NegExpr));
4652 ParseNode* operand = UnaryKid(expr);
4654 Type operandType;
4655 if (!CheckExpr(f, operand, &operandType)) {
4656 return false;
4659 if (operandType.isInt()) {
4660 *type = Type::Intish;
4661 return f.encoder().writeOp(MozOp::I32Neg);
4664 if (operandType.isMaybeDouble()) {
4665 *type = Type::Double;
4666 return f.encoder().writeOp(Op::F64Neg);
4669 if (operandType.isMaybeFloat()) {
4670 *type = Type::Floatish;
4671 return f.encoder().writeOp(Op::F32Neg);
4674 return f.failf(operand, "%s is not a subtype of int, float? or double?",
4675 operandType.toChars());
4678 template <typename Unit>
4679 static bool CheckCoerceToInt(FunctionValidator<Unit>& f, ParseNode* expr,
4680 Type* type) {
4681 MOZ_ASSERT(expr->isKind(ParseNodeKind::BitNotExpr));
4682 ParseNode* operand = UnaryKid(expr);
4684 Type operandType;
4685 if (!CheckExpr(f, operand, &operandType)) {
4686 return false;
4689 if (operandType.isMaybeDouble() || operandType.isMaybeFloat()) {
4690 *type = Type::Signed;
4691 Op opcode =
4692 operandType.isMaybeDouble() ? Op::I32TruncF64S : Op::I32TruncF32S;
4693 return f.encoder().writeOp(opcode);
4696 if (!operandType.isIntish()) {
4697 return f.failf(operand, "%s is not a subtype of double?, float? or intish",
4698 operandType.toChars());
4701 *type = Type::Signed;
4702 return true;
4705 template <typename Unit>
4706 static bool CheckBitNot(FunctionValidator<Unit>& f, ParseNode* neg,
4707 Type* type) {
4708 MOZ_ASSERT(neg->isKind(ParseNodeKind::BitNotExpr));
4709 ParseNode* operand = UnaryKid(neg);
4711 if (operand->isKind(ParseNodeKind::BitNotExpr)) {
4712 return CheckCoerceToInt(f, operand, type);
4715 Type operandType;
4716 if (!CheckExpr(f, operand, &operandType)) {
4717 return false;
4720 if (!operandType.isIntish()) {
4721 return f.failf(operand, "%s is not a subtype of intish",
4722 operandType.toChars());
4725 if (!f.encoder().writeOp(MozOp::I32BitNot)) {
4726 return false;
4729 *type = Type::Signed;
4730 return true;
4733 template <typename Unit>
4734 static bool CheckAsExprStatement(FunctionValidator<Unit>& f,
4735 ParseNode* exprStmt);
4737 template <typename Unit>
4738 static bool CheckComma(FunctionValidator<Unit>& f, ParseNode* comma,
4739 Type* type) {
4740 MOZ_ASSERT(comma->isKind(ParseNodeKind::CommaExpr));
4741 ParseNode* operands = ListHead(comma);
4743 // The block depth isn't taken into account here, because a comma list can't
4744 // contain breaks and continues and nested control flow structures.
4745 if (!f.encoder().writeOp(Op::Block)) {
4746 return false;
4749 size_t typeAt;
4750 if (!f.encoder().writePatchableFixedU7(&typeAt)) {
4751 return false;
4754 ParseNode* pn = operands;
4755 for (; NextNode(pn); pn = NextNode(pn)) {
4756 if (!CheckAsExprStatement(f, pn)) {
4757 return false;
4761 if (!CheckExpr(f, pn, type)) {
4762 return false;
4765 f.encoder().patchFixedU7(typeAt, uint8_t(type->toWasmBlockSignatureType()));
4767 return f.encoder().writeOp(Op::End);
4770 template <typename Unit>
4771 static bool CheckConditional(FunctionValidator<Unit>& f, ParseNode* ternary,
4772 Type* type) {
4773 MOZ_ASSERT(ternary->isKind(ParseNodeKind::ConditionalExpr));
4775 ParseNode* cond = TernaryKid1(ternary);
4776 ParseNode* thenExpr = TernaryKid2(ternary);
4777 ParseNode* elseExpr = TernaryKid3(ternary);
4779 Type condType;
4780 if (!CheckExpr(f, cond, &condType)) {
4781 return false;
4784 if (!condType.isInt()) {
4785 return f.failf(cond, "%s is not a subtype of int", condType.toChars());
4788 size_t typeAt;
4789 if (!f.pushIf(&typeAt)) {
4790 return false;
4793 Type thenType;
4794 if (!CheckExpr(f, thenExpr, &thenType)) {
4795 return false;
4798 if (!f.switchToElse()) {
4799 return false;
4802 Type elseType;
4803 if (!CheckExpr(f, elseExpr, &elseType)) {
4804 return false;
4807 if (thenType.isInt() && elseType.isInt()) {
4808 *type = Type::Int;
4809 } else if (thenType.isDouble() && elseType.isDouble()) {
4810 *type = Type::Double;
4811 } else if (thenType.isFloat() && elseType.isFloat()) {
4812 *type = Type::Float;
4813 } else {
4814 return f.failf(
4815 ternary,
4816 "then/else branches of conditional must both produce int, float, "
4817 "double, current types are %s and %s",
4818 thenType.toChars(), elseType.toChars());
4821 return f.popIf(typeAt, type->toWasmBlockSignatureType());
4824 template <typename Unit>
4825 static bool IsValidIntMultiplyConstant(ModuleValidator<Unit>& m,
4826 ParseNode* expr) {
4827 if (!IsNumericLiteral(m, expr)) {
4828 return false;
4831 NumLit lit = ExtractNumericLiteral(m, expr);
4832 switch (lit.which()) {
4833 case NumLit::Fixnum:
4834 case NumLit::NegativeInt:
4835 if (Abs(lit.toInt32()) < (uint32_t(1) << 20)) {
4836 return true;
4838 return false;
4839 case NumLit::BigUnsigned:
4840 case NumLit::Double:
4841 case NumLit::Float:
4842 case NumLit::OutOfRangeInt:
4843 return false;
4846 MOZ_CRASH("Bad literal");
4849 template <typename Unit>
4850 static bool CheckMultiply(FunctionValidator<Unit>& f, ParseNode* star,
4851 Type* type) {
4852 MOZ_ASSERT(star->isKind(ParseNodeKind::MulExpr));
4853 ParseNode* lhs = MultiplyLeft(star);
4854 ParseNode* rhs = MultiplyRight(star);
4856 Type lhsType;
4857 if (!CheckExpr(f, lhs, &lhsType)) {
4858 return false;
4861 Type rhsType;
4862 if (!CheckExpr(f, rhs, &rhsType)) {
4863 return false;
4866 if (lhsType.isInt() && rhsType.isInt()) {
4867 if (!IsValidIntMultiplyConstant(f.m(), lhs) &&
4868 !IsValidIntMultiplyConstant(f.m(), rhs)) {
4869 return f.fail(
4870 star,
4871 "one arg to int multiply must be a small (-2^20, 2^20) int literal");
4873 *type = Type::Intish;
4874 return f.encoder().writeOp(Op::I32Mul);
4877 if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
4878 *type = Type::Double;
4879 return f.encoder().writeOp(Op::F64Mul);
4882 if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
4883 *type = Type::Floatish;
4884 return f.encoder().writeOp(Op::F32Mul);
4887 return f.fail(
4888 star, "multiply operands must be both int, both double? or both float?");
4891 template <typename Unit>
4892 static bool CheckAddOrSub(FunctionValidator<Unit>& f, ParseNode* expr,
4893 Type* type, unsigned* numAddOrSubOut = nullptr) {
4894 AutoCheckRecursionLimit recursion(f.fc());
4895 if (!recursion.checkDontReport(f.fc())) {
4896 return f.m().failOverRecursed();
4899 MOZ_ASSERT(expr->isKind(ParseNodeKind::AddExpr) ||
4900 expr->isKind(ParseNodeKind::SubExpr));
4901 ParseNode* lhs = AddSubLeft(expr);
4902 ParseNode* rhs = AddSubRight(expr);
4904 Type lhsType, rhsType;
4905 unsigned lhsNumAddOrSub, rhsNumAddOrSub;
4907 if (lhs->isKind(ParseNodeKind::AddExpr) ||
4908 lhs->isKind(ParseNodeKind::SubExpr)) {
4909 if (!CheckAddOrSub(f, lhs, &lhsType, &lhsNumAddOrSub)) {
4910 return false;
4912 if (lhsType == Type::Intish) {
4913 lhsType = Type::Int;
4915 } else {
4916 if (!CheckExpr(f, lhs, &lhsType)) {
4917 return false;
4919 lhsNumAddOrSub = 0;
4922 if (rhs->isKind(ParseNodeKind::AddExpr) ||
4923 rhs->isKind(ParseNodeKind::SubExpr)) {
4924 if (!CheckAddOrSub(f, rhs, &rhsType, &rhsNumAddOrSub)) {
4925 return false;
4927 if (rhsType == Type::Intish) {
4928 rhsType = Type::Int;
4930 } else {
4931 if (!CheckExpr(f, rhs, &rhsType)) {
4932 return false;
4934 rhsNumAddOrSub = 0;
4937 unsigned numAddOrSub = lhsNumAddOrSub + rhsNumAddOrSub + 1;
4938 if (numAddOrSub > (1 << 20)) {
4939 return f.fail(expr, "too many + or - without intervening coercion");
4942 if (lhsType.isInt() && rhsType.isInt()) {
4943 if (!f.encoder().writeOp(
4944 expr->isKind(ParseNodeKind::AddExpr) ? Op::I32Add : Op::I32Sub)) {
4945 return false;
4947 *type = Type::Intish;
4948 } else if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
4949 if (!f.encoder().writeOp(
4950 expr->isKind(ParseNodeKind::AddExpr) ? Op::F64Add : Op::F64Sub)) {
4951 return false;
4953 *type = Type::Double;
4954 } else if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
4955 if (!f.encoder().writeOp(
4956 expr->isKind(ParseNodeKind::AddExpr) ? Op::F32Add : Op::F32Sub)) {
4957 return false;
4959 *type = Type::Floatish;
4960 } else {
4961 return f.failf(
4962 expr,
4963 "operands to + or - must both be int, float? or double?, got %s and %s",
4964 lhsType.toChars(), rhsType.toChars());
4967 if (numAddOrSubOut) {
4968 *numAddOrSubOut = numAddOrSub;
4970 return true;
4973 template <typename Unit>
4974 static bool CheckDivOrMod(FunctionValidator<Unit>& f, ParseNode* expr,
4975 Type* type) {
4976 MOZ_ASSERT(expr->isKind(ParseNodeKind::DivExpr) ||
4977 expr->isKind(ParseNodeKind::ModExpr));
4979 ParseNode* lhs = DivOrModLeft(expr);
4980 ParseNode* rhs = DivOrModRight(expr);
4982 Type lhsType, rhsType;
4983 if (!CheckExpr(f, lhs, &lhsType)) {
4984 return false;
4986 if (!CheckExpr(f, rhs, &rhsType)) {
4987 return false;
4990 if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
4991 *type = Type::Double;
4992 if (expr->isKind(ParseNodeKind::DivExpr)) {
4993 return f.encoder().writeOp(Op::F64Div);
4995 return f.encoder().writeOp(MozOp::F64Mod);
4998 if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
4999 *type = Type::Floatish;
5000 if (expr->isKind(ParseNodeKind::DivExpr)) {
5001 return f.encoder().writeOp(Op::F32Div);
5003 return f.fail(expr, "modulo cannot receive float arguments");
5006 if (lhsType.isSigned() && rhsType.isSigned()) {
5007 *type = Type::Intish;
5008 return f.encoder().writeOp(
5009 expr->isKind(ParseNodeKind::DivExpr) ? Op::I32DivS : Op::I32RemS);
5012 if (lhsType.isUnsigned() && rhsType.isUnsigned()) {
5013 *type = Type::Intish;
5014 return f.encoder().writeOp(
5015 expr->isKind(ParseNodeKind::DivExpr) ? Op::I32DivU : Op::I32RemU);
5018 return f.failf(
5019 expr,
5020 "arguments to / or %% must both be double?, float?, signed, or unsigned; "
5021 "%s and %s are given",
5022 lhsType.toChars(), rhsType.toChars());
5025 template <typename Unit>
5026 static bool CheckComparison(FunctionValidator<Unit>& f, ParseNode* comp,
5027 Type* type) {
5028 MOZ_ASSERT(comp->isKind(ParseNodeKind::LtExpr) ||
5029 comp->isKind(ParseNodeKind::LeExpr) ||
5030 comp->isKind(ParseNodeKind::GtExpr) ||
5031 comp->isKind(ParseNodeKind::GeExpr) ||
5032 comp->isKind(ParseNodeKind::EqExpr) ||
5033 comp->isKind(ParseNodeKind::NeExpr));
5035 ParseNode* lhs = ComparisonLeft(comp);
5036 ParseNode* rhs = ComparisonRight(comp);
5038 Type lhsType, rhsType;
5039 if (!CheckExpr(f, lhs, &lhsType)) {
5040 return false;
5042 if (!CheckExpr(f, rhs, &rhsType)) {
5043 return false;
5046 if (!(lhsType.isSigned() && rhsType.isSigned()) &&
5047 !(lhsType.isUnsigned() && rhsType.isUnsigned()) &&
5048 !(lhsType.isDouble() && rhsType.isDouble()) &&
5049 !(lhsType.isFloat() && rhsType.isFloat())) {
5050 return f.failf(comp,
5051 "arguments to a comparison must both be signed, unsigned, "
5052 "floats or doubles; "
5053 "%s and %s are given",
5054 lhsType.toChars(), rhsType.toChars());
5057 Op stmt;
5058 if (lhsType.isSigned() && rhsType.isSigned()) {
5059 switch (comp->getKind()) {
5060 case ParseNodeKind::EqExpr:
5061 stmt = Op::I32Eq;
5062 break;
5063 case ParseNodeKind::NeExpr:
5064 stmt = Op::I32Ne;
5065 break;
5066 case ParseNodeKind::LtExpr:
5067 stmt = Op::I32LtS;
5068 break;
5069 case ParseNodeKind::LeExpr:
5070 stmt = Op::I32LeS;
5071 break;
5072 case ParseNodeKind::GtExpr:
5073 stmt = Op::I32GtS;
5074 break;
5075 case ParseNodeKind::GeExpr:
5076 stmt = Op::I32GeS;
5077 break;
5078 default:
5079 MOZ_CRASH("unexpected comparison op");
5081 } else if (lhsType.isUnsigned() && rhsType.isUnsigned()) {
5082 switch (comp->getKind()) {
5083 case ParseNodeKind::EqExpr:
5084 stmt = Op::I32Eq;
5085 break;
5086 case ParseNodeKind::NeExpr:
5087 stmt = Op::I32Ne;
5088 break;
5089 case ParseNodeKind::LtExpr:
5090 stmt = Op::I32LtU;
5091 break;
5092 case ParseNodeKind::LeExpr:
5093 stmt = Op::I32LeU;
5094 break;
5095 case ParseNodeKind::GtExpr:
5096 stmt = Op::I32GtU;
5097 break;
5098 case ParseNodeKind::GeExpr:
5099 stmt = Op::I32GeU;
5100 break;
5101 default:
5102 MOZ_CRASH("unexpected comparison op");
5104 } else if (lhsType.isDouble()) {
5105 switch (comp->getKind()) {
5106 case ParseNodeKind::EqExpr:
5107 stmt = Op::F64Eq;
5108 break;
5109 case ParseNodeKind::NeExpr:
5110 stmt = Op::F64Ne;
5111 break;
5112 case ParseNodeKind::LtExpr:
5113 stmt = Op::F64Lt;
5114 break;
5115 case ParseNodeKind::LeExpr:
5116 stmt = Op::F64Le;
5117 break;
5118 case ParseNodeKind::GtExpr:
5119 stmt = Op::F64Gt;
5120 break;
5121 case ParseNodeKind::GeExpr:
5122 stmt = Op::F64Ge;
5123 break;
5124 default:
5125 MOZ_CRASH("unexpected comparison op");
5127 } else if (lhsType.isFloat()) {
5128 switch (comp->getKind()) {
5129 case ParseNodeKind::EqExpr:
5130 stmt = Op::F32Eq;
5131 break;
5132 case ParseNodeKind::NeExpr:
5133 stmt = Op::F32Ne;
5134 break;
5135 case ParseNodeKind::LtExpr:
5136 stmt = Op::F32Lt;
5137 break;
5138 case ParseNodeKind::LeExpr:
5139 stmt = Op::F32Le;
5140 break;
5141 case ParseNodeKind::GtExpr:
5142 stmt = Op::F32Gt;
5143 break;
5144 case ParseNodeKind::GeExpr:
5145 stmt = Op::F32Ge;
5146 break;
5147 default:
5148 MOZ_CRASH("unexpected comparison op");
5150 } else {
5151 MOZ_CRASH("unexpected type");
5154 *type = Type::Int;
5155 return f.encoder().writeOp(stmt);
5158 template <typename Unit>
5159 static bool CheckBitwise(FunctionValidator<Unit>& f, ParseNode* bitwise,
5160 Type* type) {
5161 ParseNode* lhs = BitwiseLeft(bitwise);
5162 ParseNode* rhs = BitwiseRight(bitwise);
5164 int32_t identityElement;
5165 bool onlyOnRight;
5166 switch (bitwise->getKind()) {
5167 case ParseNodeKind::BitOrExpr:
5168 identityElement = 0;
5169 onlyOnRight = false;
5170 *type = Type::Signed;
5171 break;
5172 case ParseNodeKind::BitAndExpr:
5173 identityElement = -1;
5174 onlyOnRight = false;
5175 *type = Type::Signed;
5176 break;
5177 case ParseNodeKind::BitXorExpr:
5178 identityElement = 0;
5179 onlyOnRight = false;
5180 *type = Type::Signed;
5181 break;
5182 case ParseNodeKind::LshExpr:
5183 identityElement = 0;
5184 onlyOnRight = true;
5185 *type = Type::Signed;
5186 break;
5187 case ParseNodeKind::RshExpr:
5188 identityElement = 0;
5189 onlyOnRight = true;
5190 *type = Type::Signed;
5191 break;
5192 case ParseNodeKind::UrshExpr:
5193 identityElement = 0;
5194 onlyOnRight = true;
5195 *type = Type::Unsigned;
5196 break;
5197 default:
5198 MOZ_CRASH("not a bitwise op");
5201 uint32_t i;
5202 if (!onlyOnRight && IsLiteralInt(f.m(), lhs, &i) &&
5203 i == uint32_t(identityElement)) {
5204 Type rhsType;
5205 if (!CheckExpr(f, rhs, &rhsType)) {
5206 return false;
5208 if (!rhsType.isIntish()) {
5209 return f.failf(bitwise, "%s is not a subtype of intish",
5210 rhsType.toChars());
5212 return true;
5215 if (IsLiteralInt(f.m(), rhs, &i) && i == uint32_t(identityElement)) {
5216 if (bitwise->isKind(ParseNodeKind::BitOrExpr) &&
5217 lhs->isKind(ParseNodeKind::CallExpr)) {
5218 return CheckCoercedCall(f, lhs, Type::Int, type);
5221 Type lhsType;
5222 if (!CheckExpr(f, lhs, &lhsType)) {
5223 return false;
5225 if (!lhsType.isIntish()) {
5226 return f.failf(bitwise, "%s is not a subtype of intish",
5227 lhsType.toChars());
5229 return true;
5232 Type lhsType;
5233 if (!CheckExpr(f, lhs, &lhsType)) {
5234 return false;
5237 Type rhsType;
5238 if (!CheckExpr(f, rhs, &rhsType)) {
5239 return false;
5242 if (!lhsType.isIntish()) {
5243 return f.failf(lhs, "%s is not a subtype of intish", lhsType.toChars());
5245 if (!rhsType.isIntish()) {
5246 return f.failf(rhs, "%s is not a subtype of intish", rhsType.toChars());
5249 switch (bitwise->getKind()) {
5250 case ParseNodeKind::BitOrExpr:
5251 if (!f.encoder().writeOp(Op::I32Or)) return false;
5252 break;
5253 case ParseNodeKind::BitAndExpr:
5254 if (!f.encoder().writeOp(Op::I32And)) return false;
5255 break;
5256 case ParseNodeKind::BitXorExpr:
5257 if (!f.encoder().writeOp(Op::I32Xor)) return false;
5258 break;
5259 case ParseNodeKind::LshExpr:
5260 if (!f.encoder().writeOp(Op::I32Shl)) return false;
5261 break;
5262 case ParseNodeKind::RshExpr:
5263 if (!f.encoder().writeOp(Op::I32ShrS)) return false;
5264 break;
5265 case ParseNodeKind::UrshExpr:
5266 if (!f.encoder().writeOp(Op::I32ShrU)) return false;
5267 break;
5268 default:
5269 MOZ_CRASH("not a bitwise op");
5272 return true;
5275 template <typename Unit>
5276 static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
5277 AutoCheckRecursionLimit recursion(f.fc());
5278 if (!recursion.checkDontReport(f.fc())) {
5279 return f.m().failOverRecursed();
5282 if (IsNumericLiteral(f.m(), expr)) {
5283 return CheckNumericLiteral(f, expr, type);
5286 switch (expr->getKind()) {
5287 case ParseNodeKind::Name:
5288 return CheckVarRef(f, expr, type);
5289 case ParseNodeKind::ElemExpr:
5290 return CheckLoadArray(f, expr, type);
5291 case ParseNodeKind::AssignExpr:
5292 return CheckAssign(f, expr, type);
5293 case ParseNodeKind::PosExpr:
5294 return CheckPos(f, expr, type);
5295 case ParseNodeKind::NotExpr:
5296 return CheckNot(f, expr, type);
5297 case ParseNodeKind::NegExpr:
5298 return CheckNeg(f, expr, type);
5299 case ParseNodeKind::BitNotExpr:
5300 return CheckBitNot(f, expr, type);
5301 case ParseNodeKind::CommaExpr:
5302 return CheckComma(f, expr, type);
5303 case ParseNodeKind::ConditionalExpr:
5304 return CheckConditional(f, expr, type);
5305 case ParseNodeKind::MulExpr:
5306 return CheckMultiply(f, expr, type);
5307 case ParseNodeKind::CallExpr:
5308 return CheckUncoercedCall(f, expr, type);
5310 case ParseNodeKind::AddExpr:
5311 case ParseNodeKind::SubExpr:
5312 return CheckAddOrSub(f, expr, type);
5314 case ParseNodeKind::DivExpr:
5315 case ParseNodeKind::ModExpr:
5316 return CheckDivOrMod(f, expr, type);
5318 case ParseNodeKind::LtExpr:
5319 case ParseNodeKind::LeExpr:
5320 case ParseNodeKind::GtExpr:
5321 case ParseNodeKind::GeExpr:
5322 case ParseNodeKind::EqExpr:
5323 case ParseNodeKind::NeExpr:
5324 return CheckComparison(f, expr, type);
5326 case ParseNodeKind::BitOrExpr:
5327 case ParseNodeKind::BitAndExpr:
5328 case ParseNodeKind::BitXorExpr:
5329 case ParseNodeKind::LshExpr:
5330 case ParseNodeKind::RshExpr:
5331 case ParseNodeKind::UrshExpr:
5332 return CheckBitwise(f, expr, type);
5334 default:;
5337 return f.fail(expr, "unsupported expression");
5340 template <typename Unit>
5341 static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt);
5343 template <typename Unit>
5344 static bool CheckAsExprStatement(FunctionValidator<Unit>& f, ParseNode* expr) {
5345 if (expr->isKind(ParseNodeKind::CallExpr)) {
5346 Type ignored;
5347 return CheckCoercedCall(f, expr, Type::Void, &ignored);
5350 Type resultType;
5351 if (!CheckExpr(f, expr, &resultType)) {
5352 return false;
5355 if (!resultType.isVoid()) {
5356 if (!f.encoder().writeOp(Op::Drop)) {
5357 return false;
5361 return true;
5364 template <typename Unit>
5365 static bool CheckExprStatement(FunctionValidator<Unit>& f,
5366 ParseNode* exprStmt) {
5367 MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt));
5368 return CheckAsExprStatement(f, UnaryKid(exprStmt));
5371 template <typename Unit>
5372 static bool CheckLoopConditionOnEntry(FunctionValidator<Unit>& f,
5373 ParseNode* cond) {
5374 uint32_t maybeLit;
5375 if (IsLiteralInt(f.m(), cond, &maybeLit) && maybeLit) {
5376 return true;
5379 Type condType;
5380 if (!CheckExpr(f, cond, &condType)) {
5381 return false;
5383 if (!condType.isInt()) {
5384 return f.failf(cond, "%s is not a subtype of int", condType.toChars());
5387 if (!f.encoder().writeOp(Op::I32Eqz)) {
5388 return false;
5391 // brIf (i32.eqz $f) $out
5392 return f.writeBreakIf();
5395 template <typename Unit>
5396 static bool CheckWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
5397 const LabelVector* labels = nullptr) {
5398 MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::WhileStmt));
5399 ParseNode* cond = BinaryLeft(whileStmt);
5400 ParseNode* body = BinaryRight(whileStmt);
5402 // A while loop `while(#cond) #body` is equivalent to:
5403 // (block $after_loop
5404 // (loop $top
5405 // (brIf $after_loop (i32.eq 0 #cond))
5406 // #body
5407 // (br $top)
5408 // )
5409 // )
5410 if (labels && !f.addLabels(*labels, 0, 1)) {
5411 return false;
5414 if (!f.pushLoop()) {
5415 return false;
5418 if (!CheckLoopConditionOnEntry(f, cond)) {
5419 return false;
5421 if (!CheckStatement(f, body)) {
5422 return false;
5424 if (!f.writeContinue()) {
5425 return false;
5428 if (!f.popLoop()) {
5429 return false;
5431 if (labels) {
5432 f.removeLabels(*labels);
5434 return true;
5437 template <typename Unit>
5438 static bool CheckFor(FunctionValidator<Unit>& f, ParseNode* forStmt,
5439 const LabelVector* labels = nullptr) {
5440 MOZ_ASSERT(forStmt->isKind(ParseNodeKind::ForStmt));
5441 ParseNode* forHead = BinaryLeft(forStmt);
5442 ParseNode* body = BinaryRight(forStmt);
5444 if (!forHead->isKind(ParseNodeKind::ForHead)) {
5445 return f.fail(forHead, "unsupported for-loop statement");
5448 ParseNode* maybeInit = TernaryKid1(forHead);
5449 ParseNode* maybeCond = TernaryKid2(forHead);
5450 ParseNode* maybeInc = TernaryKid3(forHead);
5452 // A for-loop `for (#init; #cond; #inc) #body` is equivalent to:
5453 // (block // depth X
5454 // (#init)
5455 // (block $after_loop // depth X+1 (block)
5456 // (loop $loop_top // depth X+2 (loop)
5457 // (brIf $after (eq 0 #cond))
5458 // (block $after_body #body) // depth X+3
5459 // #inc
5460 // (br $loop_top)
5461 // )
5462 // )
5463 // )
5464 // A break in the body should break out to $after_loop, i.e. depth + 1.
5465 // A continue in the body should break out to $after_body, i.e. depth + 3.
5466 if (labels && !f.addLabels(*labels, 1, 3)) {
5467 return false;
5470 if (!f.pushUnbreakableBlock()) {
5471 return false;
5474 if (maybeInit && !CheckAsExprStatement(f, maybeInit)) {
5475 return false;
5479 if (!f.pushLoop()) {
5480 return false;
5483 if (maybeCond && !CheckLoopConditionOnEntry(f, maybeCond)) {
5484 return false;
5488 // Continuing in the body should just break out to the increment.
5489 if (!f.pushContinuableBlock()) {
5490 return false;
5492 if (!CheckStatement(f, body)) {
5493 return false;
5495 if (!f.popContinuableBlock()) {
5496 return false;
5500 if (maybeInc && !CheckAsExprStatement(f, maybeInc)) {
5501 return false;
5504 if (!f.writeContinue()) {
5505 return false;
5507 if (!f.popLoop()) {
5508 return false;
5512 if (!f.popUnbreakableBlock()) {
5513 return false;
5516 if (labels) {
5517 f.removeLabels(*labels);
5520 return true;
5523 template <typename Unit>
5524 static bool CheckDoWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
5525 const LabelVector* labels = nullptr) {
5526 MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::DoWhileStmt));
5527 ParseNode* body = BinaryLeft(whileStmt);
5528 ParseNode* cond = BinaryRight(whileStmt);
5530 // A do-while loop `do { #body } while (#cond)` is equivalent to:
5531 // (block $after_loop // depth X
5532 // (loop $top // depth X+1
5533 // (block #body) // depth X+2
5534 // (brIf #cond $top)
5535 // )
5536 // )
5537 // A break should break out of the entire loop, i.e. at depth 0.
5538 // A continue should break out to the condition, i.e. at depth 2.
5539 if (labels && !f.addLabels(*labels, 0, 2)) {
5540 return false;
5543 if (!f.pushLoop()) {
5544 return false;
5548 // An unlabeled continue in the body should break out to the condition.
5549 if (!f.pushContinuableBlock()) {
5550 return false;
5552 if (!CheckStatement(f, body)) {
5553 return false;
5555 if (!f.popContinuableBlock()) {
5556 return false;
5560 Type condType;
5561 if (!CheckExpr(f, cond, &condType)) {
5562 return false;
5564 if (!condType.isInt()) {
5565 return f.failf(cond, "%s is not a subtype of int", condType.toChars());
5568 if (!f.writeContinueIf()) {
5569 return false;
5572 if (!f.popLoop()) {
5573 return false;
5575 if (labels) {
5576 f.removeLabels(*labels);
5578 return true;
5581 template <typename Unit>
5582 static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode*,
5583 const LabelVector* = nullptr);
5585 template <typename Unit>
5586 static bool CheckLabel(FunctionValidator<Unit>& f, ParseNode* labeledStmt) {
5587 MOZ_ASSERT(labeledStmt->isKind(ParseNodeKind::LabelStmt));
5589 LabelVector labels;
5590 ParseNode* innermost = labeledStmt;
5591 do {
5592 if (!labels.append(LabeledStatementLabel(innermost))) {
5593 return false;
5595 innermost = LabeledStatementStatement(innermost);
5596 } while (innermost->getKind() == ParseNodeKind::LabelStmt);
5598 switch (innermost->getKind()) {
5599 case ParseNodeKind::ForStmt:
5600 return CheckFor(f, innermost, &labels);
5601 case ParseNodeKind::DoWhileStmt:
5602 return CheckDoWhile(f, innermost, &labels);
5603 case ParseNodeKind::WhileStmt:
5604 return CheckWhile(f, innermost, &labels);
5605 case ParseNodeKind::StatementList:
5606 return CheckStatementList(f, innermost, &labels);
5607 default:
5608 break;
5611 if (!f.pushUnbreakableBlock(&labels)) {
5612 return false;
5615 if (!CheckStatement(f, innermost)) {
5616 return false;
5619 return f.popUnbreakableBlock(&labels);
5622 template <typename Unit>
5623 static bool CheckIf(FunctionValidator<Unit>& f, ParseNode* ifStmt) {
5624 uint32_t numIfEnd = 1;
5626 recurse:
5627 MOZ_ASSERT(ifStmt->isKind(ParseNodeKind::IfStmt));
5628 ParseNode* cond = TernaryKid1(ifStmt);
5629 ParseNode* thenStmt = TernaryKid2(ifStmt);
5630 ParseNode* elseStmt = TernaryKid3(ifStmt);
5632 Type condType;
5633 if (!CheckExpr(f, cond, &condType)) {
5634 return false;
5636 if (!condType.isInt()) {
5637 return f.failf(cond, "%s is not a subtype of int", condType.toChars());
5640 size_t typeAt;
5641 if (!f.pushIf(&typeAt)) {
5642 return false;
5645 f.setIfType(typeAt, TypeCode::BlockVoid);
5647 if (!CheckStatement(f, thenStmt)) {
5648 return false;
5651 if (elseStmt) {
5652 if (!f.switchToElse()) {
5653 return false;
5656 if (elseStmt->isKind(ParseNodeKind::IfStmt)) {
5657 ifStmt = elseStmt;
5658 if (numIfEnd++ == UINT32_MAX) {
5659 return false;
5661 goto recurse;
5664 if (!CheckStatement(f, elseStmt)) {
5665 return false;
5669 for (uint32_t i = 0; i != numIfEnd; ++i) {
5670 if (!f.popIf()) {
5671 return false;
5675 return true;
5678 static bool CheckCaseExpr(FunctionValidatorShared& f, ParseNode* caseExpr,
5679 int32_t* value) {
5680 if (!IsNumericLiteral(f.m(), caseExpr)) {
5681 return f.fail(caseExpr,
5682 "switch case expression must be an integer literal");
5685 NumLit lit = ExtractNumericLiteral(f.m(), caseExpr);
5686 switch (lit.which()) {
5687 case NumLit::Fixnum:
5688 case NumLit::NegativeInt:
5689 *value = lit.toInt32();
5690 break;
5691 case NumLit::OutOfRangeInt:
5692 case NumLit::BigUnsigned:
5693 return f.fail(caseExpr, "switch case expression out of integer range");
5694 case NumLit::Double:
5695 case NumLit::Float:
5696 return f.fail(caseExpr,
5697 "switch case expression must be an integer literal");
5700 return true;
5703 static bool CheckDefaultAtEnd(FunctionValidatorShared& f, ParseNode* stmt) {
5704 for (; stmt; stmt = NextNode(stmt)) {
5705 if (IsDefaultCase(stmt) && NextNode(stmt) != nullptr) {
5706 return f.fail(stmt, "default label must be at the end");
5710 return true;
5713 static bool CheckSwitchRange(FunctionValidatorShared& f, ParseNode* stmt,
5714 int32_t* low, int32_t* high,
5715 uint32_t* tableLength) {
5716 if (IsDefaultCase(stmt)) {
5717 *low = 0;
5718 *high = -1;
5719 *tableLength = 0;
5720 return true;
5723 int32_t i = 0;
5724 if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) {
5725 return false;
5728 *low = *high = i;
5730 ParseNode* initialStmt = stmt;
5731 for (stmt = NextNode(stmt); stmt && !IsDefaultCase(stmt);
5732 stmt = NextNode(stmt)) {
5733 int32_t i = 0;
5734 if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) {
5735 return false;
5738 *low = std::min(*low, i);
5739 *high = std::max(*high, i);
5742 int64_t i64 = (int64_t(*high) - int64_t(*low)) + 1;
5743 if (i64 > MaxBrTableElems) {
5744 return f.fail(
5745 initialStmt,
5746 "all switch statements generate tables; this table would be too big");
5749 *tableLength = uint32_t(i64);
5750 return true;
5753 template <typename Unit>
5754 static bool CheckSwitchExpr(FunctionValidator<Unit>& f, ParseNode* switchExpr) {
5755 Type exprType;
5756 if (!CheckExpr(f, switchExpr, &exprType)) {
5757 return false;
5759 if (!exprType.isSigned()) {
5760 return f.failf(switchExpr, "%s is not a subtype of signed",
5761 exprType.toChars());
5763 return true;
5766 // A switch will be constructed as:
5767 // - the default block wrapping all the other blocks, to be able to break
5768 // out of the switch with an unlabeled break statement. It has two statements
5769 // (an inner block and the default expr). asm.js rules require default to be at
5770 // the end, so the default block always encloses all the cases blocks.
5771 // - one block per case between low and high; undefined cases just jump to the
5772 // default case. Each of these blocks contain two statements: the next case's
5773 // block and the possibly empty statement list comprising the case body. The
5774 // last block pushed is the first case so the (relative) branch target therefore
5775 // matches the sequential order of cases.
5776 // - one block for the br_table, so that the first break goes to the first
5777 // case's block.
5778 template <typename Unit>
5779 static bool CheckSwitch(FunctionValidator<Unit>& f, ParseNode* switchStmt) {
5780 MOZ_ASSERT(switchStmt->isKind(ParseNodeKind::SwitchStmt));
5782 ParseNode* switchExpr = BinaryLeft(switchStmt);
5783 ParseNode* switchBody = BinaryRight(switchStmt);
5785 if (switchBody->is<LexicalScopeNode>()) {
5786 LexicalScopeNode* scope = &switchBody->as<LexicalScopeNode>();
5787 if (!scope->isEmptyScope()) {
5788 return f.fail(scope, "switch body may not contain lexical declarations");
5790 switchBody = scope->scopeBody();
5793 ParseNode* stmt = ListHead(switchBody);
5794 if (!stmt) {
5795 if (!CheckSwitchExpr(f, switchExpr)) {
5796 return false;
5798 return f.encoder().writeOp(Op::Drop);
5801 if (!CheckDefaultAtEnd(f, stmt)) {
5802 return false;
5805 int32_t low = 0, high = 0;
5806 uint32_t tableLength = 0;
5807 if (!CheckSwitchRange(f, stmt, &low, &high, &tableLength)) {
5808 return false;
5811 static const uint32_t CASE_NOT_DEFINED = UINT32_MAX;
5813 Uint32Vector caseDepths;
5814 if (!caseDepths.appendN(CASE_NOT_DEFINED, tableLength)) {
5815 return false;
5818 uint32_t numCases = 0;
5819 for (ParseNode* s = stmt; s && !IsDefaultCase(s); s = NextNode(s)) {
5820 int32_t caseValue = ExtractNumericLiteral(f.m(), CaseExpr(s)).toInt32();
5822 MOZ_ASSERT(caseValue >= low);
5823 unsigned i = caseValue - low;
5824 if (caseDepths[i] != CASE_NOT_DEFINED) {
5825 return f.fail(s, "no duplicate case labels");
5828 MOZ_ASSERT(numCases != CASE_NOT_DEFINED);
5829 caseDepths[i] = numCases++;
5832 // Open the wrapping breakable default block.
5833 if (!f.pushBreakableBlock()) {
5834 return false;
5837 // Open all the case blocks.
5838 for (uint32_t i = 0; i < numCases; i++) {
5839 if (!f.pushUnbreakableBlock()) {
5840 return false;
5844 // Open the br_table block.
5845 if (!f.pushUnbreakableBlock()) {
5846 return false;
5849 // The default block is the last one.
5850 uint32_t defaultDepth = numCases;
5852 // Subtract lowest case value, so that all the cases start from 0.
5853 if (low) {
5854 if (!CheckSwitchExpr(f, switchExpr)) {
5855 return false;
5857 if (!f.writeInt32Lit(low)) {
5858 return false;
5860 if (!f.encoder().writeOp(Op::I32Sub)) {
5861 return false;
5863 } else {
5864 if (!CheckSwitchExpr(f, switchExpr)) {
5865 return false;
5869 // Start the br_table block.
5870 if (!f.encoder().writeOp(Op::BrTable)) {
5871 return false;
5874 // Write the number of cases (tableLength - 1 + 1 (default)).
5875 // Write the number of cases (tableLength - 1 + 1 (default)).
5876 if (!f.encoder().writeVarU32(tableLength)) {
5877 return false;
5880 // Each case value describes the relative depth to the actual block. When
5881 // a case is not explicitly defined, it goes to the default.
5882 for (size_t i = 0; i < tableLength; i++) {
5883 uint32_t target =
5884 caseDepths[i] == CASE_NOT_DEFINED ? defaultDepth : caseDepths[i];
5885 if (!f.encoder().writeVarU32(target)) {
5886 return false;
5890 // Write the default depth.
5891 if (!f.encoder().writeVarU32(defaultDepth)) {
5892 return false;
5895 // Our br_table is done. Close its block, write the cases down in order.
5896 if (!f.popUnbreakableBlock()) {
5897 return false;
5900 for (; stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) {
5901 if (!CheckStatement(f, CaseBody(stmt))) {
5902 return false;
5904 if (!f.popUnbreakableBlock()) {
5905 return false;
5909 // Write the default block.
5910 if (stmt && IsDefaultCase(stmt)) {
5911 if (!CheckStatement(f, CaseBody(stmt))) {
5912 return false;
5916 // Close the wrapping block.
5917 return f.popBreakableBlock();
5920 static bool CheckReturnType(FunctionValidatorShared& f, ParseNode* usepn,
5921 Type ret) {
5922 Maybe<ValType> type = ret.canonicalToReturnType();
5924 if (!f.hasAlreadyReturned()) {
5925 f.setReturnedType(type);
5926 return true;
5929 if (f.returnedType() != type) {
5930 return f.failf(usepn, "%s incompatible with previous return of type %s",
5931 ToString(type, nullptr).get(),
5932 ToString(f.returnedType(), nullptr).get());
5935 return true;
5938 template <typename Unit>
5939 static bool CheckReturn(FunctionValidator<Unit>& f, ParseNode* returnStmt) {
5940 ParseNode* expr = ReturnExpr(returnStmt);
5942 if (!expr) {
5943 if (!CheckReturnType(f, returnStmt, Type::Void)) {
5944 return false;
5946 } else {
5947 Type type;
5948 if (!CheckExpr(f, expr, &type)) {
5949 return false;
5952 if (!type.isReturnType()) {
5953 return f.failf(expr, "%s is not a valid return type", type.toChars());
5956 if (!CheckReturnType(f, expr, Type::canonicalize(type))) {
5957 return false;
5961 return f.encoder().writeOp(Op::Return);
5964 template <typename Unit>
5965 static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode* stmtList,
5966 const LabelVector* labels /*= nullptr */) {
5967 MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
5969 if (!f.pushUnbreakableBlock(labels)) {
5970 return false;
5973 for (ParseNode* stmt = ListHead(stmtList); stmt; stmt = NextNode(stmt)) {
5974 if (!CheckStatement(f, stmt)) {
5975 return false;
5979 return f.popUnbreakableBlock(labels);
5982 template <typename Unit>
5983 static bool CheckLexicalScope(FunctionValidator<Unit>& f, ParseNode* node) {
5984 LexicalScopeNode* lexicalScope = &node->as<LexicalScopeNode>();
5985 if (!lexicalScope->isEmptyScope()) {
5986 return f.fail(lexicalScope, "cannot have 'let' or 'const' declarations");
5989 return CheckStatement(f, lexicalScope->scopeBody());
5992 static bool CheckBreakOrContinue(FunctionValidatorShared& f, bool isBreak,
5993 ParseNode* stmt) {
5994 if (TaggedParserAtomIndex maybeLabel = LoopControlMaybeLabel(stmt)) {
5995 return f.writeLabeledBreakOrContinue(maybeLabel, isBreak);
5997 return f.writeUnlabeledBreakOrContinue(isBreak);
6000 template <typename Unit>
6001 static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt) {
6002 AutoCheckRecursionLimit recursion(f.fc());
6003 if (!recursion.checkDontReport(f.fc())) {
6004 return f.m().failOverRecursed();
6007 switch (stmt->getKind()) {
6008 case ParseNodeKind::EmptyStmt:
6009 return true;
6010 case ParseNodeKind::ExpressionStmt:
6011 return CheckExprStatement(f, stmt);
6012 case ParseNodeKind::WhileStmt:
6013 return CheckWhile(f, stmt);
6014 case ParseNodeKind::ForStmt:
6015 return CheckFor(f, stmt);
6016 case ParseNodeKind::DoWhileStmt:
6017 return CheckDoWhile(f, stmt);
6018 case ParseNodeKind::LabelStmt:
6019 return CheckLabel(f, stmt);
6020 case ParseNodeKind::IfStmt:
6021 return CheckIf(f, stmt);
6022 case ParseNodeKind::SwitchStmt:
6023 return CheckSwitch(f, stmt);
6024 case ParseNodeKind::ReturnStmt:
6025 return CheckReturn(f, stmt);
6026 case ParseNodeKind::StatementList:
6027 return CheckStatementList(f, stmt);
6028 case ParseNodeKind::BreakStmt:
6029 return CheckBreakOrContinue(f, true, stmt);
6030 case ParseNodeKind::ContinueStmt:
6031 return CheckBreakOrContinue(f, false, stmt);
6032 case ParseNodeKind::LexicalScope:
6033 return CheckLexicalScope(f, stmt);
6034 default:;
6037 return f.fail(stmt, "unexpected statement kind");
6040 template <typename Unit>
6041 static bool ParseFunction(ModuleValidator<Unit>& m, FunctionNode** funNodeOut,
6042 unsigned* line) {
6043 auto& tokenStream = m.tokenStream();
6045 tokenStream.consumeKnownToken(TokenKind::Function,
6046 TokenStreamShared::SlashIsRegExp);
6048 auto& anyChars = tokenStream.anyCharsAccess();
6049 uint32_t toStringStart = anyChars.currentToken().pos.begin;
6050 *line = anyChars.lineNumber(anyChars.lineToken(toStringStart));
6052 TokenKind tk;
6053 if (!tokenStream.getToken(&tk, TokenStreamShared::SlashIsRegExp)) {
6054 return false;
6056 if (tk == TokenKind::Mul) {
6057 return m.failCurrentOffset("unexpected generator function");
6059 if (!TokenKindIsPossibleIdentifier(tk)) {
6060 return false; // The regular parser will throw a SyntaxError, no need to
6061 // m.fail.
6064 TaggedParserAtomIndex name = m.parser().bindingIdentifier(YieldIsName);
6065 if (!name) {
6066 return false;
6069 FunctionNode* funNode;
6070 MOZ_TRY_VAR_OR_RETURN(funNode,
6071 m.parser().handler_.newFunction(
6072 FunctionSyntaxKind::Statement, m.parser().pos()),
6073 false);
6075 ParseContext* outerpc = m.parser().pc_;
6076 Directives directives(outerpc);
6077 FunctionFlags flags(FunctionFlags::INTERPRETED_NORMAL);
6078 FunctionBox* funbox = m.parser().newFunctionBox(
6079 funNode, name, flags, toStringStart, directives,
6080 GeneratorKind::NotGenerator, FunctionAsyncKind::SyncFunction);
6081 if (!funbox) {
6082 return false;
6084 funbox->initWithEnclosingParseContext(outerpc, FunctionSyntaxKind::Statement);
6086 Directives newDirectives = directives;
6087 SourceParseContext funpc(&m.parser(), funbox, &newDirectives);
6088 if (!funpc.init()) {
6089 return false;
6092 if (!m.parser().functionFormalParametersAndBody(
6093 InAllowed, YieldIsName, &funNode, FunctionSyntaxKind::Statement)) {
6094 if (anyChars.hadError() || directives == newDirectives) {
6095 return false;
6098 return m.fail(funNode, "encountered new directive in function");
6101 MOZ_ASSERT(!anyChars.hadError());
6102 MOZ_ASSERT(directives == newDirectives);
6104 *funNodeOut = funNode;
6105 return true;
6108 template <typename Unit>
6109 static bool CheckFunction(ModuleValidator<Unit>& m) {
6110 // asm.js modules can be quite large when represented as parse trees so pop
6111 // the backing LifoAlloc after parsing/compiling each function. Release the
6112 // parser's lifo memory after the last use of a parse node.
6113 frontend::ParserBase::Mark mark = m.parser().mark();
6114 auto releaseMark =
6115 mozilla::MakeScopeExit([&m, &mark] { m.parser().release(mark); });
6117 FunctionNode* funNode = nullptr;
6118 unsigned line = 0;
6119 if (!ParseFunction(m, &funNode, &line)) {
6120 return false;
6123 if (!CheckFunctionHead(m, funNode)) {
6124 return false;
6127 FunctionValidator<Unit> f(m, funNode);
6129 ParseNode* stmtIter = ListHead(FunctionStatementList(funNode));
6131 if (!CheckProcessingDirectives(m, &stmtIter)) {
6132 return false;
6135 ValTypeVector args;
6136 if (!CheckArguments(f, &stmtIter, &args)) {
6137 return false;
6140 if (!CheckVariables(f, &stmtIter)) {
6141 return false;
6144 ParseNode* lastNonEmptyStmt = nullptr;
6145 for (; stmtIter; stmtIter = NextNonEmptyStatement(stmtIter)) {
6146 lastNonEmptyStmt = stmtIter;
6147 if (!CheckStatement(f, stmtIter)) {
6148 return false;
6152 if (!CheckFinalReturn(f, lastNonEmptyStmt)) {
6153 return false;
6156 ValTypeVector results;
6157 if (f.returnedType()) {
6158 if (!results.append(f.returnedType().ref())) {
6159 return false;
6163 FuncType sig(std::move(args), std::move(results));
6165 ModuleValidatorShared::Func* func = nullptr;
6166 if (!CheckFunctionSignature(m, funNode, std::move(sig), FunctionName(funNode),
6167 &func)) {
6168 return false;
6171 if (func->defined()) {
6172 return m.failName(funNode, "function '%s' already defined",
6173 FunctionName(funNode));
6176 f.define(func, line);
6178 return true;
6181 static bool CheckAllFunctionsDefined(ModuleValidatorShared& m) {
6182 for (unsigned i = 0; i < m.numFuncDefs(); i++) {
6183 const ModuleValidatorShared::Func& f = m.funcDef(i);
6184 if (!f.defined()) {
6185 return m.failNameOffset(f.firstUse(), "missing definition of function %s",
6186 f.name());
6190 return true;
6193 template <typename Unit>
6194 static bool CheckFunctions(ModuleValidator<Unit>& m) {
6195 while (true) {
6196 TokenKind tk;
6197 if (!PeekToken(m.parser(), &tk)) {
6198 return false;
6201 if (tk != TokenKind::Function) {
6202 break;
6205 if (!CheckFunction(m)) {
6206 return false;
6210 return CheckAllFunctionsDefined(m);
6213 template <typename Unit>
6214 static bool CheckFuncPtrTable(ModuleValidator<Unit>& m, ParseNode* decl) {
6215 if (!decl->isKind(ParseNodeKind::AssignExpr)) {
6216 return m.fail(decl, "function-pointer table must have initializer");
6218 AssignmentNode* assignNode = &decl->as<AssignmentNode>();
6220 ParseNode* var = assignNode->left();
6222 if (!var->isKind(ParseNodeKind::Name)) {
6223 return m.fail(var, "function-pointer table name is not a plain name");
6226 ParseNode* arrayLiteral = assignNode->right();
6228 if (!arrayLiteral->isKind(ParseNodeKind::ArrayExpr)) {
6229 return m.fail(
6230 var, "function-pointer table's initializer must be an array literal");
6233 unsigned length = ListLength(arrayLiteral);
6235 if (!IsPowerOfTwo(length)) {
6236 return m.failf(arrayLiteral,
6237 "function-pointer table length must be a power of 2 (is %u)",
6238 length);
6241 unsigned mask = length - 1;
6243 Uint32Vector elemFuncDefIndices;
6244 const FuncType* sig = nullptr;
6245 for (ParseNode* elem = ListHead(arrayLiteral); elem; elem = NextNode(elem)) {
6246 if (!elem->isKind(ParseNodeKind::Name)) {
6247 return m.fail(
6248 elem, "function-pointer table's elements must be names of functions");
6251 TaggedParserAtomIndex funcName = elem->as<NameNode>().name();
6252 const ModuleValidatorShared::Func* func = m.lookupFuncDef(funcName);
6253 if (!func) {
6254 return m.fail(
6255 elem, "function-pointer table's elements must be names of functions");
6258 const FuncType& funcSig = m.env().types->type(func->sigIndex()).funcType();
6259 if (sig) {
6260 if (!FuncType::strictlyEquals(*sig, funcSig)) {
6261 return m.fail(elem, "all functions in table must have same signature");
6263 } else {
6264 sig = &funcSig;
6267 if (!elemFuncDefIndices.append(func->funcDefIndex())) {
6268 return false;
6272 FuncType copy;
6273 if (!copy.clone(*sig)) {
6274 return false;
6277 uint32_t tableIndex;
6278 if (!CheckFuncPtrTableAgainstExisting(m, var, var->as<NameNode>().name(),
6279 std::move(copy), mask, &tableIndex)) {
6280 return false;
6283 if (!m.defineFuncPtrTable(tableIndex, std::move(elemFuncDefIndices))) {
6284 return m.fail(var, "duplicate function-pointer definition");
6287 return true;
6290 template <typename Unit>
6291 static bool CheckFuncPtrTables(ModuleValidator<Unit>& m) {
6292 while (true) {
6293 ParseNode* varStmt;
6294 if (!ParseVarOrConstStatement(m.parser(), &varStmt)) {
6295 return false;
6297 if (!varStmt) {
6298 break;
6300 for (ParseNode* var = VarListHead(varStmt); var; var = NextNode(var)) {
6301 if (!CheckFuncPtrTable(m, var)) {
6302 return false;
6307 for (unsigned i = 0; i < m.numFuncPtrTables(); i++) {
6308 ModuleValidatorShared::Table& table = m.table(i);
6309 if (!table.defined()) {
6310 return m.failNameOffset(table.firstUse(),
6311 "function-pointer table %s wasn't defined",
6312 table.name());
6316 return true;
6319 static bool CheckModuleExportFunction(
6320 ModuleValidatorShared& m, ParseNode* pn,
6321 TaggedParserAtomIndex maybeFieldName = TaggedParserAtomIndex::null()) {
6322 if (!pn->isKind(ParseNodeKind::Name)) {
6323 return m.fail(pn, "expected name of exported function");
6326 TaggedParserAtomIndex funcName = pn->as<NameNode>().name();
6327 const ModuleValidatorShared::Func* func = m.lookupFuncDef(funcName);
6328 if (!func) {
6329 return m.failName(pn, "function '%s' not found", funcName);
6332 return m.addExportField(*func, maybeFieldName);
6335 static bool CheckModuleExportObject(ModuleValidatorShared& m,
6336 ParseNode* object) {
6337 MOZ_ASSERT(object->isKind(ParseNodeKind::ObjectExpr));
6339 for (ParseNode* pn = ListHead(object); pn; pn = NextNode(pn)) {
6340 if (!IsNormalObjectField(pn)) {
6341 return m.fail(pn,
6342 "only normal object properties may be used in the export "
6343 "object literal");
6346 TaggedParserAtomIndex fieldName = ObjectNormalFieldName(pn);
6348 ParseNode* initNode = ObjectNormalFieldInitializer(pn);
6349 if (!initNode->isKind(ParseNodeKind::Name)) {
6350 return m.fail(
6351 initNode,
6352 "initializer of exported object literal must be name of function");
6355 if (!CheckModuleExportFunction(m, initNode, fieldName)) {
6356 return false;
6360 return true;
6363 template <typename Unit>
6364 static bool CheckModuleReturn(ModuleValidator<Unit>& m) {
6365 TokenKind tk;
6366 if (!GetToken(m.parser(), &tk)) {
6367 return false;
6369 auto& ts = m.parser().tokenStream;
6370 if (tk != TokenKind::Return) {
6371 return m.failCurrentOffset(
6372 (tk == TokenKind::RightCurly || tk == TokenKind::Eof)
6373 ? "expecting return statement"
6374 : "invalid asm.js. statement");
6376 ts.anyCharsAccess().ungetToken();
6378 ParseNode* returnStmt;
6379 MOZ_TRY_VAR_OR_RETURN(returnStmt, m.parser().statementListItem(YieldIsName),
6380 false);
6382 ParseNode* returnExpr = ReturnExpr(returnStmt);
6383 if (!returnExpr) {
6384 return m.fail(returnStmt, "export statement must return something");
6387 if (returnExpr->isKind(ParseNodeKind::ObjectExpr)) {
6388 if (!CheckModuleExportObject(m, returnExpr)) {
6389 return false;
6391 } else {
6392 if (!CheckModuleExportFunction(m, returnExpr)) {
6393 return false;
6397 return true;
6400 template <typename Unit>
6401 static bool CheckModuleEnd(ModuleValidator<Unit>& m) {
6402 TokenKind tk;
6403 if (!GetToken(m.parser(), &tk)) {
6404 return false;
6407 if (tk != TokenKind::Eof && tk != TokenKind::RightCurly) {
6408 return m.failCurrentOffset(
6409 "top-level export (return) must be the last statement");
6412 m.parser().tokenStream.anyCharsAccess().ungetToken();
6413 return true;
6416 template <typename Unit>
6417 static SharedModule CheckModule(FrontendContext* fc,
6418 ParserAtomsTable& parserAtoms,
6419 AsmJSParser<Unit>& parser, ParseNode* stmtList,
6420 unsigned* time) {
6421 int64_t before = PRMJ_Now();
6423 FunctionNode* moduleFunctionNode = parser.pc_->functionBox()->functionNode;
6425 ModuleValidator<Unit> m(fc, parserAtoms, parser, moduleFunctionNode);
6426 if (!m.init()) {
6427 return nullptr;
6430 if (!CheckFunctionHead(m, moduleFunctionNode)) {
6431 return nullptr;
6434 if (!CheckModuleArguments(m, moduleFunctionNode)) {
6435 return nullptr;
6438 if (!CheckPrecedingStatements(m, stmtList)) {
6439 return nullptr;
6442 if (!CheckModuleProcessingDirectives(m)) {
6443 return nullptr;
6446 if (!CheckModuleGlobals(m)) {
6447 return nullptr;
6450 if (!m.startFunctionBodies()) {
6451 return nullptr;
6454 if (!CheckFunctions(m)) {
6455 return nullptr;
6458 if (!CheckFuncPtrTables(m)) {
6459 return nullptr;
6462 if (!CheckModuleReturn(m)) {
6463 return nullptr;
6466 if (!CheckModuleEnd(m)) {
6467 return nullptr;
6470 SharedModule module = m.finish();
6471 if (!module) {
6472 return nullptr;
6475 *time = (PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC;
6476 return module;
6479 /*****************************************************************************/
6480 // Link-time validation
6482 static bool LinkFail(JSContext* cx, const char* str) {
6483 WarnNumberASCII(cx, JSMSG_USE_ASM_LINK_FAIL, str);
6484 return false;
6487 static bool IsMaybeWrappedScriptedProxy(JSObject* obj) {
6488 JSObject* unwrapped = UncheckedUnwrap(obj);
6489 return unwrapped && IsScriptedProxy(unwrapped);
6492 static bool GetDataProperty(JSContext* cx, HandleValue objVal,
6493 Handle<JSAtom*> field, MutableHandleValue v) {
6494 if (!objVal.isObject()) {
6495 return LinkFail(cx, "accessing property of non-object");
6498 RootedObject obj(cx, &objVal.toObject());
6499 if (IsMaybeWrappedScriptedProxy(obj)) {
6500 return LinkFail(cx, "accessing property of a Proxy");
6503 RootedId id(cx, AtomToId(field));
6504 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
6505 RootedObject holder(cx);
6506 if (!GetPropertyDescriptor(cx, obj, id, &desc, &holder)) {
6507 return false;
6510 if (!desc.isSome()) {
6511 return LinkFail(cx, "property not present on object");
6514 if (!desc->isDataDescriptor()) {
6515 return LinkFail(cx, "property is not a data property");
6518 v.set(desc->value());
6519 return true;
6522 static bool GetDataProperty(JSContext* cx, HandleValue objVal,
6523 const char* fieldChars, MutableHandleValue v) {
6524 Rooted<JSAtom*> field(cx,
6525 AtomizeUTF8Chars(cx, fieldChars, strlen(fieldChars)));
6526 if (!field) {
6527 return false;
6530 return GetDataProperty(cx, objVal, field, v);
6533 static bool GetDataProperty(JSContext* cx, HandleValue objVal,
6534 const ImmutableTenuredPtr<PropertyName*>& field,
6535 MutableHandleValue v) {
6536 Handle<PropertyName*> fieldHandle = field;
6537 return GetDataProperty(cx, objVal, fieldHandle, v);
6540 static bool HasObjectValueOfMethodPure(JSObject* obj, JSContext* cx) {
6541 Value v;
6542 if (!GetPropertyPure(cx, obj, NameToId(cx->names().valueOf), &v)) {
6543 return false;
6546 JSFunction* fun;
6547 if (!IsFunctionObject(v, &fun)) {
6548 return false;
6551 return IsSelfHostedFunctionWithName(fun, cx->names().Object_valueOf);
6554 static bool HasPureCoercion(JSContext* cx, HandleValue v) {
6555 // Ideally, we'd reject all non-primitives, but Emscripten has a bug that
6556 // generates code that passes functions for some imports. To avoid breaking
6557 // all the code that contains this bug, we make an exception for functions
6558 // that don't have user-defined valueOf or toString, for their coercions
6559 // are not observable and coercion via ToNumber/ToInt32 definitely produces
6560 // NaN/0. We should remove this special case later once most apps have been
6561 // built with newer Emscripten.
6562 return v.toObject().is<JSFunction>() &&
6563 HasNoToPrimitiveMethodPure(&v.toObject(), cx) &&
6564 HasObjectValueOfMethodPure(&v.toObject(), cx) &&
6565 HasNativeMethodPure(&v.toObject(), cx->names().toString, fun_toString,
6566 cx);
6569 static bool ValidateGlobalVariable(JSContext* cx, const AsmJSGlobal& global,
6570 HandleValue importVal,
6571 Maybe<LitValPOD>* val) {
6572 switch (global.varInitKind()) {
6573 case AsmJSGlobal::InitConstant:
6574 val->emplace(global.varInitVal());
6575 return true;
6577 case AsmJSGlobal::InitImport: {
6578 RootedValue v(cx);
6579 if (!GetDataProperty(cx, importVal, global.field(), &v)) {
6580 return false;
6583 if (!v.isPrimitive() && !HasPureCoercion(cx, v)) {
6584 return LinkFail(cx, "Imported values must be primitives");
6587 switch (global.varInitImportType().kind()) {
6588 case ValType::I32: {
6589 int32_t i32;
6590 if (!ToInt32(cx, v, &i32)) {
6591 return false;
6593 val->emplace(uint32_t(i32));
6594 return true;
6596 case ValType::I64:
6597 MOZ_CRASH("int64");
6598 case ValType::V128:
6599 MOZ_CRASH("v128");
6600 case ValType::F32: {
6601 float f;
6602 if (!RoundFloat32(cx, v, &f)) {
6603 return false;
6605 val->emplace(f);
6606 return true;
6608 case ValType::F64: {
6609 double d;
6610 if (!ToNumber(cx, v, &d)) {
6611 return false;
6613 val->emplace(d);
6614 return true;
6616 case ValType::Ref: {
6617 MOZ_CRASH("not available in asm.js");
6623 MOZ_CRASH("unreachable");
6626 static bool ValidateFFI(JSContext* cx, const AsmJSGlobal& global,
6627 HandleValue importVal,
6628 MutableHandle<FunctionVector> ffis) {
6629 RootedValue v(cx);
6630 if (!GetDataProperty(cx, importVal, global.field(), &v)) {
6631 return false;
6634 if (!IsFunctionObject(v)) {
6635 return LinkFail(cx, "FFI imports must be functions");
6638 ffis[global.ffiIndex()].set(&v.toObject().as<JSFunction>());
6639 return true;
6642 static bool ValidateArrayView(JSContext* cx, const AsmJSGlobal& global,
6643 HandleValue globalVal) {
6644 if (!global.field()) {
6645 return true;
6648 if (Scalar::isBigIntType(global.viewType())) {
6649 return LinkFail(cx, "bad typed array constructor");
6652 RootedValue v(cx);
6653 if (!GetDataProperty(cx, globalVal, global.field(), &v)) {
6654 return false;
6657 bool tac = IsTypedArrayConstructor(v, global.viewType());
6658 if (!tac) {
6659 return LinkFail(cx, "bad typed array constructor");
6662 return true;
6665 static InlinableNative ToInlinableNative(AsmJSMathBuiltinFunction func) {
6666 switch (func) {
6667 case AsmJSMathBuiltin_sin:
6668 return InlinableNative::MathSin;
6669 case AsmJSMathBuiltin_cos:
6670 return InlinableNative::MathCos;
6671 case AsmJSMathBuiltin_tan:
6672 return InlinableNative::MathTan;
6673 case AsmJSMathBuiltin_asin:
6674 return InlinableNative::MathASin;
6675 case AsmJSMathBuiltin_acos:
6676 return InlinableNative::MathACos;
6677 case AsmJSMathBuiltin_atan:
6678 return InlinableNative::MathATan;
6679 case AsmJSMathBuiltin_ceil:
6680 return InlinableNative::MathCeil;
6681 case AsmJSMathBuiltin_floor:
6682 return InlinableNative::MathFloor;
6683 case AsmJSMathBuiltin_exp:
6684 return InlinableNative::MathExp;
6685 case AsmJSMathBuiltin_log:
6686 return InlinableNative::MathLog;
6687 case AsmJSMathBuiltin_pow:
6688 return InlinableNative::MathPow;
6689 case AsmJSMathBuiltin_sqrt:
6690 return InlinableNative::MathSqrt;
6691 case AsmJSMathBuiltin_abs:
6692 return InlinableNative::MathAbs;
6693 case AsmJSMathBuiltin_atan2:
6694 return InlinableNative::MathATan2;
6695 case AsmJSMathBuiltin_imul:
6696 return InlinableNative::MathImul;
6697 case AsmJSMathBuiltin_fround:
6698 return InlinableNative::MathFRound;
6699 case AsmJSMathBuiltin_min:
6700 return InlinableNative::MathMin;
6701 case AsmJSMathBuiltin_max:
6702 return InlinableNative::MathMax;
6703 case AsmJSMathBuiltin_clz32:
6704 return InlinableNative::MathClz32;
6706 MOZ_CRASH("Invalid asm.js math builtin function");
6709 static bool ValidateMathBuiltinFunction(JSContext* cx,
6710 const AsmJSMetadata& metadata,
6711 const AsmJSGlobal& global,
6712 HandleValue globalVal) {
6713 RootedValue v(cx);
6714 if (!GetDataProperty(cx, globalVal, cx->names().Math, &v)) {
6715 return false;
6718 if (!GetDataProperty(cx, v, global.field(), &v)) {
6719 return false;
6722 InlinableNative native = ToInlinableNative(global.mathBuiltinFunction());
6724 JSFunction* fun;
6725 if (!IsFunctionObject(v, &fun) || !fun->hasJitInfo() ||
6726 fun->jitInfo()->type() != JSJitInfo::InlinableNative ||
6727 fun->jitInfo()->inlinableNative != native) {
6728 return LinkFail(cx, "bad Math.* builtin function");
6730 if (fun->realm()->creationOptions().alwaysUseFdlibm() !=
6731 metadata.alwaysUseFdlibm) {
6732 return LinkFail(cx,
6733 "Math.* builtin function and asm.js use different native"
6734 " math implementations.");
6737 return true;
6740 static bool ValidateConstant(JSContext* cx, const AsmJSGlobal& global,
6741 HandleValue globalVal) {
6742 RootedValue v(cx, globalVal);
6744 if (global.constantKind() == AsmJSGlobal::MathConstant) {
6745 if (!GetDataProperty(cx, v, cx->names().Math, &v)) {
6746 return false;
6750 if (!GetDataProperty(cx, v, global.field(), &v)) {
6751 return false;
6754 if (!v.isNumber()) {
6755 return LinkFail(cx, "math / global constant value needs to be a number");
6758 // NaN != NaN
6759 if (std::isnan(global.constantValue())) {
6760 if (!std::isnan(v.toNumber())) {
6761 return LinkFail(cx, "global constant value needs to be NaN");
6763 } else {
6764 if (v.toNumber() != global.constantValue()) {
6765 return LinkFail(cx, "global constant value mismatch");
6769 return true;
6772 static bool CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata,
6773 HandleValue bufferVal,
6774 MutableHandle<ArrayBufferObject*> buffer) {
6775 if (!bufferVal.isObject()) {
6776 return LinkFail(cx, "buffer must be an object");
6778 JSObject* bufferObj = &bufferVal.toObject();
6780 if (metadata.memories[0].isShared()) {
6781 if (!bufferObj->is<SharedArrayBufferObject>()) {
6782 return LinkFail(
6783 cx, "shared views can only be constructed onto SharedArrayBuffer");
6785 return LinkFail(cx, "Unable to prepare SharedArrayBuffer for asm.js use");
6788 if (!bufferObj->is<ArrayBufferObject>()) {
6789 return LinkFail(cx,
6790 "unshared views can only be constructed onto ArrayBuffer");
6793 buffer.set(&bufferObj->as<ArrayBufferObject>());
6795 size_t memoryLength = buffer->byteLength();
6797 if (!IsValidAsmJSHeapLength(memoryLength)) {
6798 UniqueChars msg;
6799 if (memoryLength > MaxHeapLength) {
6800 msg = JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64
6801 " is not a valid heap length - it is too long."
6802 " The longest valid length is 0x%" PRIx64,
6803 uint64_t(memoryLength), MaxHeapLength);
6804 } else {
6805 msg = JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64
6806 " is not a valid heap length. The next "
6807 "valid length is 0x%" PRIx64,
6808 uint64_t(memoryLength),
6809 RoundUpToNextValidAsmJSHeapLength(memoryLength));
6811 if (!msg) {
6812 return false;
6814 return LinkFail(cx, msg.get());
6817 // This check is sufficient without considering the size of the loaded datum
6818 // because heap loads and stores start on an aligned boundary and the heap
6819 // byteLength has larger alignment.
6820 uint64_t minMemoryLength = metadata.memories.length() != 0
6821 ? metadata.memories[0].initialLength32()
6822 : 0;
6823 MOZ_ASSERT((minMemoryLength - 1) <= INT32_MAX);
6824 if (memoryLength < minMemoryLength) {
6825 UniqueChars msg(JS_smprintf("ArrayBuffer byteLength of 0x%" PRIx64
6826 " is less than 0x%" PRIx64 " (the "
6827 "size implied "
6828 "by const heap accesses).",
6829 uint64_t(memoryLength), minMemoryLength));
6830 if (!msg) {
6831 return false;
6833 return LinkFail(cx, msg.get());
6836 // ArrayBuffer lengths in SpiderMonkey used to be restricted to <= INT32_MAX,
6837 // but that has since been relaxed for the benefit of wasm. We keep the old
6838 // limit for asm.js so as to avoid having to worry about whether the asm.js
6839 // implementation is safe for larger heaps.
6840 if (memoryLength >= INT32_MAX) {
6841 UniqueChars msg(
6842 JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64
6843 " is too large for asm.js (implementation limit).",
6844 uint64_t(memoryLength)));
6845 if (!msg) {
6846 return false;
6848 return LinkFail(cx, msg.get());
6851 if (buffer->isResizable()) {
6852 return LinkFail(cx,
6853 "Unable to prepare resizable ArrayBuffer for asm.js use");
6856 if (!buffer->prepareForAsmJS()) {
6857 return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
6860 MOZ_ASSERT(buffer->isPreparedForAsmJS());
6861 return true;
6864 static bool GetImports(JSContext* cx, const AsmJSMetadata& metadata,
6865 HandleValue globalVal, HandleValue importVal,
6866 ImportValues* imports) {
6867 Rooted<FunctionVector> ffis(cx, FunctionVector(cx));
6868 if (!ffis.resize(metadata.numFFIs)) {
6869 return false;
6872 for (const AsmJSGlobal& global : metadata.asmJSGlobals) {
6873 switch (global.which()) {
6874 case AsmJSGlobal::Variable: {
6875 Maybe<LitValPOD> litVal;
6876 if (!ValidateGlobalVariable(cx, global, importVal, &litVal)) {
6877 return false;
6879 if (!imports->globalValues.append(Val(litVal->asLitVal()))) {
6880 return false;
6882 break;
6884 case AsmJSGlobal::FFI:
6885 if (!ValidateFFI(cx, global, importVal, &ffis)) {
6886 return false;
6888 break;
6889 case AsmJSGlobal::ArrayView:
6890 case AsmJSGlobal::ArrayViewCtor:
6891 if (!ValidateArrayView(cx, global, globalVal)) {
6892 return false;
6894 break;
6895 case AsmJSGlobal::MathBuiltinFunction:
6896 if (!ValidateMathBuiltinFunction(cx, metadata, global, globalVal)) {
6897 return false;
6899 break;
6900 case AsmJSGlobal::Constant:
6901 if (!ValidateConstant(cx, global, globalVal)) {
6902 return false;
6904 break;
6908 for (const AsmJSImport& import : metadata.asmJSImports) {
6909 if (!imports->funcs.append(ffis[import.ffiIndex()])) {
6910 return false;
6914 return true;
6917 static bool TryInstantiate(JSContext* cx, CallArgs args, const Module& module,
6918 const AsmJSMetadata& metadata,
6919 MutableHandle<WasmInstanceObject*> instanceObj,
6920 MutableHandleObject exportObj) {
6921 HandleValue globalVal = args.get(0);
6922 HandleValue importVal = args.get(1);
6923 HandleValue bufferVal = args.get(2);
6925 MOZ_RELEASE_ASSERT(HasPlatformSupport());
6927 if (!wasm::EnsureFullSignalHandlers(cx)) {
6928 return LinkFail(cx, "failed to install signal handlers");
6931 Rooted<ImportValues> imports(cx);
6933 if (module.metadata().memories.length() != 0) {
6934 MOZ_ASSERT(module.metadata().memories.length() == 1);
6935 Rooted<ArrayBufferObject*> buffer(cx);
6936 if (!CheckBuffer(cx, metadata, bufferVal, &buffer)) {
6937 return false;
6940 Rooted<WasmMemoryObject*> memory(
6941 cx, WasmMemoryObject::create(cx, buffer, /* isHuge= */ false, nullptr));
6942 if (!memory || !imports.get().memories.append(memory)) {
6943 return false;
6947 if (!GetImports(cx, metadata, globalVal, importVal, imports.address())) {
6948 return false;
6951 if (!module.instantiate(cx, imports.get(), nullptr, instanceObj)) {
6952 return false;
6955 exportObj.set(&instanceObj->exportsObj());
6956 return true;
6959 static bool HandleInstantiationFailure(JSContext* cx, CallArgs args,
6960 const AsmJSMetadata& metadata) {
6961 using js::frontend::FunctionSyntaxKind;
6963 Rooted<JSAtom*> name(cx, args.callee().as<JSFunction>().fullExplicitName());
6965 if (cx->isExceptionPending()) {
6966 return false;
6969 ScriptSource* source = metadata.maybeScriptSource();
6971 // Source discarding is allowed to affect JS semantics because it is never
6972 // enabled for normal JS content.
6973 bool haveSource;
6974 if (!ScriptSource::loadSource(cx, source, &haveSource)) {
6975 return false;
6977 if (!haveSource) {
6978 JS_ReportErrorASCII(cx,
6979 "asm.js link failure with source discarding enabled");
6980 return false;
6983 uint32_t begin = metadata.toStringStart;
6984 uint32_t end = metadata.srcEndAfterCurly();
6985 Rooted<JSLinearString*> src(cx, source->substringDontDeflate(cx, begin, end));
6986 if (!src) {
6987 return false;
6990 JS::CompileOptions options(cx);
6991 options.setMutedErrors(source->mutedErrors())
6992 .setFile(source->filename())
6993 .setNoScriptRval(false);
6994 options.setAsmJSOption(AsmJSOption::DisabledByLinker);
6996 // The exported function inherits an implicit strict context if the module
6997 // also inherited it somehow.
6998 if (metadata.strict) {
6999 options.setForceStrictMode();
7002 AutoStableStringChars linearChars(cx);
7003 if (!linearChars.initTwoByte(cx, src)) {
7004 return false;
7007 SourceText<char16_t> srcBuf;
7008 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
7009 return false;
7012 FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement;
7014 RootedFunction fun(cx, frontend::CompileStandaloneFunction(
7015 cx, options, srcBuf, Nothing(), syntaxKind));
7016 if (!fun) {
7017 return false;
7020 fun->initEnvironment(&cx->global()->lexicalEnvironment());
7022 // Call the function we just recompiled.
7023 args.setCallee(ObjectValue(*fun));
7024 return InternalCallOrConstruct(
7025 cx, args, args.isConstructing() ? CONSTRUCT : NO_CONSTRUCT);
7028 static const Module& AsmJSModuleFunctionToModule(JSFunction* fun) {
7029 MOZ_ASSERT(IsAsmJSModule(fun));
7030 const Value& v = fun->getExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT);
7031 return v.toObject().as<WasmModuleObject>().module();
7034 // Implements the semantics of an asm.js module function that has been
7035 // successfully validated.
7036 bool js::InstantiateAsmJS(JSContext* cx, unsigned argc, JS::Value* vp) {
7037 CallArgs args = CallArgsFromVp(argc, vp);
7039 JSFunction* callee = &args.callee().as<JSFunction>();
7040 const Module& module = AsmJSModuleFunctionToModule(callee);
7041 const AsmJSMetadata& metadata = module.metadata().asAsmJS();
7043 Rooted<WasmInstanceObject*> instanceObj(cx);
7044 RootedObject exportObj(cx);
7045 if (!TryInstantiate(cx, args, module, metadata, &instanceObj, &exportObj)) {
7046 // Link-time validation checks failed, so reparse the entire asm.js
7047 // module from scratch to get normal interpreted bytecode which we can
7048 // simply Invoke. Very slow.
7049 return HandleInstantiationFailure(cx, args, metadata);
7052 args.rval().set(ObjectValue(*exportObj));
7053 return true;
7056 /*****************************************************************************/
7057 // Top-level js::CompileAsmJS
7059 static bool NoExceptionPending(FrontendContext* fc) { return !fc->hadErrors(); }
7061 static bool SuccessfulValidation(frontend::ParserBase& parser,
7062 unsigned compilationTime) {
7063 unsigned errNum = js::SupportDifferentialTesting()
7064 ? JSMSG_USE_ASM_TYPE_OK_NO_TIME
7065 : JSMSG_USE_ASM_TYPE_OK;
7067 char timeChars[20];
7068 SprintfLiteral(timeChars, "%u", compilationTime);
7070 return parser.warningNoOffset(errNum, timeChars);
7073 static bool TypeFailureWarning(frontend::ParserBase& parser, const char* str) {
7074 if (parser.options().throwOnAsmJSValidationFailure()) {
7075 parser.errorNoOffset(JSMSG_USE_ASM_TYPE_FAIL, str ? str : "");
7076 return false;
7079 // Per the asm.js standard convention, whether failure sets a pending
7080 // exception determines whether to attempt non-asm.js reparsing, so ignore
7081 // the return value below.
7082 (void)parser.warningNoOffset(JSMSG_USE_ASM_TYPE_FAIL, str ? str : "");
7083 return false;
7086 // asm.js requires Ion to be available on the current hardware/OS and to be
7087 // enabled for wasm, since asm.js compilation goes via wasm.
7088 static bool IsAsmJSCompilerAvailable(JSContext* cx) {
7089 return HasPlatformSupport() && WasmCompilerForAsmJSAvailable(cx);
7092 static bool EstablishPreconditions(frontend::ParserBase& parser) {
7093 switch (parser.options().asmJSOption()) {
7094 case AsmJSOption::DisabledByAsmJSPref:
7095 return TypeFailureWarning(
7096 parser, "Asm.js optimizer disabled by 'asmjs' runtime option");
7097 case AsmJSOption::DisabledByLinker:
7098 return TypeFailureWarning(
7099 parser,
7100 "Asm.js optimizer disabled by linker (instantiation failure)");
7101 case AsmJSOption::DisabledByNoWasmCompiler:
7102 return TypeFailureWarning(parser,
7103 "Asm.js optimizer disabled because no suitable "
7104 "wasm compiler is available");
7105 case AsmJSOption::DisabledByDebugger:
7106 return TypeFailureWarning(
7107 parser, "Asm.js optimizer disabled because debugger is active");
7108 case AsmJSOption::Enabled:
7109 break;
7112 if (parser.pc_->isGenerator()) {
7113 return TypeFailureWarning(parser,
7114 "Asm.js optimizer disabled in generator context");
7117 if (parser.pc_->isAsync()) {
7118 return TypeFailureWarning(parser,
7119 "Asm.js optimizer disabled in async context");
7122 if (parser.pc_->isArrowFunction()) {
7123 return TypeFailureWarning(
7124 parser, "Asm.js optimizer disabled in arrow function context");
7127 // Class constructors are also methods
7128 if (parser.pc_->isMethod() || parser.pc_->isGetterOrSetter()) {
7129 return TypeFailureWarning(
7130 parser,
7131 "Asm.js optimizer disabled in class constructor or method context");
7134 return true;
7137 template <typename Unit>
7138 static bool DoCompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms,
7139 AsmJSParser<Unit>& parser, ParseNode* stmtList,
7140 bool* validated) {
7141 *validated = false;
7143 // Various conditions disable asm.js optimizations.
7144 if (!EstablishPreconditions(parser)) {
7145 return NoExceptionPending(fc);
7148 // "Checking" parses, validates and compiles, producing a fully compiled
7149 // WasmModuleObject as result.
7150 unsigned time;
7151 SharedModule module = CheckModule(fc, parserAtoms, parser, stmtList, &time);
7152 if (!module) {
7153 return NoExceptionPending(fc);
7156 // Finished! Save the ref-counted module on the FunctionBox. When JSFunctions
7157 // are eventually allocated we will create an asm.js constructor for it.
7158 FunctionBox* funbox = parser.pc_->functionBox();
7159 MOZ_ASSERT(funbox->isInterpreted());
7160 if (!funbox->setAsmJSModule(module)) {
7161 return NoExceptionPending(fc);
7164 // Success! Write to the console with a "warning" message indicating
7165 // total compilation time.
7166 *validated = true;
7167 SuccessfulValidation(parser, time);
7168 return NoExceptionPending(fc);
7171 bool js::CompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms,
7172 AsmJSParser<char16_t>& parser, ParseNode* stmtList,
7173 bool* validated) {
7174 return DoCompileAsmJS(fc, parserAtoms, parser, stmtList, validated);
7177 bool js::CompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms,
7178 AsmJSParser<Utf8Unit>& parser, ParseNode* stmtList,
7179 bool* validated) {
7180 return DoCompileAsmJS(fc, parserAtoms, parser, stmtList, validated);
7183 /*****************************************************************************/
7184 // asm.js testing functions
7186 bool js::IsAsmJSModuleNative(Native native) {
7187 return native == InstantiateAsmJS;
7190 bool js::IsAsmJSModule(JSFunction* fun) {
7191 return fun->maybeNative() == InstantiateAsmJS;
7194 bool js::IsAsmJSFunction(JSFunction* fun) {
7195 return fun->kind() == FunctionFlags::AsmJS;
7198 bool js::IsAsmJSStrictModeModuleOrFunction(JSFunction* fun) {
7199 if (IsAsmJSModule(fun)) {
7200 return AsmJSModuleFunctionToModule(fun).metadata().asAsmJS().strict;
7203 if (IsAsmJSFunction(fun)) {
7204 return ExportedFunctionToInstance(fun).metadata().asAsmJS().strict;
7207 return false;
7210 bool js::IsAsmJSCompilationAvailable(JSContext* cx) {
7211 return cx->options().asmJS() && IsAsmJSCompilerAvailable(cx);
7214 bool js::IsAsmJSCompilationAvailable(JSContext* cx, unsigned argc, Value* vp) {
7215 CallArgs args = CallArgsFromVp(argc, vp);
7216 bool available = IsAsmJSCompilationAvailable(cx);
7217 args.rval().set(BooleanValue(available));
7218 return true;
7221 static JSFunction* MaybeWrappedNativeFunction(const Value& v) {
7222 if (!v.isObject()) {
7223 return nullptr;
7226 return v.toObject().maybeUnwrapIf<JSFunction>();
7229 bool js::IsAsmJSModule(JSContext* cx, unsigned argc, Value* vp) {
7230 CallArgs args = CallArgsFromVp(argc, vp);
7232 bool rval = false;
7233 if (JSFunction* fun = MaybeWrappedNativeFunction(args.get(0))) {
7234 rval = IsAsmJSModule(fun);
7237 args.rval().set(BooleanValue(rval));
7238 return true;
7241 bool js::IsAsmJSFunction(JSContext* cx, unsigned argc, Value* vp) {
7242 CallArgs args = CallArgsFromVp(argc, vp);
7244 bool rval = false;
7245 if (JSFunction* fun = MaybeWrappedNativeFunction(args.get(0))) {
7246 rval = IsAsmJSFunction(fun);
7249 args.rval().set(BooleanValue(rval));
7250 return true;
7253 /*****************************************************************************/
7254 // asm.js toString/toSource support
7256 JSString* js::AsmJSModuleToString(JSContext* cx, HandleFunction fun,
7257 bool isToSource) {
7258 MOZ_ASSERT(IsAsmJSModule(fun));
7260 const AsmJSMetadata& metadata =
7261 AsmJSModuleFunctionToModule(fun).metadata().asAsmJS();
7262 uint32_t begin = metadata.toStringStart;
7263 uint32_t end = metadata.srcEndAfterCurly();
7264 ScriptSource* source = metadata.maybeScriptSource();
7266 JSStringBuilder out(cx);
7268 if (isToSource && fun->isLambda() && !out.append("(")) {
7269 return nullptr;
7272 bool haveSource;
7273 if (!ScriptSource::loadSource(cx, source, &haveSource)) {
7274 return nullptr;
7277 if (!haveSource) {
7278 if (!out.append("function ")) {
7279 return nullptr;
7281 if (fun->fullExplicitName() && !out.append(fun->fullExplicitName())) {
7282 return nullptr;
7284 if (!out.append("() {\n [native code]\n}")) {
7285 return nullptr;
7287 } else {
7288 Rooted<JSLinearString*> src(cx, source->substring(cx, begin, end));
7289 if (!src) {
7290 return nullptr;
7293 if (!out.append(src)) {
7294 return nullptr;
7298 if (isToSource && fun->isLambda() && !out.append(")")) {
7299 return nullptr;
7302 return out.finishString();
7305 JSString* js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) {
7306 MOZ_ASSERT(IsAsmJSFunction(fun));
7308 const AsmJSMetadata& metadata =
7309 ExportedFunctionToInstance(fun).metadata().asAsmJS();
7310 const AsmJSExport& f =
7311 metadata.lookupAsmJSExport(ExportedFunctionToFuncIndex(fun));
7313 uint32_t begin = metadata.srcStart + f.startOffsetInModule();
7314 uint32_t end = metadata.srcStart + f.endOffsetInModule();
7316 ScriptSource* source = metadata.maybeScriptSource();
7317 JSStringBuilder out(cx);
7319 if (!out.append("function ")) {
7320 return nullptr;
7323 bool haveSource;
7324 if (!ScriptSource::loadSource(cx, source, &haveSource)) {
7325 return nullptr;
7328 if (!haveSource) {
7329 // asm.js functions can't be anonymous
7330 MOZ_ASSERT(fun->fullExplicitName());
7331 if (!out.append(fun->fullExplicitName())) {
7332 return nullptr;
7334 if (!out.append("() {\n [native code]\n}")) {
7335 return nullptr;
7337 } else {
7338 Rooted<JSLinearString*> src(cx, source->substring(cx, begin, end));
7339 if (!src) {
7340 return nullptr;
7342 if (!out.append(src)) {
7343 return nullptr;
7347 return out.finishString();
7350 bool js::IsValidAsmJSHeapLength(size_t length) {
7351 if (length < MinHeapLength) {
7352 return false;
7355 // The heap length is limited by what a wasm memory32 can handle.
7356 if (length > MaxMemoryBytes(IndexType::I32)) {
7357 return false;
7360 return wasm::IsValidARMImmediate(length);