/* * (c) 2022 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 "opcodes.hpp" #include "program.hpp" #include #include namespace bast { struct invalid_opcode : brq::error { using brq::error::error; }; struct assert_fail : brq::error { using brq::error::error; }; template< typename tag_t, typename state_t, word static_type = static_cast< word >( builtin_type::_count_ ) - 1 > step type_dispatch( word type, state_t &state, word opcode ) { ASSERT( type <= static_type ); if ( type == static_type ) return op< tag_t, builtin_type( static_type ) >::apply( state, opcode ); else if constexpr ( static_type > 0 ) return type_dispatch< tag_t, state_t, static_type - 1 >( type, state, opcode ); else __builtin_unreachable(); } template< typename tag_t > using word_ops = op< tag_t, builtin_type::word >; template< typename tag_t > using type_ops = op< tag_t, builtin_type::type >; template< typename tag_t > using label_ops = op< tag_t, builtin_type::label >; template< typename tag_t_, typename state_t_, typename heap_t_ > struct eval { using state_t = state_t_; using heap_t = heap_t_; using tag_t = tag_t_; program &prog; heap_t &heap; eval( program &p, heap_t &h ) : prog( p ), heap( h ) {} /* Decode and evaluate a single instruction, mutating the given state, * returning a label to jump to, if relevant. */ step instruction( state_t &state, word op ) { if ( ( op & 0xfe000000 ) != 0xaa000000 ) return word_ops< tag_t >::push_word( state, op ); if ( ( op & 0xff000000 ) == 0xab000000 ) return type_dispatch< tag_t >( ( op & 0x00fff000 ) >> 12, state, op & 0xfff ); switch ( op & 0xfff00000 ) { case opcodes::block_label(): brq::raise< invalid_opcode >() << "unexpected label in the middle of a block"; case opcodes::block_next(): return jump{ op & 0x000fffff }; case opcodes::block_push(): return label_ops< tag_t >::push_label( state, { op & 0x000fffff } ); case opcodes::swap_at(): { word i = ( op & 0xf0000 ) >> 16, j = ( op & 0xffff ); state.swap( i, j ); return next; } default: ; } switch ( op & 0xffff0000 ) { case opcodes::push_half(): return word_ops< tag_t >::push_half( state, op ); case opcodes::push_upper(): word_ops< tag_t >::apply( state, opcodes::bv_zext_h< void > ); word_ops< tag_t >::push_word( state, ( op & 0xffff ) << 16 ); return word_ops< tag_t >::apply( state, opcodes::bv_or< void > ); case fail: return error; #if 0 case opcodes::drop_2: type_dispatch< state_t >( ( op & 0xff ) >> 8, state, opcodes::op_drop ); type_dispatch< state_t >( ( op & 0xff ) >> 0, state, opcodes::op_drop ); return next; case opcodes::drop_4: type_dispatch< state_t >( ( op & 0xf ) >> 12, state, opcodes::op_drop ); case opcodes::drop_3: type_dispatch< state_t >( ( op & 0xf ) >> 8, state, opcodes::op_drop ); type_dispatch< state_t >( ( op & 0xf ) >> 4, state, opcodes::op_drop ); type_dispatch< state_t >( ( op & 0xf ) >> 0, state, opcodes::op_drop ); return next; case opcodes::dup: struct_op = opcodes::op_dup_x; case opcodes::swap_0: struct_op = struct_op ?: opcodes::op_swap_0; type = ( op & 0xff00 ) >> 8; depth = ( op & 0x00ff ); return type_dispatch< state_t >( type, state, struct_op | depth ); #endif default: brq::raise< invalid_opcode >() << "invalid opcode " << /*std::hex <<*/ op; } __builtin_unreachable(); } /* Evaluate a single block, yielding the labels of successors. The * state is updated in place. */ template< typename yield_t > bool block( state_t &state, types::label label, yield_t yield ) { for ( word op : prog.block( label ) ) { auto step = instruction( state, op ); if ( step == stop ) return true; if ( step == error ) return false; if ( auto jump = std::get_if< bast::jump >( &step ) ) yield( jump->to ); } return true; } }; struct queue_common { template< typename container_t > static auto pop_back( container_t &c ) { ASSERT( !c.empty() ); auto r = c.back(); c.pop_back(); return r; } template< typename state_t > void error( const state_t &s, types::label w ) { brq::raise< assert_fail >() << "assertion failed in block " << w.v << " and state " << s; } void new_state() {} }; template< typename eval_t, typename queue_t, typename visited_t > struct visit { eval_t eval; queue_t queue; visited_t visited; using state_t = typename eval_t::state_t; using heap_t = typename eval_t::heap_t; visit( program &p, heap_t &heap ) : eval( p, heap ) {} visit( program &p, heap_t &heap, const queue_t &q, const visited_t &v ) : eval( p, heap ), queue( q ), visited( v ) {} void start( types::label label = { 0 } ) { queue.new_state(); queue.push( state_t(), label ); visited.store( state_t(), label ); } bool step() { auto top = queue.pop(); auto &[ state, label ] = top; auto push = [&]( types::label next ) { auto &[ state, label ] = top; if ( visited.store( state, next ) ) queue.push( state, next ); }; queue.new_state(); if ( !eval.block( state, label, push ) ) queue.error( state, label ); return !queue.empty(); } }; }