| /* |
| * 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 <map> |
| #include <ostream> |
| #include <string> |
| #include <vector> |
| |
| #include "literal.h" |
| #include "mixed_arena.h" |
| #include "support/index.h" |
| #include "support/name.h" |
| #include "wasm-features.h" |
| #include "wasm-type.h" |
| |
| namespace wasm { |
| |
| // An index in a wasm module |
| typedef uint32_t Index; |
| |
| // An address in linear memory. |
| struct Address { |
| typedef uint32_t address32_t; |
| typedef uint64_t address64_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 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, |
| // reintepret 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 insetad 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, |
| 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, |
| |
| 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, |
| 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, |
| 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, |
| DotI8x16I7x16SToVecI16x8, |
| DotI8x16I7x16UToVecI16x8, |
| |
| InvalidBinary |
| }; |
| |
| enum AtomicRMWOp { RMWAdd, RMWSub, RMWAnd, RMWOr, RMWXor, RMWXchg }; |
| |
| enum SIMDExtractOp { |
| ExtractLaneSVecI8x16, |
| ExtractLaneUVecI8x16, |
| ExtractLaneSVecI16x8, |
| ExtractLaneUVecI16x8, |
| ExtractLaneVecI32x4, |
| ExtractLaneVecI64x2, |
| ExtractLaneVecF32x4, |
| ExtractLaneVecF64x2 |
| }; |
| |
| enum SIMDReplaceOp { |
| ReplaceLaneVecI8x16, |
| ReplaceLaneVecI16x8, |
| ReplaceLaneVecI32x4, |
| ReplaceLaneVecI64x2, |
| 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 |
| RelaxedFmaVecF32x4, |
| RelaxedFmsVecF32x4, |
| RelaxedFmaVecF64x2, |
| RelaxedFmsVecF64x2, |
| LaneselectI8x16, |
| LaneselectI16x8, |
| LaneselectI32x4, |
| LaneselectI64x2, |
| DotI8x16I7x16AddSToVecI32x4, |
| DotI8x16I7x16AddUToVecI32x4, |
| }; |
| |
| enum RefIsOp { |
| RefIsNull, |
| RefIsFunc, |
| RefIsData, |
| RefIsI31, |
| }; |
| |
| enum RefAsOp { |
| RefAsNonNull, |
| RefAsFunc, |
| RefAsData, |
| RefAsI31, |
| }; |
| |
| enum BrOnOp { |
| BrOnNull, |
| BrOnNonNull, |
| BrOnCast, |
| BrOnCastFail, |
| BrOnFunc, |
| BrOnNonFunc, |
| BrOnData, |
| BrOnNonData, |
| BrOnI31, |
| BrOnNonI31, |
| }; |
| |
| // |
| // 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 { |
| 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, |
| SIMDExtractId, |
| SIMDReplaceId, |
| SIMDShuffleId, |
| SIMDTernaryId, |
| SIMDShiftId, |
| SIMDLoadId, |
| SIMDLoadStoreLaneId, |
| MemoryInitId, |
| DataDropId, |
| MemoryCopyId, |
| MemoryFillId, |
| PopId, |
| RefNullId, |
| RefIsId, |
| RefFuncId, |
| RefEqId, |
| TableGetId, |
| TableSetId, |
| TableSizeId, |
| TableGrowId, |
| TryId, |
| ThrowId, |
| RethrowId, |
| TupleMakeId, |
| TupleExtractId, |
| I31NewId, |
| I31GetId, |
| CallRefId, |
| RefTestId, |
| RefCastId, |
| BrOnId, |
| RttCanonId, |
| RttSubId, |
| StructNewId, |
| StructGetId, |
| StructSetId, |
| ArrayNewId, |
| ArrayInitId, |
| ArrayGetId, |
| ArraySetId, |
| ArrayLenId, |
| ArrayCopyId, |
| RefAsId, |
| NumExpressionIds |
| }; |
| Id _id; |
| |
| // the type of the expression: its *output*, not necessarily its input(s) |
| Type type = Type::none; |
| |
| Expression(Id id) : _id(id) {} |
| |
| 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); |
| |
| typedef ArenaVector<Expression*> ExpressionList; |
| |
| template<Expression::Id SID> class SpecificExpression : public Expression { |
| public: |
| enum { |
| SpecificId = SID // compile-time access to the type for the class |
| }; |
| |
| 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; |
| |
| // set the type purely based on its contents. this scans the block, so it is |
| // not fast. |
| void finalize(); |
| |
| // 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) |
| void finalize(Type type_); |
| |
| enum Breakability { Unknown, HasBreak, NoBreak }; |
| |
| // set the type given you know its type, and 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(Type type_, Breakability breakability); |
| }; |
| |
| class If : public SpecificExpression<Expression::IfId> { |
| public: |
| If() : ifFalse(nullptr) {} |
| If(MixedArena& allocator) : If() {} |
| |
| Expression* condition; |
| Expression* ifTrue; |
| Expression* ifFalse; |
| |
| // 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(Type type_); |
| |
| // set the type purely based on its contents. |
| void finalize(); |
| }; |
| |
| class Loop : public SpecificExpression<Expression::LoopId> { |
| public: |
| Loop() = default; |
| Loop(MixedArena& allocator) {} |
| |
| Name name; |
| Expression* body; |
| |
| // 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(Type type_); |
| |
| // set the type purely based on its contents. |
| void finalize(); |
| }; |
| |
| 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; |
| bool isAtomic; |
| Expression* ptr; |
| |
| // 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; |
| bool isAtomic; |
| Expression* ptr; |
| Expression* value; |
| Type valueType; |
| |
| 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; |
| |
| 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; |
| |
| 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; |
| |
| void finalize(); |
| }; |
| |
| class AtomicNotify : public SpecificExpression<Expression::AtomicNotifyId> { |
| public: |
| AtomicNotify() = default; |
| AtomicNotify(MixedArena& allocator) : AtomicNotify() {} |
| |
| Address offset; |
| Expression* ptr; |
| Expression* notifyCount; |
| |
| void finalize(); |
| }; |
| |
| class AtomicFence : public SpecificExpression<Expression::AtomicFenceId> { |
| public: |
| AtomicFence() = default; |
| AtomicFence(MixedArena& allocator) : AtomicFence() {} |
| |
| // Current wasm threads only supports sequentialy consistent atomics, but |
| // other orderings may be added in the future. This field is reserved for |
| // that, and currently set to 0. |
| uint8_t order = 0; |
| |
| void finalize(); |
| }; |
| |
| 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; |
| |
| 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; |
| |
| bool isStore(); |
| bool isLoad() { return !isStore(); } |
| Index getMemBytes(); |
| void finalize(); |
| }; |
| |
| class MemoryInit : public SpecificExpression<Expression::MemoryInitId> { |
| public: |
| MemoryInit() = default; |
| MemoryInit(MixedArena& allocator) : MemoryInit() {} |
| |
| Index segment; |
| Expression* dest; |
| Expression* offset; |
| Expression* size; |
| |
| void finalize(); |
| }; |
| |
| class DataDrop : public SpecificExpression<Expression::DataDropId> { |
| public: |
| DataDrop() = default; |
| DataDrop(MixedArena& allocator) : DataDrop() {} |
| |
| Index segment; |
| |
| void finalize(); |
| }; |
| |
| class MemoryCopy : public SpecificExpression<Expression::MemoryCopyId> { |
| public: |
| MemoryCopy() = default; |
| MemoryCopy(MixedArena& allocator) : MemoryCopy() {} |
| |
| Expression* dest; |
| Expression* source; |
| Expression* size; |
| |
| void finalize(); |
| }; |
| |
| class MemoryFill : public SpecificExpression<Expression::MemoryFillId> { |
| public: |
| MemoryFill() = default; |
| MemoryFill(MixedArena& allocator) : MemoryFill() {} |
| |
| Expression* dest; |
| Expression* value; |
| Expression* size; |
| |
| 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 Select : public SpecificExpression<Expression::SelectId> { |
| public: |
| Select() = default; |
| Select(MixedArena& allocator) {} |
| |
| Expression* ifTrue; |
| Expression* ifFalse; |
| Expression* condition; |
| |
| void finalize(); |
| void finalize(Type type_); |
| }; |
| |
| 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() {} |
| |
| Type ptrType = Type::i32; |
| |
| void make64(); |
| void finalize(); |
| }; |
| |
| class MemoryGrow : public SpecificExpression<Expression::MemoryGrowId> { |
| public: |
| MemoryGrow() { type = Type::i32; } |
| MemoryGrow(MixedArena& allocator) : MemoryGrow() {} |
| |
| Expression* delta = nullptr; |
| Type ptrType = Type::i32; |
| |
| void make64(); |
| 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 RefIs : public SpecificExpression<Expression::RefIsId> { |
| public: |
| RefIs(MixedArena& allocator) {} |
| |
| // RefIs can represent ref.is_null, ref.is_func, ref.is_data, and ref.is_i31. |
| RefIsOp op; |
| |
| Expression* value; |
| |
| void finalize(); |
| }; |
| |
| class RefFunc : public SpecificExpression<Expression::RefFuncId> { |
| public: |
| RefFunc(MixedArena& allocator) {} |
| |
| Name func; |
| |
| void finalize(); |
| void finalize(Type type_); |
| }; |
| |
| class RefEq : public SpecificExpression<Expression::RefEqId> { |
| public: |
| RefEq(MixedArena& allocator) {} |
| |
| Expression* left; |
| Expression* right; |
| |
| void finalize(); |
| }; |
| |
| class TableGet : public SpecificExpression<Expression::TableGetId> { |
| public: |
| TableGet(MixedArena& allocator) {} |
| |
| Name table; |
| |
| Expression* index; |
| |
| void finalize(); |
| }; |
| |
| class TableSet : public SpecificExpression<Expression::TableSetId> { |
| public: |
| 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 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(); |
| void finalize(Type type_); |
| }; |
| |
| class Throw : public SpecificExpression<Expression::ThrowId> { |
| public: |
| Throw(MixedArena& allocator) : operands(allocator) {} |
| |
| Name tag; |
| ExpressionList operands; |
| |
| void finalize(); |
| }; |
| |
| class Rethrow : public SpecificExpression<Expression::RethrowId> { |
| public: |
| Rethrow(MixedArena& allocator) {} |
| |
| Name target; |
| |
| 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(MixedArena& allocator) {} |
| |
| Expression* tuple; |
| Index index; |
| |
| void finalize(); |
| }; |
| |
| class I31New : public SpecificExpression<Expression::I31NewId> { |
| public: |
| I31New(MixedArena& allocator) {} |
| |
| Expression* value; |
| |
| void finalize(); |
| }; |
| |
| class I31Get : public SpecificExpression<Expression::I31GetId> { |
| public: |
| 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(); |
| void finalize(Type type_); |
| }; |
| |
| class RefTest : public SpecificExpression<Expression::RefTestId> { |
| public: |
| RefTest(MixedArena& allocator) {} |
| |
| Expression* ref; |
| |
| // If rtt is provided then this is a dynamic test with an rtt. If nullptr then |
| // this is a static cast and intendedType is set, and it contains the type we |
| // intend to cast to. |
| Expression* rtt = nullptr; |
| HeapType intendedType; |
| |
| void finalize(); |
| |
| // Returns the type we intend to cast to. |
| HeapType getIntendedType(); |
| }; |
| |
| class RefCast : public SpecificExpression<Expression::RefCastId> { |
| public: |
| RefCast(MixedArena& allocator) {} |
| |
| Expression* ref; |
| |
| // See above with RefTest. |
| Expression* rtt = nullptr; |
| HeapType intendedType; |
| |
| // Support the unsafe `ref.cast_nop_static` to enable precise cast overhead |
| // measurements. |
| enum Safety { Safe, Unsafe }; |
| Safety safety; |
| |
| void finalize(); |
| |
| // Returns the type we intend to cast to. |
| HeapType getIntendedType(); |
| }; |
| |
| class BrOn : public SpecificExpression<Expression::BrOnId> { |
| public: |
| BrOn(MixedArena& allocator) {} |
| |
| BrOnOp op; |
| Name name; |
| Expression* ref; |
| |
| // BrOnCast* has, like RefCast and RefTest, either an rtt or a static intended |
| // type. |
| Expression* rtt = nullptr; |
| HeapType intendedType; |
| |
| // TODO: BrOnNull also has an optional extra value in the spec, which we do |
| // not support. See also the discussion on |
| // https://github.com/WebAssembly/function-references/issues/45 |
| // - depending on the decision there, we may want to move BrOnNull into |
| // Break or a new class of its own. |
| |
| void finalize(); |
| |
| // Returns the type we intend to cast to. Relevant only for the cast variants. |
| HeapType getIntendedType(); |
| |
| // Returns the type sent on the branch, if it is taken. |
| Type getSentType(); |
| }; |
| |
| class RttCanon : public SpecificExpression<Expression::RttCanonId> { |
| public: |
| RttCanon(MixedArena& allocator) {} |
| |
| void finalize(); |
| }; |
| |
| class RttSub : public SpecificExpression<Expression::RttSubId> { |
| public: |
| RttSub(MixedArena& allocator) {} |
| |
| Expression* parent; |
| |
| // rtt.fresh_sub is like rtt.sub, but never caching or canonicalizing (i.e., |
| // it always returns a fresh RTT, non-identical to any other RTT in the |
| // system). |
| bool fresh = false; |
| |
| void finalize(); |
| }; |
| |
| class StructNew : public SpecificExpression<Expression::StructNewId> { |
| public: |
| StructNew(MixedArena& allocator) : operands(allocator) {} |
| |
| // A dynamic StructNew has an rtt, while a static one declares the type using |
| // the type field. |
| Expression* rtt = nullptr; |
| |
| // 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; |
| |
| bool isWithDefault() { return operands.empty(); } |
| |
| void finalize(); |
| }; |
| |
| class StructGet : public SpecificExpression<Expression::StructGetId> { |
| public: |
| StructGet(MixedArena& allocator) {} |
| |
| Index index; |
| Expression* ref; |
| // Packed fields have a sign. |
| bool signed_ = false; |
| |
| void finalize(); |
| }; |
| |
| class StructSet : public SpecificExpression<Expression::StructSetId> { |
| public: |
| StructSet(MixedArena& allocator) {} |
| |
| Index index; |
| Expression* ref; |
| Expression* value; |
| |
| void finalize(); |
| }; |
| |
| class ArrayNew : public SpecificExpression<Expression::ArrayNewId> { |
| public: |
| 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; |
| |
| // A dynamic ArrayNew has an rtt, while a static one declares the type using |
| // the type field. |
| Expression* rtt = nullptr; |
| |
| bool isWithDefault() { return !init; } |
| |
| void finalize(); |
| }; |
| |
| class ArrayInit : public SpecificExpression<Expression::ArrayInitId> { |
| public: |
| ArrayInit(MixedArena& allocator) : values(allocator) {} |
| |
| ExpressionList values; |
| |
| // A dynamic ArrayInit has an rtt, while a static one declares the type using |
| // the type field. |
| Expression* rtt = nullptr; |
| |
| void finalize(); |
| }; |
| |
| class ArrayGet : public SpecificExpression<Expression::ArrayGetId> { |
| public: |
| ArrayGet(MixedArena& allocator) {} |
| |
| Expression* ref; |
| Expression* index; |
| // Packed fields have a sign. |
| bool signed_ = false; |
| |
| void finalize(); |
| }; |
| |
| class ArraySet : public SpecificExpression<Expression::ArraySetId> { |
| public: |
| ArraySet(MixedArena& allocator) {} |
| |
| Expression* ref; |
| Expression* index; |
| Expression* value; |
| |
| void finalize(); |
| }; |
| |
| class ArrayLen : public SpecificExpression<Expression::ArrayLenId> { |
| public: |
| ArrayLen(MixedArena& allocator) {} |
| |
| Expression* ref; |
| |
| void finalize(); |
| }; |
| |
| class ArrayCopy : public SpecificExpression<Expression::ArrayCopyId> { |
| public: |
| ArrayCopy(MixedArena& allocator) {} |
| |
| Expression* destRef; |
| Expression* destIndex; |
| Expression* srcRef; |
| Expression* srcIndex; |
| Expression* length; |
| |
| void finalize(); |
| }; |
| |
| class RefAs : public SpecificExpression<Expression::RefAsId> { |
| public: |
| RefAs(MixedArena& allocator) {} |
| |
| RefAsOp op; |
| |
| Expression* value; |
| |
| 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(); } |
| }; |
| |
| 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 declarations of Stack IR, as functions can contain it, see |
| // the stackIR property. |
| // Stack IR is a secondary IR to the main IR defined in this file (Binaryen |
| // IR). See wasm-stack.h. |
| class StackInst; |
| |
| using StackIR = std::vector<StackInst*>; |
| |
| class Function : public Importable { |
| public: |
| HeapType type = HeapType(Signature()); // parameters and return value |
| IRProfile profile = IRProfile::Normal; |
| std::vector<Type> vars; // non-param locals |
| |
| // The body of the function |
| Expression* body = nullptr; |
| |
| // If present, this stack IR was generated from the main Binaryen IR body, |
| // and possibly optimized. If it is present when writing to wasm binary, |
| // it will be emitted instead of the main Binaryen IR. |
| // |
| // Note that no special care is taken to synchronize the two IRs - if you |
| // emit stack IR and then optimize the main IR, you need to recompute the |
| // stack IR. The Pass system will throw away Stack IR if a pass is run |
| // that declares it may modify Binaryen IR. |
| std::unique_ptr<StackIR> stackIR; |
| |
| // 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. |
| struct DebugLocation { |
| BinaryLocation fileIndex, lineNumber, columnNumber; |
| bool operator==(const DebugLocation& other) const { |
| return fileIndex == other.fileIndex && lineNumber == other.lineNumber && |
| columnNumber == other.columnNumber; |
| } |
| 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; |
| } |
| }; |
| std::unordered_map<Expression*, DebugLocation> debugLocations; |
| std::set<DebugLocation> prologLocation; |
| std::set<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; |
| |
| Signature getSig() { return type.getSignature(); } |
| Type getParams() { return getSig().params; } |
| Type getResults() { return getSig().results; } |
| void setParams(Type params) { type = Signature(params, getResults()); } |
| void setResults(Type results) { type = 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); |
| 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. |
| enum class ExternalKind { |
| Function = 0, |
| Table = 1, |
| Memory = 2, |
| Global = 3, |
| Tag = 4, |
| Invalid = -1 |
| }; |
| |
| class Export { |
| public: |
| // exported name - note that this is the key, as the internal name is |
| // non-unique (can have multiple exports for an internal, also over kinds) |
| Name name; |
| Name value; // internal name |
| ExternalKind kind; |
| }; |
| |
| class ElementSegment : public Named { |
| public: |
| Name table; |
| Expression* offset; |
| Type type = Type::funcref; |
| std::vector<Expression*> data; |
| |
| ElementSegment() = default; |
| ElementSegment(Name table, Expression* offset, Type type = Type::funcref) |
| : 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 type = Type::funcref; |
| |
| bool hasMax() { return max != kUnlimitedSize; } |
| void clear() { |
| name = ""; |
| initial = 0; |
| max = kMaxSize; |
| } |
| }; |
| |
| class Memory : public Importable { |
| public: |
| static const Address::address32_t kPageSize = 64 * 1024; |
| static const Address::address64_t kUnlimitedSize = Address::address64_t(-1); |
| // In wasm32, the maximum memory size is limited by a 32-bit pointer: 4GB |
| static const Address::address32_t kMaxSize32 = |
| (uint64_t(4) * 1024 * 1024 * 1024) / kPageSize; |
| |
| struct Segment { |
| // For use in name section only |
| Name name; |
| bool isPassive = false; |
| Expression* offset = nullptr; |
| std::vector<char> data; // TODO: optimize |
| Segment() = default; |
| Segment(Expression* offset) : offset(offset) {} |
| Segment(Expression* offset, const char* init, Address size) |
| : offset(offset) { |
| data.resize(size); |
| std::copy_n(init, size, data.begin()); |
| } |
| Segment(Expression* offset, std::vector<char>& init) : offset(offset) { |
| data.swap(init); |
| } |
| Segment(Name name, |
| bool isPassive, |
| Expression* offset, |
| const char* init, |
| Address size) |
| : name(name), isPassive(isPassive), offset(offset) { |
| data.resize(size); |
| std::copy_n(init, size, data.begin()); |
| } |
| }; |
| |
| bool exists = false; |
| Address initial = 0; // sizes are in pages |
| Address max = kMaxSize32; |
| std::vector<Segment> segments; |
| |
| bool shared = false; |
| Type indexType = Type::i32; |
| |
| Memory() { name = Name::fromInt(0); } |
| bool hasMax() { return max != kUnlimitedSize; } |
| bool is64() { return indexType == Type::i64; } |
| void clear() { |
| exists = false; |
| name = ""; |
| initial = 0; |
| max = kMaxSize32; |
| segments.clear(); |
| shared = false; |
| indexType = Type::i32; |
| } |
| }; |
| |
| class Global : public Importable { |
| public: |
| Type type; |
| Expression* init = nullptr; |
| bool mutable_ = false; |
| }; |
| |
| class Tag : public Importable { |
| public: |
| Signature sig; |
| }; |
| |
| // "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 UserSection { |
| 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<Table>> tables; |
| |
| Memory memory; |
| Name start; |
| |
| std::vector<UserSection> userSections; |
| |
| // Optional user section IR representation. |
| std::unique_ptr<DylinkSection> dylinkSection; |
| |
| // Source maps debug info. |
| std::vector<std::string> debugInfoFileNames; |
| |
| // `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; |
| |
| 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, ElementSegment*> elementSegmentsMap; |
| std::unordered_map<Name, Global*> globalsMap; |
| std::unordered_map<Name, Tag*> tagsMap; |
| |
| public: |
| Module() = default; |
| |
| Export* getExport(Name name); |
| Function* getFunction(Name name); |
| Table* getTable(Name name); |
| ElementSegment* getElementSegment(Name name); |
| Global* getGlobal(Name name); |
| Tag* getTag(Name name); |
| |
| Export* getExportOrNull(Name name); |
| Table* getTableOrNull(Name name); |
| ElementSegment* getElementSegmentOrNull(Name name); |
| Function* getFunctionOrNull(Name name); |
| Global* getGlobalOrNull(Name name); |
| Tag* getTagOrNull(Name name); |
| |
| 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); |
| 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 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 removeGlobals(std::function<bool(Global*)> pred); |
| void removeTags(std::function<bool(Tag*)> pred); |
| |
| void updateMaps(); |
| |
| void clearDebugInfo(); |
| }; |
| |
| using ModuleExpression = std::pair<Module&, Expression*>; |
| |
| } // 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); |
| } |
| }; |
| |
| std::ostream& operator<<(std::ostream& o, wasm::Module& module); |
| 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::StackInst& inst); |
| std::ostream& operator<<(std::ostream& o, wasm::StackIR& ir); |
| |
| } // namespace std |
| |
| #endif // wasm_wasm_h |