LCOV - code coverage report
Current view: top level - src/backend - StackMachine.hpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 22 22 100.0 %
Date: 2025-03-25 01:19:55 Functions: 8 8 100.0 %
Branches: 4 4 100.0 %

           Branch data     Line data    Source code
       1                 :            : #pragma once
       2                 :            : 
       3                 :            : #include <cstdint>
       4                 :            : #include <mutable/catalog/Schema.hpp>
       5                 :            : #include <mutable/IR/Tuple.hpp>
       6                 :            : 
       7                 :            : 
       8                 :            : namespace m {
       9                 :            : 
      10                 :            : namespace ast {
      11                 :            : 
      12                 :            : struct Expr;
      13                 :            : 
      14                 :            : }
      15                 :            : 
      16                 :            : namespace cnf {
      17                 :            : 
      18                 :            :     struct CNF;
      19                 :            : 
      20                 :            : }
      21                 :            : 
      22                 :            : struct StackMachineBuilder;
      23                 :            : 
      24                 :            : /** A stack machine that evaluates an expression. */
      25                 :            : struct StackMachine
      26                 :            : {
      27                 :            :     friend struct Interpreter;
      28                 :            :     friend struct StackMachineBuilder;
      29                 :            : 
      30                 :            :     static constexpr std::size_t SIZE_OF_MEMORY = 4 * 1024; // 4 KiB
      31                 :            : 
      32                 :            :     enum class Opcode : uint8_t
      33                 :            :     {
      34                 :            : #define M_OPCODE(CODE, ...) CODE,
      35                 :            : #include "tables/Opcodes.tbl"
      36                 :            : #undef M_OPCODE
      37                 :            :         Last
      38                 :            :     };
      39                 :            :     static_assert(uint64_t(Opcode::Last) < (1UL << (sizeof(Opcode) * 8)), "too many opcodes");
      40                 :            : 
      41                 :            :     using index_t = std::size_t;
      42                 :            : 
      43                 :            :     private:
      44                 :            :     static constexpr const char *OPCODE_TO_STR[] = {
      45                 :            : #define M_OPCODE(CODE, ...) #CODE,
      46                 :            : #include "tables/Opcodes.tbl"
      47                 :            : #undef M_OPCODE
      48                 :            :     };
      49                 :            :     static const std::unordered_map<std::string, Opcode> STR_TO_OPCODE;
      50                 :            : 
      51                 :            :     public:
      52                 :            :     static Opcode str_to_opcode(const std::string &str) { return STR_TO_OPCODE.at(str); }
      53                 :            : 
      54                 :            :     private:
      55                 :            :     /*----- The schema of incoming and outgoing tuples. --------------------------------------------------------------*/
      56                 :            :     Schema in_schema; ///< schema of the input tuple
      57                 :            :     std::vector<const Type*> out_schema; ///< schema of the output tuple
      58                 :            : 
      59                 :            :     /*----- Fields defining the structure of the machine. ------------------------------------------------------------*/
      60                 :            :     std::vector<Opcode> ops; ///< the sequence of operations to perform
      61                 :            :     std::vector<Value> context_; ///< the context of the stack machine, e.g. constants or global variables
      62                 :       2149 :     int64_t required_stack_size_ = 0; ///< the required size of the stack
      63                 :       2149 :     int64_t current_stack_size_ = 0; ///< the "current" stack size; i.e. after the last operation is executed
      64                 :            : 
      65                 :            :     /*----- Fields capturing the internal state during execution. ----------------------------------------------------*/
      66                 :       2149 :     mutable Value *values_ = nullptr; ///< array of values used as a stack
      67                 :       2149 :     mutable bool *null_bits_ = nullptr; ///< array of NULL bits used as a stack
      68                 :            :     mutable decltype(ops)::const_iterator op_; ///< the next operation to execute
      69                 :       2149 :     mutable std::size_t top_ = 0; ///< the top of the stack
      70                 :            :     mutable uint8_t memory_[SIZE_OF_MEMORY]; ///< memory usable by the stack machine, e.g. to work on BLOBs
      71                 :            : 
      72                 :            :     public:
      73                 :            :     /** Create a `StackMachine` that does not accept input. */
      74                 :       3342 :     StackMachine() { }
      75                 :            : 
      76                 :            :     /** Create a `StackMachine` with the given input `Schema` `in_schema`.  This is the c'tor used when constructing the
      77                 :            :      * opcode sequence from the outside. */
      78                 :       3105 :     explicit StackMachine(Schema in_schema) : in_schema(in_schema) { }
      79                 :            : 
      80                 :            :     /** Create a `StackMachine` with the given input `Schema` `in_schema`, compile the `Expr` `expr`, and emit the
      81                 :            :      * result to the output `Tuple` at index `0`.  This is a *convenience* c'tor to construct a `StackMachine` that
      82                 :            :      * evaluates exactly one expression. */
      83                 :            :     StackMachine(Schema in_schema, const ast::Expr &expr);
      84                 :            : 
      85                 :            :     /** Create a `StackMachine` with the given input `Schema` `in_schema`, compile the `cnf::CNF` `cnf`, and emit the
      86                 :            :      * result to the output `Tuple` at index `0`.  This is a *convenience* c'tor to construct a `StackMachine` that
      87                 :            :      * evaluates exactly one CNF formula. */
      88                 :            :     StackMachine(Schema in_schema, const cnf::CNF &cnf);
      89                 :            : 
      90                 :            :     StackMachine(const StackMachine&) = delete;
      91                 :        801 :     StackMachine(StackMachine&&) = default;
      92                 :            : 
      93                 :       2950 :     ~StackMachine() {
      94         [ +  + ]:       2950 :         delete[] values_;
      95         [ +  + ]:       2950 :         delete[] null_bits_;
      96                 :       2950 :     }
      97                 :            : 
      98                 :            :     /** Returns the `Schema` of input `Tuple`s. */
      99                 :            :     const Schema & schema_in() const { return in_schema; }
     100                 :            : 
     101                 :            :     /** Returns a sequence of `Type`s defining the schema of output `Tuple`s. */
     102                 :            :     const std::vector<const Type*> & schema_out() const { return out_schema; }
     103                 :            : 
     104                 :            :     std::size_t num_ops() const { return ops.size(); }
     105                 :            : 
     106                 :            :     /** Returns the required size of the stack to evaluate the opcode sequence. */
     107                 :     446219 :     std::size_t required_stack_size() const { return required_stack_size_; }
     108                 :            : 
     109                 :            :     /** Emit operations evaluating the `Expr` `expr`. */
     110                 :            :     void emit(const ast::Expr &expr, std::size_t tuple_id = 0);
     111                 :            : 
     112                 :            :     /** Emit operations evaluating the `Expr` `expr`. */
     113                 :            :     void emit(const ast::Expr &expr, const Schema &schema, std::size_t tuple_id = 0);
     114                 :            : 
     115                 :            :     /** Emit operations evaluating the `Expr` `expr`. */
     116                 :            :     void emit(const ast::Expr &expr,
     117                 :            :               std::vector<Schema> &schemas,
     118                 :            :               std::vector<std::size_t> &tuple_ids);
     119                 :            : 
     120                 :            :     /** Emit operations evaluating the `CNF` formula `cnf`. */
     121                 :            :     void emit(const cnf::CNF &cnf, std::size_t tuple_id = 0);
     122                 :            : 
     123                 :            :     /** Emit operations evaluating the `Expr` `expr`. */
     124                 :            :     void emit(const cnf::CNF &cnf,
     125                 :            :               std::vector<Schema> &schemas,
     126                 :            :               std::vector<std::size_t> &tuple_ids);
     127                 :            : 
     128                 :            :     /* The following macros are used to automatically generate methods to emit a particular opcode.  For example, for
     129                 :            :      * the opcode `Pop`, we will define a function `emit_Pop()`, that appends the `Pop` opcode to the current opcode
     130                 :            :      * sequence.  For opcodes that require an argument, a function with the respective parameter is defined and that
     131                 :            :      * parameter is inserted into the opcode sequence.  For example, the opcode `Ld_Ctx` requires a single parameter
     132                 :            :      * with the index of the context value.  The macro will expand to the method `emit_Ld_Ctx(uint8_t idx)`, that first
     133                 :            :      * appends the `Ld_Ctx` opcode to the opcode sequence and then appends the `idx` parameter to the opcode sequence.
     134                 :            :      */
     135                 :            : #define SELECT(XXX, _1, _2, _3, FN, ...) FN(__VA_ARGS__)
     136                 :            : #define ARGS_0(XXX, ...)
     137                 :            : #define ARGS_1(I, XXX, ARG0, ...) uint8_t ARG0
     138                 :            : #define ARGS_2(I, II, XXX, ARG0, ARG1, ...) uint8_t ARG0, uint8_t ARG1
     139                 :            : #define ARGS_3(I, II, III, XXX, ARG0, ARG1, ARG2, ...) uint8_t ARG0, uint8_t ARG1, uint8_t ARG2
     140                 :            : #define ARGS(...) SELECT(__VA_ARGS__, ARGS_3, ARGS_2, ARGS_1, ARGS_0, __VA_ARGS__)
     141                 :            : #define PUSH_0(XXX, ...)
     142                 :            : #define PUSH_1(I, XXX, ARG0, ...) \
     143                 :            :     ops.push_back(static_cast<Opcode>((ARG0)));
     144                 :            : #define PUSH_2(I, II, XXX, ARG0, ARG1, ...) \
     145                 :            :     ops.push_back(static_cast<Opcode>((ARG0))); \
     146                 :            :     ops.push_back(static_cast<Opcode>((ARG1)));
     147                 :            : #define PUSH_3(I, II, III, XXX, ARG0, ARG1, ARG2, ...) \
     148                 :            :     ops.push_back(static_cast<Opcode>((ARG0))); \
     149                 :            :     ops.push_back(static_cast<Opcode>((ARG1))); \
     150                 :            :     ops.push_back(static_cast<Opcode>((ARG2)));
     151                 :            : #define PUSH(...) SELECT(__VA_ARGS__, PUSH_3, PUSH_2, PUSH_1, PUSH_0, __VA_ARGS__)
     152                 :            : 
     153                 :            : #define M_OPCODE(CODE, DELTA, ...) \
     154                 :            :     void emit_ ## CODE ( ARGS(XXX, ##__VA_ARGS__) ) { \
     155                 :            :         ops.push_back(StackMachine::Opcode:: CODE ); \
     156                 :            :         current_stack_size_ += DELTA; \
     157                 :            :         M_insist(current_stack_size_ >= 0); \
     158                 :            :         required_stack_size_ = std::max(required_stack_size_, current_stack_size_); \
     159                 :            :         PUSH(XXX, ##__VA_ARGS__) \
     160                 :            :     }
     161                 :            : 
     162                 :            : #include "tables/Opcodes.tbl"
     163                 :            : 
     164                 :            : #undef M_OPCODE
     165                 :            : #undef SELECT
     166                 :            : #undef ARGS_0
     167                 :            : #undef ARGS_1
     168                 :            : #undef ARGS_2
     169                 :            : #undef ARGS
     170                 :            : #undef PUSH_0
     171                 :            : #undef PUSH_1
     172                 :            : #undef PUSH_2
     173                 :            : #undef PUSH
     174                 :            : 
     175                 :            :     /** Append the given opcode to the opcode sequence. */
     176                 :      12007 :     void emit(Opcode opc) { ops.push_back(opc); }
     177                 :            : 
     178                 :            :     /** Emit a `Ld_X` instruction based on `Type` `ty`, e.g.\ `Ld_i32` for 4 byte integral types. */
     179                 :            :     void emit_Ld(const Type *ty);
     180                 :            : 
     181                 :            :     /** Emit a `St_X` instruction based on `Type` `ty`, e.g.\ `St_i32` for 4 byte integral types. */
     182                 :            :     void emit_St(const Type *ty);
     183                 :            : 
     184                 :            :     /** Emit a `St_Tup_X` instruction based on `Type` `ty`, e.g.\ `St_Tup_i` for integral `Type`s. */
     185                 :            :     void emit_St_Tup(std::size_t tuple_id, std::size_t index, const Type *ty);
     186                 :            : 
     187                 :            :     /** Emit a `Print_X` instruction based on `Type` `ty`, e.g.\ `Print_i` for integral `Type`s. */
     188                 :            :     void emit_Print(std::size_t ostream_index, const Type *ty);
     189                 :            : 
     190                 :            :     /** Emit opcodes to convert a value of `Type` `from_ty` to `Type` `to_ty`. */
     191                 :            :     void emit_Cast(const Type *to_ty, const Type *from_ty);
     192                 :            : 
     193                 :            :     /** Appends the `Value` `val` to the context and returns its assigned index. */
     194                 :      44695 :     std::size_t add(Value val) {
     195                 :      44695 :         auto idx = context_.size();
     196                 :      44695 :         context_.push_back(val);
     197                 :      44695 :         return idx;
     198                 :            :     }
     199                 :            : 
     200                 :            :     /** Sets the `Value` in the context at index `idx` to `val`. */
     201                 :            :     void set(std::size_t idx, Value val) {
     202                 :            :         M_insist(idx < context_.size(), "index out of bounds");
     203                 :            :         context_[idx] = val;
     204                 :            :     }
     205                 :            : 
     206                 :            :     /** Adds the `Value` `val` to the context and emits a `load` instruction to load this value to the top of the stack.
     207                 :            :      */
     208                 :      36883 :     std::size_t add_and_emit_load(Value val) {
     209                 :      36883 :         auto idx = add(val);
     210                 :      36883 :         emit_Ld_Ctx(idx);
     211                 :      36883 :         return idx;
     212                 :            :     }
     213                 :            : 
     214                 :            :     /** Evaluate this `StackMachine` given the `Tuple`s referenced by `tuples`.
     215                 :            :      *
     216                 :            :      * By convention, the *output* `Tuple`s should be given before the *input* `Tuple`s.  However, a `Tuple` can be used
     217                 :            :      * for both input and output. */
     218                 :            :     void operator()(Tuple **tuples) const;
     219                 :            : 
     220                 :            :     void dump(std::ostream &out) const;
     221                 :            :     void dump() const;
     222                 :            : };
     223                 :            : 
     224                 :            : }

Generated by: LCOV version 1.16