summaryrefslogtreecommitdiff
path: root/src/mir/check_full.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mir/check_full.cpp')
-rw-r--r--src/mir/check_full.cpp191
1 files changed, 189 insertions, 2 deletions
diff --git a/src/mir/check_full.cpp b/src/mir/check_full.cpp
index 8dcd359c..1f527ff2 100644
--- a/src/mir/check_full.cpp
+++ b/src/mir/check_full.cpp
@@ -12,7 +12,8 @@
#include <mir/helpers.hpp>
#include <mir/visit_crate_mir.hpp>
-#define ENABLE_LEAK_DETECTOR 1
+// DISABLED: Unsizing intentionally leaks
+#define ENABLE_LEAK_DETECTOR 0
namespace
{
@@ -151,6 +152,134 @@ namespace
ensure_valid(mir_res, lv, vs, path);
}
private:
+ struct InvalidReason {
+ enum {
+ Unwritten,
+ Moved,
+ Invalidated,
+ } ty;
+ size_t bb;
+ size_t stmt;
+
+ void fmt(::std::ostream& os) const {
+ switch(this->ty)
+ {
+ case Unwritten: os << "Not Written"; break;
+ case Moved: os << "Moved at BB" << bb << "/" << stmt; break;
+ case Invalidated: os << "Invalidated at BB" << bb << "/" << stmt; break;
+ }
+ }
+ };
+ InvalidReason find_invalid_reason(const ::MIR::TypeResolve& mir_res, const ::MIR::LValue& root_lv) const
+ {
+ using ::MIR::visit::ValUsage;
+ using ::MIR::visit::visit_mir_lvalues;
+
+ ::HIR::TypeRef tmp;
+ bool is_copy = mir_res.m_resolve.type_is_copy( mir_res.sp, mir_res.get_lvalue_type(tmp, root_lv) );
+
+ size_t cur_stmt = mir_res.get_cur_stmt_ofs();
+ if( !is_copy )
+ {
+ // Walk backwards through the BBs and find where it's used by value
+ assert(this->bb_path.size() > 0);
+ size_t bb_idx;
+ size_t stmt_idx;
+
+ bool was_moved = false;
+ size_t moved_bb, moved_stmt;
+ auto visit_cb = [&](const auto& lv, auto vu) {
+ if(lv == root_lv && vu == ValUsage::Move) {
+ was_moved = true;
+ moved_bb = bb_idx;
+ moved_stmt = stmt_idx;
+ return false;
+ }
+ return false;
+ };
+ // Most recent block (incomplete)
+ {
+ bb_idx = this->bb_path.back();
+ const auto& bb = mir_res.m_fcn.blocks.at(bb_idx);
+ for(stmt_idx = cur_stmt; stmt_idx -- && !was_moved; )
+ {
+ visit_mir_lvalues(bb.statements[stmt_idx], visit_cb);
+ }
+ }
+ for(size_t i = this->bb_path.size()-1; i -- && !was_moved; )
+ {
+ bb_idx = this->bb_path[i];
+ const auto& bb = mir_res.m_fcn.blocks.at(bb_idx);
+ stmt_idx = bb.statements.size();
+
+ visit_mir_lvalues(bb.terminator, visit_cb);
+
+ for(stmt_idx = bb.statements.size(); stmt_idx -- && !was_moved; )
+ {
+ visit_mir_lvalues(bb.statements[stmt_idx], visit_cb);
+ }
+ }
+
+ if( was_moved )
+ {
+ // Reason found, the value was moved
+ DEBUG("- Moved in BB" << moved_bb << "/" << moved_stmt);
+ return InvalidReason { InvalidReason::Moved, moved_bb, moved_stmt };
+ }
+ }
+ else
+ {
+ // Walk backwards to find assignment (if none, it's never initialized)
+ assert(this->bb_path.size() > 0);
+ size_t bb_idx;
+ size_t stmt_idx;
+
+ bool assigned = false;
+ auto visit_cb = [&](const auto& lv, auto vu) {
+ if(lv == root_lv && vu == ValUsage::Write) {
+ assigned = true;
+ //assigned_bb = this->bb_path[i];
+ //assigned_stmt = j;
+ return true;
+ }
+ return false;
+ };
+
+ // Most recent block (incomplete)
+ {
+ bb_idx = this->bb_path.back();
+ const auto& bb = mir_res.m_fcn.blocks.at(bb_idx);
+ for(stmt_idx = cur_stmt; stmt_idx -- && !assigned; )
+ {
+ visit_mir_lvalues(bb.statements[stmt_idx], visit_cb);
+ }
+ }
+ for(size_t i = this->bb_path.size()-1; i -- && !assigned; )
+ {
+ bb_idx = this->bb_path[i];
+ const auto& bb = mir_res.m_fcn.blocks.at(bb_idx);
+ stmt_idx = bb.statements.size();
+
+ visit_mir_lvalues(bb.terminator, visit_cb);
+
+ for(stmt_idx = bb.statements.size(); stmt_idx -- && !assigned; )
+ {
+ visit_mir_lvalues(bb.statements[stmt_idx], visit_cb);
+ }
+ }
+
+ if( !assigned )
+ {
+ // Value wasn't ever assigned, that's why it's not valid.
+ DEBUG("- Not assigned");
+ return InvalidReason { InvalidReason::Unwritten, 0, 0 };
+ }
+ }
+ // If neither of the above return a reason, check for blocks that don't have the value valid.
+ // TODO: This requires access to the lifetime bitmaps to know where it was invalidated
+ DEBUG("- (assume) lifetime invalidated [is_copy=" << is_copy << "]");
+ return InvalidReason { InvalidReason::Invalidated, 0, 0 };
+ }
void ensure_valid(const ::MIR::TypeResolve& mir_res, const ::MIR::LValue& root_lv, const State& vs, ::std::vector<unsigned int>& path) const
{
if( vs.is_composite() )
@@ -168,7 +297,9 @@ namespace
}
else if( !vs.is_valid() )
{
- MIR_BUG(mir_res, "Accessing invalidated lvalue - " << root_lv << " - field path=[" << path << "], BBs=[" << this->bb_path << "]");
+ // Locate where it was invalidated.
+ auto reason = find_invalid_reason(mir_res, root_lv);
+ MIR_BUG(mir_res, "Accessing invalidated lvalue - " << root_lv << " - " << FMT_CB(s,reason.fmt(s);) << " - field path=[" << path << "], BBs=[" << this->bb_path << "]");
}
else
{
@@ -509,6 +640,11 @@ void MIR_Validate_FullValState(::MIR::TypeResolve& mir_res, const ::MIR::Functio
{
::std::vector<StateSet> block_entry_states( fcn.blocks.size() );
+ // Determine value lifetimes (BBs in which Copy values are valid)
+ // - Used to mask out Copy value (prevents combinatorial explosion)
+ auto lifetimes = MIR_Helper_GetLifetimes(mir_res, fcn, /*dump_debug=*/true);
+ DEBUG(lifetimes.m_block_offsets);
+
ValueStates state;
state.arguments.resize( mir_res.m_args.size(), State(true) );
state.vars.resize( fcn.named_variables.size() );
@@ -523,8 +659,56 @@ void MIR_Validate_FullValState(::MIR::TypeResolve& mir_res, const ::MIR::Functio
auto state = mv$(todo_queue.back().second);
todo_queue.pop_back();
+ // Mask off any values which aren't valid in the first statement of this block
+ {
+ for(unsigned i = 0; i < state.vars.size(); i ++)
+ {
+ /*if( !variables_copy[i] )
+ {
+ // Not Copy, don't apply masking
+ }
+ else*/ if( ! state.vars[i].is_valid() )
+ {
+ // Already invalid
+ }
+ else if( lifetimes.var_valid(i, cur_block, 0) )
+ {
+ // Expected to be valid in this block, leave as-is
+ }
+ else
+ {
+ // Copy value not used at/after this block, mask to false
+ DEBUG("BB" << cur_block << " - var$" << i << " - Outside lifetime, discard");
+ state.vars[i] = State(false);
+ }
+ }
+ for(unsigned i = 0; i < state.temporaries.size(); i ++)
+ {
+ /*if( !variables_copy[i] )
+ {
+ // Not Copy, don't apply masking
+ }
+ else*/ if( ! state.temporaries[i].is_valid() )
+ {
+ // Already invalid
+ }
+ else if( lifetimes.tmp_valid(i, cur_block, 0) )
+ {
+ // Expected to be valid in this block, leave as-is
+ }
+ else
+ {
+ // Copy value not used at/after this block, mask to false
+ DEBUG("BB" << cur_block << " - tmp$" << i << " - Outside lifetime, discard");
+ state.temporaries[i] = State(false);
+ }
+ }
+ }
+
+ // If this state already exists in the map, skip
if( ! block_entry_states[cur_block].add_state(state) )
{
+ DEBUG("BB" << cur_block << " - Nothing new");
continue ;
}
DEBUG("BB" << cur_block << " - " << state);
@@ -660,6 +844,9 @@ void MIR_Validate_FullValState(::MIR::TypeResolve& mir_res, const ::MIR::Functio
state.move_lvalue(mir_res, se.slot);
}
}
+ ),
+ (ScopeEnd,
+ // TODO: Mark all mentioned variables as invalid
)
)
}