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:
3 *
4 * Copyright 2015 Mozilla Foundation
5 *
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
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
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.
17 */
18
19 #ifndef wasm_types_h
20 #define wasm_types_h
21
22 #include "mozilla/Alignment.h"
23 #include "mozilla/Atomics.h"
24 #include "mozilla/BinarySearch.h"
25 #include "mozilla/EnumeratedArray.h"
26 #include "mozilla/HashFunctions.h"
27 #include "mozilla/Maybe.h"
28 #include "mozilla/RefPtr.h"
29
30 #include <type_traits>
31
32 #include "NamespaceImports.h"
33
34 #include "ds/LifoAlloc.h"
35 #include "jit/IonTypes.h"
36 #include "js/RefCounted.h"
37 #include "js/UniquePtr.h"
38 #include "js/Utility.h"
39 #include "js/Vector.h"
40 #include "vm/MallocProvider.h"
41 #include "vm/NativeObject.h"
42 #include "wasm/WasmBuiltins.h"
43 #include "wasm/WasmConstants.h"
44 #include "wasm/WasmInitExpr.h"
45 #include "wasm/WasmPages.h"
46 #include "wasm/WasmSerialize.h"
47 #include "wasm/WasmShareable.h"
48 #include "wasm/WasmTlsData.h"
49 #include "wasm/WasmTypeDecls.h"
50 #include "wasm/WasmTypeDef.h"
51 #include "wasm/WasmUtility.h"
52 #include "wasm/WasmValType.h"
53 #include "wasm/WasmValue.h"
54
55 namespace js {
56
57 namespace jit {
58 enum class RoundingMode;
59 template <class VecT, class ABIArgGeneratorT>
60 class ABIArgIterBase;
61 } // namespace jit
62
63 namespace wasm {
64
65 using mozilla::Atomic;
66 using mozilla::DebugOnly;
67 using mozilla::EnumeratedArray;
68 using mozilla::MallocSizeOf;
69 using mozilla::Maybe;
70 using mozilla::Nothing;
71 using mozilla::PodCopy;
72 using mozilla::PodZero;
73 using mozilla::Some;
74
75 class Code;
76 class DebugState;
77 class GeneratedSourceMap;
78 class Memory;
79 class Module;
80 class Instance;
81 class Table;
82
83 // Exception tags are used to uniquely identify exceptions. They are stored
84 // in a vector in Instances and used by both WebAssembly.Exception for import
85 // and export, and by the representation of thrown exceptions.
86 //
87 // Since an exception tag is a (trivial) substructure of AtomicRefCounted, the
88 // RefPtr SharedExceptionTag can have many instances/modules referencing a
89 // single constant exception tag.
90
91 struct ExceptionTag : AtomicRefCounted<ExceptionTag> {
92 ExceptionTag() = default;
93 };
94 using SharedExceptionTag = RefPtr<ExceptionTag>;
95 using SharedExceptionTagVector =
96 Vector<SharedExceptionTag, 0, SystemAllocPolicy>;
97
98 // WasmJSExceptionObject wraps a JS Value in order to provide a uniform
99 // method of handling JS thrown exceptions. Exceptions originating in Wasm
100 // are WebAssemby.RuntimeException objects, whereas exceptions from JS are
101 // wrapped as WasmJSExceptionObject objects.
102 class WasmJSExceptionObject : public NativeObject {
103 static const unsigned VALUE_SLOT = 0;
104
105 public:
106 static const unsigned RESERVED_SLOTS = 1;
107 static const JSClass class_;
value()108 const Value& value() const { return getFixedSlot(VALUE_SLOT); }
109
110 static WasmJSExceptionObject* create(JSContext* cx, MutableHandleValue value);
111 };
112
113 // A Module can either be asm.js or wasm.
114
115 enum ModuleKind { Wasm, AsmJS };
116
117 // ArgTypeVector type.
118 //
119 // Functions usually receive one ABI argument per WebAssembly argument. However
120 // if a function has multiple results and some of those results go to the stack,
121 // then it additionally receives a synthetic ABI argument holding a pointer to
122 // the stack result area.
123 //
124 // Given the presence of synthetic arguments, sometimes we need a name for
125 // non-synthetic arguments. We call those "natural" arguments.
126
127 enum class StackResults { HasStackResults, NoStackResults };
128
129 class ArgTypeVector {
130 const ValTypeVector& args_;
131 bool hasStackResults_;
132
133 // To allow ABIArgIterBase<VecT, ABIArgGeneratorT>, we define a private
134 // length() method. To prevent accidental errors, other users need to be
135 // explicit and call lengthWithStackResults() or
136 // lengthWithoutStackResults().
length()137 size_t length() const { return args_.length() + size_t(hasStackResults_); }
138 template <class VecT, class ABIArgGeneratorT>
139 friend class jit::ABIArgIterBase;
140
141 public:
ArgTypeVector(const ValTypeVector & args,StackResults stackResults)142 ArgTypeVector(const ValTypeVector& args, StackResults stackResults)
143 : args_(args),
144 hasStackResults_(stackResults == StackResults::HasStackResults) {}
145 explicit ArgTypeVector(const FuncType& funcType);
146
hasSyntheticStackResultPointerArg()147 bool hasSyntheticStackResultPointerArg() const { return hasStackResults_; }
stackResults()148 StackResults stackResults() const {
149 return hasSyntheticStackResultPointerArg() ? StackResults::HasStackResults
150 : StackResults::NoStackResults;
151 }
lengthWithoutStackResults()152 size_t lengthWithoutStackResults() const { return args_.length(); }
isSyntheticStackResultPointerArg(size_t idx)153 bool isSyntheticStackResultPointerArg(size_t idx) const {
154 // The pointer to stack results area, if present, is a synthetic argument
155 // tacked on at the end.
156 MOZ_ASSERT(idx < lengthWithStackResults());
157 return idx == args_.length();
158 }
isNaturalArg(size_t idx)159 bool isNaturalArg(size_t idx) const {
160 return !isSyntheticStackResultPointerArg(idx);
161 }
naturalIndex(size_t idx)162 size_t naturalIndex(size_t idx) const {
163 MOZ_ASSERT(isNaturalArg(idx));
164 // Because the synthetic argument, if present, is tacked on the end, an
165 // argument index that isn't synthetic is natural.
166 return idx;
167 }
168
lengthWithStackResults()169 size_t lengthWithStackResults() const { return length(); }
170 jit::MIRType operator[](size_t i) const {
171 MOZ_ASSERT(i < lengthWithStackResults());
172 if (isSyntheticStackResultPointerArg(i)) {
173 return jit::MIRType::StackResults;
174 }
175 return ToMIRType(args_[naturalIndex(i)]);
176 }
177 };
178
179 template <typename PointerType>
180 class TaggedValue {
181 public:
182 enum Kind {
183 ImmediateKind1 = 0,
184 ImmediateKind2 = 1,
185 PointerKind1 = 2,
186 PointerKind2 = 3
187 };
188 using PackedRepr = uintptr_t;
189
190 private:
191 PackedRepr bits_;
192
193 static constexpr PackedRepr PayloadShift = 2;
194 static constexpr PackedRepr KindMask = 0x3;
195 static constexpr PackedRepr PointerKindBit = 0x2;
196
IsPointerKind(Kind kind)197 constexpr static bool IsPointerKind(Kind kind) {
198 return PackedRepr(kind) & PointerKindBit;
199 }
IsImmediateKind(Kind kind)200 constexpr static bool IsImmediateKind(Kind kind) {
201 return !IsPointerKind(kind);
202 }
203
204 static_assert(IsImmediateKind(ImmediateKind1), "immediate kind 1");
205 static_assert(IsImmediateKind(ImmediateKind2), "immediate kind 2");
206 static_assert(IsPointerKind(PointerKind1), "pointer kind 1");
207 static_assert(IsPointerKind(PointerKind2), "pointer kind 2");
208
PackImmediate(Kind kind,PackedRepr imm)209 static PackedRepr PackImmediate(Kind kind, PackedRepr imm) {
210 MOZ_ASSERT(IsImmediateKind(kind));
211 MOZ_ASSERT((PackedRepr(kind) & KindMask) == kind);
212 MOZ_ASSERT((imm & (PackedRepr(KindMask)
213 << ((sizeof(PackedRepr) * 8) - PayloadShift))) == 0);
214 return PackedRepr(kind) | (PackedRepr(imm) << PayloadShift);
215 }
216
PackPointer(Kind kind,PointerType * ptr)217 static PackedRepr PackPointer(Kind kind, PointerType* ptr) {
218 PackedRepr ptrBits = reinterpret_cast<PackedRepr>(ptr);
219 MOZ_ASSERT(IsPointerKind(kind));
220 MOZ_ASSERT((PackedRepr(kind) & KindMask) == kind);
221 MOZ_ASSERT((ptrBits & KindMask) == 0);
222 return PackedRepr(kind) | ptrBits;
223 }
224
225 public:
TaggedValue(Kind kind,PackedRepr imm)226 TaggedValue(Kind kind, PackedRepr imm) : bits_(PackImmediate(kind, imm)) {}
TaggedValue(Kind kind,PointerType * ptr)227 TaggedValue(Kind kind, PointerType* ptr) : bits_(PackPointer(kind, ptr)) {}
228
bits()229 PackedRepr bits() const { return bits_; }
kind()230 Kind kind() const { return Kind(bits() & KindMask); }
immediate()231 PackedRepr immediate() const {
232 MOZ_ASSERT(IsImmediateKind(kind()));
233 return mozilla::AssertedCast<PackedRepr>(bits() >> PayloadShift);
234 }
pointer()235 PointerType* pointer() const {
236 MOZ_ASSERT(IsPointerKind(kind()));
237 return reinterpret_cast<PointerType*>(bits() & ~KindMask);
238 }
239 };
240
241 static_assert(
242 std::is_same<TaggedValue<void*>::PackedRepr, PackedTypeCode::PackedRepr>(),
243 "can use pointer tagging with PackedTypeCode");
244
245 // ResultType represents the WebAssembly spec's `resulttype`. Semantically, a
246 // result type is just a vec(valtype). For effiency, though, the ResultType
247 // value is packed into a word, with separate encodings for these 3 cases:
248 // []
249 // [valtype]
250 // pointer to ValTypeVector
251 //
252 // Additionally there is an encoding indicating uninitialized ResultType
253 // values.
254 //
255 // Generally in the latter case the ValTypeVector is the args() or results() of
256 // a FuncType in the compilation unit, so as long as the lifetime of the
257 // ResultType value is less than the OpIter, we can just borrow the pointer
258 // without ownership or copying.
259 class ResultType {
260 using Tagged = TaggedValue<const ValTypeVector>;
261 Tagged tagged_;
262
263 enum Kind {
264 EmptyKind = Tagged::ImmediateKind1,
265 SingleKind = Tagged::ImmediateKind2,
266 VectorKind = Tagged::PointerKind1,
267 InvalidKind = Tagged::PointerKind2,
268 };
269
ResultType(Kind kind,uint32_t imm)270 ResultType(Kind kind, uint32_t imm) : tagged_(Tagged::Kind(kind), imm) {}
ResultType(const ValTypeVector * ptr)271 explicit ResultType(const ValTypeVector* ptr)
272 : tagged_(Tagged::Kind(VectorKind), ptr) {}
273
kind()274 Kind kind() const { return Kind(tagged_.kind()); }
275
singleValType()276 ValType singleValType() const {
277 MOZ_ASSERT(kind() == SingleKind);
278 return ValType(PackedTypeCode::fromBits(tagged_.immediate()));
279 }
280
values()281 const ValTypeVector& values() const {
282 MOZ_ASSERT(kind() == VectorKind);
283 return *tagged_.pointer();
284 }
285
286 public:
ResultType()287 ResultType() : tagged_(Tagged::Kind(InvalidKind), nullptr) {}
288
Empty()289 static ResultType Empty() { return ResultType(EmptyKind, uint32_t(0)); }
Single(ValType vt)290 static ResultType Single(ValType vt) {
291 return ResultType(SingleKind, vt.bitsUnsafe());
292 }
Vector(const ValTypeVector & vals)293 static ResultType Vector(const ValTypeVector& vals) {
294 switch (vals.length()) {
295 case 0:
296 return Empty();
297 case 1:
298 return Single(vals[0]);
299 default:
300 return ResultType(&vals);
301 }
302 }
303
cloneToVector(ValTypeVector * out)304 [[nodiscard]] bool cloneToVector(ValTypeVector* out) {
305 MOZ_ASSERT(out->empty());
306 switch (kind()) {
307 case EmptyKind:
308 return true;
309 case SingleKind:
310 return out->append(singleValType());
311 case VectorKind:
312 return out->appendAll(values());
313 default:
314 MOZ_CRASH("bad resulttype");
315 }
316 }
317
empty()318 bool empty() const { return kind() == EmptyKind; }
319
length()320 size_t length() const {
321 switch (kind()) {
322 case EmptyKind:
323 return 0;
324 case SingleKind:
325 return 1;
326 case VectorKind:
327 return values().length();
328 default:
329 MOZ_CRASH("bad resulttype");
330 }
331 }
332
333 ValType operator[](size_t i) const {
334 switch (kind()) {
335 case SingleKind:
336 MOZ_ASSERT(i == 0);
337 return singleValType();
338 case VectorKind:
339 return values()[i];
340 default:
341 MOZ_CRASH("bad resulttype");
342 }
343 }
344
345 bool operator==(ResultType rhs) const {
346 switch (kind()) {
347 case EmptyKind:
348 case SingleKind:
349 case InvalidKind:
350 return tagged_.bits() == rhs.tagged_.bits();
351 case VectorKind: {
352 if (rhs.kind() != VectorKind) {
353 return false;
354 }
355 return EqualContainers(values(), rhs.values());
356 }
357 default:
358 MOZ_CRASH("bad resulttype");
359 }
360 }
361 bool operator!=(ResultType rhs) const { return !(*this == rhs); }
362 };
363
364 // BlockType represents the WebAssembly spec's `blocktype`. Semantically, a
365 // block type is just a (vec(valtype) -> vec(valtype)) with four special
366 // encodings which are represented explicitly in BlockType:
367 // [] -> []
368 // [] -> [valtype]
369 // [params] -> [results] via pointer to FuncType
370 // [] -> [results] via pointer to FuncType (ignoring [params])
371
372 class BlockType {
373 using Tagged = TaggedValue<const FuncType>;
374 Tagged tagged_;
375
376 enum Kind {
377 VoidToVoidKind = Tagged::ImmediateKind1,
378 VoidToSingleKind = Tagged::ImmediateKind2,
379 FuncKind = Tagged::PointerKind1,
380 FuncResultsKind = Tagged::PointerKind2
381 };
382
BlockType(Kind kind,uint32_t imm)383 BlockType(Kind kind, uint32_t imm) : tagged_(Tagged::Kind(kind), imm) {}
BlockType(Kind kind,const FuncType & type)384 BlockType(Kind kind, const FuncType& type)
385 : tagged_(Tagged::Kind(kind), &type) {}
386
kind()387 Kind kind() const { return Kind(tagged_.kind()); }
singleValType()388 ValType singleValType() const {
389 MOZ_ASSERT(kind() == VoidToSingleKind);
390 return ValType(PackedTypeCode::fromBits(tagged_.immediate()));
391 }
392
funcType()393 const FuncType& funcType() const { return *tagged_.pointer(); }
394
395 public:
BlockType()396 BlockType()
397 : tagged_(Tagged::Kind(VoidToVoidKind),
398 PackedTypeCode::invalid().bits()) {}
399
VoidToVoid()400 static BlockType VoidToVoid() {
401 return BlockType(VoidToVoidKind, uint32_t(0));
402 }
VoidToSingle(ValType vt)403 static BlockType VoidToSingle(ValType vt) {
404 return BlockType(VoidToSingleKind, vt.bitsUnsafe());
405 }
Func(const FuncType & type)406 static BlockType Func(const FuncType& type) {
407 if (type.args().length() == 0) {
408 return FuncResults(type);
409 }
410 return BlockType(FuncKind, type);
411 }
FuncResults(const FuncType & type)412 static BlockType FuncResults(const FuncType& type) {
413 switch (type.results().length()) {
414 case 0:
415 return VoidToVoid();
416 case 1:
417 return VoidToSingle(type.results()[0]);
418 default:
419 return BlockType(FuncResultsKind, type);
420 }
421 }
422
params()423 ResultType params() const {
424 switch (kind()) {
425 case VoidToVoidKind:
426 case VoidToSingleKind:
427 case FuncResultsKind:
428 return ResultType::Empty();
429 case FuncKind:
430 return ResultType::Vector(funcType().args());
431 default:
432 MOZ_CRASH("unexpected kind");
433 }
434 }
435
results()436 ResultType results() const {
437 switch (kind()) {
438 case VoidToVoidKind:
439 return ResultType::Empty();
440 case VoidToSingleKind:
441 return ResultType::Single(singleValType());
442 case FuncKind:
443 case FuncResultsKind:
444 return ResultType::Vector(funcType().results());
445 default:
446 MOZ_CRASH("unexpected kind");
447 }
448 }
449
450 bool operator==(BlockType rhs) const {
451 if (kind() != rhs.kind()) {
452 return false;
453 }
454 switch (kind()) {
455 case VoidToVoidKind:
456 case VoidToSingleKind:
457 return tagged_.bits() == rhs.tagged_.bits();
458 case FuncKind:
459 return funcType() == rhs.funcType();
460 case FuncResultsKind:
461 return EqualContainers(funcType().results(), rhs.funcType().results());
462 default:
463 MOZ_CRASH("unexpected kind");
464 }
465 }
466
467 bool operator!=(BlockType rhs) const { return !(*this == rhs); }
468 };
469
470 // CacheableChars is used to cacheably store UniqueChars.
471
472 struct CacheableChars : UniqueChars {
473 CacheableChars() = default;
CacheableCharsCacheableChars474 explicit CacheableChars(char* ptr) : UniqueChars(ptr) {}
CacheableCharsCacheableChars475 MOZ_IMPLICIT CacheableChars(UniqueChars&& rhs)
476 : UniqueChars(std::move(rhs)) {}
477 WASM_DECLARE_SERIALIZABLE(CacheableChars)
478 };
479
480 using CacheableCharsVector = Vector<CacheableChars, 0, SystemAllocPolicy>;
481
482 // Import describes a single wasm import. An ImportVector describes all
483 // of a single module's imports.
484 //
485 // ImportVector is built incrementally by ModuleGenerator and then stored
486 // immutably by Module.
487
488 struct Import {
489 CacheableChars module;
490 CacheableChars field;
491 DefinitionKind kind;
492
493 Import() = default;
ImportImport494 Import(UniqueChars&& module, UniqueChars&& field, DefinitionKind kind)
495 : module(std::move(module)), field(std::move(field)), kind(kind) {}
496
497 WASM_DECLARE_SERIALIZABLE(Import)
498 };
499
500 using ImportVector = Vector<Import, 0, SystemAllocPolicy>;
501
502 // Export describes the export of a definition in a Module to a field in the
503 // export object. The Export stores the index of the exported item in the
504 // appropriate type-specific module data structure (function table, global
505 // table, table table, and - eventually - memory table).
506 //
507 // Note a single definition can be exported by multiple Exports in the
508 // ExportVector.
509 //
510 // ExportVector is built incrementally by ModuleGenerator and then stored
511 // immutably by Module.
512
513 class Export {
514 CacheableChars fieldName_;
515 struct CacheablePod {
516 DefinitionKind kind_;
517 uint32_t index_;
518 } pod;
519
520 public:
521 Export() = default;
522 explicit Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind);
523 explicit Export(UniqueChars fieldName, DefinitionKind kind);
524
fieldName()525 const char* fieldName() const { return fieldName_.get(); }
526
kind()527 DefinitionKind kind() const { return pod.kind_; }
528 uint32_t funcIndex() const;
529 #ifdef ENABLE_WASM_EXCEPTIONS
530 uint32_t eventIndex() const;
531 #endif
532 uint32_t globalIndex() const;
533 uint32_t tableIndex() const;
534
535 WASM_DECLARE_SERIALIZABLE(Export)
536 };
537
538 using ExportVector = Vector<Export, 0, SystemAllocPolicy>;
539
540 // FuncFlags provides metadata for a function definition.
541
542 enum class FuncFlags : uint8_t {
543 None = 0x0,
544 // The function maybe be accessible by JS and needs thunks generated for it.
545 // See `[SMDOC] Exported wasm functions and the jit-entry stubs` in
546 // WasmJS.cpp for more information.
547 Exported = 0x1,
548 // The function should have thunks generated upon instantiation, not upon
549 // first call. May only be set if `Exported` is set.
550 Eager = 0x2,
551 // The function can be the target of a ref.func instruction in the code
552 // section. May only be set if `Exported` is set.
553 CanRefFunc = 0x4,
554 };
555
556 // A FuncDesc describes a single function definition.
557
558 class TypeIdDesc;
559
560 struct FuncDesc {
561 FuncType* type;
562 TypeIdDesc* typeId;
563 // Bit pack to keep this struct small on 32-bit systems
564 uint32_t typeIndex : 24;
565 FuncFlags flags : 8;
566
567 // Assert that the bit packing scheme is viable
568 static_assert(MaxTypes <= (1 << 24) - 1);
569 static_assert(sizeof(FuncFlags) == sizeof(uint8_t));
570
571 FuncDesc() = default;
FuncDescFuncDesc572 FuncDesc(FuncType* type, TypeIdDesc* typeId, uint32_t typeIndex)
573 : type(type),
574 typeId(typeId),
575 typeIndex(typeIndex),
576 flags(FuncFlags::None) {}
577
isExportedFuncDesc578 bool isExported() const {
579 return uint8_t(flags) & uint8_t(FuncFlags::Exported);
580 }
isEagerFuncDesc581 bool isEager() const { return uint8_t(flags) & uint8_t(FuncFlags::Eager); }
canRefFuncFuncDesc582 bool canRefFunc() const {
583 return uint8_t(flags) & uint8_t(FuncFlags::CanRefFunc);
584 }
585 };
586
587 using FuncDescVector = Vector<FuncDesc, 0, SystemAllocPolicy>;
588
589 // A GlobalDesc describes a single global variable.
590 //
591 // wasm can import and export mutable and immutable globals.
592 //
593 // asm.js can import mutable and immutable globals, but a mutable global has a
594 // location that is private to the module, and its initial value is copied into
595 // that cell from the environment. asm.js cannot export globals.
596
597 enum class GlobalKind { Import, Constant, Variable };
598
599 class GlobalDesc {
600 GlobalKind kind_;
601 // Stores the value type of this global for all kinds, and the initializer
602 // expression when `constant` or `variable`.
603 InitExpr initial_;
604 // Metadata for the global when `variable` or `import`.
605 unsigned offset_;
606 bool isMutable_;
607 bool isWasm_;
608 bool isExport_;
609 // Metadata for the global when `import`.
610 uint32_t importIndex_;
611
612 // Private, as they have unusual semantics.
613
isExport()614 bool isExport() const { return !isConstant() && isExport_; }
isWasm()615 bool isWasm() const { return !isConstant() && isWasm_; }
616
617 public:
618 GlobalDesc() = default;
619
620 explicit GlobalDesc(InitExpr&& initial, bool isMutable,
621 ModuleKind kind = ModuleKind::Wasm)
622 : kind_((isMutable || !initial.isLiteral()) ? GlobalKind::Variable
623 : GlobalKind::Constant) {
624 initial_ = std::move(initial);
625 if (isVariable()) {
626 isMutable_ = isMutable;
627 isWasm_ = kind == Wasm;
628 isExport_ = false;
629 offset_ = UINT32_MAX;
630 }
631 }
632
633 explicit GlobalDesc(ValType type, bool isMutable, uint32_t importIndex,
634 ModuleKind kind = ModuleKind::Wasm)
kind_(GlobalKind::Import)635 : kind_(GlobalKind::Import) {
636 initial_ = InitExpr(LitVal(type));
637 importIndex_ = importIndex;
638 isMutable_ = isMutable;
639 isWasm_ = kind == Wasm;
640 isExport_ = false;
641 offset_ = UINT32_MAX;
642 }
643
setOffset(unsigned offset)644 void setOffset(unsigned offset) {
645 MOZ_ASSERT(!isConstant());
646 MOZ_ASSERT(offset_ == UINT32_MAX);
647 offset_ = offset;
648 }
offset()649 unsigned offset() const {
650 MOZ_ASSERT(!isConstant());
651 MOZ_ASSERT(offset_ != UINT32_MAX);
652 return offset_;
653 }
654
setIsExport()655 void setIsExport() {
656 if (!isConstant()) {
657 isExport_ = true;
658 }
659 }
660
kind()661 GlobalKind kind() const { return kind_; }
isVariable()662 bool isVariable() const { return kind_ == GlobalKind::Variable; }
isConstant()663 bool isConstant() const { return kind_ == GlobalKind::Constant; }
isImport()664 bool isImport() const { return kind_ == GlobalKind::Import; }
665
isMutable()666 bool isMutable() const { return !isConstant() && isMutable_; }
initExpr()667 const InitExpr& initExpr() const {
668 MOZ_ASSERT(!isImport());
669 return initial_;
670 }
importIndex()671 uint32_t importIndex() const {
672 MOZ_ASSERT(isImport());
673 return importIndex_;
674 }
675
constantValue()676 LitVal constantValue() const { return initial_.literal(); }
677
678 // If isIndirect() is true then storage for the value is not in the
679 // instance's global area, but in a WasmGlobalObject::Cell hanging off a
680 // WasmGlobalObject; the global area contains a pointer to the Cell.
681 //
682 // We don't want to indirect unless we must, so only mutable, exposed
683 // globals are indirected - in all other cases we copy values into and out
684 // of their module.
685 //
686 // Note that isIndirect() isn't equivalent to getting a WasmGlobalObject:
687 // an immutable exported global will still get an object, but will not be
688 // indirect.
isIndirect()689 bool isIndirect() const {
690 return isMutable() && isWasm() && (isImport() || isExport());
691 }
692
type()693 ValType type() const { return initial_.type(); }
694
695 WASM_DECLARE_SERIALIZABLE(GlobalDesc)
696 };
697
698 using GlobalDescVector = Vector<GlobalDesc, 0, SystemAllocPolicy>;
699
700 // An EventDesc describes a single event for non-local control flow, such as
701 // for exceptions.
702
703 #ifdef ENABLE_WASM_EXCEPTIONS
704 struct EventDesc {
705 EventKind kind;
706 ValTypeVector type;
707 bool isExport;
708
709 EventDesc(EventKind kind, ValTypeVector&& type, bool isExport = false)
kindEventDesc710 : kind(kind), type(std::move(type)), isExport(isExport) {}
711
resultTypeEventDesc712 ResultType resultType() const { return ResultType::Vector(type); }
713 };
714
715 using EventDescVector = Vector<EventDesc, 0, SystemAllocPolicy>;
716 #endif
717
718 // When a ElemSegment is "passive" it is shared between a wasm::Module and its
719 // wasm::Instances. To allow each segment to be released as soon as the last
720 // Instance elem.drops it and the Module is destroyed, each ElemSegment is
721 // individually atomically ref-counted.
722
723 struct ElemSegment : AtomicRefCounted<ElemSegment> {
724 enum class Kind {
725 Active,
726 Passive,
727 Declared,
728 };
729
730 Kind kind;
731 uint32_t tableIndex;
732 RefType elemType;
733 Maybe<InitExpr> offsetIfActive;
734 Uint32Vector elemFuncIndices; // Element may be NullFuncIndex
735
activeElemSegment736 bool active() const { return kind == Kind::Active; }
737
offsetElemSegment738 const InitExpr& offset() const { return *offsetIfActive; }
739
lengthElemSegment740 size_t length() const { return elemFuncIndices.length(); }
741
742 WASM_DECLARE_SERIALIZABLE(ElemSegment)
743 };
744
745 // NullFuncIndex represents the case when an element segment (of type funcref)
746 // contains a null element.
747 constexpr uint32_t NullFuncIndex = UINT32_MAX;
748 static_assert(NullFuncIndex > MaxFuncs, "Invariant");
749
750 using MutableElemSegment = RefPtr<ElemSegment>;
751 using SharedElemSegment = SerializableRefPtr<const ElemSegment>;
752 using ElemSegmentVector = Vector<SharedElemSegment, 0, SystemAllocPolicy>;
753
754 // DataSegmentEnv holds the initial results of decoding a data segment from the
755 // bytecode and is stored in the ModuleEnvironment during compilation. When
756 // compilation completes, (non-Env) DataSegments are created and stored in
757 // the wasm::Module which contain copies of the data segment payload. This
758 // allows non-compilation uses of wasm validation to avoid expensive copies.
759 //
760 // When a DataSegment is "passive" it is shared between a wasm::Module and its
761 // wasm::Instances. To allow each segment to be released as soon as the last
762 // Instance mem.drops it and the Module is destroyed, each DataSegment is
763 // individually atomically ref-counted.
764
765 struct DataSegmentEnv {
766 Maybe<InitExpr> offsetIfActive;
767 uint32_t bytecodeOffset;
768 uint32_t length;
769 };
770
771 using DataSegmentEnvVector = Vector<DataSegmentEnv, 0, SystemAllocPolicy>;
772
773 struct DataSegment : AtomicRefCounted<DataSegment> {
774 Maybe<InitExpr> offsetIfActive;
775 Bytes bytes;
776
777 DataSegment() = default;
778
activeDataSegment779 bool active() const { return !!offsetIfActive; }
780
offsetDataSegment781 const InitExpr& offset() const { return *offsetIfActive; }
782
initDataSegment783 [[nodiscard]] bool init(const ShareableBytes& bytecode,
784 const DataSegmentEnv& src) {
785 if (src.offsetIfActive) {
786 offsetIfActive.emplace();
787 if (!offsetIfActive->clone(*src.offsetIfActive)) {
788 return false;
789 }
790 }
791 return bytes.append(bytecode.begin() + src.bytecodeOffset, src.length);
792 }
793
794 WASM_DECLARE_SERIALIZABLE(DataSegment)
795 };
796
797 using MutableDataSegment = RefPtr<DataSegment>;
798 using SharedDataSegment = SerializableRefPtr<const DataSegment>;
799 using DataSegmentVector = Vector<SharedDataSegment, 0, SystemAllocPolicy>;
800
801 // The CustomSection(Env) structs are like DataSegment(Env): CustomSectionEnv is
802 // stored in the ModuleEnvironment and CustomSection holds a copy of the payload
803 // and is stored in the wasm::Module.
804
805 struct CustomSectionEnv {
806 uint32_t nameOffset;
807 uint32_t nameLength;
808 uint32_t payloadOffset;
809 uint32_t payloadLength;
810 };
811
812 using CustomSectionEnvVector = Vector<CustomSectionEnv, 0, SystemAllocPolicy>;
813
814 struct CustomSection {
815 Bytes name;
816 SharedBytes payload;
817
818 WASM_DECLARE_SERIALIZABLE(CustomSection)
819 };
820
821 using CustomSectionVector = Vector<CustomSection, 0, SystemAllocPolicy>;
822
823 // A Name represents a string of utf8 chars embedded within the name custom
824 // section. The offset of a name is expressed relative to the beginning of the
825 // name section's payload so that Names can stored in wasm::Code, which only
826 // holds the name section's bytes, not the whole bytecode.
827
828 struct Name {
829 // All fields are treated as cacheable POD:
830 uint32_t offsetInNamePayload;
831 uint32_t length;
832
NameName833 Name() : offsetInNamePayload(UINT32_MAX), length(0) {}
834 };
835
836 using NameVector = Vector<Name, 0, SystemAllocPolicy>;
837
838 // TypeIdDesc describes the runtime representation of a TypeDef suitable for
839 // type equality checks. The kind of representation depends on whether the type
840 // is a function or a struct. This will likely be simplified in the future once
841 // mutually recursives types are able to be collected.
842 //
843 // For functions, a FuncType is allocated and stored in a process-wide hash
844 // table, so that pointer equality implies structural equality. As an
845 // optimization for the 99% case where the FuncType has a small number of
846 // parameters, the FuncType is bit-packed into a uint32 immediate value so that
847 // integer equality implies structural equality. Both cases can be handled with
848 // a single comparison by always setting the LSB for the immediates
849 // (the LSB is necessarily 0 for allocated FuncType pointers due to alignment).
850 //
851 // TODO: Write description for StructTypes once it is well formed.
852
853 class TypeIdDesc {
854 public:
855 static const uintptr_t ImmediateBit = 0x1;
856
857 private:
858 TypeIdDescKind kind_;
859 size_t bits_;
860
TypeIdDesc(TypeIdDescKind kind,size_t bits)861 TypeIdDesc(TypeIdDescKind kind, size_t bits) : kind_(kind), bits_(bits) {}
862
863 public:
kind()864 TypeIdDescKind kind() const { return kind_; }
865 static bool isGlobal(const TypeDef& type);
866
TypeIdDesc()867 TypeIdDesc() : kind_(TypeIdDescKind::None), bits_(0) {}
868 static TypeIdDesc global(const TypeDef& type, uint32_t globalDataOffset);
869 static TypeIdDesc immediate(const TypeDef& type);
870
isGlobal()871 bool isGlobal() const { return kind_ == TypeIdDescKind::Global; }
872
immediate()873 size_t immediate() const {
874 MOZ_ASSERT(kind_ == TypeIdDescKind::Immediate);
875 return bits_;
876 }
globalDataOffset()877 uint32_t globalDataOffset() const {
878 MOZ_ASSERT(kind_ == TypeIdDescKind::Global);
879 return bits_;
880 }
881 };
882
883 using TypeIdDescVector = Vector<TypeIdDesc, 0, SystemAllocPolicy>;
884
885 // TypeDefWithId pairs a FuncType with TypeIdDesc, describing either how to
886 // compile code that compares this signature's id or, at instantiation what
887 // signature ids to allocate in the global hash and where to put them.
888
889 struct TypeDefWithId : public TypeDef {
890 TypeIdDesc id;
891
892 TypeDefWithId() = default;
TypeDefWithIdTypeDefWithId893 explicit TypeDefWithId(TypeDef&& typeDef)
894 : TypeDef(std::move(typeDef)), id() {}
TypeDefWithIdTypeDefWithId895 TypeDefWithId(TypeDef&& typeDef, TypeIdDesc id)
896 : TypeDef(std::move(typeDef)), id(id) {}
897
898 WASM_DECLARE_SERIALIZABLE(TypeDefWithId)
899 };
900
901 using TypeDefWithIdVector = Vector<TypeDefWithId, 0, SystemAllocPolicy>;
902 using TypeDefWithIdPtrVector =
903 Vector<const TypeDefWithId*, 0, SystemAllocPolicy>;
904
905 // A wrapper around the bytecode offset of a wasm instruction within a whole
906 // module, used for trap offsets or call offsets. These offsets should refer to
907 // the first byte of the instruction that triggered the trap / did the call and
908 // should ultimately derive from OpIter::bytecodeOffset.
909
910 class BytecodeOffset {
911 static const uint32_t INVALID = -1;
912 uint32_t offset_;
913
914 public:
BytecodeOffset()915 BytecodeOffset() : offset_(INVALID) {}
BytecodeOffset(uint32_t offset)916 explicit BytecodeOffset(uint32_t offset) : offset_(offset) {}
917
isValid()918 bool isValid() const { return offset_ != INVALID; }
offset()919 uint32_t offset() const {
920 MOZ_ASSERT(isValid());
921 return offset_;
922 }
923 };
924
925 // A TrapSite (in the TrapSiteVector for a given Trap code) represents a wasm
926 // instruction at a given bytecode offset that can fault at the given pc offset.
927 // When such a fault occurs, a signal/exception handler looks up the TrapSite to
928 // confirm the fault is intended/safe and redirects pc to the trap stub.
929
930 struct TrapSite {
931 uint32_t pcOffset;
932 BytecodeOffset bytecode;
933
TrapSiteTrapSite934 TrapSite() : pcOffset(-1), bytecode() {}
TrapSiteTrapSite935 TrapSite(uint32_t pcOffset, BytecodeOffset bytecode)
936 : pcOffset(pcOffset), bytecode(bytecode) {}
937
offsetByTrapSite938 void offsetBy(uint32_t offset) { pcOffset += offset; }
939 };
940
941 WASM_DECLARE_POD_VECTOR(TrapSite, TrapSiteVector)
942
943 struct TrapSiteVectorArray
944 : EnumeratedArray<Trap, Trap::Limit, TrapSiteVector> {
945 bool empty() const;
946 void clear();
947 void swap(TrapSiteVectorArray& rhs);
948 void shrinkStorageToFit();
949
950 WASM_DECLARE_SERIALIZABLE(TrapSiteVectorArray)
951 };
952
953 // On trap, the bytecode offset to be reported in callstacks is saved.
954
955 struct TrapData {
956 // The resumePC indicates where, if the trap doesn't throw, the trap stub
957 // should jump to after restoring all register state.
958 void* resumePC;
959
960 // The unwoundPC is the PC after adjustment by wasm::StartUnwinding(), which
961 // basically unwinds partially-construted wasm::Frames when pc is in the
962 // prologue/epilogue. Stack traces during a trap should use this PC since
963 // it corresponds to the JitActivation::wasmExitFP.
964 void* unwoundPC;
965
966 Trap trap;
967 uint32_t bytecodeOffset;
968 };
969
970 // The (,Callable,Func)Offsets classes are used to record the offsets of
971 // different key points in a CodeRange during compilation.
972
973 struct Offsets {
974 explicit Offsets(uint32_t begin = 0, uint32_t end = 0)
beginOffsets975 : begin(begin), end(end) {}
976
977 // These define a [begin, end) contiguous range of instructions compiled
978 // into a CodeRange.
979 uint32_t begin;
980 uint32_t end;
981 };
982
983 struct CallableOffsets : Offsets {
OffsetsCallableOffsets984 MOZ_IMPLICIT CallableOffsets(uint32_t ret = 0) : Offsets(), ret(ret) {}
985
986 // The offset of the return instruction precedes 'end' by a variable number
987 // of instructions due to out-of-line codegen.
988 uint32_t ret;
989 };
990
991 struct JitExitOffsets : CallableOffsets {
JitExitOffsetsJitExitOffsets992 MOZ_IMPLICIT JitExitOffsets()
993 : CallableOffsets(), untrustedFPStart(0), untrustedFPEnd(0) {}
994
995 // There are a few instructions in the Jit exit where FP may be trash
996 // (because it may have been clobbered by the JS Jit), known as the
997 // untrusted FP zone.
998 uint32_t untrustedFPStart;
999 uint32_t untrustedFPEnd;
1000 };
1001
1002 struct FuncOffsets : CallableOffsets {
FuncOffsetsFuncOffsets1003 MOZ_IMPLICIT FuncOffsets()
1004 : CallableOffsets(), uncheckedCallEntry(0), tierEntry(0) {}
1005
1006 // Function CodeRanges have a checked call entry which takes an extra
1007 // signature argument which is checked against the callee's signature before
1008 // falling through to the normal prologue. The checked call entry is thus at
1009 // the beginning of the CodeRange and the unchecked call entry is at some
1010 // offset after the checked call entry.
1011 uint32_t uncheckedCallEntry;
1012
1013 // The tierEntry is the point within a function to which the patching code
1014 // within a Tier-1 function jumps. It could be the instruction following
1015 // the jump in the Tier-1 function, or the point following the standard
1016 // prologue within a Tier-2 function.
1017 uint32_t tierEntry;
1018 };
1019
1020 using FuncOffsetsVector = Vector<FuncOffsets, 0, SystemAllocPolicy>;
1021
1022 // A CodeRange describes a single contiguous range of code within a wasm
1023 // module's code segment. A CodeRange describes what the code does and, for
1024 // function bodies, the name and source coordinates of the function.
1025
1026 class CodeRange {
1027 public:
1028 enum Kind {
1029 Function, // function definition
1030 InterpEntry, // calls into wasm from C++
1031 JitEntry, // calls into wasm from jit code
1032 ImportInterpExit, // slow-path calling from wasm into C++ interp
1033 ImportJitExit, // fast-path calling from wasm into jit code
1034 BuiltinThunk, // fast-path calling from wasm into a C++ native
1035 TrapExit, // calls C++ to report and jumps to throw stub
1036 DebugTrap, // calls C++ to handle debug event
1037 FarJumpIsland, // inserted to connect otherwise out-of-range insns
1038 Throw // special stack-unwinding stub jumped to by other stubs
1039 };
1040
1041 private:
1042 // All fields are treated as cacheable POD:
1043 uint32_t begin_;
1044 uint32_t ret_;
1045 uint32_t end_;
1046 union {
1047 struct {
1048 uint32_t funcIndex_;
1049 union {
1050 struct {
1051 uint32_t lineOrBytecode_;
1052 uint8_t beginToUncheckedCallEntry_;
1053 uint8_t beginToTierEntry_;
1054 } func;
1055 struct {
1056 uint16_t beginToUntrustedFPStart_;
1057 uint16_t beginToUntrustedFPEnd_;
1058 } jitExit;
1059 };
1060 };
1061 Trap trap_;
1062 } u;
1063 Kind kind_ : 8;
1064
1065 public:
1066 CodeRange() = default;
1067 CodeRange(Kind kind, Offsets offsets);
1068 CodeRange(Kind kind, uint32_t funcIndex, Offsets offsets);
1069 CodeRange(Kind kind, CallableOffsets offsets);
1070 CodeRange(Kind kind, uint32_t funcIndex, CallableOffsets);
1071 CodeRange(uint32_t funcIndex, JitExitOffsets offsets);
1072 CodeRange(uint32_t funcIndex, uint32_t lineOrBytecode, FuncOffsets offsets);
1073
offsetBy(uint32_t offset)1074 void offsetBy(uint32_t offset) {
1075 begin_ += offset;
1076 end_ += offset;
1077 if (hasReturn()) {
1078 ret_ += offset;
1079 }
1080 }
1081
1082 // All CodeRanges have a begin and end.
1083
begin()1084 uint32_t begin() const { return begin_; }
end()1085 uint32_t end() const { return end_; }
1086
1087 // Other fields are only available for certain CodeRange::Kinds.
1088
kind()1089 Kind kind() const { return kind_; }
1090
isFunction()1091 bool isFunction() const { return kind() == Function; }
isImportExit()1092 bool isImportExit() const {
1093 return kind() == ImportJitExit || kind() == ImportInterpExit ||
1094 kind() == BuiltinThunk;
1095 }
isImportInterpExit()1096 bool isImportInterpExit() const { return kind() == ImportInterpExit; }
isImportJitExit()1097 bool isImportJitExit() const { return kind() == ImportJitExit; }
isTrapExit()1098 bool isTrapExit() const { return kind() == TrapExit; }
isDebugTrap()1099 bool isDebugTrap() const { return kind() == DebugTrap; }
isThunk()1100 bool isThunk() const { return kind() == FarJumpIsland; }
1101
1102 // Function, import exits and trap exits have standard callable prologues
1103 // and epilogues. Asynchronous frame iteration needs to know the offset of
1104 // the return instruction to calculate the frame pointer.
1105
hasReturn()1106 bool hasReturn() const {
1107 return isFunction() || isImportExit() || isDebugTrap();
1108 }
ret()1109 uint32_t ret() const {
1110 MOZ_ASSERT(hasReturn());
1111 return ret_;
1112 }
1113
1114 // Functions, export stubs and import stubs all have an associated function
1115 // index.
1116
isJitEntry()1117 bool isJitEntry() const { return kind() == JitEntry; }
isInterpEntry()1118 bool isInterpEntry() const { return kind() == InterpEntry; }
isEntry()1119 bool isEntry() const { return isInterpEntry() || isJitEntry(); }
hasFuncIndex()1120 bool hasFuncIndex() const {
1121 return isFunction() || isImportExit() || isEntry();
1122 }
funcIndex()1123 uint32_t funcIndex() const {
1124 MOZ_ASSERT(hasFuncIndex());
1125 return u.funcIndex_;
1126 }
1127
1128 // TrapExit CodeRanges have a Trap field.
1129
trap()1130 Trap trap() const {
1131 MOZ_ASSERT(isTrapExit());
1132 return u.trap_;
1133 }
1134
1135 // Function CodeRanges have two entry points: one for normal calls (with a
1136 // known signature) and one for table calls (which involves dynamic
1137 // signature checking).
1138
funcCheckedCallEntry()1139 uint32_t funcCheckedCallEntry() const {
1140 MOZ_ASSERT(isFunction());
1141 return begin_;
1142 }
funcUncheckedCallEntry()1143 uint32_t funcUncheckedCallEntry() const {
1144 MOZ_ASSERT(isFunction());
1145 return begin_ + u.func.beginToUncheckedCallEntry_;
1146 }
funcTierEntry()1147 uint32_t funcTierEntry() const {
1148 MOZ_ASSERT(isFunction());
1149 return begin_ + u.func.beginToTierEntry_;
1150 }
funcLineOrBytecode()1151 uint32_t funcLineOrBytecode() const {
1152 MOZ_ASSERT(isFunction());
1153 return u.func.lineOrBytecode_;
1154 }
1155
1156 // ImportJitExit have a particular range where the value of FP can't be
1157 // trusted for profiling and thus must be ignored.
1158
jitExitUntrustedFPStart()1159 uint32_t jitExitUntrustedFPStart() const {
1160 MOZ_ASSERT(isImportJitExit());
1161 return begin_ + u.jitExit.beginToUntrustedFPStart_;
1162 }
jitExitUntrustedFPEnd()1163 uint32_t jitExitUntrustedFPEnd() const {
1164 MOZ_ASSERT(isImportJitExit());
1165 return begin_ + u.jitExit.beginToUntrustedFPEnd_;
1166 }
1167
1168 // A sorted array of CodeRanges can be looked up via BinarySearch and
1169 // OffsetInCode.
1170
1171 struct OffsetInCode {
1172 size_t offset;
OffsetInCodeOffsetInCode1173 explicit OffsetInCode(size_t offset) : offset(offset) {}
1174 bool operator==(const CodeRange& rhs) const {
1175 return offset >= rhs.begin() && offset < rhs.end();
1176 }
1177 bool operator<(const CodeRange& rhs) const { return offset < rhs.begin(); }
1178 };
1179 };
1180
1181 WASM_DECLARE_POD_VECTOR(CodeRange, CodeRangeVector)
1182
1183 extern const CodeRange* LookupInSorted(const CodeRangeVector& codeRanges,
1184 CodeRange::OffsetInCode target);
1185
1186 // While the frame-pointer chain allows the stack to be unwound without
1187 // metadata, Error.stack still needs to know the line/column of every call in
1188 // the chain. A CallSiteDesc describes a single callsite to which CallSite adds
1189 // the metadata necessary to walk up to the next frame. Lastly CallSiteAndTarget
1190 // adds the function index of the callee.
1191
1192 class CallSiteDesc {
1193 static constexpr size_t LINE_OR_BYTECODE_BITS_SIZE = 29;
1194 uint32_t lineOrBytecode_ : LINE_OR_BYTECODE_BITS_SIZE;
1195 uint32_t kind_ : 3;
1196
1197 public:
1198 static constexpr uint32_t MAX_LINE_OR_BYTECODE_VALUE =
1199 (1 << LINE_OR_BYTECODE_BITS_SIZE) - 1;
1200
1201 enum Kind {
1202 Func, // pc-relative call to a specific function
1203 Dynamic, // dynamic callee called via register
1204 Symbolic, // call to a single symbolic callee
1205 EnterFrame, // call to a enter frame handler
1206 LeaveFrame, // call to a leave frame handler
1207 Breakpoint // call to instruction breakpoint
1208 };
CallSiteDesc()1209 CallSiteDesc() : lineOrBytecode_(0), kind_(0) {}
CallSiteDesc(Kind kind)1210 explicit CallSiteDesc(Kind kind) : lineOrBytecode_(0), kind_(kind) {
1211 MOZ_ASSERT(kind == Kind(kind_));
1212 }
CallSiteDesc(uint32_t lineOrBytecode,Kind kind)1213 CallSiteDesc(uint32_t lineOrBytecode, Kind kind)
1214 : lineOrBytecode_(lineOrBytecode), kind_(kind) {
1215 MOZ_ASSERT(kind == Kind(kind_));
1216 MOZ_ASSERT(lineOrBytecode == lineOrBytecode_);
1217 }
lineOrBytecode()1218 uint32_t lineOrBytecode() const { return lineOrBytecode_; }
kind()1219 Kind kind() const { return Kind(kind_); }
mightBeCrossInstance()1220 bool mightBeCrossInstance() const { return kind() == CallSiteDesc::Dynamic; }
1221 };
1222
1223 class CallSite : public CallSiteDesc {
1224 uint32_t returnAddressOffset_;
1225
1226 public:
CallSite()1227 CallSite() : returnAddressOffset_(0) {}
1228
CallSite(CallSiteDesc desc,uint32_t returnAddressOffset)1229 CallSite(CallSiteDesc desc, uint32_t returnAddressOffset)
1230 : CallSiteDesc(desc), returnAddressOffset_(returnAddressOffset) {}
1231
offsetBy(int32_t delta)1232 void offsetBy(int32_t delta) { returnAddressOffset_ += delta; }
returnAddressOffset()1233 uint32_t returnAddressOffset() const { return returnAddressOffset_; }
1234 };
1235
WASM_DECLARE_POD_VECTOR(CallSite,CallSiteVector)1236 WASM_DECLARE_POD_VECTOR(CallSite, CallSiteVector)
1237
1238 // A CallSiteTarget describes the callee of a CallSite, either a function or a
1239 // trap exit. Although checked in debug builds, a CallSiteTarget doesn't
1240 // officially know whether it targets a function or trap, relying on the Kind of
1241 // the CallSite to discriminate.
1242
1243 class CallSiteTarget {
1244 uint32_t packed_;
1245 #ifdef DEBUG
1246 enum Kind { None, FuncIndex, TrapExit } kind_;
1247 #endif
1248
1249 public:
1250 explicit CallSiteTarget()
1251 : packed_(UINT32_MAX)
1252 #ifdef DEBUG
1253 ,
1254 kind_(None)
1255 #endif
1256 {
1257 }
1258
1259 explicit CallSiteTarget(uint32_t funcIndex)
1260 : packed_(funcIndex)
1261 #ifdef DEBUG
1262 ,
1263 kind_(FuncIndex)
1264 #endif
1265 {
1266 }
1267
1268 explicit CallSiteTarget(Trap trap)
1269 : packed_(uint32_t(trap))
1270 #ifdef DEBUG
1271 ,
1272 kind_(TrapExit)
1273 #endif
1274 {
1275 }
1276
1277 uint32_t funcIndex() const {
1278 MOZ_ASSERT(kind_ == FuncIndex);
1279 return packed_;
1280 }
1281
1282 Trap trap() const {
1283 MOZ_ASSERT(kind_ == TrapExit);
1284 MOZ_ASSERT(packed_ < uint32_t(Trap::Limit));
1285 return Trap(packed_);
1286 }
1287 };
1288
1289 using CallSiteTargetVector = Vector<CallSiteTarget, 0, SystemAllocPolicy>;
1290
1291 // WasmTryNotes are stored in a vector that acts as an exception table for
1292 // wasm try-catch blocks. These represent the information needed to take
1293 // exception handling actions after a throw is executed.
1294 struct WasmTryNote {
1295 explicit WasmTryNote(uint32_t begin = 0, uint32_t end = 0,
1296 uint32_t framePushed = 0)
beginWasmTryNote1297 : begin(begin), end(end), framePushed(framePushed) {}
1298
1299 uint32_t begin; // Begin code offset of try instructions.
1300 uint32_t end; // End code offset of try instructions.
1301 uint32_t entryPoint; // The offset of the landing pad.
1302 uint32_t framePushed; // Track offset from frame of stack pointer.
1303
offsetByWasmTryNote1304 void offsetBy(uint32_t offset) {
1305 begin += offset;
1306 end += offset;
1307 entryPoint += offset;
1308 }
1309
1310 bool operator<(const WasmTryNote& other) const {
1311 if (end == other.end) {
1312 return begin > other.begin;
1313 }
1314 return end < other.end;
1315 }
1316 };
1317
1318 WASM_DECLARE_POD_VECTOR(WasmTryNote, WasmTryNoteVector)
1319
1320 // Represents the resizable limits of memories and tables.
1321
1322 struct Limits {
1323 uint64_t initial;
1324 Maybe<uint64_t> maximum;
1325
1326 // `shared` is Shareable::False for tables but may be Shareable::True for
1327 // memories.
1328 Shareable shared;
1329
1330 Limits() = default;
1331 explicit Limits(uint64_t initial, const Maybe<uint64_t>& maximum = Nothing(),
1332 Shareable shared = Shareable::False)
initialLimits1333 : initial(initial), maximum(maximum), shared(shared) {}
1334 };
1335
1336 // Memories can be 32-bit (indices are 32 bits and the max is 4GB) or 64-bit
1337 // (indices are 64 bits and the max is XXX).
1338
1339 enum class MemoryKind { Memory32, Memory64 };
1340
1341 // MemoryDesc describes a memory.
1342
1343 struct MemoryDesc {
1344 MemoryKind kind;
1345 Limits limits;
1346
isSharedMemoryDesc1347 bool isShared() const { return limits.shared == Shareable::True; }
1348
1349 // Whether a backing store for this memory may move when grown.
canMovingGrowMemoryDesc1350 bool canMovingGrow() const { return limits.maximum.isNothing(); }
1351
1352 // Whether the bounds check limit (see the doc comment in
1353 // ArrayBufferObject.cpp regarding linear memory structure) can ever be
1354 // larger than 32-bits.
boundsCheckLimitIs32BitsMemoryDesc1355 bool boundsCheckLimitIs32Bits() const {
1356 return limits.maximum.isSome() &&
1357 limits.maximum.value() < (0x100000000 / PageSize);
1358 }
1359
1360 // The initial length of this memory in pages.
initialPagesMemoryDesc1361 Pages initialPages() const { return Pages(limits.initial); }
1362
1363 // The maximum length of this memory in pages.
maximumPagesMemoryDesc1364 Maybe<Pages> maximumPages() const {
1365 return limits.maximum.map([](uint64_t x) { return Pages(x); });
1366 }
1367
1368 // The initial length of this memory in bytes. Only valid for memory32.
initialLength32MemoryDesc1369 uint64_t initialLength32() const {
1370 MOZ_ASSERT(kind == MemoryKind::Memory32);
1371 // See static_assert after MemoryDesc for why this is safe.
1372 return limits.initial * PageSize;
1373 }
1374
1375 // The maximum length of this memory in bytes. Only valid for memory32.
maximumLength32MemoryDesc1376 Maybe<uint64_t> maximumLength32() const {
1377 MOZ_ASSERT(kind == MemoryKind::Memory32);
1378 if (limits.maximum) {
1379 // See static_assert after MemoryDesc for why this is safe.
1380 return Some(*limits.maximum * PageSize);
1381 }
1382 return Nothing();
1383 }
1384
1385 MemoryDesc() = default;
MemoryDescMemoryDesc1386 MemoryDesc(MemoryKind kind, Limits limits) : kind(kind), limits(limits) {}
1387 };
1388
1389 // We don't need to worry about overflow with a Memory32 field when
1390 // using a uint64_t.
1391 static_assert(MaxMemory32LimitField <= UINT64_MAX / PageSize);
1392
1393 // TableDesc describes a table as well as the offset of the table's base pointer
1394 // in global memory.
1395 //
1396 // A TableDesc contains the element type and whether the table is for asm.js,
1397 // which determines the table representation.
1398 // - ExternRef: a wasm anyref word (wasm::AnyRef)
1399 // - FuncRef: a two-word FunctionTableElem (wasm indirect call ABI)
1400 // - FuncRef (if `isAsmJS`): a two-word FunctionTableElem (asm.js ABI)
1401 // Eventually there should be a single unified AnyRef representation.
1402
1403 struct TableDesc {
1404 RefType elemType;
1405 bool importedOrExported;
1406 bool isAsmJS;
1407 uint32_t globalDataOffset;
1408 uint32_t initialLength;
1409 Maybe<uint32_t> maximumLength;
1410
1411 TableDesc() = default;
1412 TableDesc(RefType elemType, uint32_t initialLength,
1413 Maybe<uint32_t> maximumLength, bool isAsmJS,
1414 bool importedOrExported = false)
elemTypeTableDesc1415 : elemType(elemType),
1416 importedOrExported(importedOrExported),
1417 isAsmJS(isAsmJS),
1418 globalDataOffset(UINT32_MAX),
1419 initialLength(initialLength),
1420 maximumLength(maximumLength) {}
1421 };
1422
1423 using TableDescVector = Vector<TableDesc, 0, SystemAllocPolicy>;
1424
1425 // CalleeDesc describes how to compile one of the variety of asm.js/wasm calls.
1426 // This is hoisted into WasmTypes.h for sharing between Ion and Baseline.
1427
1428 class CalleeDesc {
1429 public:
1430 enum Which {
1431 // Calls a function defined in the same module by its index.
1432 Func,
1433
1434 // Calls the import identified by the offset of its FuncImportTls in
1435 // thread-local data.
1436 Import,
1437
1438 // Calls a WebAssembly table (heterogeneous, index must be bounds
1439 // checked, callee instance depends on TableDesc).
1440 WasmTable,
1441
1442 // Calls an asm.js table (homogeneous, masked index, same-instance).
1443 AsmJSTable,
1444
1445 // Call a C++ function identified by SymbolicAddress.
1446 Builtin,
1447
1448 // Like Builtin, but automatically passes Instance* as first argument.
1449 BuiltinInstanceMethod
1450 };
1451
1452 private:
1453 // which_ shall be initialized in the static constructors
1454 MOZ_INIT_OUTSIDE_CTOR Which which_;
1455 union U {
U()1456 U() : funcIndex_(0) {}
1457 uint32_t funcIndex_;
1458 struct {
1459 uint32_t globalDataOffset_;
1460 } import;
1461 struct {
1462 uint32_t globalDataOffset_;
1463 uint32_t minLength_;
1464 TypeIdDesc funcTypeId_;
1465 } table;
1466 SymbolicAddress builtin_;
1467 } u;
1468
1469 public:
1470 CalleeDesc() = default;
function(uint32_t funcIndex)1471 static CalleeDesc function(uint32_t funcIndex) {
1472 CalleeDesc c;
1473 c.which_ = Func;
1474 c.u.funcIndex_ = funcIndex;
1475 return c;
1476 }
import(uint32_t globalDataOffset)1477 static CalleeDesc import(uint32_t globalDataOffset) {
1478 CalleeDesc c;
1479 c.which_ = Import;
1480 c.u.import.globalDataOffset_ = globalDataOffset;
1481 return c;
1482 }
wasmTable(const TableDesc & desc,TypeIdDesc funcTypeId)1483 static CalleeDesc wasmTable(const TableDesc& desc, TypeIdDesc funcTypeId) {
1484 CalleeDesc c;
1485 c.which_ = WasmTable;
1486 c.u.table.globalDataOffset_ = desc.globalDataOffset;
1487 c.u.table.minLength_ = desc.initialLength;
1488 c.u.table.funcTypeId_ = funcTypeId;
1489 return c;
1490 }
asmJSTable(const TableDesc & desc)1491 static CalleeDesc asmJSTable(const TableDesc& desc) {
1492 CalleeDesc c;
1493 c.which_ = AsmJSTable;
1494 c.u.table.globalDataOffset_ = desc.globalDataOffset;
1495 return c;
1496 }
builtin(SymbolicAddress callee)1497 static CalleeDesc builtin(SymbolicAddress callee) {
1498 CalleeDesc c;
1499 c.which_ = Builtin;
1500 c.u.builtin_ = callee;
1501 return c;
1502 }
builtinInstanceMethod(SymbolicAddress callee)1503 static CalleeDesc builtinInstanceMethod(SymbolicAddress callee) {
1504 CalleeDesc c;
1505 c.which_ = BuiltinInstanceMethod;
1506 c.u.builtin_ = callee;
1507 return c;
1508 }
which()1509 Which which() const { return which_; }
funcIndex()1510 uint32_t funcIndex() const {
1511 MOZ_ASSERT(which_ == Func);
1512 return u.funcIndex_;
1513 }
importGlobalDataOffset()1514 uint32_t importGlobalDataOffset() const {
1515 MOZ_ASSERT(which_ == Import);
1516 return u.import.globalDataOffset_;
1517 }
isTable()1518 bool isTable() const { return which_ == WasmTable || which_ == AsmJSTable; }
tableLengthGlobalDataOffset()1519 uint32_t tableLengthGlobalDataOffset() const {
1520 MOZ_ASSERT(isTable());
1521 return u.table.globalDataOffset_ + offsetof(TableTls, length);
1522 }
tableFunctionBaseGlobalDataOffset()1523 uint32_t tableFunctionBaseGlobalDataOffset() const {
1524 MOZ_ASSERT(isTable());
1525 return u.table.globalDataOffset_ + offsetof(TableTls, functionBase);
1526 }
wasmTableSigId()1527 TypeIdDesc wasmTableSigId() const {
1528 MOZ_ASSERT(which_ == WasmTable);
1529 return u.table.funcTypeId_;
1530 }
wasmTableMinLength()1531 uint32_t wasmTableMinLength() const {
1532 MOZ_ASSERT(which_ == WasmTable);
1533 return u.table.minLength_;
1534 }
builtin()1535 SymbolicAddress builtin() const {
1536 MOZ_ASSERT(which_ == Builtin || which_ == BuiltinInstanceMethod);
1537 return u.builtin_;
1538 }
1539 };
1540
1541 // Because ARM has a fixed-width instruction encoding, ARM can only express a
1542 // limited subset of immediates (in a single instruction).
1543
1544 static const uint64_t HighestValidARMImmediate = 0xff000000;
1545
1546 extern bool IsValidARMImmediate(uint32_t i);
1547
1548 extern uint64_t RoundUpToNextValidARMImmediate(uint64_t i);
1549
1550 // Bounds checks always compare the base of the memory access with the bounds
1551 // check limit. If the memory access is unaligned, this means that, even if the
1552 // bounds check succeeds, a few bytes of the access can extend past the end of
1553 // memory. To guard against this, extra space is included in the guard region to
1554 // catch the overflow. MaxMemoryAccessSize is a conservative approximation of
1555 // the maximum guard space needed to catch all unaligned overflows.
1556
1557 static const unsigned MaxMemoryAccessSize = LitVal::sizeofLargestValue();
1558
1559 #ifdef WASM_SUPPORTS_HUGE_MEMORY
1560
1561 // On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly 32-bit
1562 // memory unconditionally allocates a huge region of virtual memory of size
1563 // wasm::HugeMappedSize. This allows all memory resizing to work without
1564 // reallocation and provides enough guard space for all offsets to be folded
1565 // into memory accesses.
1566
1567 static const uint64_t HugeIndexRange = uint64_t(UINT32_MAX) + 1;
1568 static const uint64_t HugeOffsetGuardLimit = uint64_t(INT32_MAX) + 1;
1569 static const uint64_t HugeUnalignedGuardPage = PageSize;
1570 static const uint64_t HugeMappedSize =
1571 HugeIndexRange + HugeOffsetGuardLimit + HugeUnalignedGuardPage;
1572
1573 static_assert(MaxMemoryAccessSize <= HugeUnalignedGuardPage,
1574 "rounded up to static page size");
1575 static_assert(HugeOffsetGuardLimit < UINT32_MAX,
1576 "checking for overflow against OffsetGuardLimit is enough.");
1577
1578 #endif
1579
1580 // On !WASM_SUPPORTS_HUGE_MEMORY platforms:
1581 // - To avoid OOM in ArrayBuffer::prepareForAsmJS, asm.js continues to use the
1582 // original ArrayBuffer allocation which has no guard region at all.
1583 // - For WebAssembly memories, an additional GuardSize is mapped after the
1584 // accessible region of the memory to catch folded (base+offset) accesses
1585 // where `offset < OffsetGuardLimit` as well as the overflow from unaligned
1586 // accesses, as described above for MaxMemoryAccessSize.
1587
1588 static const size_t OffsetGuardLimit = PageSize - MaxMemoryAccessSize;
1589 static const size_t GuardSize = PageSize;
1590
1591 static_assert(MaxMemoryAccessSize < GuardSize,
1592 "Guard page handles partial out-of-bounds");
1593 static_assert(OffsetGuardLimit < UINT32_MAX,
1594 "checking for overflow against OffsetGuardLimit is enough.");
1595
GetMaxOffsetGuardLimit(bool hugeMemory)1596 static constexpr size_t GetMaxOffsetGuardLimit(bool hugeMemory) {
1597 #ifdef WASM_SUPPORTS_HUGE_MEMORY
1598 return hugeMemory ? HugeOffsetGuardLimit : OffsetGuardLimit;
1599 #else
1600 return OffsetGuardLimit;
1601 #endif
1602 }
1603
1604 static const size_t MinOffsetGuardLimit = OffsetGuardLimit;
1605
1606 // Return whether the given immediate satisfies the constraints of the platform
1607 // (viz. that, on ARM, IsValidARMImmediate).
1608
1609 extern bool IsValidBoundsCheckImmediate(uint32_t i);
1610
1611 // For a given WebAssembly/asm.js max pages, return the number of bytes to
1612 // map which will necessarily be a multiple of the system page size and greater
1613 // than maxPages in bytes. For a returned mappedSize:
1614 // boundsCheckLimit = mappedSize - GuardSize
1615 // IsValidBoundsCheckImmediate(boundsCheckLimit)
1616
1617 extern size_t ComputeMappedSize(Pages maxPages);
1618
1619 // The following thresholds were derived from a microbenchmark. If we begin to
1620 // ship this optimization for more platforms, we will need to extend this list.
1621
1622 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
1623 static const uint32_t MaxInlineMemoryCopyLength = 64;
1624 static const uint32_t MaxInlineMemoryFillLength = 64;
1625 #elif defined(JS_CODEGEN_X86)
1626 static const uint32_t MaxInlineMemoryCopyLength = 32;
1627 static const uint32_t MaxInlineMemoryFillLength = 32;
1628 #else
1629 static const uint32_t MaxInlineMemoryCopyLength = 0;
1630 static const uint32_t MaxInlineMemoryFillLength = 0;
1631 #endif
1632
1633 static_assert(MaxInlineMemoryCopyLength < MinOffsetGuardLimit, "precondition");
1634 static_assert(MaxInlineMemoryFillLength < MinOffsetGuardLimit, "precondition");
1635
1636 // Verbose logging support.
1637
1638 extern void Log(JSContext* cx, const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
1639
1640 // Codegen debug support.
1641
1642 enum class DebugChannel {
1643 Function,
1644 Import,
1645 };
1646
1647 #ifdef WASM_CODEGEN_DEBUG
1648 bool IsCodegenDebugEnabled(DebugChannel channel);
1649 #endif
1650
1651 void DebugCodegen(DebugChannel channel, const char* fmt, ...)
1652 MOZ_FORMAT_PRINTF(2, 3);
1653
1654 using PrintCallback = void (*)(const char*);
1655
1656 #ifdef ENABLE_WASM_SIMD_WORMHOLE
1657 bool IsWormholeTrigger(const V128& shuffleMask);
1658 jit::SimdConstant WormholeSignature();
1659 #endif
1660
1661 } // namespace wasm
1662 } // namespace js
1663
1664 #endif // wasm_types_h
1665