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