// -*- C++ -*- (c) 2014 Petr Rockai #include #ifndef DIVINE_LLVM_SILK_EVAL_H #define DIVINE_LLVM_SILK_EVAL_H namespace divine { namespace silk { /* * A very simple strict evaluator for silk expressions. Uses 3 stacks: * - primitive values: integers and lambdas * - operations: prims and application * - scopes: keep track of variable mappings * * Each lambda in the program is compiled down to a pair of stacks, one for * operations and second for values and symbol refs. When the evaluator sees an * application, the top of the value stack is interpreted as an index into the * compiled program text. The opstack from the program text is pushed (in * reverse order) on the evaluator's opstack, the value stack is copied onto * the value stack (again, reverse order), dereferencing (copying) all free * variables in the process (sort-of lambda lifting). */ namespace eval { struct Value { int v; explicit Value( int v ) : v( v ) {} Value() : v( 0 ) {} }; using SymbolMap = std::map< std::string, int >; struct LabelMap { int counter; LabelMap() : counter( 0 ) {} }; struct Symbol { int id; Symbol( SymbolMap &m, std::string n ) { if ( m.find( n ) == m.end() ) id = m[ n ] = m.size(); else id = m[ n ]; } }; /* * An immediate use of a symbol (as opposed to a 'quoted symbol or a * right-hand-side of subscope access -- .). */ struct Use : Symbol { Use( SymbolMap &m, std::string n ) : Symbol( m, n ) {} }; using Op = parse::TI; // a little bit of abuse to avoid conversions struct Label { int id; explicit Label( LabelMap &m ) : id( m.counter++ ) {} explicit Label( int i ) : id( i ) {} }; using SymItem = brick::types::Union< Value, Use, Symbol, Op >; using ConItem = brick::types::Union< Value, Op >; using SymStack = std::vector< SymItem >; using ConStack = std::vector< ConItem >; using DefBlock = std::map< int, int >; using DefBlocks = std::vector< DefBlock >; /* map an identifier to a value; each lambda binds exactly one value, hence we * always know where to pop when leaving a lambda; indexed by symbol IDs */ using ScopeStack = std::vector< std::vector< Value > >; struct Text { void add( Label l, Symbol bind, const SymStack &c ) { ASSERT_EQ( l.id, int( code.size() ) ); ASSERT_EQ( l.id, int( binders.size() ) ); code.push_back( c ); binders.push_back( bind ); } std::vector< SymStack > code; std::vector< Symbol > binders; DefBlocks defs; }; struct Compiler { Symbol symbol( parse::Identifier i ) { return Symbol( syms, i.name ); } Symbol symbol( parse::Pattern p ) { return Symbol( syms, p.name() ); } void compile( parse::Expression &e, SymStack &code ) { e.e.match( [&]( const parse::Constant &x ) { if ( x.scope ) code.push_back( Value( compile( *x.scope ) ) ); else code.push_back( Value( x.value ) ); }, [&]( const parse::Identifier &x ) { code.push_back( Use( syms, x.name ) ); }, [&]( const parse::Lambda &x ) { compile( x, code ); }, [&]( const parse::SubScope &x ) { code.push_back( Op::SubScope ); compile( *x.lhs, code ); if ( x.rhs->e.is< parse::Identifier >() ) code.push_back( Symbol( syms, x.rhs->e.get< parse::Identifier >().name ) ); else compile( *x.rhs, code ); }, [&]( const parse::BinOp &x ) { code.push_back( x.op ); compile( *x.lhs, code ); compile( *x.rhs, code ); }, [&]( const parse::Application &x ) { code.push_back( Op::Application ); compile( *x.lhs, code ); compile( *x.rhs, code ); }, [&]( const parse::IfThenElse &x ) { code.push_back( Op::If ); compile( *x.cond, code ); thunk( *x.yes, code ); thunk( *x.no, code ); } ); } Label lambda( parse::Expression &e, Symbol bind ) { SymStack code; compile( e, code ); Label lbl( labels ); text.add( lbl, bind, code ); return lbl; } Label thunk( parse::Expression &e ) { return lambda( e, Symbol( syms, "_dummy" ) ); } void thunk( parse::Expression &e, SymStack &code ) { code.push_back( Value( thunk( e ).id ) ); } Label compile( const parse::Lambda &l ) { return lambda( *l.body, symbol( l.bind ) ); } void compile( const parse::Lambda &l, SymStack &code ) { code.push_back( Value( compile( l ).id ) ); } int compile( parse::Scope &s ) { DefBlock def; for ( auto &b: s.bindings ) def[ symbol( b.name ).id ] = thunk( b.value ).id; text.defs.push_back( def ); return text.defs.size() - 1; } Text text; /* the result */ private: LabelMap labels; SymbolMap syms; }; struct Evaluator { const Text &text; Evaluator( const Text &text ) : text( text ) {} Value vpop() { ASSERT( !stack.empty() ); auto v = stack.back(); stack.pop_back(); return v; } ConItem cpop() { ASSERT( !code.empty() ); auto v = code.back(); code.pop_back(); return v; } void enter( Label l, Value v ) { auto &body = text.code[ l.id ]; int binder = text.binders[ l.id ].id; scope.resize( std::max( int( scope.size() ), binder + 1 ) ); scope[ binder ].push_back( v ); std::transform( body.begin(), body.end(), std::back_inserter( code ), [&]( SymItem x ) { return x.match( [&]( Value v ) -> ConItem { return v; }, [&]( Use s ) -> ConItem { ASSERT( int( scope.size() ) > s.id ); ASSERT( scope[ s.id ].size() ); return Value( scope[ s.id ].back() ); }, [&]( Symbol s ) -> ConItem { return Value( s.id ); }, [&]( Op o ) -> ConItem { return o; } ).value(); } ); } int binop( Op op, int a, int b ) { switch ( op ) { case Op::Bool_Or: return a || b; case Op::Bool_And: return a && b; case Op::LEq: return a <= b; case Op::Lt: return a < b; case Op::GEq: return a >= b; case Op::Gt: return a > b; case Op::Eq: return a == b; case Op::NEq: return a != b; case Op::Plus: return a + b; case Op::Minus: return a - b; case Op::Mult: return a * b; case Op::Div: return a / b; case Op::Mod: return a % b; case Op::Or: return a | b; case Op::And: return a & b; case Op::Xor: return a ^ b; case Op::LShift: return a << b; case Op::RShift: return a >> b; default: ASSERT_UNREACHABLE( "unknown binop" ); } } void step() { auto op = cpop(); op.match( [&]( Op op ) { step( op ); }, [&]( Value v ) { stack.push_back( v ); } ); } void step( Op op ) { switch ( op ) { case Op::Application: { Value fun = vpop(); Value param = vpop(); return enter( Label( fun.v ), param ); } case Op::SubScope: { Value block = vpop(); Value sym = vpop(); ASSERT( text.defs[ block.v ].count( sym.v ) ); /* bindings are always thunks */ return enter( Label( text.defs[ block.v ].find( sym.v )->second ), Value() ); } case Op::Bind: { /* more horrible token-id abuse */ Value symbol = vpop(); scope[ symbol.v ].pop_back(); return; } case Op::If: { Value cond = vpop(); Value yes = vpop(), no = vpop(); return enter( Label( cond.v ? yes.v : no.v ), Value() ); // thunks } default: { Value a = vpop(), b = vpop(); stack.push_back( Value( binop( op, a.v, b.v ) ) ); return; } } } int reduce() { while ( !code.empty() ) step(); ASSERT_EQ( int(stack.size()), 1 ); Value r = stack.back(); stack.pop_back(); return r.v; } private: ConStack code; ScopeStack scope; std::vector< Value > stack; }; } } } namespace divine_test { namespace silk { using namespace divine::silk; using namespace eval; struct Eval { Compiler comp; template< typename T > std::shared_ptr< T > _parse( std::string s ) { return std::make_shared< T >( Parse::_parse< T >( s ) ); } int _expression( std::string s ) { auto top = _parse< Expression >( s ); Label l = comp.thunk( *top ); Evaluator eval( comp.text ); eval.enter( l, Value() ); return eval.reduce(); } TEST(basic) { ASSERT_EQ( _expression( "1 + 1" ), 2 ); ASSERT_EQ( _expression( "2 - 1" ), 1 ); ASSERT_EQ( _expression( "2 - (1 + 1)" ), 0 ); ASSERT_EQ( _expression( "2 * 2 + 1" ), 5 ); ASSERT_EQ( _expression( "2 * (2 + 1)" ), 6 ); } TEST(lambda) { ASSERT_EQ( _expression( "1 + ((|x| x + 1) 4)" ), 6 ); ASSERT_EQ( _expression( "1 + ((|x| x - 1) 4)" ), 4 ); ASSERT_EQ( _expression( "1 + ((|x| |y| x - y) 4 2)" ), 3 ); ASSERT_EQ( _expression( "1 + ((|x| 1 + (|y| x - y) 2) 4)" ), 4 ); ASSERT_EQ( _expression( "(|x| 1 + (|y| x * y) 2) 4" ), 9 ); } TEST(conditional) { ASSERT_EQ( _expression( "if 1 then 5 else 2" ), 5 ); ASSERT_EQ( _expression( "1 + (if 1 then 5 else 2)" ), 6 ); ASSERT_EQ( _expression( "(|x| if x then x + 5 else x + 2) 0" ), 2 ); ASSERT_EQ( _expression( "(|x| if x then x + 5 else x + 2) 1" ), 6 ); } TEST(scope) { ASSERT_EQ( _expression( "{ a = 5 }.a" ), 5 ); ASSERT_EQ( _expression( "{ a = 5 + 3 }.a" ), 8 ); ASSERT_EQ( _expression( "{ a = |x| x + 3 }.a 5" ), 8 ); ASSERT_EQ( _expression( "(|x| x.x + 3) { x = 1 }" ), 4 ); ASSERT_EQ( _expression( "{ a = |x| x.x + 3 }.a { x = 1 }" ), 4 ); } }; } } #endif