| /* |
| * Copyright 2020 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. |
| */ |
| |
| // |
| // stack-utils.h: Utilities for manipulating and analyzing stack machine code in |
| // the form of Poppy IR. See src/passes/Poppify.cpp for Poppy IR documentation. |
| // |
| |
| #ifndef wasm_ir_stack_h |
| #define wasm_ir_stack_h |
| |
| #include "ir/properties.h" |
| #include "wasm-type.h" |
| #include "wasm.h" |
| |
| namespace wasm { |
| |
| namespace StackUtils { |
| |
| // Iterate through `block` and remove nops. |
| void removeNops(Block* block); |
| |
| // Whether `expr` may be unreachable in Poppy IR. True for control flow |
| // structures and polymorphic unreachable instructions. |
| bool mayBeUnreachable(Expression* expr); |
| |
| } // namespace StackUtils |
| |
| // Stack signatures are like regular function signatures, but they are used to |
| // represent the stack parameters and results of arbitrary sequences of stack |
| // machine instructions. Stack signatures that represent instruction sequences |
| // including unreachable instructions are `Polymorphic` and otherwise they are |
| // `Fixed`. For example, the following instruction sequences both have params |
| // {i32, i32} and results {f32}, but sequence A is `Fixed` while sequence B is |
| // `Polymorphic.` |
| // |
| // A: |
| // i32.add |
| // drop |
| // f32.const 42 |
| // |
| // B: |
| // i32.add |
| // unreachable |
| // f32.const 42 |
| // |
| // Notice that this distinction is important because sequence B can be the body |
| // of the blocks below but sequence A cannot. In other words, the stack |
| // signature of sequence B is a subtype of the signatures of these blocks, but |
| // the stack signature of sequence A is not. |
| // |
| // (block (param f64 i32 i32) (result f32) ... ) |
| // (block (param i32 i32) (result f64 f32) ... ) |
| // (block (param f64 i32 i32) (result f64 f32) ... ) |
| // |
| struct StackSignature { |
| Type params; |
| Type results; |
| enum Kind { |
| Fixed, |
| Polymorphic, |
| } kind; |
| |
| StackSignature() : params(Type::none), results(Type::none), kind(Fixed) {} |
| StackSignature(Type params, Type results, Kind kind) |
| : params(params), results(results), kind(kind) {} |
| |
| StackSignature(const StackSignature&) = default; |
| StackSignature& operator=(const StackSignature&) = default; |
| |
| // Get the stack signature of `expr` |
| explicit StackSignature(Expression* expr); |
| |
| // Get the stack signature of the range of instructions [first, last). The |
| // sequence of instructions is assumed to be valid, i.e. their signatures |
| // compose. |
| template<class InputIt> |
| explicit StackSignature(InputIt first, InputIt last) |
| : params(Type::none), results(Type::none), kind(Fixed) { |
| // TODO: It would be more efficient to build the signature directly and |
| // construct the params in reverse to avoid quadratic behavior. |
| while (first != last) { |
| *this += StackSignature(*first++); |
| } |
| } |
| |
| // Return `true` iff `next` composes after this stack signature. |
| bool composes(const StackSignature& next) const; |
| |
| // Compose stack signatures. Assumes they actually compose. |
| StackSignature& operator+=(const StackSignature& next); |
| StackSignature operator+(const StackSignature& next) const; |
| |
| bool operator==(const StackSignature& other) const { |
| return params == other.params && results == other.results && |
| kind == other.kind; |
| } |
| |
| // Whether a block whose contents have stack signature `a` could be typed with |
| // stack signature `b`, i.e. whether it could be used in a context that |
| // expects signature `b`. Formally, where `a` is the LHS and `b` the RHS: |
| // |
| // [t1*] -> [t2*] <: [s1* t1'*] -> [s2* t2'*] iff |
| // |
| // - t1' <: t1 |
| // - t2 <: t2' |
| // - s1 <: s2 |
| // |
| // Note that neither signature is polymorphic in this rule and that the |
| // cardinalities of t1* and t1'*, t2* and t2'*, and s1* and s2* must match. |
| // |
| // [t1*] -> [t2*] {poly} <: [s1* t1'*] -> [s2* t2'*] {poly?} iff |
| // |
| // - [t1*] -> [t2*] <: [t1'*] -> [t2'*] |
| // |
| // Note that s1* and s2* can have different cardinalities and arbitrary types |
| // in this rule. |
| // |
| // As an example of the first rule, consider this instruction sequence: |
| // |
| // ref.cast (ref i31) |
| // drop |
| // i32.add |
| // |
| // The most specific type you could give this sequence is [i32, i32, anyref] |
| // -> [i32]. But it could also be used in a context that expects [i32, i32, |
| // structref] -> [i32] because ref.cast can accept structref or any other |
| // subtype of anyref. That's where the contravariance comes from. This |
| // instruction sequence could also be used anywhere that expects [f32, i32, |
| // i32, anyref] -> [f32, i32] because the f32 simply stays on the stack |
| // throughout the sequence. That's where the the prefix extension comes from. |
| // |
| // For the second rule, consider this sequence: |
| // |
| // ref.cast (ref i31) |
| // drop |
| // i32.add |
| // unreachable |
| // i32.const 0 |
| // |
| // This instruction sequence has the specific type [i32, i32, anyref] -> [i32] |
| // {poly}. It can be used in any situation the previous block can be used in, |
| // but can additionally be used in contexts that expect something like [f32, |
| // i32, i32, anyref] -> [v128, i32]. Because of the polymorphism, the |
| // additional prefixes on the params and results do not need to match. |
| // |
| // Note that a fixed stack signature (without a {poly}) is never a subtype of |
| // any polymorphic stack signature (with a {poly}). This makes sense because a |
| // sequence of instructions that has no polymorphic behavior cannot be given a |
| // type that says it does have polymorphic behavior. |
| // |
| // Also, [] -> [] {poly} is the bottom type here; it is a subtype of every |
| // other stack signature. This corresponds to the `unreachable` instruction |
| // being able to be given any stack signature. |
| static bool isSubType(StackSignature a, StackSignature b); |
| |
| // Returns true iff `a` and `b` have a LUB, i.e. a minimal StackSignature that |
| // could type block contents of either type `a` or type `b`. |
| static bool haveLeastUpperBound(StackSignature a, StackSignature b); |
| |
| // Returns the LUB of `a` and `b`. Assumes that the LUB exists. |
| static StackSignature getLeastUpperBound(StackSignature a, StackSignature b); |
| }; |
| |
| // Calculates stack machine data flow, associating the sources and destinations |
| // of all values in a block. |
| struct StackFlow { |
| // The destination (source) location at which a value of type `type` is |
| // consumed (produced), corresponding to the `index`th value consumed by |
| // (produced by) instruction `expr`. For destination locations, `unreachable` |
| // is true iff the corresponding value is consumed by the polymorphic behavior |
| // of an unreachable instruction rather than being used directly. For source |
| // locations, `unreachable` is true iff the corresponding value is produced by |
| // an unreachable instruction. For produced values that are not consumed |
| // within the block (TODO: also for consumed values that are not produced |
| // within the block), `expr` will be the enclosing block. |
| struct Location { |
| Expression* expr; |
| Index index; |
| Type type; |
| bool unreachable; |
| |
| bool operator==(const Location& other) const { |
| return expr == other.expr && index == other.index && type == other.type && |
| unreachable == other.unreachable; |
| } |
| }; |
| |
| using LocationMap = std::unordered_map<Expression*, std::vector<Location>>; |
| |
| // Maps each instruction to the set of source locations producing its inputs. |
| LocationMap srcs; |
| |
| // Maps each instruction to the set of output locations consuming its results. |
| LocationMap dests; |
| |
| // Gets the effective stack signature of `expr`, which must be a child of the |
| // block. If `expr` is unreachable, the returned signature will reflect the |
| // values consumed and produced by its polymorphic unreachable behavior. |
| StackSignature getSignature(Expression* expr); |
| |
| // Calculates `srcs` and `dests`. |
| StackFlow(Block* curr); |
| }; |
| |
| } // namespace wasm |
| |
| #endif // wasm_ir_stack_h |