Program Listing for File spirv_cross_parsed_ir.hpp

Return to documentation for file (include\spirv_cross\spirv_cross_parsed_ir.hpp)

/*
 * Copyright 2018-2021 Arm Limited
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 * 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.
 */

/*
 * At your option, you may choose to accept this material under either:
 *  1. The Apache License, Version 2.0, found at <http://www.apache.org/licenses/LICENSE-2.0>, or
 *  2. The MIT License, found at <http://opensource.org/licenses/MIT>.
 */

#ifndef SPIRV_CROSS_PARSED_IR_HPP
#define SPIRV_CROSS_PARSED_IR_HPP

#include "spirv_common.hpp"
#include <stdint.h>
#include <unordered_map>

namespace SPIRV_CROSS_NAMESPACE
{

// This data structure holds all information needed to perform cross-compilation and reflection.
// It is the output of the Parser, but any implementation could create this structure.
// It is intentionally very "open" and struct-like with some helper functions to deal with decorations.
// Parser is the reference implementation of how this data structure should be filled in.

class ParsedIR
{
private:
    // This must be destroyed after the "ids" vector.
    std::unique_ptr<ObjectPoolGroup> pool_group;

public:
    ParsedIR();

    // Due to custom allocations from object pools, we cannot use a default copy constructor.
    ParsedIR(const ParsedIR &other);
    ParsedIR &operator=(const ParsedIR &other);

    // Moves are unproblematic, but we need to implement it anyways, since MSVC 2013 does not understand
    // how to default-implement these.
    ParsedIR(ParsedIR &&other) SPIRV_CROSS_NOEXCEPT;
    ParsedIR &operator=(ParsedIR &&other) SPIRV_CROSS_NOEXCEPT;

    // Resizes ids, meta and block_meta.
    void set_id_bounds(uint32_t bounds);

    // The raw SPIR-V, instructions and opcodes refer to this by offset + count.
    std::vector<uint32_t> spirv;

    // Holds various data structures which inherit from IVariant.
    SmallVector<Variant> ids;

    // Various meta data for IDs, decorations, names, etc.
    std::unordered_map<ID, Meta> meta;

    // Holds all IDs which have a certain type.
    // This is needed so we can iterate through a specific kind of resource quickly,
    // and in-order of module declaration.
    SmallVector<ID> ids_for_type[TypeCount];

    // Special purpose lists which contain a union of types.
    // This is needed so we can declare specialization constants and structs in an interleaved fashion,
    // among other things.
    // Constants can be undef or of struct type, and struct array sizes can use specialization constants.
    SmallVector<ID> ids_for_constant_undef_or_type;
    SmallVector<ID> ids_for_constant_or_variable;

    // We need to keep track of the width the Ops that contains a type for the
    // OpSwitch instruction, since this one doesn't contains the type in the
    // instruction itself. And in some case we need to cast the condition to
    // wider types. We only need the width to do the branch fixup since the
    // type check itself can be done at runtime
    std::unordered_map<ID, uint32_t> load_type_width;

    // Declared capabilities and extensions in the SPIR-V module.
    // Not really used except for reflection at the moment.
    SmallVector<spv::Capability> declared_capabilities;
    SmallVector<std::string> declared_extensions;

    // Meta data about blocks. The cross-compiler needs to query if a block is either of these types.
    // It is a bitset as there can be more than one tag per block.
    enum BlockMetaFlagBits
    {
        BLOCK_META_LOOP_HEADER_BIT = 1 << 0,
        BLOCK_META_CONTINUE_BIT = 1 << 1,
        BLOCK_META_LOOP_MERGE_BIT = 1 << 2,
        BLOCK_META_SELECTION_MERGE_BIT = 1 << 3,
        BLOCK_META_MULTISELECT_MERGE_BIT = 1 << 4
    };
    using BlockMetaFlags = uint8_t;
    SmallVector<BlockMetaFlags> block_meta;
    std::unordered_map<BlockID, BlockID> continue_block_to_loop_header;

    // Normally, we'd stick SPIREntryPoint in ids array, but it conflicts with SPIRFunction.
    // Entry points can therefore be seen as some sort of meta structure.
    std::unordered_map<FunctionID, SPIREntryPoint> entry_points;
    FunctionID default_entry_point = 0;

    struct Source
    {
        uint32_t version = 0;
        bool es = false;
        bool known = false;
        bool hlsl = false;

        Source() = default;
    };

    Source source;

    spv::AddressingModel addressing_model = spv::AddressingModelMax;
    spv::MemoryModel memory_model = spv::MemoryModelMax;

    // Decoration handling methods.
    // Can be useful for simple "raw" reflection.
    // However, most members are here because the Parser needs most of these,
    // and might as well just have the whole suite of decoration/name handling in one place.
    void set_name(ID id, const std::string &name);
    const std::string &get_name(ID id) const;
    void set_decoration(ID id, spv::Decoration decoration, uint32_t argument = 0);
    void set_decoration_string(ID id, spv::Decoration decoration, const std::string &argument);
    bool has_decoration(ID id, spv::Decoration decoration) const;
    uint32_t get_decoration(ID id, spv::Decoration decoration) const;
    const std::string &get_decoration_string(ID id, spv::Decoration decoration) const;
    const Bitset &get_decoration_bitset(ID id) const;
    void unset_decoration(ID id, spv::Decoration decoration);

    // Decoration handling methods (for members of a struct).
    void set_member_name(TypeID id, uint32_t index, const std::string &name);
    const std::string &get_member_name(TypeID id, uint32_t index) const;
    void set_member_decoration(TypeID id, uint32_t index, spv::Decoration decoration, uint32_t argument = 0);
    void set_member_decoration_string(TypeID id, uint32_t index, spv::Decoration decoration,
                                      const std::string &argument);
    uint32_t get_member_decoration(TypeID id, uint32_t index, spv::Decoration decoration) const;
    const std::string &get_member_decoration_string(TypeID id, uint32_t index, spv::Decoration decoration) const;
    bool has_member_decoration(TypeID id, uint32_t index, spv::Decoration decoration) const;
    const Bitset &get_member_decoration_bitset(TypeID id, uint32_t index) const;
    void unset_member_decoration(TypeID id, uint32_t index, spv::Decoration decoration);

    void mark_used_as_array_length(ID id);
    uint32_t increase_bound_by(uint32_t count);
    Bitset get_buffer_block_flags(const SPIRVariable &var) const;
    Bitset get_buffer_block_type_flags(const SPIRType &type) const;

    void add_typed_id(Types type, ID id);
    void remove_typed_id(Types type, ID id);

    class LoopLock
    {
    public:
        explicit LoopLock(uint32_t *counter);
        LoopLock(const LoopLock &) = delete;
        void operator=(const LoopLock &) = delete;
        LoopLock(LoopLock &&other) SPIRV_CROSS_NOEXCEPT;
        LoopLock &operator=(LoopLock &&other) SPIRV_CROSS_NOEXCEPT;
        ~LoopLock();

    private:
        uint32_t *lock = nullptr;
    };

    // This must be held while iterating over a type ID array.
    // It is undefined if someone calls set<>() while we're iterating over a data structure, so we must
    // make sure that this case is avoided.

    // If we have a hard lock, it is an error to call set<>(), and an exception is thrown.
    // If we have a soft lock, we silently ignore any additions to the typed arrays.
    // This should only be used for physical ID remapping where we need to create an ID, but we will never
    // care about iterating over them.
    LoopLock create_loop_hard_lock() const;
    LoopLock create_loop_soft_lock() const;

    template <typename T, typename Op>
    void for_each_typed_id(const Op &op)
    {
        auto loop_lock = create_loop_hard_lock();
        for (auto &id : ids_for_type[T::type])
        {
            if (ids[id].get_type() == static_cast<Types>(T::type))
                op(id, get<T>(id));
        }
    }

    template <typename T, typename Op>
    void for_each_typed_id(const Op &op) const
    {
        auto loop_lock = create_loop_hard_lock();
        for (auto &id : ids_for_type[T::type])
        {
            if (ids[id].get_type() == static_cast<Types>(T::type))
                op(id, get<T>(id));
        }
    }

    template <typename T>
    void reset_all_of_type()
    {
        reset_all_of_type(static_cast<Types>(T::type));
    }

    void reset_all_of_type(Types type);

    Meta *find_meta(ID id);
    const Meta *find_meta(ID id) const;

    const std::string &get_empty_string() const
    {
        return empty_string;
    }

    void make_constant_null(uint32_t id, uint32_t type, bool add_to_typed_id_set);

    void fixup_reserved_names();

    static void sanitize_underscores(std::string &str);
    static void sanitize_identifier(std::string &str, bool member, bool allow_reserved_prefixes);
    static bool is_globally_reserved_identifier(std::string &str, bool allow_reserved_prefixes);

    uint32_t get_spirv_version() const;

private:
    template <typename T>
    T &get(uint32_t id)
    {
        return variant_get<T>(ids[id]);
    }

    template <typename T>
    const T &get(uint32_t id) const
    {
        return variant_get<T>(ids[id]);
    }

    mutable uint32_t loop_iteration_depth_hard = 0;
    mutable uint32_t loop_iteration_depth_soft = 0;
    std::string empty_string;
    Bitset cleared_bitset;

    std::unordered_set<uint32_t> meta_needing_name_fixup;
};
} // namespace SPIRV_CROSS_NAMESPACE

#endif