| /* | |
| * Copyright 2015 WebAssembly Community Group participants | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| // | |
| // wasm.h: Define Binaryen IR, a representation for WebAssembly, with | |
| // all core parts in one simple header file. | |
| // | |
| // For more overview, see README.md | |
| // | |
| #ifndef wasm_wasm_h | |
| #define wasm_wasm_h | |
| #include <algorithm> | |
| #include <array> | |
| #include <cassert> | |
| #include <limits> | |
| #include <map> | |
| #include <optional> | |
| #include <ostream> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <vector> | |
| #include "ir/import-names.h" | |
| #include "literal.h" | |
| #include "support/index.h" | |
| #include "support/mixed_arena.h" | |
| #include "support/name.h" | |
| #include "wasm-features.h" | |
| #include "wasm-type.h" | |
| namespace wasm { | |
| class Module; | |
| // An index in a wasm module | |
| using Index = uint32_t; | |
| // An address in linear memory. | |
| struct Address { | |
| using address32_t = uint32_t; | |
| using address64_t = uint64_t; | |
| address64_t addr; | |
| constexpr Address() : addr(0) {} | |
| constexpr Address(uint64_t a) : addr(a) {} | |
| Address& operator=(uint64_t a) { | |
| addr = a; | |
| return *this; | |
| } | |
| operator address64_t() const { return addr; } | |
| Address& operator++(int) { | |
| ++addr; | |
| return *this; | |
| } | |
| }; | |
| enum class MemoryOrder : uint8_t { | |
| Unordered, | |
| SeqCst, | |
| AcqRel, | |
| }; | |
| enum class IRProfile { Normal, Poppy }; | |
| // Operators | |
| enum UnaryOp { | |
| // int | |
| ClzInt32, | |
| ClzInt64, | |
| CtzInt32, | |
| CtzInt64, | |
| PopcntInt32, | |
| PopcntInt64, | |
| // float | |
| NegFloat32, | |
| NegFloat64, | |
| AbsFloat32, | |
| AbsFloat64, | |
| CeilFloat32, | |
| CeilFloat64, | |
| FloorFloat32, | |
| FloorFloat64, | |
| TruncFloat32, | |
| TruncFloat64, | |
| NearestFloat32, | |
| NearestFloat64, | |
| SqrtFloat32, | |
| SqrtFloat64, | |
| // relational | |
| EqZInt32, | |
| EqZInt64, | |
| // conversions | |
| // extend i32 to i64 | |
| ExtendSInt32, | |
| ExtendUInt32, | |
| // i64 to i32 | |
| WrapInt64, | |
| // float to int | |
| TruncSFloat32ToInt32, | |
| TruncSFloat32ToInt64, | |
| TruncUFloat32ToInt32, | |
| TruncUFloat32ToInt64, | |
| TruncSFloat64ToInt32, | |
| TruncSFloat64ToInt64, | |
| TruncUFloat64ToInt32, | |
| TruncUFloat64ToInt64, | |
| // reinterpret bits to int | |
| ReinterpretFloat32, | |
| ReinterpretFloat64, | |
| // int to float | |
| ConvertSInt32ToFloat32, | |
| ConvertSInt32ToFloat64, | |
| ConvertUInt32ToFloat32, | |
| ConvertUInt32ToFloat64, | |
| ConvertSInt64ToFloat32, | |
| ConvertSInt64ToFloat64, | |
| ConvertUInt64ToFloat32, | |
| ConvertUInt64ToFloat64, | |
| // f32 to f64 | |
| PromoteFloat32, | |
| // f64 to f32 | |
| DemoteFloat64, | |
| // reinterpret bits to float | |
| ReinterpretInt32, | |
| ReinterpretInt64, | |
| // Extend signed subword-sized integer. This differs from e.g. ExtendSInt32 | |
| // because the input integer is in an i64 value instead of an i32 value. | |
| ExtendS8Int32, | |
| ExtendS16Int32, | |
| ExtendS8Int64, | |
| ExtendS16Int64, | |
| ExtendS32Int64, | |
| // Saturating float-to-int | |
| TruncSatSFloat32ToInt32, | |
| TruncSatUFloat32ToInt32, | |
| TruncSatSFloat64ToInt32, | |
| TruncSatUFloat64ToInt32, | |
| TruncSatSFloat32ToInt64, | |
| TruncSatUFloat32ToInt64, | |
| TruncSatSFloat64ToInt64, | |
| TruncSatUFloat64ToInt64, | |
| // SIMD splats | |
| SplatVecI8x16, | |
| SplatVecI16x8, | |
| SplatVecI32x4, | |
| SplatVecI64x2, | |
| SplatVecF32x4, | |
| SplatVecF64x2, | |
| // SIMD arithmetic | |
| NotVec128, | |
| AnyTrueVec128, | |
| AbsVecI8x16, | |
| NegVecI8x16, | |
| AllTrueVecI8x16, | |
| BitmaskVecI8x16, | |
| PopcntVecI8x16, | |
| AbsVecI16x8, | |
| NegVecI16x8, | |
| AllTrueVecI16x8, | |
| BitmaskVecI16x8, | |
| AbsVecI32x4, | |
| NegVecI32x4, | |
| AllTrueVecI32x4, | |
| BitmaskVecI32x4, | |
| AbsVecI64x2, | |
| NegVecI64x2, | |
| AllTrueVecI64x2, | |
| BitmaskVecI64x2, | |
| AbsVecF16x8, | |
| NegVecF16x8, | |
| SqrtVecF16x8, | |
| CeilVecF16x8, | |
| FloorVecF16x8, | |
| TruncVecF16x8, | |
| NearestVecF16x8, | |
| AbsVecF32x4, | |
| NegVecF32x4, | |
| SqrtVecF32x4, | |
| CeilVecF32x4, | |
| FloorVecF32x4, | |
| TruncVecF32x4, | |
| NearestVecF32x4, | |
| AbsVecF64x2, | |
| NegVecF64x2, | |
| SqrtVecF64x2, | |
| CeilVecF64x2, | |
| FloorVecF64x2, | |
| TruncVecF64x2, | |
| NearestVecF64x2, | |
| ExtAddPairwiseSVecI8x16ToI16x8, | |
| ExtAddPairwiseUVecI8x16ToI16x8, | |
| ExtAddPairwiseSVecI16x8ToI32x4, | |
| ExtAddPairwiseUVecI16x8ToI32x4, | |
| // SIMD conversions | |
| TruncSatSVecF32x4ToVecI32x4, | |
| TruncSatUVecF32x4ToVecI32x4, | |
| ConvertSVecI32x4ToVecF32x4, | |
| ConvertUVecI32x4ToVecF32x4, | |
| ExtendLowSVecI8x16ToVecI16x8, | |
| ExtendHighSVecI8x16ToVecI16x8, | |
| ExtendLowUVecI8x16ToVecI16x8, | |
| ExtendHighUVecI8x16ToVecI16x8, | |
| ExtendLowSVecI16x8ToVecI32x4, | |
| ExtendHighSVecI16x8ToVecI32x4, | |
| ExtendLowUVecI16x8ToVecI32x4, | |
| ExtendHighUVecI16x8ToVecI32x4, | |
| ExtendLowSVecI32x4ToVecI64x2, | |
| ExtendHighSVecI32x4ToVecI64x2, | |
| ExtendLowUVecI32x4ToVecI64x2, | |
| ExtendHighUVecI32x4ToVecI64x2, | |
| ConvertLowSVecI32x4ToVecF64x2, | |
| ConvertLowUVecI32x4ToVecF64x2, | |
| TruncSatZeroSVecF64x2ToVecI32x4, | |
| TruncSatZeroUVecF64x2ToVecI32x4, | |
| DemoteZeroVecF64x2ToVecF32x4, | |
| PromoteLowVecF32x4ToVecF64x2, | |
| // Relaxed SIMD | |
| RelaxedTruncSVecF32x4ToVecI32x4, | |
| RelaxedTruncUVecF32x4ToVecI32x4, | |
| RelaxedTruncZeroSVecF64x2ToVecI32x4, | |
| RelaxedTruncZeroUVecF64x2ToVecI32x4, | |
| // Half precision SIMD | |
| SplatVecF16x8, | |
| TruncSatSVecF16x8ToVecI16x8, | |
| TruncSatUVecF16x8ToVecI16x8, | |
| ConvertSVecI16x8ToVecF16x8, | |
| ConvertUVecI16x8ToVecF16x8, | |
| PromoteLowVecF16x8ToVecF32x4, | |
| DemoteZeroVecF32x4ToVecF16x8, | |
| DemoteZeroVecF64x2ToVecF16x8, | |
| InvalidUnary | |
| }; | |
| enum BinaryOp { | |
| // int or float | |
| AddInt32, | |
| SubInt32, | |
| MulInt32, | |
| // int | |
| DivSInt32, | |
| DivUInt32, | |
| RemSInt32, | |
| RemUInt32, | |
| AndInt32, | |
| OrInt32, | |
| XorInt32, | |
| ShlInt32, | |
| ShrSInt32, | |
| ShrUInt32, | |
| RotLInt32, | |
| RotRInt32, | |
| // relational ops | |
| // int or float | |
| EqInt32, | |
| NeInt32, | |
| // int | |
| LtSInt32, | |
| LtUInt32, | |
| LeSInt32, | |
| LeUInt32, | |
| GtSInt32, | |
| GtUInt32, | |
| GeSInt32, | |
| GeUInt32, | |
| // int or float | |
| AddInt64, | |
| SubInt64, | |
| MulInt64, | |
| // int | |
| DivSInt64, | |
| DivUInt64, | |
| RemSInt64, | |
| RemUInt64, | |
| AndInt64, | |
| OrInt64, | |
| XorInt64, | |
| ShlInt64, | |
| ShrSInt64, | |
| ShrUInt64, | |
| RotLInt64, | |
| RotRInt64, | |
| // relational ops | |
| // int or float | |
| EqInt64, | |
| NeInt64, | |
| // int | |
| LtSInt64, | |
| LtUInt64, | |
| LeSInt64, | |
| LeUInt64, | |
| GtSInt64, | |
| GtUInt64, | |
| GeSInt64, | |
| GeUInt64, | |
| // int or float | |
| AddFloat32, | |
| SubFloat32, | |
| MulFloat32, | |
| // float | |
| DivFloat32, | |
| CopySignFloat32, | |
| MinFloat32, | |
| MaxFloat32, | |
| // relational ops | |
| // int or float | |
| EqFloat32, | |
| NeFloat32, | |
| // float | |
| LtFloat32, | |
| LeFloat32, | |
| GtFloat32, | |
| GeFloat32, | |
| // int or float | |
| AddFloat64, | |
| SubFloat64, | |
| MulFloat64, | |
| // float | |
| DivFloat64, | |
| CopySignFloat64, | |
| MinFloat64, | |
| MaxFloat64, | |
| // relational ops | |
| // int or float | |
| EqFloat64, | |
| NeFloat64, | |
| // float | |
| LtFloat64, | |
| LeFloat64, | |
| GtFloat64, | |
| GeFloat64, | |
| // SIMD relational ops (return vectors) | |
| EqVecI8x16, | |
| NeVecI8x16, | |
| LtSVecI8x16, | |
| LtUVecI8x16, | |
| GtSVecI8x16, | |
| GtUVecI8x16, | |
| LeSVecI8x16, | |
| LeUVecI8x16, | |
| GeSVecI8x16, | |
| GeUVecI8x16, | |
| EqVecI16x8, | |
| NeVecI16x8, | |
| LtSVecI16x8, | |
| LtUVecI16x8, | |
| GtSVecI16x8, | |
| GtUVecI16x8, | |
| LeSVecI16x8, | |
| LeUVecI16x8, | |
| GeSVecI16x8, | |
| GeUVecI16x8, | |
| EqVecI32x4, | |
| NeVecI32x4, | |
| LtSVecI32x4, | |
| LtUVecI32x4, | |
| GtSVecI32x4, | |
| GtUVecI32x4, | |
| LeSVecI32x4, | |
| LeUVecI32x4, | |
| GeSVecI32x4, | |
| GeUVecI32x4, | |
| EqVecI64x2, | |
| NeVecI64x2, | |
| LtSVecI64x2, | |
| GtSVecI64x2, | |
| LeSVecI64x2, | |
| GeSVecI64x2, | |
| EqVecF16x8, | |
| NeVecF16x8, | |
| LtVecF16x8, | |
| GtVecF16x8, | |
| LeVecF16x8, | |
| GeVecF16x8, | |
| EqVecF32x4, | |
| NeVecF32x4, | |
| LtVecF32x4, | |
| GtVecF32x4, | |
| LeVecF32x4, | |
| GeVecF32x4, | |
| EqVecF64x2, | |
| NeVecF64x2, | |
| LtVecF64x2, | |
| GtVecF64x2, | |
| LeVecF64x2, | |
| GeVecF64x2, | |
| // SIMD arithmetic | |
| AndVec128, | |
| OrVec128, | |
| XorVec128, | |
| AndNotVec128, | |
| AddVecI8x16, | |
| AddSatSVecI8x16, | |
| AddSatUVecI8x16, | |
| SubVecI8x16, | |
| SubSatSVecI8x16, | |
| SubSatUVecI8x16, | |
| MinSVecI8x16, | |
| MinUVecI8x16, | |
| MaxSVecI8x16, | |
| MaxUVecI8x16, | |
| AvgrUVecI8x16, | |
| AddVecI16x8, | |
| AddSatSVecI16x8, | |
| AddSatUVecI16x8, | |
| SubVecI16x8, | |
| SubSatSVecI16x8, | |
| SubSatUVecI16x8, | |
| MulVecI16x8, | |
| MinSVecI16x8, | |
| MinUVecI16x8, | |
| MaxSVecI16x8, | |
| MaxUVecI16x8, | |
| AvgrUVecI16x8, | |
| Q15MulrSatSVecI16x8, | |
| ExtMulLowSVecI16x8, | |
| ExtMulHighSVecI16x8, | |
| ExtMulLowUVecI16x8, | |
| ExtMulHighUVecI16x8, | |
| AddVecI32x4, | |
| SubVecI32x4, | |
| MulVecI32x4, | |
| MinSVecI32x4, | |
| MinUVecI32x4, | |
| MaxSVecI32x4, | |
| MaxUVecI32x4, | |
| DotSVecI16x8ToVecI32x4, | |
| ExtMulLowSVecI32x4, | |
| ExtMulHighSVecI32x4, | |
| ExtMulLowUVecI32x4, | |
| ExtMulHighUVecI32x4, | |
| AddVecI64x2, | |
| SubVecI64x2, | |
| MulVecI64x2, | |
| ExtMulLowSVecI64x2, | |
| ExtMulHighSVecI64x2, | |
| ExtMulLowUVecI64x2, | |
| ExtMulHighUVecI64x2, | |
| AddVecF16x8, | |
| SubVecF16x8, | |
| MulVecF16x8, | |
| DivVecF16x8, | |
| MinVecF16x8, | |
| MaxVecF16x8, | |
| PMinVecF16x8, | |
| PMaxVecF16x8, | |
| AddVecF32x4, | |
| SubVecF32x4, | |
| MulVecF32x4, | |
| DivVecF32x4, | |
| MinVecF32x4, | |
| MaxVecF32x4, | |
| PMinVecF32x4, | |
| PMaxVecF32x4, | |
| AddVecF64x2, | |
| SubVecF64x2, | |
| MulVecF64x2, | |
| DivVecF64x2, | |
| MinVecF64x2, | |
| MaxVecF64x2, | |
| PMinVecF64x2, | |
| PMaxVecF64x2, | |
| // SIMD Conversion | |
| NarrowSVecI16x8ToVecI8x16, | |
| NarrowUVecI16x8ToVecI8x16, | |
| NarrowSVecI32x4ToVecI16x8, | |
| NarrowUVecI32x4ToVecI16x8, | |
| // SIMD Swizzle | |
| SwizzleVecI8x16, | |
| // Relaxed SIMD | |
| RelaxedSwizzleVecI8x16, | |
| RelaxedMinVecF32x4, | |
| RelaxedMaxVecF32x4, | |
| RelaxedMinVecF64x2, | |
| RelaxedMaxVecF64x2, | |
| RelaxedQ15MulrSVecI16x8, | |
| RelaxedDotI8x16I7x16SToVecI16x8, | |
| InvalidBinary | |
| }; | |
| enum AtomicRMWOp { RMWAdd, RMWSub, RMWAnd, RMWOr, RMWXor, RMWXchg }; | |
| enum SIMDExtractOp { | |
| ExtractLaneSVecI8x16, | |
| ExtractLaneUVecI8x16, | |
| ExtractLaneSVecI16x8, | |
| ExtractLaneUVecI16x8, | |
| ExtractLaneVecI32x4, | |
| ExtractLaneVecI64x2, | |
| ExtractLaneVecF16x8, | |
| ExtractLaneVecF32x4, | |
| ExtractLaneVecF64x2 | |
| }; | |
| enum SIMDReplaceOp { | |
| ReplaceLaneVecI8x16, | |
| ReplaceLaneVecI16x8, | |
| ReplaceLaneVecI32x4, | |
| ReplaceLaneVecI64x2, | |
| ReplaceLaneVecF16x8, | |
| ReplaceLaneVecF32x4, | |
| ReplaceLaneVecF64x2, | |
| }; | |
| enum SIMDShiftOp { | |
| ShlVecI8x16, | |
| ShrSVecI8x16, | |
| ShrUVecI8x16, | |
| ShlVecI16x8, | |
| ShrSVecI16x8, | |
| ShrUVecI16x8, | |
| ShlVecI32x4, | |
| ShrSVecI32x4, | |
| ShrUVecI32x4, | |
| ShlVecI64x2, | |
| ShrSVecI64x2, | |
| ShrUVecI64x2 | |
| }; | |
| enum SIMDLoadOp { | |
| Load8SplatVec128, | |
| Load16SplatVec128, | |
| Load32SplatVec128, | |
| Load64SplatVec128, | |
| Load8x8SVec128, | |
| Load8x8UVec128, | |
| Load16x4SVec128, | |
| Load16x4UVec128, | |
| Load32x2SVec128, | |
| Load32x2UVec128, | |
| Load32ZeroVec128, | |
| Load64ZeroVec128, | |
| }; | |
| enum SIMDLoadStoreLaneOp { | |
| Load8LaneVec128, | |
| Load16LaneVec128, | |
| Load32LaneVec128, | |
| Load64LaneVec128, | |
| Store8LaneVec128, | |
| Store16LaneVec128, | |
| Store32LaneVec128, | |
| Store64LaneVec128, | |
| }; | |
| enum SIMDTernaryOp { | |
| Bitselect, | |
| // Relaxed SIMD | |
| RelaxedMaddVecF32x4, | |
| RelaxedNmaddVecF32x4, | |
| RelaxedMaddVecF64x2, | |
| RelaxedNmaddVecF64x2, | |
| RelaxedLaneselectI8x16, | |
| RelaxedLaneselectI16x8, | |
| RelaxedLaneselectI32x4, | |
| RelaxedLaneselectI64x2, | |
| RelaxedDotI8x16I7x16AddSToVecI32x4, | |
| // FP16 | |
| MaddVecF16x8, | |
| NmaddVecF16x8, | |
| }; | |
| enum RefAsOp { | |
| RefAsNonNull, | |
| AnyConvertExtern, | |
| ExternConvertAny, | |
| }; | |
| enum BrOnOp { | |
| BrOnNull, | |
| BrOnNonNull, | |
| BrOnCast, | |
| BrOnCastFail, | |
| BrOnCastDescEq, | |
| BrOnCastDescEqFail, | |
| }; | |
| enum StringNewOp { | |
| StringNewLossyUTF8Array, | |
| StringNewWTF16Array, | |
| StringNewFromCodePoint, | |
| }; | |
| enum StringMeasureOp { | |
| StringMeasureUTF8, | |
| StringMeasureWTF16, | |
| }; | |
| enum StringEncodeOp { | |
| StringEncodeLossyUTF8Array, | |
| StringEncodeWTF16Array, | |
| }; | |
| enum StringEqOp { | |
| StringEqEqual, | |
| StringEqCompare, | |
| }; | |
| enum WideIntAddSubOp { | |
| AddInt128, | |
| SubInt128, | |
| }; | |
| enum WideIntMulOp { | |
| MulWideSInt64, | |
| MulWideUInt64, | |
| }; | |
| // | |
| // Expressions | |
| // | |
| // Note that little is provided in terms of constructors for these. The | |
| // rationale is that writing `new Something(a, b, c, d, e)` is not the clearest, | |
| // and it would be better to write new `Something(name=a, leftOperand=b...` | |
| // etc., but C++ lacks named operands so you will see things like | |
| // auto x = new Something(); | |
| // x->name = a; | |
| // x->leftOperand = b; | |
| // .. | |
| // which is less compact but less ambiguous. See wasm-builder.h for a more | |
| // friendly API for building nodes. | |
| // | |
| // Most nodes have no need of internal allocation, and when arena-allocated | |
| // they drop the provided arena on the floor. You can create random instances | |
| // of those that are not in an arena without issue. However, the nodes that | |
| // have internal allocation will need an allocator provided to them in order | |
| // to be constructed. | |
| class Expression { | |
| public: | |
| enum Id : uint8_t { | |
| InvalidId = 0, | |
| BlockId, | |
| IfId, | |
| LoopId, | |
| BreakId, | |
| SwitchId, | |
| CallId, | |
| CallIndirectId, | |
| LocalGetId, | |
| LocalSetId, | |
| GlobalGetId, | |
| GlobalSetId, | |
| LoadId, | |
| StoreId, | |
| ConstId, | |
| UnaryId, | |
| BinaryId, | |
| SelectId, | |
| DropId, | |
| ReturnId, | |
| MemorySizeId, | |
| MemoryGrowId, | |
| NopId, | |
| UnreachableId, | |
| AtomicRMWId, | |
| AtomicCmpxchgId, | |
| AtomicWaitId, | |
| AtomicNotifyId, | |
| AtomicFenceId, | |
| PauseId, | |
| SIMDExtractId, | |
| SIMDReplaceId, | |
| SIMDShuffleId, | |
| SIMDTernaryId, | |
| SIMDShiftId, | |
| SIMDLoadId, | |
| SIMDLoadStoreLaneId, | |
| MemoryInitId, | |
| DataDropId, | |
| MemoryCopyId, | |
| MemoryFillId, | |
| PopId, | |
| RefNullId, | |
| RefIsNullId, | |
| RefFuncId, | |
| RefEqId, | |
| TableGetId, | |
| TableSetId, | |
| TableSizeId, | |
| TableGrowId, | |
| TableFillId, | |
| TableCopyId, | |
| TableInitId, | |
| ElemDropId, | |
| TryId, | |
| TryTableId, | |
| ThrowId, | |
| RethrowId, | |
| ThrowRefId, | |
| TupleMakeId, | |
| TupleExtractId, | |
| RefI31Id, | |
| I31GetId, | |
| CallRefId, | |
| RefTestId, | |
| RefCastId, | |
| RefGetDescId, | |
| BrOnId, | |
| StructNewId, | |
| StructGetId, | |
| StructSetId, | |
| StructRMWId, | |
| StructCmpxchgId, | |
| ArrayNewId, | |
| ArrayNewDataId, | |
| ArrayNewElemId, | |
| ArrayNewFixedId, | |
| ArrayGetId, | |
| ArraySetId, | |
| ArrayLoadId, | |
| ArrayStoreId, | |
| ArrayLenId, | |
| ArrayCopyId, | |
| ArrayFillId, | |
| ArrayInitDataId, | |
| ArrayInitElemId, | |
| ArrayRMWId, | |
| ArrayCmpxchgId, | |
| RefAsId, | |
| StringNewId, | |
| StringConstId, | |
| StringMeasureId, | |
| StringEncodeId, | |
| StringConcatId, | |
| StringEqId, | |
| StringTestId, | |
| StringWTF16GetId, | |
| StringSliceWTFId, | |
| ContNewId, | |
| ContBindId, | |
| SuspendId, | |
| ResumeId, | |
| ResumeThrowId, | |
| StackSwitchId, | |
| StructWaitId, | |
| WideIntAddSubId, | |
| WideIntMulId, | |
| WaitqueueNewId, | |
| WaitqueueNotifyId, | |
| NumExpressionIds | |
| }; | |
| Id _id; | |
| // the type of the expression: its *output*, not necessarily its input(s) | |
| Type type = Type::none; | |
| Expression(Id id) : _id(id) {} | |
| protected: | |
| // An expression cannot be constructed without knowing what kind of expression | |
| // it should be. | |
| Expression(const Expression& other) = default; | |
| Expression(Expression&& other) = default; | |
| Expression& operator=(Expression& other) = default; | |
| Expression& operator=(Expression&& other) = default; | |
| public: | |
| void finalize() {} | |
| template<class T> bool is() const { | |
| static_assert(std::is_base_of<Expression, T>::value, | |
| "Expression is not a base of destination type T"); | |
| return int(_id) == int(T::SpecificId); | |
| } | |
| template<class T> T* dynCast() { | |
| static_assert(std::is_base_of<Expression, T>::value, | |
| "Expression is not a base of destination type T"); | |
| return int(_id) == int(T::SpecificId) ? (T*)this : nullptr; | |
| } | |
| template<class T> const T* dynCast() const { | |
| static_assert(std::is_base_of<Expression, T>::value, | |
| "Expression is not a base of destination type T"); | |
| return int(_id) == int(T::SpecificId) ? (const T*)this : nullptr; | |
| } | |
| template<class T> T* cast() { | |
| static_assert(std::is_base_of<Expression, T>::value, | |
| "Expression is not a base of destination type T"); | |
| assert(int(_id) == int(T::SpecificId)); | |
| return (T*)this; | |
| } | |
| template<class T> const T* cast() const { | |
| static_assert(std::is_base_of<Expression, T>::value, | |
| "Expression is not a base of destination type T"); | |
| assert(int(_id) == int(T::SpecificId)); | |
| return (const T*)this; | |
| } | |
| // Print the expression to stderr. Meant for use while debugging. | |
| void dump(); | |
| }; | |
| const char* getExpressionName(Expression* curr); | |
| Literal getLiteralFromConstExpression(Expression* curr); | |
| Literals getLiteralsFromConstExpression(Expression* curr); | |
| using ExpressionList = ArenaVector<Expression*>; | |
| template<Expression::Id SID> class SpecificExpression : public Expression { | |
| public: | |
| // Compile-time access to the type for the class. | |
| static constexpr Id SpecificId = SID; | |
| SpecificExpression() : Expression(SID) {} | |
| }; | |
| class Nop : public SpecificExpression<Expression::NopId> { | |
| public: | |
| Nop() = default; | |
| Nop(MixedArena& allocator) {} | |
| }; | |
| class Block : public SpecificExpression<Expression::BlockId> { | |
| public: | |
| Block(MixedArena& allocator) : list(allocator) {} | |
| Name name; | |
| ExpressionList list; | |
| enum Breakability { Unknown, HasBreak, NoBreak }; | |
| // If type_ is not given, set the type purely based on its contents. this | |
| // scans the block, so it is not fast. | |
| // If type_ is given, set the type given you know its type, which is the case | |
| // when parsing s-expression or binary, as explicit types are given. the only | |
| // additional work this does is to set the type to unreachable in the cases | |
| // that is needed (which may require scanning the block) | |
| // | |
| // If breakability is given, you know if there is a break to this block. this | |
| // avoids the need to scan the contents of the block in the case that it might | |
| // be unreachable, so it is recommended if you already know the type and | |
| // breakability anyhow. | |
| void finalize(std::optional<Type> type_ = std::nullopt, | |
| Breakability breakability = Unknown); | |
| }; | |
| class If : public SpecificExpression<Expression::IfId> { | |
| public: | |
| If() : ifFalse(nullptr) {} | |
| If(MixedArena& allocator) : If() {} | |
| Expression* condition; | |
| Expression* ifTrue; | |
| Expression* ifFalse; | |
| // If type_ is not given, set the type purely based on its contents. | |
| // If type_ is given, set the type given you know its type, which is the case | |
| // when parsing s-expression or binary, as explicit types are given. the only | |
| // additional work this does is to set the type to unreachable in the cases | |
| // that is needed. | |
| void finalize(std::optional<Type> type_ = std::nullopt); | |
| }; | |
| class Loop : public SpecificExpression<Expression::LoopId> { | |
| public: | |
| Loop() = default; | |
| Loop(MixedArena& allocator) {} | |
| Name name; | |
| Expression* body; | |
| // If type_ is not given, set the type purely based on its contents. | |
| // If type_ is given, set the type given you know its type, which is the case | |
| // when parsing s-expression or binary, as explicit types are given. the only | |
| // additional work this does is to set the type to unreachable in the cases | |
| // that is needed. | |
| void finalize(std::optional<Type> type_ = std::nullopt); | |
| }; | |
| class Break : public SpecificExpression<Expression::BreakId> { | |
| public: | |
| Break() : value(nullptr), condition(nullptr) {} | |
| Break(MixedArena& allocator) : Break() { type = Type::unreachable; } | |
| Name name; | |
| Expression* value; | |
| Expression* condition; | |
| void finalize(); | |
| }; | |
| class Switch : public SpecificExpression<Expression::SwitchId> { | |
| public: | |
| Switch(MixedArena& allocator) : targets(allocator) { | |
| type = Type::unreachable; | |
| } | |
| ArenaVector<Name> targets; | |
| Name default_; | |
| Expression* value = nullptr; | |
| Expression* condition = nullptr; | |
| void finalize(); | |
| }; | |
| class Call : public SpecificExpression<Expression::CallId> { | |
| public: | |
| Call(MixedArena& allocator) : operands(allocator) {} | |
| ExpressionList operands; | |
| Name target; | |
| bool isReturn = false; | |
| void finalize(); | |
| }; | |
| class CallIndirect : public SpecificExpression<Expression::CallIndirectId> { | |
| public: | |
| CallIndirect(MixedArena& allocator) : operands(allocator) {} | |
| HeapType heapType; | |
| ExpressionList operands; | |
| Expression* target; | |
| Name table; | |
| bool isReturn = false; | |
| void finalize(); | |
| }; | |
| class LocalGet : public SpecificExpression<Expression::LocalGetId> { | |
| public: | |
| LocalGet() = default; | |
| LocalGet(MixedArena& allocator) {} | |
| Index index; | |
| }; | |
| class LocalSet : public SpecificExpression<Expression::LocalSetId> { | |
| public: | |
| LocalSet() = default; | |
| LocalSet(MixedArena& allocator) {} | |
| void finalize(); | |
| Index index; | |
| Expression* value; | |
| bool isTee() const; | |
| void makeTee(Type type); | |
| void makeSet(); | |
| }; | |
| class GlobalGet : public SpecificExpression<Expression::GlobalGetId> { | |
| public: | |
| GlobalGet() = default; | |
| GlobalGet(MixedArena& allocator) {} | |
| Name name; | |
| }; | |
| class GlobalSet : public SpecificExpression<Expression::GlobalSetId> { | |
| public: | |
| GlobalSet() = default; | |
| GlobalSet(MixedArena& allocator) {} | |
| Name name; | |
| Expression* value; | |
| void finalize(); | |
| }; | |
| class Load : public SpecificExpression<Expression::LoadId> { | |
| public: | |
| Load() = default; | |
| Load(MixedArena& allocator) {} | |
| uint8_t bytes; | |
| bool signed_ = false; | |
| Address offset; | |
| Address align; | |
| Expression* ptr; | |
| Name memory; | |
| MemoryOrder order = MemoryOrder::Unordered; | |
| bool isAtomic() const { return order != MemoryOrder::Unordered; } | |
| // type must be set during creation, cannot be inferred | |
| void finalize(); | |
| }; | |
| class Store : public SpecificExpression<Expression::StoreId> { | |
| public: | |
| Store() = default; | |
| Store(MixedArena& allocator) : Store() {} | |
| uint8_t bytes; | |
| Address offset; | |
| Address align; | |
| Expression* ptr; | |
| Expression* value; | |
| Type valueType; | |
| Name memory; | |
| MemoryOrder order; | |
| bool isAtomic() const { return order != MemoryOrder::Unordered; } | |
| void finalize(); | |
| }; | |
| class AtomicRMW : public SpecificExpression<Expression::AtomicRMWId> { | |
| public: | |
| AtomicRMW() = default; | |
| AtomicRMW(MixedArena& allocator) : AtomicRMW() {} | |
| AtomicRMWOp op; | |
| uint8_t bytes; | |
| Address offset; | |
| Expression* ptr; | |
| Expression* value; | |
| Name memory; | |
| MemoryOrder order = MemoryOrder::SeqCst; | |
| void finalize(); | |
| }; | |
| class AtomicCmpxchg : public SpecificExpression<Expression::AtomicCmpxchgId> { | |
| public: | |
| AtomicCmpxchg() = default; | |
| AtomicCmpxchg(MixedArena& allocator) : AtomicCmpxchg() {} | |
| uint8_t bytes; | |
| Address offset; | |
| Expression* ptr; | |
| Expression* expected; | |
| Expression* replacement; | |
| Name memory; | |
| MemoryOrder order = MemoryOrder::SeqCst; | |
| void finalize(); | |
| }; | |
| class AtomicWait : public SpecificExpression<Expression::AtomicWaitId> { | |
| public: | |
| AtomicWait() = default; | |
| AtomicWait(MixedArena& allocator) : AtomicWait() {} | |
| Address offset; | |
| Expression* ptr; | |
| Expression* expected; | |
| Expression* timeout; | |
| Type expectedType; | |
| Name memory; | |
| void finalize(); | |
| }; | |
| class AtomicNotify : public SpecificExpression<Expression::AtomicNotifyId> { | |
| public: | |
| AtomicNotify() = default; | |
| AtomicNotify(MixedArena& allocator) : AtomicNotify() {} | |
| Address offset; | |
| Expression* ptr; | |
| Expression* notifyCount; | |
| Name memory; | |
| void finalize(); | |
| }; | |
| class AtomicFence : public SpecificExpression<Expression::AtomicFenceId> { | |
| public: | |
| AtomicFence() = default; | |
| AtomicFence(MixedArena& allocator) : AtomicFence() {} | |
| MemoryOrder order = MemoryOrder::SeqCst; | |
| }; | |
| class Pause : public SpecificExpression<Expression::PauseId> { | |
| public: | |
| Pause() = default; | |
| Pause(MixedArena& allocator) {} | |
| }; | |
| class SIMDExtract : public SpecificExpression<Expression::SIMDExtractId> { | |
| public: | |
| SIMDExtract() = default; | |
| SIMDExtract(MixedArena& allocator) : SIMDExtract() {} | |
| SIMDExtractOp op; | |
| Expression* vec; | |
| uint8_t index; | |
| void finalize(); | |
| }; | |
| class SIMDReplace : public SpecificExpression<Expression::SIMDReplaceId> { | |
| public: | |
| SIMDReplace() = default; | |
| SIMDReplace(MixedArena& allocator) : SIMDReplace() {} | |
| SIMDReplaceOp op; | |
| Expression* vec; | |
| uint8_t index; | |
| Expression* value; | |
| void finalize(); | |
| }; | |
| class SIMDShuffle : public SpecificExpression<Expression::SIMDShuffleId> { | |
| public: | |
| SIMDShuffle() = default; | |
| SIMDShuffle(MixedArena& allocator) : SIMDShuffle() {} | |
| Expression* left; | |
| Expression* right; | |
| std::array<uint8_t, 16> mask; | |
| void finalize(); | |
| }; | |
| class SIMDTernary : public SpecificExpression<Expression::SIMDTernaryId> { | |
| public: | |
| SIMDTernary() = default; | |
| SIMDTernary(MixedArena& allocator) : SIMDTernary() {} | |
| SIMDTernaryOp op; | |
| Expression* a; | |
| Expression* b; | |
| Expression* c; | |
| void finalize(); | |
| }; | |
| class SIMDShift : public SpecificExpression<Expression::SIMDShiftId> { | |
| public: | |
| SIMDShift() = default; | |
| SIMDShift(MixedArena& allocator) : SIMDShift() {} | |
| SIMDShiftOp op; | |
| Expression* vec; | |
| Expression* shift; | |
| void finalize(); | |
| }; | |
| class SIMDLoad : public SpecificExpression<Expression::SIMDLoadId> { | |
| public: | |
| SIMDLoad() = default; | |
| SIMDLoad(MixedArena& allocator) {} | |
| SIMDLoadOp op; | |
| Address offset; | |
| Address align; | |
| Expression* ptr; | |
| Name memory; | |
| Index getMemBytes(); | |
| void finalize(); | |
| }; | |
| class SIMDLoadStoreLane | |
| : public SpecificExpression<Expression::SIMDLoadStoreLaneId> { | |
| public: | |
| SIMDLoadStoreLane() = default; | |
| SIMDLoadStoreLane(MixedArena& allocator) {} | |
| SIMDLoadStoreLaneOp op; | |
| Address offset; | |
| Address align; | |
| uint8_t index; | |
| Expression* ptr; | |
| Expression* vec; | |
| Name memory; | |
| bool isStore(); | |
| bool isLoad() { return !isStore(); } | |
| Index getMemBytes(); | |
| void finalize(); | |
| }; | |
| class MemoryInit : public SpecificExpression<Expression::MemoryInitId> { | |
| public: | |
| MemoryInit() = default; | |
| MemoryInit(MixedArena& allocator) : MemoryInit() {} | |
| Name segment; | |
| Expression* dest; | |
| Expression* offset; | |
| Expression* size; | |
| Name memory; | |
| void finalize(); | |
| }; | |
| class DataDrop : public SpecificExpression<Expression::DataDropId> { | |
| public: | |
| DataDrop() = default; | |
| DataDrop(MixedArena& allocator) : DataDrop() {} | |
| Name segment; | |
| void finalize(); | |
| }; | |
| class MemoryCopy : public SpecificExpression<Expression::MemoryCopyId> { | |
| public: | |
| MemoryCopy() = default; | |
| MemoryCopy(MixedArena& allocator) : MemoryCopy() {} | |
| Expression* dest; | |
| Expression* source; | |
| Expression* size; | |
| Name destMemory; | |
| Name sourceMemory; | |
| void finalize(); | |
| }; | |
| class MemoryFill : public SpecificExpression<Expression::MemoryFillId> { | |
| public: | |
| MemoryFill() = default; | |
| MemoryFill(MixedArena& allocator) : MemoryFill() {} | |
| Expression* dest; | |
| Expression* value; | |
| Expression* size; | |
| Name memory; | |
| void finalize(); | |
| }; | |
| class Const : public SpecificExpression<Expression::ConstId> { | |
| public: | |
| Const() = default; | |
| Const(MixedArena& allocator) {} | |
| Literal value; | |
| Const* set(Literal value_); | |
| void finalize(); | |
| }; | |
| class Unary : public SpecificExpression<Expression::UnaryId> { | |
| public: | |
| Unary() = default; | |
| Unary(MixedArena& allocator) {} | |
| UnaryOp op; | |
| Expression* value; | |
| bool isRelational(); | |
| void finalize(); | |
| }; | |
| class Binary : public SpecificExpression<Expression::BinaryId> { | |
| public: | |
| Binary() = default; | |
| Binary(MixedArena& allocator) {} | |
| BinaryOp op; | |
| Expression* left; | |
| Expression* right; | |
| // the type is always the type of the operands, | |
| // except for relationals | |
| bool isRelational(); | |
| void finalize(); | |
| }; | |
| class WideIntAddSub : public SpecificExpression<Expression::WideIntAddSubId> { | |
| public: | |
| WideIntAddSub() = default; | |
| WideIntAddSub(MixedArena& allocator) {} | |
| WideIntAddSubOp op; | |
| Expression* leftLow; | |
| Expression* leftHigh; | |
| Expression* rightLow; | |
| Expression* rightHigh; | |
| void finalize(); | |
| }; | |
| class WideIntMul : public SpecificExpression<Expression::WideIntMulId> { | |
| public: | |
| WideIntMul() = default; | |
| WideIntMul(MixedArena& allocator) {} | |
| WideIntMulOp op; | |
| Expression* left; | |
| Expression* right; | |
| void finalize(); | |
| }; | |
| class Select : public SpecificExpression<Expression::SelectId> { | |
| public: | |
| Select() = default; | |
| Select(MixedArena& allocator) {} | |
| Expression* ifTrue; | |
| Expression* ifFalse; | |
| Expression* condition; | |
| void finalize(); | |
| }; | |
| class Drop : public SpecificExpression<Expression::DropId> { | |
| public: | |
| Drop() = default; | |
| Drop(MixedArena& allocator) {} | |
| Expression* value; | |
| void finalize(); | |
| }; | |
| class Return : public SpecificExpression<Expression::ReturnId> { | |
| public: | |
| Return() { type = Type::unreachable; } | |
| Return(MixedArena& allocator) : Return() {} | |
| Expression* value = nullptr; | |
| }; | |
| class MemorySize : public SpecificExpression<Expression::MemorySizeId> { | |
| public: | |
| MemorySize() { type = Type::i32; } | |
| MemorySize(MixedArena& allocator) : MemorySize() {} | |
| Name memory; | |
| void finalize(); | |
| }; | |
| class MemoryGrow : public SpecificExpression<Expression::MemoryGrowId> { | |
| public: | |
| MemoryGrow() { type = Type::i32; } | |
| MemoryGrow(MixedArena& allocator) : MemoryGrow() {} | |
| Expression* delta = nullptr; | |
| Name memory; | |
| void finalize(); | |
| }; | |
| class Unreachable : public SpecificExpression<Expression::UnreachableId> { | |
| public: | |
| Unreachable() { type = Type::unreachable; } | |
| Unreachable(MixedArena& allocator) : Unreachable() {} | |
| }; | |
| // Represents a pop of a value that arrives as an implicit argument to the | |
| // current block. Currently used in exception handling. | |
| class Pop : public SpecificExpression<Expression::PopId> { | |
| public: | |
| Pop() = default; | |
| Pop(MixedArena& allocator) {} | |
| }; | |
| class RefNull : public SpecificExpression<Expression::RefNullId> { | |
| public: | |
| RefNull() = default; | |
| RefNull(MixedArena& allocator) {} | |
| void finalize(); | |
| void finalize(HeapType heapType); | |
| void finalize(Type type); | |
| }; | |
| class RefIsNull : public SpecificExpression<Expression::RefIsNullId> { | |
| public: | |
| RefIsNull() = default; | |
| RefIsNull(MixedArena& allocator) {} | |
| Expression* value; | |
| void finalize(); | |
| }; | |
| class RefFunc : public SpecificExpression<Expression::RefFuncId> { | |
| public: | |
| RefFunc(MixedArena& allocator) {} | |
| Name func; | |
| void finalize(); | |
| void finalize(Module& wasm); | |
| }; | |
| class RefEq : public SpecificExpression<Expression::RefEqId> { | |
| public: | |
| RefEq() = default; | |
| RefEq(MixedArena& allocator) {} | |
| Expression* left; | |
| Expression* right; | |
| void finalize(); | |
| }; | |
| class TableGet : public SpecificExpression<Expression::TableGetId> { | |
| public: | |
| TableGet() = default; | |
| TableGet(MixedArena& allocator) {} | |
| Name table; | |
| Expression* index; | |
| void finalize(); | |
| }; | |
| class TableSet : public SpecificExpression<Expression::TableSetId> { | |
| public: | |
| TableSet() = default; | |
| TableSet(MixedArena& allocator) {} | |
| Name table; | |
| Expression* index; | |
| Expression* value; | |
| void finalize(); | |
| }; | |
| class TableSize : public SpecificExpression<Expression::TableSizeId> { | |
| public: | |
| TableSize() { type = Type::i32; } | |
| TableSize(MixedArena& allocator) : TableSize() {} | |
| Name table; | |
| void finalize(); | |
| }; | |
| class TableGrow : public SpecificExpression<Expression::TableGrowId> { | |
| public: | |
| TableGrow() { type = Type::i32; } | |
| TableGrow(MixedArena& allocator) : TableGrow() {} | |
| Name table; | |
| Expression* value; | |
| Expression* delta; | |
| void finalize(); | |
| }; | |
| class TableFill : public SpecificExpression<Expression::TableFillId> { | |
| public: | |
| TableFill() = default; | |
| TableFill(MixedArena& allocator) : TableFill() {} | |
| Name table; | |
| Expression* dest; | |
| Expression* value; | |
| Expression* size; | |
| void finalize(); | |
| }; | |
| class TableCopy : public SpecificExpression<Expression::TableCopyId> { | |
| public: | |
| TableCopy() = default; | |
| TableCopy(MixedArena& allocator) : TableCopy() {} | |
| Expression* dest; | |
| Expression* source; | |
| Expression* size; | |
| Name destTable; | |
| Name sourceTable; | |
| void finalize(); | |
| }; | |
| class TableInit : public SpecificExpression<Expression::TableInitId> { | |
| public: | |
| TableInit() = default; | |
| TableInit(MixedArena& allocator) : TableInit() {} | |
| Name segment; | |
| Expression* dest; | |
| Expression* offset; | |
| Expression* size; | |
| Name table; | |
| void finalize(); | |
| }; | |
| class ElemDrop : public SpecificExpression<Expression::ElemDropId> { | |
| public: | |
| ElemDrop() = default; | |
| ElemDrop(MixedArena& allocator) : ElemDrop() {} | |
| Name segment; | |
| void finalize(); | |
| }; | |
| // 'try' from the old (Phase 3) EH proposal | |
| class Try : public SpecificExpression<Expression::TryId> { | |
| public: | |
| Try(MixedArena& allocator) : catchTags(allocator), catchBodies(allocator) {} | |
| Name name; // label that can only be targeted by 'delegate's | |
| Expression* body; | |
| ArenaVector<Name> catchTags; | |
| ExpressionList catchBodies; | |
| Name delegateTarget; // target try's label | |
| bool hasCatchAll() const { | |
| return catchBodies.size() - catchTags.size() == 1; | |
| } | |
| bool isCatch() const { return !catchBodies.empty(); } | |
| bool isDelegate() const { return delegateTarget.is(); } | |
| void finalize(std::optional<Type> type_ = std::nullopt); | |
| }; | |
| // 'try_table' from the new EH proposal | |
| class TryTable : public SpecificExpression<Expression::TryTableId> { | |
| public: | |
| TryTable(MixedArena& allocator) | |
| : catchTags(allocator), catchDests(allocator), catchRefs(allocator), | |
| sentTypes(allocator) {} | |
| Expression* body; | |
| // Tag names. Empty names (Name()) for catch_all and catch_all_ref | |
| ArenaVector<Name> catchTags; | |
| // catches' destination blocks | |
| ArenaVector<Name> catchDests; | |
| // true for catch_ref and catch_all_ref | |
| ArenaVector<bool> catchRefs; | |
| bool hasCatchAll() const; | |
| // When 'Module*' parameter is given, we cache catch tags' types into | |
| // 'sentTypes' array, so that the types can be accessed in other analyses | |
| // without accessing the module. | |
| void finalize(std::optional<Type> type_ = std::nullopt, | |
| Module* wasm = nullptr); | |
| // Caches tags' types in the catch clauses in order not to query the module | |
| // every time we query the sent types | |
| ArenaVector<Type> sentTypes; | |
| }; | |
| class Throw : public SpecificExpression<Expression::ThrowId> { | |
| public: | |
| Throw(MixedArena& allocator) : operands(allocator) {} | |
| Name tag; | |
| ExpressionList operands; | |
| void finalize(); | |
| }; | |
| // 'rethrow' from the old (Phase 3) EH proposal | |
| class Rethrow : public SpecificExpression<Expression::RethrowId> { | |
| public: | |
| Rethrow(MixedArena& allocator) {} | |
| Name target; | |
| void finalize(); | |
| }; | |
| // 'throw_ref' from the new EH proposal | |
| class ThrowRef : public SpecificExpression<Expression::ThrowRefId> { | |
| public: | |
| ThrowRef() = default; | |
| ThrowRef(MixedArena& allocator) {} | |
| Expression* exnref; | |
| void finalize(); | |
| }; | |
| class TupleMake : public SpecificExpression<Expression::TupleMakeId> { | |
| public: | |
| TupleMake(MixedArena& allocator) : operands(allocator) {} | |
| ExpressionList operands; | |
| void finalize(); | |
| }; | |
| class TupleExtract : public SpecificExpression<Expression::TupleExtractId> { | |
| public: | |
| TupleExtract() = default; | |
| TupleExtract(MixedArena& allocator) {} | |
| Expression* tuple; | |
| Index index; | |
| void finalize(); | |
| }; | |
| class RefI31 : public SpecificExpression<Expression::RefI31Id> { | |
| public: | |
| RefI31() = default; | |
| RefI31(MixedArena& allocator) {} | |
| Expression* value; | |
| void finalize(); | |
| }; | |
| class I31Get : public SpecificExpression<Expression::I31GetId> { | |
| public: | |
| I31Get() = default; | |
| I31Get(MixedArena& allocator) {} | |
| Expression* i31; | |
| bool signed_ = false; | |
| void finalize(); | |
| }; | |
| class CallRef : public SpecificExpression<Expression::CallRefId> { | |
| public: | |
| CallRef(MixedArena& allocator) : operands(allocator) {} | |
| ExpressionList operands; | |
| Expression* target; | |
| bool isReturn = false; | |
| void finalize(); | |
| }; | |
| class RefTest : public SpecificExpression<Expression::RefTestId> { | |
| public: | |
| RefTest() = default; | |
| RefTest(MixedArena& allocator) {} | |
| Expression* ref; | |
| Type castType; | |
| void finalize(); | |
| Type& getCastType() { return castType; } | |
| }; | |
| class RefCast : public SpecificExpression<Expression::RefCastId> { | |
| public: | |
| RefCast() = default; | |
| RefCast(MixedArena& allocator) {} | |
| Expression* ref; | |
| // Used only for ref.cast_desc_eq. | |
| Expression* desc; | |
| void finalize(); | |
| Type& getCastType() { return type; } | |
| }; | |
| class RefGetDesc : public SpecificExpression<Expression::RefGetDescId> { | |
| public: | |
| RefGetDesc() = default; | |
| RefGetDesc(MixedArena& allocator) {} | |
| Expression* ref; | |
| void finalize(); | |
| }; | |
| class BrOn : public SpecificExpression<Expression::BrOnId> { | |
| public: | |
| BrOn() = default; | |
| BrOn(MixedArena& allocator) {} | |
| BrOnOp op; | |
| Name name; | |
| Expression* ref; | |
| // Only used for br_on_cast_desc_eq{,_fail} | |
| Expression* desc; | |
| // Only used for br_on_cast{,_desc}{,_fail} | |
| Type castType; | |
| void finalize(); | |
| Type& getCastType() { return castType; } | |
| // Returns the type sent on the branch, if it is taken. | |
| Type getSentType(); | |
| }; | |
| class StructNew : public SpecificExpression<Expression::StructNewId> { | |
| public: | |
| StructNew(MixedArena& allocator) : operands(allocator) {} | |
| // A struct.new_with_default has empty operands. This does leave the case of a | |
| // struct with no fields ambiguous, but it doesn't make a difference in that | |
| // case, and binaryen doesn't guarantee roundtripping binaries anyhow. | |
| ExpressionList operands; | |
| Expression* desc = nullptr; | |
| bool isWithDefault() { return operands.empty(); } | |
| void finalize(); | |
| }; | |
| class StructGet : public SpecificExpression<Expression::StructGetId> { | |
| public: | |
| StructGet() = default; | |
| StructGet(MixedArena& allocator) {} | |
| Index index; | |
| Expression* ref; | |
| // Packed fields have a sign. | |
| bool signed_ = false; | |
| MemoryOrder order = MemoryOrder::Unordered; | |
| bool isAtomic() const { return order != MemoryOrder::Unordered; } | |
| void finalize(); | |
| }; | |
| class StructSet : public SpecificExpression<Expression::StructSetId> { | |
| public: | |
| StructSet() = default; | |
| StructSet(MixedArena& allocator) {} | |
| Index index; | |
| Expression* ref; | |
| Expression* value; | |
| MemoryOrder order = MemoryOrder::Unordered; | |
| bool isAtomic() const { return order != MemoryOrder::Unordered; } | |
| void finalize(); | |
| }; | |
| class StructRMW : public SpecificExpression<Expression::StructRMWId> { | |
| public: | |
| StructRMW() = default; | |
| StructRMW(MixedArena& allocator) {} | |
| AtomicRMWOp op; | |
| Index index; | |
| Expression* ref; | |
| Expression* value; | |
| MemoryOrder order; | |
| void finalize(); | |
| }; | |
| class StructCmpxchg : public SpecificExpression<Expression::StructCmpxchgId> { | |
| public: | |
| StructCmpxchg() = default; | |
| StructCmpxchg(MixedArena& allocator) {} | |
| Index index; | |
| Expression* ref; | |
| Expression* expected; | |
| Expression* replacement; | |
| MemoryOrder order; | |
| void finalize(); | |
| }; | |
| class StructWait : public SpecificExpression<Expression::StructWaitId> { | |
| public: | |
| StructWait() = default; | |
| StructWait(MixedArena& allocator) : StructWait() {} | |
| Expression* ref; | |
| Expression* waitqueue; | |
| Expression* expected; | |
| Expression* timeout; | |
| Index index; | |
| void finalize(); | |
| }; | |
| class WaitqueueNew : public SpecificExpression<Expression::WaitqueueNewId> { | |
| public: | |
| WaitqueueNew() = default; | |
| WaitqueueNew(MixedArena& allocator) : WaitqueueNew() {} | |
| void finalize(); | |
| }; | |
| class WaitqueueNotify | |
| : public SpecificExpression<Expression::WaitqueueNotifyId> { | |
| public: | |
| WaitqueueNotify() = default; | |
| WaitqueueNotify(MixedArena& allocator) : WaitqueueNotify() {} | |
| Expression* waitqueue; | |
| Expression* count; | |
| void finalize(); | |
| }; | |
| class ArrayNew : public SpecificExpression<Expression::ArrayNewId> { | |
| public: | |
| ArrayNew() = default; | |
| ArrayNew(MixedArena& allocator) {} | |
| // If set, then the initial value is assigned to all entries in the array. If | |
| // not set, this is array.new_with_default and the default of the type is | |
| // used. | |
| Expression* init = nullptr; | |
| Expression* size; | |
| bool isWithDefault() { return !init; } | |
| void finalize(); | |
| }; | |
| class ArrayNewData : public SpecificExpression<Expression::ArrayNewDataId> { | |
| public: | |
| ArrayNewData() = default; | |
| ArrayNewData(MixedArena& allocator) {} | |
| Name segment; | |
| Expression* offset; | |
| Expression* size; | |
| void finalize(); | |
| }; | |
| class ArrayNewElem : public SpecificExpression<Expression::ArrayNewElemId> { | |
| public: | |
| ArrayNewElem() = default; | |
| ArrayNewElem(MixedArena& allocator) {} | |
| Name segment; | |
| Expression* offset; | |
| Expression* size; | |
| void finalize(); | |
| }; | |
| class ArrayNewFixed : public SpecificExpression<Expression::ArrayNewFixedId> { | |
| public: | |
| ArrayNewFixed(MixedArena& allocator) : values(allocator) {} | |
| ExpressionList values; | |
| void finalize(); | |
| }; | |
| class ArrayGet : public SpecificExpression<Expression::ArrayGetId> { | |
| public: | |
| ArrayGet() = default; | |
| ArrayGet(MixedArena& allocator) {} | |
| Expression* ref; | |
| Expression* index; | |
| // Packed fields have a sign. | |
| bool signed_ = false; | |
| MemoryOrder order = MemoryOrder::Unordered; | |
| bool isAtomic() const { return order != MemoryOrder::Unordered; } | |
| void finalize(); | |
| }; | |
| class ArraySet : public SpecificExpression<Expression::ArraySetId> { | |
| public: | |
| ArraySet() = default; | |
| ArraySet(MixedArena& allocator) {} | |
| Expression* ref; | |
| Expression* index; | |
| Expression* value; | |
| MemoryOrder order = MemoryOrder::Unordered; | |
| bool isAtomic() const { return order != MemoryOrder::Unordered; } | |
| void finalize(); | |
| }; | |
| class ArrayLoad : public SpecificExpression<Expression::ArrayLoadId> { | |
| public: | |
| ArrayLoad() = default; | |
| ArrayLoad(MixedArena& allocator) {} | |
| uint8_t bytes; | |
| bool signed_ = false; | |
| Address offset = 0; | |
| Address align = 0; | |
| Expression* ref; | |
| Expression* index; | |
| void finalize(); | |
| }; | |
| class ArrayStore : public SpecificExpression<Expression::ArrayStoreId> { | |
| public: | |
| ArrayStore() = default; | |
| ArrayStore(MixedArena& allocator) {} | |
| uint8_t bytes; | |
| Address offset = 0; | |
| Address align = 0; | |
| Expression* ref; | |
| Expression* index; | |
| Expression* value; | |
| void finalize(); | |
| }; | |
| class ArrayLen : public SpecificExpression<Expression::ArrayLenId> { | |
| public: | |
| ArrayLen() = default; | |
| ArrayLen(MixedArena& allocator) {} | |
| Expression* ref; | |
| void finalize(); | |
| }; | |
| class ArrayCopy : public SpecificExpression<Expression::ArrayCopyId> { | |
| public: | |
| ArrayCopy() = default; | |
| ArrayCopy(MixedArena& allocator) {} | |
| Expression* destRef; | |
| Expression* destIndex; | |
| Expression* srcRef; | |
| Expression* srcIndex; | |
| Expression* length; | |
| void finalize(); | |
| }; | |
| class ArrayFill : public SpecificExpression<Expression::ArrayFillId> { | |
| public: | |
| ArrayFill() = default; | |
| ArrayFill(MixedArena& allocator) {} | |
| Expression* ref; | |
| Expression* index; | |
| Expression* value; | |
| Expression* size; | |
| void finalize(); | |
| }; | |
| class ArrayInitData : public SpecificExpression<Expression::ArrayInitDataId> { | |
| public: | |
| ArrayInitData() = default; | |
| ArrayInitData(MixedArena& allocator) {} | |
| Name segment; | |
| Expression* ref; | |
| Expression* index; | |
| Expression* offset; | |
| Expression* size; | |
| void finalize(); | |
| }; | |
| class ArrayInitElem : public SpecificExpression<Expression::ArrayInitElemId> { | |
| public: | |
| ArrayInitElem() = default; | |
| ArrayInitElem(MixedArena& allocator) {} | |
| Name segment; | |
| Expression* ref; | |
| Expression* index; | |
| Expression* offset; | |
| Expression* size; | |
| void finalize(); | |
| }; | |
| class ArrayRMW : public SpecificExpression<Expression::ArrayRMWId> { | |
| public: | |
| ArrayRMW() = default; | |
| ArrayRMW(MixedArena& allocator) {} | |
| AtomicRMWOp op; | |
| Expression* ref; | |
| Expression* index; | |
| Expression* value; | |
| MemoryOrder order; | |
| void finalize(); | |
| }; | |
| class ArrayCmpxchg : public SpecificExpression<Expression::ArrayCmpxchgId> { | |
| public: | |
| ArrayCmpxchg() = default; | |
| ArrayCmpxchg(MixedArena& allocator) {} | |
| Expression* ref; | |
| Expression* index; | |
| Expression* expected; | |
| Expression* replacement; | |
| MemoryOrder order; | |
| void finalize(); | |
| }; | |
| class RefAs : public SpecificExpression<Expression::RefAsId> { | |
| public: | |
| RefAs() = default; | |
| RefAs(MixedArena& allocator) {} | |
| RefAsOp op; | |
| Expression* value; | |
| void finalize(); | |
| }; | |
| class StringNew : public SpecificExpression<Expression::StringNewId> { | |
| public: | |
| StringNew() = default; | |
| StringNew(MixedArena& allocator) {} | |
| StringNewOp op; | |
| // In the GC variations this is an Array. In from_codepoint this is the code | |
| // point. | |
| Expression* ref; | |
| // Used only in GC variations. | |
| Expression* start = nullptr; | |
| Expression* end = nullptr; | |
| void finalize(); | |
| }; | |
| class StringConst : public SpecificExpression<Expression::StringConstId> { | |
| public: | |
| StringConst() = default; | |
| StringConst(MixedArena& allocator) {} | |
| // TODO: Use a different type to allow null bytes in the middle - | |
| // ArenaVector<char> perhaps? However, Name has the benefit of being | |
| // interned and immutable (which is appropriate here). | |
| Name string; | |
| void finalize(); | |
| }; | |
| class StringMeasure : public SpecificExpression<Expression::StringMeasureId> { | |
| public: | |
| StringMeasure() = default; | |
| StringMeasure(MixedArena& allocator) {} | |
| StringMeasureOp op; | |
| Expression* ref; | |
| void finalize(); | |
| }; | |
| class StringEncode : public SpecificExpression<Expression::StringEncodeId> { | |
| public: | |
| StringEncode() = default; | |
| StringEncode(MixedArena& allocator) {} | |
| StringEncodeOp op; | |
| Expression* str; | |
| Expression* array; | |
| Expression* start; | |
| void finalize(); | |
| }; | |
| class StringConcat : public SpecificExpression<Expression::StringConcatId> { | |
| public: | |
| StringConcat() = default; | |
| StringConcat(MixedArena& allocator) {} | |
| Expression* left; | |
| Expression* right; | |
| void finalize(); | |
| }; | |
| class StringEq : public SpecificExpression<Expression::StringEqId> { | |
| public: | |
| StringEq() = default; | |
| StringEq(MixedArena& allocator) {} | |
| StringEqOp op; | |
| Expression* left; | |
| Expression* right; | |
| void finalize(); | |
| }; | |
| class StringTest : public SpecificExpression<Expression::StringTestId> { | |
| public: | |
| StringTest() = default; | |
| StringTest(MixedArena& allocator) {} | |
| Expression* ref; | |
| void finalize(); | |
| }; | |
| class StringWTF16Get : public SpecificExpression<Expression::StringWTF16GetId> { | |
| public: | |
| StringWTF16Get() = default; | |
| StringWTF16Get(MixedArena& allocator) {} | |
| Expression* ref; | |
| Expression* pos; | |
| void finalize(); | |
| }; | |
| class StringSliceWTF : public SpecificExpression<Expression::StringSliceWTFId> { | |
| public: | |
| StringSliceWTF() = default; | |
| StringSliceWTF(MixedArena& allocator) {} | |
| Expression* ref; | |
| Expression* start; | |
| Expression* end; | |
| void finalize(); | |
| }; | |
| class ContNew : public SpecificExpression<Expression::ContNewId> { | |
| public: | |
| ContNew() = default; | |
| ContNew(MixedArena& allocator) {} | |
| Expression* func; | |
| void finalize(); | |
| }; | |
| class ContBind : public SpecificExpression<Expression::ContBindId> { | |
| public: | |
| ContBind(MixedArena& allocator) : operands(allocator) {} | |
| ExpressionList operands; | |
| Expression* cont; | |
| void finalize(); | |
| }; | |
| class Suspend : public SpecificExpression<Expression::SuspendId> { | |
| public: | |
| Suspend(MixedArena& allocator) : operands(allocator) {} | |
| Name tag; | |
| ExpressionList operands; | |
| // We need access to the module to obtain the signature of the tag, | |
| // which determines this node's type. | |
| // If no module is given, then the type must have been set already. | |
| void finalize(Module* wasm = nullptr); | |
| }; | |
| class Resume : public SpecificExpression<Expression::ResumeId> { | |
| public: | |
| Resume(MixedArena& allocator) | |
| : handlerTags(allocator), handlerBlocks(allocator), operands(allocator), | |
| sentTypes(allocator) {} | |
| // The following two vectors are to be understood together | |
| // pointwise. That is, the ith component of each vector together | |
| // classifies an on-clause `(on $tag $label)` or `(on $tag | |
| // switch)`. The first vector stores reifies the `$tag` bit of the | |
| // aforementioned syntax... | |
| ArenaVector<Name> handlerTags; | |
| // ... whilst this vector reifies the `$label` bit of the | |
| // syntax. For `switch` clauses the ith component will be the Empty | |
| // name (i.e. `Name()`). | |
| ArenaVector<Name> handlerBlocks; | |
| ExpressionList operands; | |
| Expression* cont; | |
| void finalize(); | |
| // sentTypes[i] contains the type of the values that will be sent to the block | |
| // handlerBlocks[i] if suspending with tag handlerTags[i]. Not part of the | |
| // instruction's syntax, but stored here for subsequent use. | |
| // This information is cached here in order not to query the module | |
| // every time we query the sent types. | |
| ArenaVector<Type> sentTypes; | |
| }; | |
| class ResumeThrow : public SpecificExpression<Expression::ResumeThrowId> { | |
| public: | |
| ResumeThrow(MixedArena& allocator) | |
| : handlerTags(allocator), handlerBlocks(allocator), operands(allocator), | |
| sentTypes(allocator) {} | |
| // If tag is set to a non-null Name, this is a resume_throw and |operands| | |
| // contains the values to be set in an exception of that tag. If tag is null, | |
| // this is resume_throw_ref and |operands| contains a single item, the exnref. | |
| Name tag; | |
| // See the comment on `Resume` above. | |
| ArenaVector<Name> handlerTags; | |
| ArenaVector<Name> handlerBlocks; | |
| ExpressionList operands; | |
| Expression* cont; | |
| void finalize(); | |
| // sentTypes[i] contains the type of the values that will be sent to the block | |
| // handlerBlocks[i] if suspending with tag handlerTags[i]. Not part of the | |
| // instruction's syntax, but stored here for subsequent use. | |
| // This information is cached here in order not to query the module | |
| // every time we query the sent types. | |
| ArenaVector<Type> sentTypes; | |
| }; | |
| class StackSwitch : public SpecificExpression<Expression::StackSwitchId> { | |
| public: | |
| StackSwitch(MixedArena& allocator) : operands(allocator) {} | |
| Name tag; | |
| ExpressionList operands; | |
| Expression* cont; | |
| // We need access to the module to obtain the signature of the tag. | |
| void finalize(); | |
| }; | |
| // Globals | |
| struct Named { | |
| Name name; | |
| // Explicit names are ones that we read from the input file and | |
| // will be written the name section in the output file. | |
| // Implicit names are names that binaryen generated for internal | |
| // use only and will not be written the name section. | |
| bool hasExplicitName = false; | |
| void setName(Name name_, bool hasExplicitName_) { | |
| name = name_; | |
| hasExplicitName = hasExplicitName_; | |
| } | |
| void setExplicitName(Name name_) { setName(name_, true); } | |
| }; | |
| struct Importable : Named { | |
| // If these are set, then this is an import, as module.base | |
| Name module, base; | |
| bool imported() const { return module.is(); } | |
| ImportNames importNames() const { return ImportNames{module, base}; }; | |
| }; | |
| class Function; | |
| // Represents an offset into a wasm binary file. This is used for debug info. | |
| // For now, assume this is 32 bits as that's the size limit of wasm files | |
| // anyhow. | |
| using BinaryLocation = uint32_t; | |
| // Represents a mapping of wasm module elements to their location in the | |
| // binary representation. This is used for general debugging info support. | |
| // Offsets are relative to the beginning of the code section, as in DWARF. | |
| struct BinaryLocations { | |
| struct Span { | |
| BinaryLocation start = 0, end = 0; | |
| }; | |
| // Track the range of addresses an expressions appears at. This is the | |
| // contiguous range that all instructions have - control flow instructions | |
| // have additional opcodes later (like an end for a block or loop), see | |
| // just after this. | |
| std::unordered_map<Expression*, Span> expressions; | |
| // Track the extra delimiter positions that some instructions, in particular | |
| // control flow, have, like 'end' for loop and block. We keep these in a | |
| // separate map because they are rare and we optimize for the storage space | |
| // for the common type of instruction which just needs a Span. | |
| // For "else" (from an if) we use index 0, and for catch (from a try) we use | |
| // indexes 0 and above. | |
| // We use automatic zero-initialization here because that indicates a "null" | |
| // debug value, indicating the information is not present. | |
| using DelimiterLocations = ZeroInitSmallVector<BinaryLocation, 1>; | |
| enum DelimiterId : size_t { Else = 0, Invalid = size_t(-1) }; | |
| std::unordered_map<Expression*, DelimiterLocations> delimiters; | |
| // DWARF debug info can refer to multiple interesting positions in a function. | |
| struct FunctionLocations { | |
| // The very start of the function, where the binary has a size LEB. | |
| BinaryLocation start = 0; | |
| // The area where we declare locals, which is right after the size LEB. | |
| BinaryLocation declarations = 0; | |
| // The end, which is one past the final "end" instruction byte. | |
| BinaryLocation end = 0; | |
| }; | |
| std::unordered_map<Function*, FunctionLocations> functions; | |
| }; | |
| // Forward declaration for FuncEffectsMap. | |
| class EffectAnalyzer; | |
| // Annotation for a particular piece of code. This includes std::optionals for | |
| // all possible annotations, with the ones present being filled in (or just a | |
| // bool for an annotation with one possible value). | |
| struct CodeAnnotation { | |
| // Branch Hinting proposal: Whether the branch is likely, or unlikely. | |
| std::optional<bool> branchLikely; | |
| // Compilation Hints proposal. | |
| enum { NeverInline = 0, AlwaysInline = 127 }; | |
| std::optional<uint8_t> inline_; | |
| // Toolchain hints, see | |
| // https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook#intrinsics | |
| // If this expression's result is unused, then the entire thing can be | |
| // considered dead and removable. | |
| bool removableIfUnused = false; | |
| // This should be assumed to be called from JS, even in closed world. Being | |
| // called from JS means that the call happens in a non-typed way, with only | |
| // the signature mattering ("signature-called"). In particular, rec group type | |
| // identity does not matter for such functions. | |
| bool jsCalled = false; | |
| // A function that may do something on the first call, but all subsequent | |
| // calls with the same parameters can be assumed to have no effects. If a | |
| // value is returned, it will be the same value as returned earlier (for the | |
| // same parameters). | |
| // | |
| // Note that this differs from related concepts in C, | |
| // https://en.cppreference.com/w/c/language/attributes/reproducible.html#Idempotent | |
| // There, idempotency is considered compared to the state of the program, | |
| // which means that two idempotent calls with some effect in between cannot be | |
| // optimized. Here, we do optimize such situations - the only state we care | |
| // about is what is passed in via parameters. This allows us to better | |
| // optimize things like Java class constructors. | |
| bool idempotent = false; | |
| // An inlining hint at the toolchain level, in contrast to inline_, above, | |
| // which is for VMs. (E.g., one may want to not inline at the toolchain level | |
| // to keep size small, and tell VMs to inline at runtime.) | |
| std::optional<uint8_t> toolchainInline; | |
| bool operator==(const CodeAnnotation& other) const { | |
| return equalOnSemanticsPreserving(other) && equalOnSemanticsAltering(other); | |
| } | |
| // Compares only true hints, that preserve semantics and do not change | |
| // behavior in the optimizer. | |
| bool equalOnSemanticsPreserving(const CodeAnnotation& other) const { | |
| return branchLikely == other.branchLikely && inline_ == other.inline_; | |
| } | |
| // Compares annotations that *do* alter semantics. | |
| bool equalOnSemanticsAltering(const CodeAnnotation& other) const { | |
| return removableIfUnused == other.removableIfUnused && | |
| jsCalled == other.jsCalled && idempotent == other.idempotent; | |
| } | |
| // Checks if no annotation is actually set. | |
| bool empty() { return *this == CodeAnnotation(); } | |
| }; | |
| class Function : public Importable { | |
| public: | |
| // A non-nullable reference to a function type. Exact for defined functions. | |
| Type type = Type(Signature(), NonNullable, Exact); | |
| IRProfile profile = IRProfile::Normal; | |
| std::vector<Type> vars; // non-param locals | |
| // The body of the function | |
| Expression* body = nullptr; | |
| // local names. these are optional. | |
| std::unordered_map<Index, Name> localNames; | |
| std::unordered_map<Name, Index> localIndices; | |
| // Source maps debugging info: map expression nodes to their file, line, col, | |
| // symbol name. | |
| struct DebugLocation { | |
| BinaryLocation fileIndex = -1, lineNumber = -1, columnNumber = -1; | |
| std::optional<BinaryLocation> symbolNameIndex; | |
| bool operator==(const DebugLocation& other) const { | |
| return fileIndex == other.fileIndex && lineNumber == other.lineNumber && | |
| columnNumber == other.columnNumber && | |
| symbolNameIndex == other.symbolNameIndex; | |
| } | |
| bool operator!=(const DebugLocation& other) const { | |
| return !(*this == other); | |
| } | |
| bool operator<(const DebugLocation& other) const { | |
| return fileIndex != other.fileIndex ? fileIndex < other.fileIndex | |
| : lineNumber != other.lineNumber ? lineNumber < other.lineNumber | |
| : columnNumber != other.columnNumber | |
| ? columnNumber < other.columnNumber | |
| : symbolNameIndex < other.symbolNameIndex; | |
| } | |
| void dump() const { | |
| std::cerr << (symbolNameIndex ? symbolNameIndex.value() : -1) << " @ " | |
| << fileIndex << ":" << lineNumber << ":" << columnNumber | |
| << "\n"; | |
| } | |
| }; | |
| // One can explicitly set the debug location of an expression to | |
| // nullopt to stop the propagation of debug locations. | |
| std::unordered_map<Expression*, std::optional<DebugLocation>> debugLocations; | |
| std::optional<DebugLocation> prologLocation; | |
| std::optional<DebugLocation> epilogLocation; | |
| // General debugging info support: track instructions and the function itself. | |
| std::unordered_map<Expression*, BinaryLocations::Span> expressionLocations; | |
| std::unordered_map<Expression*, BinaryLocations::DelimiterLocations> | |
| delimiterLocations; | |
| BinaryLocations::FunctionLocations funcLocation; | |
| // Annotations on expressions. As with debug info, we do not store these on | |
| // Expressions as we assume most instances are unannotated, and do not want to | |
| // add constant memory overhead. | |
| std::unordered_map<Expression*, CodeAnnotation> codeAnnotations; | |
| // Annotations on the function itself. These could be stored in the above map | |
| // with a key of nullptr (matching the binary format), but it is safer to | |
| // keep them separate: in theory a parallel pass could modify code annotations | |
| // by, say, duplicating code and adding new ones, which modifies the map, | |
| // while another thread might query that function, for whom it has a call, | |
| // about the function-level annotations. | |
| CodeAnnotation funcAnnotations; | |
| // The effects for this function, if they have been computed. | |
| // Effects are shared within connected components of the function call graph. | |
| // e.g. if A calls B and B calls A, then A and B's effects are exactly the | |
| // same and they share the same EffectAnalyzer. The same applies for indirect | |
| // calls when --closed-world is enabled (see Module::indirectCallEffects). | |
| // | |
| // See addsEffects() in pass.h for more details. | |
| std::shared_ptr<const EffectAnalyzer> effects; | |
| // Inlining metadata: whether to disallow full and/or partial inlining. This | |
| // is a toolchain-level hint. For more details, see Inlining.cpp. | |
| bool noFullInline = false; | |
| bool noPartialInline = false; | |
| // Methods | |
| Signature getSig() { return type.getHeapType().getSignature(); } | |
| Type getParams() { return getSig().params; } | |
| Type getResults() { return getSig().results; } | |
| void setParams(Type params) { | |
| type = type.with(Signature(params, getResults())); | |
| } | |
| void setResults(Type results) { | |
| type = type.with(Signature(getParams(), results)); | |
| } | |
| size_t getNumParams(); | |
| size_t getNumVars(); | |
| size_t getNumLocals(); | |
| bool isParam(Index index); | |
| bool isVar(Index index); | |
| Name getLocalName(Index index); | |
| Index getLocalIndex(Name name); | |
| bool hasLocalIndex(Name name) const; | |
| Index getVarIndexBase(); | |
| Type getLocalType(Index index); | |
| Name getLocalNameOrDefault(Index index); | |
| Name getLocalNameOrGeneric(Index index); | |
| bool hasLocalName(Index index) const; | |
| void setLocalName(Index index, Name name); | |
| void clearNames(); | |
| void clearDebugInfo(); | |
| }; | |
| // The kind of an import or export. Use a namespace to avoid polluting the wasm | |
| // namespace while maintaining implicit conversion to int, which an enum class | |
| // would not have. | |
| namespace ExternalKindImpl { | |
| enum Kind : uint32_t { | |
| Function = 0, | |
| Table = 1, | |
| Memory = 2, | |
| Global = 3, | |
| Tag = 4, | |
| Invalid = uint32_t(-1) | |
| }; | |
| } // namespace ExternalKindImpl | |
| using ExternalKind = ExternalKindImpl::Kind; | |
| // The kind of a top-level module item. (This overlaps with ExternalKind, but | |
| // C++ has no good way to extend an enum.) All such items are referred to by | |
| // name in the IR (that is, the IR is relocatable), and so they are subclasses | |
| // of the Named class. | |
| enum class ModuleItemKind { | |
| Function = 0, | |
| Table = 1, | |
| Memory = 2, | |
| Global = 3, | |
| Tag = 4, | |
| DataSegment = 5, | |
| ElementSegment = 6, | |
| Invalid = -1 | |
| }; | |
| class Export { | |
| public: | |
| // exported name - note that this is the key, as the internal name, | |
| // or the exported type, is non-unique (can have multiple exports for an | |
| // internal, also over kinds) | |
| Name name; | |
| ExternalKind kind; | |
| private: | |
| std::variant<Name, HeapType> value; // internal name or exported type | |
| public: | |
| Export(Name name, ExternalKind kind, std::variant<Name, HeapType> value) | |
| : name(name), kind(kind), value(value) { | |
| assert(std::get_if<Name>(&value)); | |
| } | |
| Name* getInternalName() { return std::get_if<Name>(&value); } | |
| void setInternalName(Name name) { | |
| assert(std::holds_alternative<Name>(value)); | |
| value = name; | |
| } | |
| }; | |
| class ElementSegment : public Named { | |
| public: | |
| Name table; | |
| Expression* offset = nullptr; | |
| Type type = Type(HeapType::func, Nullable); | |
| std::vector<Expression*> data; | |
| bool isActive() const { return bool(table); } | |
| bool isPassive() const { return !table; } | |
| ElementSegment() = default; | |
| ElementSegment(Name table, | |
| Expression* offset, | |
| Type type = Type(HeapType::func, Nullable)) | |
| : table(table), offset(offset), type(type) {} | |
| ElementSegment(Name table, | |
| Expression* offset, | |
| Type type, | |
| std::vector<Expression*>& init) | |
| : table(table), offset(offset), type(type) { | |
| data.swap(init); | |
| } | |
| }; | |
| class Table : public Importable { | |
| public: | |
| static const Address::address32_t kPageSize = 1; | |
| static const Index kUnlimitedSize = Index(-1); | |
| // In wasm32/64, the maximum table size is limited by a 32-bit pointer: 4GB | |
| static const Index kMaxSize = Index(-1); | |
| Address initial = 0; | |
| Address max = kMaxSize; | |
| Type addressType = Type::i32; | |
| Type type = Type(HeapType::func, Nullable); | |
| Expression* init = nullptr; | |
| bool hasMax() { return max != kUnlimitedSize; } | |
| bool is64() { return addressType == Type::i64; } | |
| void clear() { | |
| name = ""; | |
| initial = 0; | |
| max = kMaxSize; | |
| } | |
| }; | |
| class DataSegment : public Named { | |
| public: | |
| Name memory; | |
| Expression* offset = nullptr; | |
| std::vector<char> data; // TODO: optimize | |
| bool isActive() const { return bool(memory); } | |
| bool isPassive() const { return !memory; } | |
| }; | |
| class Memory : public Importable { | |
| public: | |
| static const Address::address64_t kUnlimitedSize = Address::address64_t(-1); | |
| static const uint8_t kDefaultPageSizeLog2 = 16; | |
| static const Address::address32_t kDefaultPageSize = 1 | |
| << kDefaultPageSizeLog2; | |
| static const Address::address32_t kDefaultMaxSize32 = | |
| 1 << (32 - kDefaultPageSizeLog2); | |
| Address initial = 0; // sizes are in pages | |
| Address max = kDefaultMaxSize32; | |
| uint8_t pageSizeLog2 = kDefaultPageSizeLog2; | |
| bool shared = false; | |
| Type addressType = Type::i32; | |
| bool hasMax() { return max != kUnlimitedSize; } | |
| bool is64() { return addressType == Type::i64; } | |
| Address::address64_t maxSize32() const { return 1ull << (32 - pageSizeLog2); } | |
| Address::address64_t maxSize64() const { | |
| if (pageSizeLog2 == 0) { | |
| return std::numeric_limits<uint64_t>::max(); | |
| } | |
| return 1ull << (64 - pageSizeLog2); | |
| } | |
| Address::address64_t pageSize() const { | |
| return 1ull << static_cast<Address::address64_t>(pageSizeLog2); | |
| } | |
| }; | |
| class Global : public Importable { | |
| public: | |
| Type type; | |
| Expression* init = nullptr; | |
| bool mutable_ = false; | |
| }; | |
| class Tag : public Importable { | |
| public: | |
| HeapType type; | |
| Type params() const { return type.getSignature().params; } | |
| Type results() const { return type.getSignature().results; } | |
| }; | |
| // "Opaque" data, not part of the core wasm spec, that is held in binaries. | |
| // May be parsed/handled by utility code elsewhere, but not in wasm.h | |
| class CustomSection { | |
| public: | |
| std::string name; | |
| std::vector<char> data; | |
| }; | |
| // The optional "dylink" section is used in dynamic linking. | |
| class DylinkSection { | |
| public: | |
| bool isLegacy = false; | |
| Index memorySize, memoryAlignment, tableSize, tableAlignment; | |
| std::vector<Name> neededDynlibs; | |
| std::vector<char> tail; | |
| }; | |
| class Module { | |
| public: | |
| // wasm contents (generally you shouldn't access these from outside, except | |
| // maybe for iterating; use add*() and the get() functions) | |
| std::vector<std::unique_ptr<Export>> exports; | |
| std::vector<std::unique_ptr<Function>> functions; | |
| std::vector<std::unique_ptr<Global>> globals; | |
| std::vector<std::unique_ptr<Tag>> tags; | |
| std::vector<std::unique_ptr<ElementSegment>> elementSegments; | |
| std::vector<std::unique_ptr<Memory>> memories; | |
| std::vector<std::unique_ptr<DataSegment>> dataSegments; | |
| std::vector<std::unique_ptr<Table>> tables; | |
| Name start; | |
| std::vector<CustomSection> customSections; | |
| // Optional user section IR representation. | |
| std::unique_ptr<DylinkSection> dylinkSection; | |
| // Source maps debug info. All of these fields are read directly in from the | |
| // source map and are encoded as in the original JSON (UTF-8 encoded with | |
| // with escaped quotes and slashes). The string values are uninterpreted in | |
| // Binaryen, and they are written directly back out without re-encoding. | |
| std::vector<std::string> debugInfoFileNames; | |
| std::vector<std::string> debugInfoSymbolNames; | |
| std::string debugInfoSourceRoot; | |
| std::string debugInfoFile; | |
| std::vector<std::string> debugInfoSourcesContent; | |
| // `features` are the features allowed to be used in this module and should be | |
| // respected regardless of the value of`hasFeaturesSection`. | |
| // `hasFeaturesSection` means we read a features section and will emit one | |
| // too. | |
| FeatureSet features = FeatureSet::MVP; | |
| bool hasFeaturesSection = false; | |
| // Module name, if specified. Serves a documentary role only. | |
| Name name; | |
| std::unordered_map<HeapType, TypeNames> typeNames; | |
| // The source binary's type indices. Used in some cases for preserving | |
| // ordering of types. | |
| std::unordered_map<HeapType, Index> typeIndices; | |
| // Potential effects for bodies of indirect calls to this type. Populated by | |
| // GlobalEffects when --closed-world is enabled. e.g. when we have a call to | |
| // HeapType $A and functions $foo and $bar have types that are subtypes of $A, | |
| // then an indirect call to $A has effects equal to the union of $foo and | |
| // $bar. | |
| // | |
| // This is stored as a shared_ptr because effects are always shared within | |
| // each connected component in the module's call graph. e.g. if A calls B | |
| // and B calls A (directly or indirectly), then A and B have the same effects | |
| // and can share an EffectAnalyzer. Also see Function::effects. | |
| // | |
| // This data is only meaningful for indirect calls. If no indirect call | |
| // exists to a function, the data can be out of date (no effort is made to | |
| // clean up the data if e.g. all indirect calls to a function are removed). | |
| // | |
| // TODO: Account for exactness here. | |
| std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>> | |
| indirectCallEffects; | |
| MixedArena allocator; | |
| private: | |
| // TODO: add a build option where Names are just indices, and then these | |
| // methods are not needed | |
| // exports map is by the *exported* name, which is unique | |
| std::unordered_map<Name, Export*> exportsMap; | |
| std::unordered_map<Name, Function*> functionsMap; | |
| std::unordered_map<Name, Table*> tablesMap; | |
| std::unordered_map<Name, Memory*> memoriesMap; | |
| std::unordered_map<Name, ElementSegment*> elementSegmentsMap; | |
| std::unordered_map<Name, DataSegment*> dataSegmentsMap; | |
| std::unordered_map<Name, Global*> globalsMap; | |
| std::unordered_map<Name, Tag*> tagsMap; | |
| public: | |
| Module() = default; | |
| Export* getExport(Name name) const; | |
| Function* getFunction(Name name) const; | |
| Table* getTable(Name name) const; | |
| ElementSegment* getElementSegment(Name name) const; | |
| Memory* getMemory(Name name) const; | |
| DataSegment* getDataSegment(Name name) const; | |
| Global* getGlobal(Name name) const; | |
| Tag* getTag(Name name) const; | |
| Export* getExportOrNull(Name name) const; | |
| Table* getTableOrNull(Name name) const; | |
| Memory* getMemoryOrNull(Name name) const; | |
| ElementSegment* getElementSegmentOrNull(Name name) const; | |
| DataSegment* getDataSegmentOrNull(Name name) const; | |
| Function* getFunctionOrNull(Name name) const; | |
| Global* getGlobalOrNull(Name name) const; | |
| Tag* getTagOrNull(Name name) const; | |
| // get* methods that are generic over the kind, that is, items are identified | |
| // by their kind and their name. Otherwise, they are similar to the above | |
| // get* methods. These return items that can be imports. | |
| // TODO: Add methods for things that cannot be imports (segments). | |
| Importable* getImport(ModuleItemKind kind, Name name) const; | |
| Importable* getImportOrNull(ModuleItemKind kind, Name name) const; | |
| Export* addExport(Export* curr); | |
| Function* addFunction(Function* curr); | |
| Global* addGlobal(Global* curr); | |
| Tag* addTag(Tag* curr); | |
| Export* addExport(std::unique_ptr<Export>&& curr); | |
| Function* addFunction(std::unique_ptr<Function>&& curr); | |
| Table* addTable(std::unique_ptr<Table>&& curr); | |
| ElementSegment* addElementSegment(std::unique_ptr<ElementSegment>&& curr); | |
| Memory* addMemory(std::unique_ptr<Memory>&& curr); | |
| DataSegment* addDataSegment(std::unique_ptr<DataSegment>&& curr); | |
| Global* addGlobal(std::unique_ptr<Global>&& curr); | |
| Tag* addTag(std::unique_ptr<Tag>&& curr); | |
| void addStart(const Name& s); | |
| void removeExport(Name name); | |
| void removeFunction(Name name); | |
| void removeTable(Name name); | |
| void removeElementSegment(Name name); | |
| void removeMemory(Name name); | |
| void removeDataSegment(Name name); | |
| void removeGlobal(Name name); | |
| void removeTag(Name name); | |
| void removeExports(std::function<bool(Export*)> pred); | |
| void removeFunctions(std::function<bool(Function*)> pred); | |
| void removeTables(std::function<bool(Table*)> pred); | |
| void removeElementSegments(std::function<bool(ElementSegment*)> pred); | |
| void removeMemories(std::function<bool(Memory*)> pred); | |
| void removeDataSegments(std::function<bool(DataSegment*)> pred); | |
| void removeGlobals(std::function<bool(Global*)> pred); | |
| void removeTags(std::function<bool(Tag*)> pred); | |
| void updateFunctionsMap(); | |
| void updateDataSegmentsMap(); | |
| void updateMaps(); | |
| void clearDebugInfo(); | |
| }; | |
| // Utility for printing an expression with named types. | |
| using ModuleExpression = std::pair<Module&, Expression*>; | |
| // Utilities for printing an type with a name, if the module defines a name. | |
| using ModuleType = std::pair<Module&, Type>; | |
| using ModuleHeapType = std::pair<Module&, HeapType>; | |
| // Utility for printing only the top level of an expression. Named types will be | |
| // used if `module` is non-null. | |
| struct ShallowExpression { | |
| Expression* expr; | |
| Module* module = nullptr; | |
| }; | |
| std::ostream& operator<<(std::ostream& o, wasm::Module& module); | |
| std::ostream& operator<<(std::ostream& o, wasm::Function& func); | |
| std::ostream& operator<<(std::ostream& o, wasm::Expression& expression); | |
| std::ostream& operator<<(std::ostream& o, wasm::ModuleExpression pair); | |
| std::ostream& operator<<(std::ostream& o, wasm::ShallowExpression expression); | |
| std::ostream& operator<<(std::ostream& o, wasm::ModuleType pair); | |
| std::ostream& operator<<(std::ostream& o, wasm::ModuleHeapType pair); | |
| std::ostream& operator<<(std::ostream& os, wasm::MemoryOrder mo); | |
| std::ostream& operator<<(std::ostream& o, const wasm::ImportNames& importNames); | |
| std::ostream& operator<<(std::ostream& o, const Table& table); | |
| std::ostream& operator<<(std::ostream& o, const wasm::Global& global); | |
| } // namespace wasm | |
| namespace std { | |
| template<> struct hash<wasm::Address> { | |
| size_t operator()(const wasm::Address a) const { | |
| return std::hash<wasm::Address::address64_t>()(a.addr); | |
| } | |
| }; | |
| } // namespace std | |
| #endif // wasm_wasm_h |