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 2021 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_module_types_h
20 #define wasm_module_types_h
21 
22 #include "mozilla/RefPtr.h"
23 
24 #include "js/AllocPolicy.h"
25 #include "js/RefCounted.h"
26 #include "js/Utility.h"
27 #include "js/Vector.h"
28 
29 #include "wasm/WasmCompileArgs.h"
30 #include "wasm/WasmConstants.h"
31 #include "wasm/WasmExprType.h"
32 #include "wasm/WasmInitExpr.h"
33 #include "wasm/WasmMemory.h"
34 #include "wasm/WasmSerialize.h"
35 #include "wasm/WasmShareable.h"
36 #include "wasm/WasmTypeDecls.h"
37 #include "wasm/WasmValType.h"
38 #include "wasm/WasmValue.h"
39 
40 namespace js {
41 namespace wasm {
42 
43 using mozilla::Maybe;
44 using mozilla::Nothing;
45 
46 class FuncType;
47 class TypeIdDesc;
48 
49 // A Module can either be asm.js or wasm.
50 
51 enum ModuleKind { Wasm, AsmJS };
52 
53 // CacheableChars is used to cacheably store UniqueChars.
54 
55 struct CacheableChars : UniqueChars {
56   CacheableChars() = default;
CacheableCharsCacheableChars57   explicit CacheableChars(char* ptr) : UniqueChars(ptr) {}
CacheableCharsCacheableChars58   MOZ_IMPLICIT CacheableChars(UniqueChars&& rhs)
59       : UniqueChars(std::move(rhs)) {}
60   WASM_DECLARE_SERIALIZABLE(CacheableChars)
61 };
62 
63 using CacheableCharsVector = Vector<CacheableChars, 0, SystemAllocPolicy>;
64 
65 // Import describes a single wasm import. An ImportVector describes all
66 // of a single module's imports.
67 //
68 // ImportVector is built incrementally by ModuleGenerator and then stored
69 // immutably by Module.
70 
71 struct Import {
72   CacheableChars module;
73   CacheableChars field;
74   DefinitionKind kind;
75 
76   Import() = default;
ImportImport77   Import(UniqueChars&& module, UniqueChars&& field, DefinitionKind kind)
78       : module(std::move(module)), field(std::move(field)), kind(kind) {}
79 
80   WASM_DECLARE_SERIALIZABLE(Import)
81 };
82 
83 using ImportVector = Vector<Import, 0, SystemAllocPolicy>;
84 
85 // Export describes the export of a definition in a Module to a field in the
86 // export object. The Export stores the index of the exported item in the
87 // appropriate type-specific module data structure (function table, global
88 // table, table table, and - eventually - memory table).
89 //
90 // Note a single definition can be exported by multiple Exports in the
91 // ExportVector.
92 //
93 // ExportVector is built incrementally by ModuleGenerator and then stored
94 // immutably by Module.
95 
96 class Export {
97   CacheableChars fieldName_;
98   struct CacheablePod {
99     DefinitionKind kind_;
100     uint32_t index_;
101   } pod;
102 
103  public:
104   Export() = default;
105   explicit Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind);
106   explicit Export(UniqueChars fieldName, DefinitionKind kind);
107 
fieldName()108   const char* fieldName() const { return fieldName_.get(); }
109 
kind()110   DefinitionKind kind() const { return pod.kind_; }
111   uint32_t funcIndex() const;
112 #ifdef ENABLE_WASM_EXCEPTIONS
113   uint32_t tagIndex() const;
114 #endif
115   uint32_t globalIndex() const;
116   uint32_t tableIndex() const;
117 
118   WASM_DECLARE_SERIALIZABLE(Export)
119 };
120 
121 using ExportVector = Vector<Export, 0, SystemAllocPolicy>;
122 
123 // FuncFlags provides metadata for a function definition.
124 
125 enum class FuncFlags : uint8_t {
126   None = 0x0,
127   // The function maybe be accessible by JS and needs thunks generated for it.
128   // See `[SMDOC] Exported wasm functions and the jit-entry stubs` in
129   // WasmJS.cpp for more information.
130   Exported = 0x1,
131   // The function should have thunks generated upon instantiation, not upon
132   // first call. May only be set if `Exported` is set.
133   Eager = 0x2,
134   // The function can be the target of a ref.func instruction in the code
135   // section. May only be set if `Exported` is set.
136   CanRefFunc = 0x4,
137 };
138 
139 // A FuncDesc describes a single function definition.
140 
141 struct FuncDesc {
142   FuncType* type;
143   TypeIdDesc* typeId;
144   // Bit pack to keep this struct small on 32-bit systems
145   uint32_t typeIndex : 24;
146   FuncFlags flags : 8;
147 
148   // Assert that the bit packing scheme is viable
149   static_assert(MaxTypes <= (1 << 24) - 1);
150   static_assert(sizeof(FuncFlags) == sizeof(uint8_t));
151 
152   FuncDesc() = default;
FuncDescFuncDesc153   FuncDesc(FuncType* type, TypeIdDesc* typeId, uint32_t typeIndex)
154       : type(type),
155         typeId(typeId),
156         typeIndex(typeIndex),
157         flags(FuncFlags::None) {}
158 
isExportedFuncDesc159   bool isExported() const {
160     return uint8_t(flags) & uint8_t(FuncFlags::Exported);
161   }
isEagerFuncDesc162   bool isEager() const { return uint8_t(flags) & uint8_t(FuncFlags::Eager); }
canRefFuncFuncDesc163   bool canRefFunc() const {
164     return uint8_t(flags) & uint8_t(FuncFlags::CanRefFunc);
165   }
166 };
167 
168 using FuncDescVector = Vector<FuncDesc, 0, SystemAllocPolicy>;
169 
170 // A GlobalDesc describes a single global variable.
171 //
172 // wasm can import and export mutable and immutable globals.
173 //
174 // asm.js can import mutable and immutable globals, but a mutable global has a
175 // location that is private to the module, and its initial value is copied into
176 // that cell from the environment.  asm.js cannot export globals.
177 
178 enum class GlobalKind { Import, Constant, Variable };
179 
180 class GlobalDesc {
181   GlobalKind kind_;
182   // Stores the value type of this global for all kinds, and the initializer
183   // expression when `constant` or `variable`.
184   InitExpr initial_;
185   // Metadata for the global when `variable` or `import`.
186   unsigned offset_;
187   bool isMutable_;
188   bool isWasm_;
189   bool isExport_;
190   // Metadata for the global when `import`.
191   uint32_t importIndex_;
192 
193   // Private, as they have unusual semantics.
194 
isExport()195   bool isExport() const { return !isConstant() && isExport_; }
isWasm()196   bool isWasm() const { return !isConstant() && isWasm_; }
197 
198  public:
199   GlobalDesc() = default;
200 
201   explicit GlobalDesc(InitExpr&& initial, bool isMutable,
202                       ModuleKind kind = ModuleKind::Wasm)
203       : kind_((isMutable || !initial.isLiteral()) ? GlobalKind::Variable
204                                                   : GlobalKind::Constant) {
205     initial_ = std::move(initial);
206     if (isVariable()) {
207       isMutable_ = isMutable;
208       isWasm_ = kind == Wasm;
209       isExport_ = false;
210       offset_ = UINT32_MAX;
211     }
212   }
213 
214   explicit GlobalDesc(ValType type, bool isMutable, uint32_t importIndex,
215                       ModuleKind kind = ModuleKind::Wasm)
kind_(GlobalKind::Import)216       : kind_(GlobalKind::Import) {
217     initial_ = InitExpr(LitVal(type));
218     importIndex_ = importIndex;
219     isMutable_ = isMutable;
220     isWasm_ = kind == Wasm;
221     isExport_ = false;
222     offset_ = UINT32_MAX;
223   }
224 
setOffset(unsigned offset)225   void setOffset(unsigned offset) {
226     MOZ_ASSERT(!isConstant());
227     MOZ_ASSERT(offset_ == UINT32_MAX);
228     offset_ = offset;
229   }
offset()230   unsigned offset() const {
231     MOZ_ASSERT(!isConstant());
232     MOZ_ASSERT(offset_ != UINT32_MAX);
233     return offset_;
234   }
235 
setIsExport()236   void setIsExport() {
237     if (!isConstant()) {
238       isExport_ = true;
239     }
240   }
241 
kind()242   GlobalKind kind() const { return kind_; }
isVariable()243   bool isVariable() const { return kind_ == GlobalKind::Variable; }
isConstant()244   bool isConstant() const { return kind_ == GlobalKind::Constant; }
isImport()245   bool isImport() const { return kind_ == GlobalKind::Import; }
246 
isMutable()247   bool isMutable() const { return !isConstant() && isMutable_; }
initExpr()248   const InitExpr& initExpr() const {
249     MOZ_ASSERT(!isImport());
250     return initial_;
251   }
importIndex()252   uint32_t importIndex() const {
253     MOZ_ASSERT(isImport());
254     return importIndex_;
255   }
256 
constantValue()257   LitVal constantValue() const { return initial_.literal(); }
258 
259   // If isIndirect() is true then storage for the value is not in the
260   // instance's global area, but in a WasmGlobalObject::Cell hanging off a
261   // WasmGlobalObject; the global area contains a pointer to the Cell.
262   //
263   // We don't want to indirect unless we must, so only mutable, exposed
264   // globals are indirected - in all other cases we copy values into and out
265   // of their module.
266   //
267   // Note that isIndirect() isn't equivalent to getting a WasmGlobalObject:
268   // an immutable exported global will still get an object, but will not be
269   // indirect.
isIndirect()270   bool isIndirect() const {
271     return isMutable() && isWasm() && (isImport() || isExport());
272   }
273 
type()274   ValType type() const { return initial_.type(); }
275 
276   WASM_DECLARE_SERIALIZABLE(GlobalDesc)
277 };
278 
279 using GlobalDescVector = Vector<GlobalDesc, 0, SystemAllocPolicy>;
280 
281 // A TagDesc represents fresh per-instance tags that are used for the
282 // exception handling proposal and potentially other future proposals.
283 
284 // The TagOffsetVector represents the offsets in the layout of the
285 // data buffer stored in a Wasm exception.
286 using TagOffsetVector = Vector<uint32_t, 0, SystemAllocPolicy>;
287 
288 // Not guarded by #ifdef like TagDesc as this is required for Wasm JS
289 // API classes in WasmJS.h.
290 struct TagType : AtomicRefCounted<TagType> {
291   ValTypeVector argTypes_;
292   TagOffsetVector argOffsets_;
293   uint32_t size_;
294 
TagTypeTagType295   TagType() : size_(0) {}
296 
resultTypeTagType297   ResultType resultType() const { return ResultType::Vector(argTypes_); }
298 
299   [[nodiscard]] bool initialize(ValTypeVector&& argTypes);
300 
cloneTagType301   [[nodiscard]] bool clone(const TagType& src) {
302     MOZ_ASSERT(argTypes_.empty() && argOffsets_.empty() && size_ == 0);
303     if (!argTypes_.appendAll(src.argTypes_) ||
304         !argOffsets_.appendAll(src.argOffsets_)) {
305       return false;
306     }
307     size_ = src.size_;
308     return true;
309   }
310 
311   WASM_DECLARE_SERIALIZABLE(TagType)
312 };
313 
314 using MutableTagType = RefPtr<TagType>;
315 using SharedTagType = SerializableRefPtr<const TagType>;
316 
317 #ifdef ENABLE_WASM_EXCEPTIONS
318 struct TagDesc {
319   TagKind kind;
320   SharedTagType type;
321   uint32_t globalDataOffset;
322   bool isExport;
323 
TagDescTagDesc324   TagDesc() : globalDataOffset(UINT32_MAX), isExport(false) {}
325   TagDesc(TagKind kind, const SharedTagType& type, bool isExport = false)
kindTagDesc326       : kind(kind),
327         type(type),
328         globalDataOffset(UINT32_MAX),
329         isExport(isExport) {}
330 
331   WASM_DECLARE_SERIALIZABLE(TagDesc)
332 };
333 
334 using TagDescVector = Vector<TagDesc, 0, SystemAllocPolicy>;
335 #endif
336 
337 // When a ElemSegment is "passive" it is shared between a wasm::Module and its
338 // wasm::Instances. To allow each segment to be released as soon as the last
339 // Instance elem.drops it and the Module is destroyed, each ElemSegment is
340 // individually atomically ref-counted.
341 
342 struct ElemSegment : AtomicRefCounted<ElemSegment> {
343   enum class Kind {
344     Active,
345     Passive,
346     Declared,
347   };
348 
349   Kind kind;
350   uint32_t tableIndex;
351   RefType elemType;
352   Maybe<InitExpr> offsetIfActive;
353   Uint32Vector elemFuncIndices;  // Element may be NullFuncIndex
354 
activeElemSegment355   bool active() const { return kind == Kind::Active; }
356 
offsetElemSegment357   const InitExpr& offset() const { return *offsetIfActive; }
358 
lengthElemSegment359   size_t length() const { return elemFuncIndices.length(); }
360 
361   WASM_DECLARE_SERIALIZABLE(ElemSegment)
362 };
363 
364 // NullFuncIndex represents the case when an element segment (of type funcref)
365 // contains a null element.
366 constexpr uint32_t NullFuncIndex = UINT32_MAX;
367 static_assert(NullFuncIndex > MaxFuncs, "Invariant");
368 
369 using MutableElemSegment = RefPtr<ElemSegment>;
370 using SharedElemSegment = SerializableRefPtr<const ElemSegment>;
371 using ElemSegmentVector = Vector<SharedElemSegment, 0, SystemAllocPolicy>;
372 
373 // DataSegmentEnv holds the initial results of decoding a data segment from the
374 // bytecode and is stored in the ModuleEnvironment during compilation. When
375 // compilation completes, (non-Env) DataSegments are created and stored in
376 // the wasm::Module which contain copies of the data segment payload. This
377 // allows non-compilation uses of wasm validation to avoid expensive copies.
378 //
379 // When a DataSegment is "passive" it is shared between a wasm::Module and its
380 // wasm::Instances. To allow each segment to be released as soon as the last
381 // Instance mem.drops it and the Module is destroyed, each DataSegment is
382 // individually atomically ref-counted.
383 
384 struct DataSegmentEnv {
385   Maybe<InitExpr> offsetIfActive;
386   uint32_t bytecodeOffset;
387   uint32_t length;
388 };
389 
390 using DataSegmentEnvVector = Vector<DataSegmentEnv, 0, SystemAllocPolicy>;
391 
392 struct DataSegment : AtomicRefCounted<DataSegment> {
393   Maybe<InitExpr> offsetIfActive;
394   Bytes bytes;
395 
396   DataSegment() = default;
397 
activeDataSegment398   bool active() const { return !!offsetIfActive; }
399 
offsetDataSegment400   const InitExpr& offset() const { return *offsetIfActive; }
401 
initDataSegment402   [[nodiscard]] bool init(const ShareableBytes& bytecode,
403                           const DataSegmentEnv& src) {
404     if (src.offsetIfActive) {
405       offsetIfActive.emplace();
406       if (!offsetIfActive->clone(*src.offsetIfActive)) {
407         return false;
408       }
409     }
410     return bytes.append(bytecode.begin() + src.bytecodeOffset, src.length);
411   }
412 
413   WASM_DECLARE_SERIALIZABLE(DataSegment)
414 };
415 
416 using MutableDataSegment = RefPtr<DataSegment>;
417 using SharedDataSegment = SerializableRefPtr<const DataSegment>;
418 using DataSegmentVector = Vector<SharedDataSegment, 0, SystemAllocPolicy>;
419 
420 // The CustomSection(Env) structs are like DataSegment(Env): CustomSectionEnv is
421 // stored in the ModuleEnvironment and CustomSection holds a copy of the payload
422 // and is stored in the wasm::Module.
423 
424 struct CustomSectionEnv {
425   uint32_t nameOffset;
426   uint32_t nameLength;
427   uint32_t payloadOffset;
428   uint32_t payloadLength;
429 };
430 
431 using CustomSectionEnvVector = Vector<CustomSectionEnv, 0, SystemAllocPolicy>;
432 
433 struct CustomSection {
434   Bytes name;
435   SharedBytes payload;
436 
437   WASM_DECLARE_SERIALIZABLE(CustomSection)
438 };
439 
440 using CustomSectionVector = Vector<CustomSection, 0, SystemAllocPolicy>;
441 
442 // A Name represents a string of utf8 chars embedded within the name custom
443 // section. The offset of a name is expressed relative to the beginning of the
444 // name section's payload so that Names can stored in wasm::Code, which only
445 // holds the name section's bytes, not the whole bytecode.
446 
447 struct Name {
448   // All fields are treated as cacheable POD:
449   uint32_t offsetInNamePayload;
450   uint32_t length;
451 
NameName452   Name() : offsetInNamePayload(UINT32_MAX), length(0) {}
453 };
454 
455 using NameVector = Vector<Name, 0, SystemAllocPolicy>;
456 
457 // The kind of limits to decode or convert from JS.
458 
459 enum class LimitsKind {
460   Memory,
461   Table,
462 };
463 
464 // Represents the resizable limits of memories and tables.
465 
466 struct Limits {
467   // `indexType` will always be I32 for tables, but may be I64 for memories
468   // when memory64 is enabled.
469   IndexType indexType;
470 
471   // The initial and maximum limit. The unit is pages for memories and elements
472   // for tables.
473   uint64_t initial;
474   Maybe<uint64_t> maximum;
475 
476   // `shared` is Shareable::False for tables but may be Shareable::True for
477   // memories.
478   Shareable shared;
479 
480   Limits() = default;
481   explicit Limits(uint64_t initial, const Maybe<uint64_t>& maximum = Nothing(),
482                   Shareable shared = Shareable::False)
indexTypeLimits483       : indexType(IndexType::I32),
484         initial(initial),
485         maximum(maximum),
486         shared(shared) {}
487 };
488 
489 // MemoryDesc describes a memory.
490 
491 struct MemoryDesc {
492   Limits limits;
493 
isSharedMemoryDesc494   bool isShared() const { return limits.shared == Shareable::True; }
495 
496   // Whether a backing store for this memory may move when grown.
canMovingGrowMemoryDesc497   bool canMovingGrow() const { return limits.maximum.isNothing(); }
498 
499   // Whether the bounds check limit (see the doc comment in
500   // ArrayBufferObject.cpp regarding linear memory structure) can ever be
501   // larger than 32-bits.
boundsCheckLimitIs32BitsMemoryDesc502   bool boundsCheckLimitIs32Bits() const {
503     return limits.maximum.isSome() &&
504            limits.maximum.value() < (0x100000000 / PageSize);
505   }
506 
indexTypeMemoryDesc507   IndexType indexType() const { return limits.indexType; }
508 
509   // The initial length of this memory in pages.
initialPagesMemoryDesc510   Pages initialPages() const { return Pages(limits.initial); }
511 
512   // The maximum length of this memory in pages.
maximumPagesMemoryDesc513   Maybe<Pages> maximumPages() const {
514     return limits.maximum.map([](uint64_t x) { return Pages(x); });
515   }
516 
517   // The initial length of this memory in bytes. Only valid for memory32.
initialLength32MemoryDesc518   uint64_t initialLength32() const {
519     MOZ_ASSERT(indexType() == IndexType::I32);
520     // See static_assert after MemoryDesc for why this is safe.
521     return limits.initial * PageSize;
522   }
523 
initialLength64MemoryDesc524   uint64_t initialLength64() const {
525     MOZ_ASSERT(indexType() == IndexType::I64);
526     return limits.initial * PageSize;
527   }
528 
529   MemoryDesc() = default;
MemoryDescMemoryDesc530   explicit MemoryDesc(Limits limits) : limits(limits) {}
531 };
532 
533 // We don't need to worry about overflow with a Memory32 field when
534 // using a uint64_t.
535 static_assert(MaxMemory32LimitField <= UINT64_MAX / PageSize);
536 
537 // TableDesc describes a table as well as the offset of the table's base pointer
538 // in global memory.
539 //
540 // A TableDesc contains the element type and whether the table is for asm.js,
541 // which determines the table representation.
542 //  - ExternRef: a wasm anyref word (wasm::AnyRef)
543 //  - FuncRef: a two-word FunctionTableElem (wasm indirect call ABI)
544 //  - FuncRef (if `isAsmJS`): a two-word FunctionTableElem (asm.js ABI)
545 // Eventually there should be a single unified AnyRef representation.
546 
547 struct TableDesc {
548   RefType elemType;
549   bool isImportedOrExported;
550   bool isAsmJS;
551   uint32_t globalDataOffset;
552   uint32_t initialLength;
553   Maybe<uint32_t> maximumLength;
554 
555   TableDesc() = default;
556   TableDesc(RefType elemType, uint32_t initialLength,
557             Maybe<uint32_t> maximumLength, bool isAsmJS,
558             bool isImportedOrExported = false)
elemTypeTableDesc559       : elemType(elemType),
560         isImportedOrExported(isImportedOrExported),
561         isAsmJS(isAsmJS),
562         globalDataOffset(UINT32_MAX),
563         initialLength(initialLength),
564         maximumLength(maximumLength) {}
565 };
566 
567 using TableDescVector = Vector<TableDesc, 0, SystemAllocPolicy>;
568 
569 }  // namespace wasm
570 }  // namespace js
571 
572 #endif  // wasm_module_types_h
573