/* * (c) 2023 Petr Ročkai * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "types.hpp" #include "eval.hpp" #include "stack.hpp" #include "builder.hpp" #include #include #include /* Structures define both value types and operations on them. Logically, each * value takes up a single slot on the value stack. However, a value type * defined by a «logical» structure may be composed of multiple values which * belong to its underlying (physical or logical) structures. For instance: * * ╷ ╷ * ↑ top ╷ … ╷ ├─────────────────────┤ * ├────────────────────┤ subst. │ value A (physical) │ * │ value A (physical) │ ╭───▶┼─────────────────────┤ * ├────────────────────┼────╯ │ value B₁ (physical) │ * │ value B (logical) │ ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤ * ├────────────────────┼────╮ │ value B₂ (physical) │ * │ value C (physical) │ ╰───▶┼─────────────────────┤ * ├────────────────────┤ │ value C (physical) │ * ↓ bottom │ value D (physical) │ ├─────────────────────┤ * ╵ … ╵ ╵ ╵ * * Nonetheless, this is an internal matter of the logical structure in question * – the program itself has no way to deal with composite logical values. This * poses some practical challenges regarding ‹swap› operations: * * • a swap of unrelated values needs to be adjusted if a logical composite * value is above either of its operands (e.g. swap of A, C above – their * distance was increased by 1 during substitution), * • a homogeneous ‹swap› of logical values needs to be performed in multiple * steps (one step for each constituent value), * • heterogeneous swaps are the most problematic, since all intervening * values (those on the stack between the two operands) may need to be * shifted (e.g. swap of B and D, where C needs to be moved one position up * as well). */ namespace bast { /* Physical structures can be directly evaluated, i.e. their operations can * be applied to a suitably represented state. The default state * implementation for ‹bast› is ‹basic_stack›, which stores each value as a * single 32b unsigned integer. However, alternative evaluators can use * alternative state representations (see e.g. ‹symba›). * * «Implementation note»: The state representation used by a given physical * structure is fixed. In normal circumstances, an evaluator is composed at * compile time (using templates), but in principle, evaluators can be * constructed using virtual calls as well (hence physical structures need * to inherit from this base type). Clearly, a single evaluator can only * use structures that share their state representation. */ template< typename state_t > struct p_struct { virtual step apply( state_t &, word ) = 0; }; /* Logical structures cannot be directly executed and are instead realized * through substitution – each operation of a logical structure is * translated into a sequence of operations of some other structure or * structures. While the target structure(s) can be again logical * structures, eventually, to execute or compile the program, all logical * structures must be removed from the program by translating them into * types and operations from physical structures. Note however, that a * single structure can be both logical and physical. */ struct l_struct { virtual builder::block *translate( word op, builder::program &, builder::block & ) = 0; /* This method is used to decide whether two logical structures are the * same. The comparison is only required to work for two instances of * the same C++ type. See ‹brq::dyn_type_map› for details. */ virtual std::strong_ordering compare( const l_struct & ) { return std::strong_ordering::equal; } virtual ~l_struct() = default; }; /* We need to be able to build programs with a dynamic selection of logical * structures, e.g. different bitvectors of non-standard lengths that arise * in LLVM programs (each LLVM program only has finitely many distinct ‹iN› * types in it, but in general, the number of such types is not bounded). * For this reason, we attach a ‹struct_map› to each bast program. Its purpose * is to map structure identifiers (‹stid›) to the implementations of the * respective logical structures (instances of ‹l_struct›). This allows the * program to be interpreted (opcodes with ‹stid›s outside of the basic * physical range do not have fixed meaning). */ struct struct_map : brq::dyn_type_map< l_struct > { struct_map() : brq::dyn_type_map< l_struct >( 8 ) {} }; template< typename state_t, typename byte_t, typename half_t, typename word_t, typename dyad_t, typename blob_t, typename memd_t, typename memp_t > struct struct_eval { }; }