#pragma once #include "program.hpp" #include "opcodes.hpp" #include #include /* The ‹builder› provides convenient means to construct ‹bast› programs. This * is meant for both constructing entirely new programs (see e.g. * ‹▶divcc/bast›) or for rewriting existing ones (see e.g. ‹▶bast::subst›). */ namespace bast::builder { struct block { /* Each builder block stores opcodes in its own chunk of memory (separate * from other blocks), so that instructions (and, perhaps more importantly, * successors) can be appended even if it's not the last block in the * program. */ std::vector< word > code; /* Each block must have a label, which is represented as an opcode in * the standard ‹bast› encoding. It is the labels that actually connect * blocks to each other (see also ‹▶bast::program›). */ explicit block( types::label id ) : code{ block_label( id ) } {} types::label label() const { return { code.front() & 0xfffff }; } /* When a block is created it is given a label, but remains otherwise * empty. The preferred way to fill in the block is by assigning a list * of instructions into it, or perhaps appending more instructions * using ‹+=›: * * a_block = { insn₁, insn₂, … }; * a_block += { insnₖ, insnₖ₊₁, … }; * * NB. Assigning to a block with ‹=› erases any of its outgoing * connections. On the other hand, appending instructions to a block * with existing connections is not supported. */ block &operator=( const std::initializer_list< word > &il ) { code.erase( code.begin() + 1, code.end() ); return *this += il; } block &operator+=( std::initializer_list< word > il ) { ASSERT_NEQ( code.back() & 0xfff00000, opcodes::block_next() ); code.insert( code.end(), il.begin(), il.end() ); return *this; } /* Blocks can be connected using ‘shift’ operators ‹>>› and ‹<<›. Like * with streams, these operators have side effects: ‹x >> y› adds * a connection from ‹x› to ‹y›. Keep in mind that the order of outgoing * connections is semantically irrelevant. */ static void connect( block &from, const block &to ) { from.code.push_back( block_next( to.label() ) ); } block &operator<<( block &o ) const { connect( o, *this ); return o; } block &operator>>( block &o ) { connect( *this, o ); return o; } /* Simple branching can be expressed using the ‹|› (bitwise or) * operator – the mnemonic is that the result of ‹|› is a set of * ‘parallel’ blocks (implemented as a vector of block references, but * the order doesn't actually matter). The ‹>>› overloads below * actually perform the connections. */ std::vector< block * > operator|( block &o ) { return { this, &o }; } std::vector< block * > operator|( std::vector< block * > o ) { o.push_back( this ); return o; } /* The final piece of connection machinery are the vector/block and * block/vector versions of the ‹<<›/‹>>› operators. These allow * connecting parallel sets to blocks (from either side). This means * branching can be written as: * * init >> ( if_branch | else_branch ) >> end * * Additionally, since a block reference can appear more than once in a * single connect expression, a simple single-block ‹while› loop could * be written as: * * init >> loop >> ( end | loop ) * * Expressions like these are mainly useful for writing small program * fragments inline (unit tests often feature them). */ std::vector< block * > operator>>( const std::vector< block * > &os ) { for ( auto o : os ) *this >> *o; return os; } friend block &operator>>( std::vector< block * > as, block &b ) { for ( auto a : as ) *a >> b; return b; } }; /* The following class represents a program in the process of being built, * and is little more than a collection of blocks. */ struct program { /* We keep the blocks in an ‹std::list› so that they don't move around * in memory and so that block references are valid as long as the * ‹program› instance that constructed them exists. */ std::list< block > blocks; /* Blocks are made using ‹add_block›. Do keep the reference. */ block &add_block() { blocks.emplace_back( label{ uint32_t( blocks.size() ) } ); return blocks.back(); } /* The final step is to actually serialize the blocks into a * ‹▶bast::program› instance, which is what the rest of ‹bast› works * with. As soon as the program is constructed, the builder can be * safely discarded. */ bast::program build() const { bast::program p; for ( auto &b : blocks ) { int start = p.code.size() + 1; p.code.insert( p.code.end(), b.code.begin(), b.code.end() ); p.label.emplace_back( start, p.code.size() ); } return p; } /* To recycle the builder instance, it can be cleared instead of * destroyed. There is no practical difference in efficiency, but * clearing is more convenient when the builder is a value attribute of * a long-lived object. */ void clear() { blocks.clear(); } }; }