diff options
author | John Hodge <tpg@mutabah.net> | 2017-02-04 10:42:21 +0800 |
---|---|---|
committer | John Hodge <tpg@mutabah.net> | 2017-02-04 10:42:21 +0800 |
commit | c3c3c98e9346f500ec955a32200f27befd0fd6da (patch) | |
tree | 4b73d76dabf3ece9fd0cb9d6d63b5a9c8626bc0d | |
parent | c72c7c2024df0d1ef53249f756f63d07d82fa581 (diff) | |
download | mrust-c3c3c98e9346f500ec955a32200f27befd0fd6da.tar.gz |
MIR Validate - Slight refactor for cleaner code
-rw-r--r-- | src/mir/check.cpp | 654 | ||||
-rw-r--r-- | src/mir/helpers.hpp | 2 |
2 files changed, 331 insertions, 325 deletions
diff --git a/src/mir/check.cpp b/src/mir/check.cpp index 4d7c198b..a2d5812a 100644 --- a/src/mir/check.cpp +++ b/src/mir/check.cpp @@ -84,6 +84,334 @@ namespace { } } +// [ValState] = Value state tracking (use after move, uninit, ...) +// - [ValState] No drops or usage of uninitalised values (Uninit, Moved, or Dropped) +// - [ValState] Temporaries are write-once. +// - Requires maintaining state information for all variables/temporaries with support for loops +void MIR_Validate_ValState(::MIR::TypeResolve& state, const ::MIR::Function& fcn) +{ + // > Iterate through code, creating state maps. Save map at the start of each bb. + struct ValStates { + enum class State { + Invalid, + Either, + Valid, + }; + State ret_state = State::Invalid; + ::std::vector<State> arguments; + ::std::vector<State> temporaries; + ::std::vector<State> variables; + + ValStates() {} + ValStates(size_t n_args, size_t n_temps, size_t n_vars): + arguments(n_args, State::Valid), + temporaries(n_temps), + variables(n_vars) + { + } + + bool operator==(const ValStates& x) const { + if( ret_state != x.ret_state ) return false; + if( arguments != x.arguments ) return false; + if( temporaries != x.temporaries ) return false; + if( variables != x.variables ) return false; + return true; + } + + bool empty() const { + return arguments.empty() && temporaries.empty() && variables.empty(); + } + + bool merge(ValStates& other) + { + if( this->empty() ) + { + *this = other; + return true; + } + else if( this->arguments == other.arguments && this->temporaries == other.temporaries && this->variables == other.variables ) + { + return false; + } + else + { + bool rv = false; + rv |= ValStates::merge_lists(this->arguments, other.arguments); + rv |= ValStates::merge_lists(this->temporaries, other.temporaries); + rv |= ValStates::merge_lists(this->variables, other.variables); + return rv; + } + } + + void mark_validity(const ::MIR::TypeResolve& state, const ::MIR::LValue& lv, bool is_valid) + { + TU_MATCH_DEF( ::MIR::LValue, (lv), (e), + ( + ), + (Return, + ret_state = is_valid ? State::Valid : State::Invalid; + ), + (Argument, + MIR_ASSERT(state, e.idx < this->arguments.size(), ""); + this->arguments[e.idx] = is_valid ? State::Valid : State::Invalid; + ), + (Variable, + MIR_ASSERT(state, e < this->variables.size(), ""); + this->variables[e] = is_valid ? State::Valid : State::Invalid; + ), + (Temporary, + MIR_ASSERT(state, e.idx < this->temporaries.size(), ""); + this->temporaries[e.idx] = is_valid ? State::Valid : State::Invalid; + ) + ) + } + void ensure_valid(const ::MIR::TypeResolve& state, const ::MIR::LValue& lv) + { + TU_MATCH( ::MIR::LValue, (lv), (e), + (Variable, + MIR_ASSERT(state, e < this->variables.size(), ""); + if( this->variables[e] != State::Valid ) + MIR_BUG(state, "Use of non-valid variable - " << lv); + ), + (Temporary, + MIR_ASSERT(state, e.idx < this->temporaries.size(), ""); + if( this->temporaries[e.idx] != State::Valid ) + MIR_BUG(state, "Use of non-valid temporary - " << lv); + ), + (Argument, + MIR_ASSERT(state, e.idx < this->arguments.size(), ""); + if( this->arguments[e.idx] != State::Valid ) + MIR_BUG(state, "Use of non-valid argument - " << lv); + ), + (Return, + if( this->ret_state != State::Valid ) + MIR_BUG(state, "Use of non-valid lvalue - " << lv); + ), + (Static, + ), + (Field, + ensure_valid(state, *e.val); + ), + (Deref, + ensure_valid(state, *e.val); + ), + (Index, + ensure_valid(state, *e.val); + ensure_valid(state, *e.idx); + ), + (Downcast, + ensure_valid(state, *e.val); + ) + ) + } + void move_val(const ::MIR::TypeResolve& state, const ::MIR::LValue& lv) + { + ensure_valid(state, lv); + ::HIR::TypeRef tmp; + if( ! state.m_resolve.type_is_copy( state.sp, state.get_lvalue_type(tmp, lv) ) ) + { + mark_validity(state, lv, false); + } + } + private: + static bool merge_lists(::std::vector<State>& a, ::std::vector<State>& b) + { + bool rv = false; + assert( a.size() == b.size() ); + for(unsigned int i = 0; i < a.size(); i++) + { + if( a[i] != b[i] ) { + if( a[i] == State::Either || b[i] == State::Either ) { + rv = true; + } + a[i] = b[i] = State::Either; + } + } + return rv; + } + }; + ::std::vector< ValStates> block_start_states( fcn.blocks.size() ); + struct ToVisit { + unsigned int bb; + ::std::vector<unsigned int> path; + ValStates state; + }; + ::std::vector<ToVisit> to_visit_blocks; + + auto add_to_visit = [&](unsigned int idx, ::std::vector<unsigned int> src_path, auto vs) { + for(const auto& b : to_visit_blocks) + if( b.bb == idx && b.state == vs) + return ; + if( block_start_states.at(idx) == vs ) + return ; + src_path.push_back(idx); + to_visit_blocks.push_back( ToVisit { idx, mv$(src_path), mv$(vs) } ); + }; + add_to_visit( 0, {}, ValStates { state.m_args.size(), fcn.temporaries.size(), fcn.named_variables.size() } ); + while( to_visit_blocks.size() > 0 ) + { + auto block = to_visit_blocks.back().bb; + auto path = mv$(to_visit_blocks.back().path); + auto val_state = mv$( to_visit_blocks.back().state ); + to_visit_blocks.pop_back(); + assert(block < fcn.blocks.size()); + + // 1. Apply current state to `block_start_states` (merging if needed) + // - If no change happened, skip. + if( ! block_start_states.at(block).merge( val_state ) ) { + continue ; + } + DEBUG("BB" << block << " via [" << path << "]"); + + // 2. Using the newly merged state, iterate statements checking the usage and updating state. + const auto& bb = fcn.blocks[block]; + for(unsigned int stmt_idx = 0; stmt_idx < bb.statements.size(); stmt_idx ++) + { + const auto& stmt = bb.statements[stmt_idx]; + state.set_cur_stmt(block, stmt_idx); + + switch( stmt.tag() ) + { + case ::MIR::Statement::TAGDEAD: + throw ""; + case ::MIR::Statement::TAG_SetDropFlag: + break; + case ::MIR::Statement::TAG_Drop: + // Invalidate the slot + if( stmt.as_Drop().flag_idx == ~0u ) + { + val_state.ensure_valid(state, stmt.as_Drop().slot); + } + val_state.mark_validity( state, stmt.as_Drop().slot, false ); + break; + case ::MIR::Statement::TAG_Asm: + for(const auto& v : stmt.as_Asm().inputs) + val_state.ensure_valid(state, v.second); + for(const auto& v : stmt.as_Asm().outputs) + val_state.mark_validity( state, v.second, true ); + break; + case ::MIR::Statement::TAG_Assign: + // Check source (and invalidate sources) + TU_MATCH( ::MIR::RValue, (stmt.as_Assign().src), (se), + (Use, + val_state.move_val(state, se); + ), + (Constant, + ), + (SizedArray, + val_state.move_val(state, se.val); + ), + (Borrow, + val_state.ensure_valid(state, se.val); + ), + (Cast, + // Well.. it's not exactly moved... + val_state.ensure_valid(state, se.val); + //val_state.move_val(state, se.val); + ), + (BinOp, + val_state.move_val(state, se.val_l); + val_state.move_val(state, se.val_r); + ), + (UniOp, + val_state.move_val(state, se.val); + ), + (DstMeta, + val_state.ensure_valid(state, se.val); + ), + (DstPtr, + val_state.ensure_valid(state, se.val); + ), + (MakeDst, + //val_state.move_val(state, se.ptr_val); + val_state.ensure_valid(state, se.ptr_val); + val_state.move_val(state, se.meta_val); + ), + (Tuple, + for(const auto& v : se.vals) + val_state.move_val(state, v); + ), + (Array, + for(const auto& v : se.vals) + val_state.move_val(state, v); + ), + (Variant, + val_state.move_val(state, se.val); + ), + (Struct, + for(const auto& v : se.vals) + val_state.move_val(state, v); + ) + ) + // Mark destination as valid + val_state.mark_validity( state, stmt.as_Assign().dst, true ); + break; + } + } + + // 3. Pass new state on to destination blocks + state.set_cur_stmt_term(block); + TU_MATCH(::MIR::Terminator, (bb.terminator), (e), + (Incomplete, + // Should be impossible here. + ), + (Return, + // Check if the return value has been set + val_state.ensure_valid( state, ::MIR::LValue::make_Return({}) ); + // Ensure that no other non-Copy values are valid + for(unsigned int i = 0; i < val_state.variables.size(); i ++) + { + if( val_state.variables[i] == ValStates::State::Invalid ) + { + } + else if( state.m_resolve.type_is_copy(state.sp, fcn.named_variables[i]) ) + { + } + else + { + // TODO: Error, becuase this has just been leaked + } + } + ), + (Diverge, + // TODO: Ensure that cleanup has been performed. + ), + (Goto, + // Push block with the new state + add_to_visit( e, mv$(path), mv$(val_state) ); + ), + (Panic, + // What should be done here? + ), + (If, + // Push blocks + val_state.ensure_valid( state, e.cond ); + add_to_visit( e.bb0, path, val_state ); + add_to_visit( e.bb1, mv$(path), mv$(val_state) ); + ), + (Switch, + val_state.ensure_valid( state, e.val ); + for(const auto& tgt : e.targets) + { + add_to_visit( tgt, path, val_state ); + } + ), + (Call, + if( e.fcn.is_Value() ) + val_state.ensure_valid( state, e.fcn.as_Value() ); + for(const auto& arg : e.args) + val_state.move_val( state, arg ); + // Push blocks (with return valid only in one) + add_to_visit(e.panic_block, path, val_state); + + // TODO: If the function returns !, don't follow the ret_block + val_state.mark_validity( state, e.ret_val, true ); + add_to_visit(e.ret_block, mv$(path), mv$(val_state)); + ) + ) + } +} + void MIR_Validate(const StaticTraitResolve& resolve, const ::HIR::ItemPath& path, const ::MIR::Function& fcn, const ::HIR::Function::args_t& args, const ::HIR::TypeRef& ret_type) { TRACE_FUNCTION_F(path); @@ -163,331 +491,7 @@ void MIR_Validate(const StaticTraitResolve& resolve, const ::HIR::ItemPath& path } // [ValState] = Value state tracking (use after move, uninit, ...) - // - [ValState] No drops or usage of uninitalised values (Uninit, Moved, or Dropped) - // - [ValState] Temporaries are write-once. - // - Requires maintaining state information for all variables/temporaries with support for loops - { - // > Iterate through code, creating state maps. Save map at the start of each bb. - struct ValStates { - enum class State { - Invalid, - Either, - Valid, - }; - State ret_state = State::Invalid; - ::std::vector<State> arguments; - ::std::vector<State> temporaries; - ::std::vector<State> variables; - - ValStates() {} - ValStates(size_t n_args, size_t n_temps, size_t n_vars): - arguments(n_args, State::Valid), - temporaries(n_temps), - variables(n_vars) - { - } - - bool operator==(const ValStates& x) const { - if( ret_state != x.ret_state ) return false; - if( arguments != x.arguments ) return false; - if( temporaries != x.temporaries ) return false; - if( variables != x.variables ) return false; - return true; - } - - bool empty() const { - return arguments.empty() && temporaries.empty() && variables.empty(); - } - - bool merge(ValStates& other) - { - if( this->empty() ) - { - *this = other; - return true; - } - else if( this->arguments == other.arguments && this->temporaries == other.temporaries && this->variables == other.variables ) - { - return false; - } - else - { - bool rv = false; - rv |= ValStates::merge_lists(this->arguments, other.arguments); - rv |= ValStates::merge_lists(this->temporaries, other.temporaries); - rv |= ValStates::merge_lists(this->variables, other.variables); - return rv; - } - } - - void mark_validity(const ::MIR::TypeResolve& state, const ::MIR::LValue& lv, bool is_valid) - { - TU_MATCH_DEF( ::MIR::LValue, (lv), (e), - ( - ), - (Return, - ret_state = is_valid ? State::Valid : State::Invalid; - ), - (Argument, - MIR_ASSERT(state, e.idx < this->arguments.size(), ""); - this->arguments[e.idx] = is_valid ? State::Valid : State::Invalid; - ), - (Variable, - MIR_ASSERT(state, e < this->variables.size(), ""); - this->variables[e] = is_valid ? State::Valid : State::Invalid; - ), - (Temporary, - MIR_ASSERT(state, e.idx < this->temporaries.size(), ""); - this->temporaries[e.idx] = is_valid ? State::Valid : State::Invalid; - ) - ) - } - void ensure_valid(const ::MIR::TypeResolve& state, const ::MIR::LValue& lv) - { - TU_MATCH( ::MIR::LValue, (lv), (e), - (Variable, - MIR_ASSERT(state, e < this->variables.size(), ""); - if( this->variables[e] != State::Valid ) - MIR_BUG(state, "Use of non-valid variable - " << lv); - ), - (Temporary, - MIR_ASSERT(state, e.idx < this->temporaries.size(), ""); - if( this->temporaries[e.idx] != State::Valid ) - MIR_BUG(state, "Use of non-valid temporary - " << lv); - ), - (Argument, - MIR_ASSERT(state, e.idx < this->arguments.size(), ""); - if( this->arguments[e.idx] != State::Valid ) - MIR_BUG(state, "Use of non-valid argument - " << lv); - ), - (Return, - if( this->ret_state != State::Valid ) - MIR_BUG(state, "Use of non-valid lvalue - " << lv); - ), - (Static, - ), - (Field, - ensure_valid(state, *e.val); - ), - (Deref, - ensure_valid(state, *e.val); - ), - (Index, - ensure_valid(state, *e.val); - ensure_valid(state, *e.idx); - ), - (Downcast, - ensure_valid(state, *e.val); - ) - ) - } - void move_val(const ::MIR::TypeResolve& state, const ::MIR::LValue& lv) - { - ensure_valid(state, lv); - ::HIR::TypeRef tmp; - if( ! state.m_resolve.type_is_copy( state.sp, state.get_lvalue_type(tmp, lv) ) ) - { - mark_validity(state, lv, false); - } - } - private: - static bool merge_lists(::std::vector<State>& a, ::std::vector<State>& b) - { - bool rv = false; - assert( a.size() == b.size() ); - for(unsigned int i = 0; i < a.size(); i++) - { - if( a[i] != b[i] ) { - if( a[i] == State::Either || b[i] == State::Either ) { - rv = true; - } - a[i] = b[i] = State::Either; - } - } - return rv; - } - }; - ::std::vector< ValStates> block_start_states( fcn.blocks.size() ); - struct ToVisit { - unsigned int bb; - ::std::vector<unsigned int> path; - ValStates state; - }; - ::std::vector<ToVisit> to_visit_blocks; - - auto add_to_visit = [&](unsigned int idx, ::std::vector<unsigned int> src_path, auto vs) { - for(const auto& b : to_visit_blocks) - if( b.bb == idx && b.state == vs) - return ; - if( block_start_states.at(idx) == vs ) - return ; - src_path.push_back(idx); - to_visit_blocks.push_back( ToVisit { idx, mv$(src_path), mv$(vs) } ); - }; - add_to_visit( 0, {}, ValStates { args.size(), fcn.temporaries.size(), fcn.named_variables.size() } ); - while( to_visit_blocks.size() > 0 ) - { - auto block = to_visit_blocks.back().bb; - auto path = mv$(to_visit_blocks.back().path); - auto val_state = mv$( to_visit_blocks.back().state ); - to_visit_blocks.pop_back(); - assert(block < fcn.blocks.size()); - - // 1. Apply current state to `block_start_states` (merging if needed) - // - If no change happened, skip. - if( ! block_start_states.at(block).merge( val_state ) ) { - continue ; - } - DEBUG("BB" << block << " via [" << path << "]"); - - // 2. Using the newly merged state, iterate statements checking the usage and updating state. - const auto& bb = fcn.blocks[block]; - for(unsigned int stmt_idx = 0; stmt_idx < bb.statements.size(); stmt_idx ++) - { - const auto& stmt = bb.statements[stmt_idx]; - state.set_cur_stmt(block, stmt_idx); - - switch( stmt.tag() ) - { - case ::MIR::Statement::TAGDEAD: - throw ""; - case ::MIR::Statement::TAG_SetDropFlag: - break; - case ::MIR::Statement::TAG_Drop: - // Invalidate the slot - if( stmt.as_Drop().flag_idx == ~0u ) - { - val_state.ensure_valid(state, stmt.as_Drop().slot); - } - val_state.mark_validity( state, stmt.as_Drop().slot, false ); - break; - case ::MIR::Statement::TAG_Asm: - for(const auto& v : stmt.as_Asm().inputs) - val_state.ensure_valid(state, v.second); - for(const auto& v : stmt.as_Asm().outputs) - val_state.mark_validity( state, v.second, true ); - break; - case ::MIR::Statement::TAG_Assign: - // Check source (and invalidate sources) - TU_MATCH( ::MIR::RValue, (stmt.as_Assign().src), (se), - (Use, - val_state.move_val(state, se); - ), - (Constant, - ), - (SizedArray, - val_state.move_val(state, se.val); - ), - (Borrow, - val_state.ensure_valid(state, se.val); - ), - (Cast, - // Well.. it's not exactly moved... - val_state.ensure_valid(state, se.val); - //val_state.move_val(state, se.val); - ), - (BinOp, - val_state.move_val(state, se.val_l); - val_state.move_val(state, se.val_r); - ), - (UniOp, - val_state.move_val(state, se.val); - ), - (DstMeta, - val_state.ensure_valid(state, se.val); - ), - (DstPtr, - val_state.ensure_valid(state, se.val); - ), - (MakeDst, - //val_state.move_val(state, se.ptr_val); - val_state.ensure_valid(state, se.ptr_val); - val_state.move_val(state, se.meta_val); - ), - (Tuple, - for(const auto& v : se.vals) - val_state.move_val(state, v); - ), - (Array, - for(const auto& v : se.vals) - val_state.move_val(state, v); - ), - (Variant, - val_state.move_val(state, se.val); - ), - (Struct, - for(const auto& v : se.vals) - val_state.move_val(state, v); - ) - ) - // Mark destination as valid - val_state.mark_validity( state, stmt.as_Assign().dst, true ); - break; - } - } - - // 3. Pass new state on to destination blocks - state.set_cur_stmt_term(block); - TU_MATCH(::MIR::Terminator, (bb.terminator), (e), - (Incomplete, - // Should be impossible here. - ), - (Return, - // Check if the return value has been set - val_state.ensure_valid( state, ::MIR::LValue::make_Return({}) ); - // Ensure that no other non-Copy values are valid - for(unsigned int i = 0; i < val_state.variables.size(); i ++) - { - if( val_state.variables[i] == ValStates::State::Invalid ) - { - } - else if( state.m_resolve.type_is_copy(state.sp, fcn.named_variables[i]) ) - { - } - else - { - // TODO: Error, becuase this has just been leaked - } - } - ), - (Diverge, - // TODO: Ensure that cleanup has been performed. - ), - (Goto, - // Push block with the new state - add_to_visit( e, mv$(path), mv$(val_state) ); - ), - (Panic, - // What should be done here? - ), - (If, - // Push blocks - val_state.ensure_valid( state, e.cond ); - add_to_visit( e.bb0, path, val_state ); - add_to_visit( e.bb1, mv$(path), mv$(val_state) ); - ), - (Switch, - val_state.ensure_valid( state, e.val ); - for(const auto& tgt : e.targets) - { - add_to_visit( tgt, path, val_state ); - } - ), - (Call, - if( e.fcn.is_Value() ) - val_state.ensure_valid( state, e.fcn.as_Value() ); - for(const auto& arg : e.args) - val_state.move_val( state, arg ); - // Push blocks (with return valid only in one) - add_to_visit(e.panic_block, path, val_state); - - // TODO: If the function returns !, don't follow the ret_block - val_state.mark_validity( state, e.ret_val, true ); - add_to_visit(e.ret_block, mv$(path), mv$(val_state)); - ) - ) - } - } + MIR_Validate_ValState(state, fcn); // [Flat] = Basic checks (just iterates BBs) // - [Flat] Types must be valid (correct type for slot etc.) diff --git a/src/mir/helpers.hpp b/src/mir/helpers.hpp index ab558791..ece20605 100644 --- a/src/mir/helpers.hpp +++ b/src/mir/helpers.hpp @@ -48,9 +48,11 @@ public: const ::HIR::Crate& m_crate; private: ::FmtLambda m_path; +public: const ::HIR::TypeRef& m_ret_type; const args_t& m_args; const ::MIR::Function& m_fcn; +private: const ::HIR::SimplePath* m_lang_Box = nullptr; unsigned int bb_idx = 0; |