summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mir/check_full.cpp4
-rw-r--r--src/mir/helpers.cpp614
-rw-r--r--src/mir/helpers.hpp47
-rw-r--r--src/mir/optimise.cpp442
4 files changed, 669 insertions, 438 deletions
diff --git a/src/mir/check_full.cpp b/src/mir/check_full.cpp
index 8dcd359c..e5230b78 100644
--- a/src/mir/check_full.cpp
+++ b/src/mir/check_full.cpp
@@ -509,6 +509,10 @@ 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=*/false);
+
ValueStates state;
state.arguments.resize( mir_res.m_args.size(), State(true) );
state.vars.resize( fcn.named_variables.size() );
diff --git a/src/mir/helpers.cpp b/src/mir/helpers.cpp
index 9dac1567..aa5d2dcb 100644
--- a/src/mir/helpers.cpp
+++ b/src/mir/helpers.cpp
@@ -10,6 +10,7 @@
#include <hir/hir.hpp>
#include <hir/type.hpp>
#include <mir/mir.hpp>
+#include <algorithm> // ::std::find
void ::MIR::TypeResolve::fmt_pos(::std::ostream& os) const
{
@@ -271,3 +272,616 @@ const ::HIR::TypeRef* ::MIR::TypeResolve::is_type_owned_box(const ::HIR::TypeRef
{
return m_resolve.is_type_owned_box(ty);
}
+
+// --------------------------------------------------------------------
+// MIR_Helper_GetLifetimes
+// --------------------------------------------------------------------
+namespace {
+ enum class ValUsage {
+ Read,
+ Write,
+ Borrow,
+ };
+
+ bool visit_mir_lvalue(const ::MIR::LValue& lv, ValUsage u, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb)
+ {
+ if( cb(lv, u) )
+ return true;
+ TU_MATCHA( (lv), (e),
+ (Variable,
+ ),
+ (Argument,
+ ),
+ (Temporary,
+ ),
+ (Static,
+ ),
+ (Return,
+ ),
+ (Field,
+ return visit_mir_lvalue(*e.val, u, cb);
+ ),
+ (Deref,
+ return visit_mir_lvalue(*e.val, ValUsage::Read, cb);
+ ),
+ (Index,
+ bool rv = false;
+ rv |= visit_mir_lvalue(*e.val, u, cb);
+ rv |= visit_mir_lvalue(*e.idx, ValUsage::Read, cb);
+ return rv;
+ ),
+ (Downcast,
+ return visit_mir_lvalue(*e.val, u, cb);
+ )
+ )
+ return false;
+ }
+
+ bool visit_mir_lvalue(const ::MIR::Param& p, ValUsage u, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb)
+ {
+ if( const auto* e = p.opt_LValue() )
+ {
+ return visit_mir_lvalue(*e, u, cb);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ bool visit_mir_lvalues(const ::MIR::RValue& rval, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb)
+ {
+ bool rv = false;
+ TU_MATCHA( (rval), (se),
+ (Use,
+ rv |= visit_mir_lvalue(se, ValUsage::Read, cb);
+ ),
+ (Constant,
+ ),
+ (SizedArray,
+ rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb);
+ ),
+ (Borrow,
+ rv |= visit_mir_lvalue(se.val, ValUsage::Borrow, cb);
+ ),
+ (Cast,
+ rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb);
+ ),
+ (BinOp,
+ rv |= visit_mir_lvalue(se.val_l, ValUsage::Read, cb);
+ rv |= visit_mir_lvalue(se.val_r, ValUsage::Read, cb);
+ ),
+ (UniOp,
+ rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb);
+ ),
+ (DstMeta,
+ rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb);
+ ),
+ (DstPtr,
+ rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb);
+ ),
+ (MakeDst,
+ rv |= visit_mir_lvalue(se.ptr_val, ValUsage::Read, cb);
+ rv |= visit_mir_lvalue(se.meta_val, ValUsage::Read, cb);
+ ),
+ (Tuple,
+ for(auto& v : se.vals)
+ rv |= visit_mir_lvalue(v, ValUsage::Read, cb);
+ ),
+ (Array,
+ for(auto& v : se.vals)
+ rv |= visit_mir_lvalue(v, ValUsage::Read, cb);
+ ),
+ (Variant,
+ rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb);
+ ),
+ (Struct,
+ for(auto& v : se.vals)
+ rv |= visit_mir_lvalue(v, ValUsage::Read, cb);
+ )
+ )
+ return rv;
+ }
+
+ bool visit_mir_lvalues(const ::MIR::Statement& stmt, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb)
+ {
+ bool rv = false;
+ TU_MATCHA( (stmt), (e),
+ (Assign,
+ rv |= visit_mir_lvalues(e.src, cb);
+ rv |= visit_mir_lvalue(e.dst, ValUsage::Write, cb);
+ ),
+ (Asm,
+ for(auto& v : e.inputs)
+ rv |= visit_mir_lvalue(v.second, ValUsage::Read, cb);
+ for(auto& v : e.outputs)
+ rv |= visit_mir_lvalue(v.second, ValUsage::Write, cb);
+ ),
+ (SetDropFlag,
+ ),
+ (Drop,
+ // Well, it mutates...
+ rv |= visit_mir_lvalue(e.slot, ValUsage::Write, cb);
+ )
+ )
+ return rv;
+ }
+
+ /*
+ void visit_mir_lvalues_mut(::MIR::Terminator& term, ::std::function<bool(::MIR::LValue& , ValUsage)> cb)
+ {
+ TU_MATCHA( (term), (e),
+ (Incomplete,
+ ),
+ (Return,
+ ),
+ (Diverge,
+ ),
+ (Goto,
+ ),
+ (Panic,
+ ),
+ (If,
+ visit_mir_lvalue_mut(e.cond, ValUsage::Read, cb);
+ ),
+ (Switch,
+ visit_mir_lvalue_mut(e.val, ValUsage::Read, cb);
+ ),
+ (Call,
+ if( e.fcn.is_Value() ) {
+ visit_mir_lvalue_mut(e.fcn.as_Value(), ValUsage::Read, cb);
+ }
+ for(auto& v : e.args)
+ visit_mir_lvalue_mut(v, ValUsage::Read, cb);
+ visit_mir_lvalue_mut(e.ret_val, ValUsage::Write, cb);
+ )
+ )
+ }
+
+ void visit_mir_lvalues_mut(::MIR::TypeResolve& state, ::MIR::Function& fcn, ::std::function<bool(::MIR::LValue& , ValUsage)> cb)
+ {
+ for(unsigned int block_idx = 0; block_idx < fcn.blocks.size(); block_idx ++)
+ {
+ auto& block = fcn.blocks[block_idx];
+ for(auto& stmt : block.statements)
+ {
+ state.set_cur_stmt(block_idx, (&stmt - &block.statements.front()));
+ visit_mir_lvalues_mut(stmt, cb);
+ }
+ if( block.terminator.tag() == ::MIR::Terminator::TAGDEAD )
+ continue ;
+ state.set_cur_stmt_term(block_idx);
+ visit_mir_lvalues_mut(block.terminator, cb);
+ }
+ }
+ void visit_mir_lvalues(::MIR::TypeResolve& state, const ::MIR::Function& fcn, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb)
+ {
+ visit_mir_lvalues_mut(state, const_cast<::MIR::Function&>(fcn), [&](auto& lv, auto im){ return cb(lv, im); });
+ }
+ */
+}
+::MIR::ValueLifetimes MIR_Helper_GetLifetimes(::MIR::TypeResolve& state, ::MIR::Function& fcn, bool dump_debug)
+{
+ // New algorithm notes:
+ // ---
+ // The lifetime of a value starts when it is written, and ends the last time it is read
+ // - When a variable is read, end any existing lifetime and start a new one.
+ // - When the value is read, update the end of its lifetime.
+ // ---
+ // A lifetime is a range in the call graph (with a start and end, including list of blocks)
+ // - Representation: Bitmap with a bit per statement.
+ // - Record the current block path in general state, along with known active lifetimes
+
+ // Scan through all possible paths in the graph (with loopback detection using a memory of the path)
+ // - If a loop is detected, determine if there were changes to the lifetime set during that pass
+ // > Changes are noticed by recording in the state structure when it triggers a change in the lifetime
+ // map.
+ struct Position
+ {
+ size_t path_index = 0; // index into the block path.
+ unsigned int stmt_idx = 0;
+
+ bool operator==(const Position& x) const {
+ return path_index == x.path_index && stmt_idx == x.stmt_idx;
+ }
+ };
+ struct ProtoLifetime
+ {
+ Position start;
+ Position end;
+
+ bool is_empty() const {
+ return start == end;
+ }
+ };
+ static unsigned NEXT_INDEX = 0;
+ struct State
+ {
+ unsigned int index = 0;
+ ::std::vector<unsigned int> block_path;
+ ::std::vector<unsigned int> block_change_idx;
+ unsigned int cur_change_idx = 0;
+
+ // if read, update. If set, save and update
+ ::std::vector<ProtoLifetime> tmp_ends;
+ ::std::vector<ProtoLifetime> var_ends;
+
+ State(const ::MIR::Function& fcn):
+ tmp_ends( fcn.temporaries.size(), ProtoLifetime() ),
+ var_ends( fcn.named_variables.size(), ProtoLifetime() )
+ {
+ }
+
+ State clone() const {
+ auto rv = *this;
+ rv.index = ++NEXT_INDEX;
+ return rv;
+ }
+ };
+ NEXT_INDEX = 0;
+
+ struct ValueLifetime
+ {
+ ::std::vector<bool> stmt_bitmap;
+ ValueLifetime(size_t stmt_count):
+ stmt_bitmap(stmt_count)
+ {}
+
+ void dump_debug(const char* suffix, unsigned i, const ::std::vector<size_t>& block_offsets)
+ {
+ ::std::string name = FMT(suffix << "$" << i);
+ while(name.size() < 3+1+3)
+ name += " ";
+ DEBUG(name << " : " << FMT_CB(os,
+ for(unsigned int j = 0; j < this->stmt_bitmap.size(); j++)
+ {
+ if(j != 0 && ::std::find(block_offsets.begin(), block_offsets.end(), j) != block_offsets.end())
+ os << "|";
+ os << (this->stmt_bitmap[j] ? "X" : " ");
+ }
+ ));
+ }
+ };
+
+ size_t statement_count = 0;
+ ::std::vector<size_t> block_offsets;
+ block_offsets.reserve( fcn.blocks.size() );
+ for(const auto& bb : fcn.blocks)
+ {
+ block_offsets.push_back(statement_count);
+ statement_count += bb.statements.size() + 1; // +1 for the terminator
+ }
+
+ ::std::vector<ValueLifetime> temporary_lifetimes( fcn.temporaries.size(), ValueLifetime(statement_count) );
+ ::std::vector<ValueLifetime> variable_lifetimes( fcn.named_variables.size(), ValueLifetime(statement_count) );
+
+ struct BlockSeenLifetimes {
+ const ::std::vector<size_t>& block_offsets;
+ ::std::vector< ::std::vector<unsigned int> > tmp;
+ ::std::vector< ::std::vector<unsigned int> > var;
+
+ BlockSeenLifetimes(const ::std::vector<size_t>& block_offsets, const ::MIR::Function& fcn):
+ block_offsets( block_offsets ),
+ tmp( fcn.temporaries.size() ),
+ var( fcn.named_variables.size() )
+ {}
+
+ bool has_state() const
+ {
+ return
+ ::std::any_of(tmp.begin(), tmp.end(), [&](const auto& x){ return !x.empty(); })
+ || ::std::any_of(var.begin(), var.end(), [&](const auto& x){ return !x.empty(); });
+ }
+
+ bool try_merge(const State& val_state) const
+ {
+ for(size_t i = 0; i < val_state.tmp_ends.size(); i++)
+ {
+ const auto& lft = val_state.tmp_ends[i];
+ const auto& seen = this->tmp[i];
+ if(lft.is_empty()) continue ;
+ auto end_idx = block_offsets.at( val_state.block_path.at(lft.end.path_index) ) + lft.end.stmt_idx;
+
+ auto it = ::std::find(seen.begin(), seen.end(), end_idx);
+ if( it == seen.end() )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ bool merge(const State& val_state)
+ {
+ bool rv = false;
+ for(size_t i = 0; i < val_state.tmp_ends.size(); i++)
+ {
+ const auto& lft = val_state.tmp_ends[i];
+ auto& seen = this->tmp[i];
+ if(lft.is_empty()) continue ;
+ auto end_idx = block_offsets.at( val_state.block_path.at(lft.end.path_index) ) + lft.end.stmt_idx;
+
+ auto it = ::std::find(seen.begin(), seen.end(), end_idx);
+ if( it == seen.end() )
+ {
+ rv = true;
+ seen.push_back( end_idx );
+ }
+ }
+ return rv;
+ }
+ };
+ ::std::vector<BlockSeenLifetimes> block_seen_lifetimes( fcn.blocks.size(), BlockSeenLifetimes(block_offsets, fcn) );
+
+ State init_state(fcn);
+
+ ::std::vector<::std::pair<unsigned int, State>> todo_queue;
+ todo_queue.push_back(::std::make_pair( 0, mv$(init_state) ));
+
+ while(!todo_queue.empty())
+ {
+ auto bb_idx = todo_queue.back().first;
+ auto val_state = mv$(todo_queue.back().second);
+ todo_queue.pop_back();
+ state.set_cur_stmt(bb_idx, 0);
+
+ // Fill alive time in the bitmap
+ // TODO: Maybe also store the range (as a sequence of {block,start,end})
+ auto add_lifetime_s = [&](State& val_state, const ::MIR::LValue& lv, const Position& start, const Position& end) {
+ assert(start.path_index <= end.path_index);
+ assert(start.path_index < end.path_index || start.stmt_idx <= end.stmt_idx);
+ if(start.path_index == end.path_index && start.stmt_idx == end.stmt_idx)
+ return;
+ //DEBUG("[add_lifetime] " << lv << " (" << start.path_index << "," << start.stmt_idx << ") -- (" << end.path_index << "," << end.stmt_idx << ")");
+ ValueLifetime* lft;
+ if(const auto* e = lv.opt_Temporary())
+ {
+ lft = &temporary_lifetimes[e->idx];
+ }
+ else
+ {
+ MIR_TODO(state, "[add_lifetime] " << lv);
+ return;
+ }
+
+ // Fill lifetime map for this temporary in the indicated range
+ bool did_set = false;
+ unsigned int j = start.stmt_idx;
+ unsigned int i = start.path_index;
+ while( i <= end.path_index )
+ {
+ auto bb_idx = val_state.block_path.at(i);
+ const auto& bb = fcn.blocks[bb_idx];
+ MIR_ASSERT(state, j <= bb.statements.size(), "");
+ MIR_ASSERT(state, bb_idx < block_offsets.size(), "");
+
+ auto block_base = block_offsets.at(bb_idx);
+ auto idx = block_base + j;
+ if( !lft->stmt_bitmap.at(idx) )
+ {
+ lft->stmt_bitmap[idx] = true;
+ did_set = true;
+ }
+
+ if( i == end.path_index && j == end.stmt_idx )
+ break;
+
+ // If the current index is the terminator (one after the size)
+ if(j == bb.statements.size())
+ {
+ j = 0;
+ i++;
+ }
+ else
+ {
+ j ++;
+ }
+ }
+
+ // - If the above set a new bit, increment `val_state.cur_change_idx`
+ if( did_set )
+ {
+ DEBUG("[add_lifetime] " << lv << " (" << start.path_index << "," << start.stmt_idx << ") -- (" << end.path_index << "," << end.stmt_idx << ") - New information");
+ val_state.cur_change_idx += 1;
+ }
+ };
+ auto add_lifetime = [&](const ::MIR::LValue& lv, const Position& start, const Position& end) {
+ add_lifetime_s(val_state, lv, start, end);
+ };
+
+ auto apply_state = [&](State& state) {
+ // Apply all changes in this state, just in case there was new information
+ for(unsigned i = 0; i < fcn.temporaries.size(); i++)
+ add_lifetime_s( state, ::MIR::LValue::make_Temporary({i}), state.tmp_ends[i].start, state.tmp_ends[i].end );
+ };
+ auto add_to_visit = [&](unsigned int new_bb_idx, State new_state) {
+ auto& bb_memory_ent = block_seen_lifetimes[new_bb_idx];
+ /*if( !bb_memory_ent.has_state() )
+ {
+ // No recorded state, needs to be visited
+ DEBUG(state << " " << new_state.index << " -> bb" << new_bb_idx << " (no existing state)");
+ }
+ else*/ if( bb_memory_ent.try_merge(new_state) )
+ {
+ // This state has new information, needs to be visited
+ DEBUG(state << " " << new_state.index << " -> bb" << new_bb_idx << " (new info)");
+ }
+ else
+ {
+ // Skip
+ DEBUG(state << " " << new_state.index << " No new state before push (to bb" << new_bb_idx << "), applying");
+ apply_state(new_state);
+ return ;
+ }
+ todo_queue.push_back(::std::make_pair( new_bb_idx, mv$(new_state) ));
+ };
+
+ // Compare this state to a composite list of lifetimes seen in this block
+ // - Just compares the end of each proto lifetime
+ {
+ auto& bb_memory_ent = block_seen_lifetimes[bb_idx];
+ bool has_new = bb_memory_ent.merge(val_state);
+
+ if( !has_new && bb_memory_ent.has_state() )
+ {
+ DEBUG(state << " " << val_state.index << " No new entry state");
+ apply_state(val_state);
+
+ continue ;
+ }
+ }
+
+ // Check if this state has visited this block before, and if anything changed since last time
+ {
+ auto it = ::std::find(val_state.block_path.rbegin(), val_state.block_path.rend(), bb_idx);
+ if( it != val_state.block_path.rend() )
+ {
+ auto idx = &*it - &val_state.block_path.front();
+ if( val_state.block_change_idx[idx] == val_state.cur_change_idx )
+ {
+ DEBUG(state << " " << val_state.index << " Loop and no change");
+ continue ;
+ }
+ else
+ {
+ assert( val_state.block_change_idx[idx] < val_state.cur_change_idx );
+ DEBUG(state << " " << val_state.index << " --- Loop, " << val_state.cur_change_idx - val_state.block_change_idx[idx] << " changes");
+ }
+ }
+ else
+ {
+ DEBUG(state << " " << val_state.index << " ---");
+ }
+ val_state.block_path.push_back(bb_idx);
+ val_state.block_change_idx.push_back( val_state.cur_change_idx );
+ }
+
+ Position cur_pos;
+ cur_pos.path_index = val_state.block_path.size() - 1;
+ cur_pos.stmt_idx = 0;
+ auto lvalue_read = [&](const ::MIR::LValue& lv) {
+ if(const auto* e = lv.opt_Temporary())
+ {
+ // Update the last read location
+ val_state.tmp_ends.at(e->idx).end = cur_pos;
+ }
+ };
+ auto lvalue_set = [&](const ::MIR::LValue& lv) {
+ if(const auto* e = lv.opt_Temporary())
+ {
+ // End whatever value was originally there, and insert this new one
+ val_state.tmp_ends.at(e->idx).end = cur_pos;
+ add_lifetime(lv, val_state.tmp_ends.at(e->idx).start, val_state.tmp_ends.at(e->idx).end);
+ val_state.tmp_ends.at(e->idx).start = cur_pos;
+ }
+ };
+
+ // Run statements
+ for(const auto& stmt : fcn.blocks[bb_idx].statements)
+ {
+ auto stmt_idx = &stmt - &fcn.blocks[bb_idx].statements.front();
+ cur_pos.stmt_idx = stmt_idx;
+ state.set_cur_stmt(bb_idx, stmt_idx);
+ DEBUG(state << " " << stmt);
+
+ if( const auto* e = stmt.opt_Drop() )
+ {
+ visit_mir_lvalues(stmt, [&](const auto& lv, ValUsage vu)->bool{
+ if(vu == ValUsage::Read)
+ lvalue_read(lv);
+ return false;
+ });
+ lvalue_read(e->slot);
+ lvalue_set(e->slot);
+ }
+ else
+ {
+ visit_mir_lvalues(stmt, [&](const auto& lv, ValUsage vu)->bool{
+ if(vu == ValUsage::Read)
+ lvalue_read(lv);
+ if(vu == ValUsage::Write)
+ lvalue_set(lv);
+ return false;
+ });
+ }
+ }
+ cur_pos.stmt_idx = fcn.blocks[bb_idx].statements.size();
+
+ state.set_cur_stmt_term(bb_idx);
+ DEBUG(state << "TERM " << fcn.blocks[bb_idx].terminator);
+ TU_MATCH(::MIR::Terminator, (fcn.blocks[bb_idx].terminator), (e),
+ (Incomplete,
+ // Should be impossible here.
+ ),
+ (Return,
+ // End all active lifetimes at their previous location.
+ apply_state(val_state);
+ ),
+ (Diverge,
+ apply_state(val_state);
+ ),
+ (Goto,
+ add_to_visit(e, mv$(val_state));
+ ),
+ (Panic,
+ // What should be done here?
+ ),
+ (If,
+ lvalue_read(e.cond);
+
+ // Push blocks
+ add_to_visit(e.bb0, val_state.clone());
+ add_to_visit(e.bb1, mv$(val_state));
+ ),
+ (Switch,
+ lvalue_read(e.val);
+ ::std::set<unsigned int> tgts;
+ for(const auto& tgt : e.targets)
+ tgts.insert(tgt);
+
+ for(const auto& tgt : tgts)
+ {
+ auto vs = (tgt == *tgts.rbegin() ? mv$(val_state) : val_state.clone());
+ add_to_visit(tgt, mv$(vs));
+ }
+ ),
+ (Call,
+ if( const auto* f = e.fcn.opt_Value() )
+ lvalue_read(*f);
+ for(const auto& arg : e.args)
+ if( const auto* e = arg.opt_LValue() )
+ lvalue_read(*e);
+
+ // Push blocks (with return valid only in one)
+ add_to_visit(e.panic_block, val_state.clone());
+
+ // TODO: If the function returns !, don't follow the ret_block
+ lvalue_set(e.ret_val);
+ add_to_visit(e.ret_block, mv$(val_state));
+ )
+ )
+ }
+
+ // Dump out variable lifetimes.
+ if( dump_debug )
+ {
+ for(unsigned int i = 0; i < temporary_lifetimes.size(); i ++)
+ {
+ temporary_lifetimes[i].dump_debug("tmp", i, block_offsets);
+ }
+ for(unsigned int i = 0; i < variable_lifetimes.size(); i ++)
+ {
+ variable_lifetimes[i].dump_debug("var", i, block_offsets);
+ }
+ }
+
+ // Move lifetime bitmaps into the variable for the below code
+ ::MIR::ValueLifetimes rv;
+ rv.m_temporaries.reserve( temporary_lifetimes.size() );
+ for(auto& lft : temporary_lifetimes)
+ rv.m_temporaries.push_back( ::MIR::ValueLifetime(mv$(lft.stmt_bitmap)) );
+ rv.m_variables.reserve( variable_lifetimes.size() );
+ for(auto& lft : variable_lifetimes)
+ rv.m_variables.push_back( ::MIR::ValueLifetime(mv$(lft.stmt_bitmap)) );
+
+ return rv;
+}
diff --git a/src/mir/helpers.hpp b/src/mir/helpers.hpp
index e8f52651..c84222bd 100644
--- a/src/mir/helpers.hpp
+++ b/src/mir/helpers.hpp
@@ -107,4 +107,51 @@ public:
}
};
+
+// --------------------------------------------------------------------
+// MIR_Helper_GetLifetimes
+// --------------------------------------------------------------------
+class ValueLifetime
+{
+ ::std::vector<bool> statements;
+
+public:
+ ValueLifetime(::std::vector<bool> stmts):
+ statements( mv$(stmts) )
+ {}
+
+ // true if this value is used at any point
+ bool is_used() const {
+ for(auto v : statements)
+ if( v )
+ return true;
+ return false;
+ }
+ bool overlaps(const ValueLifetime& x) const {
+ assert(statements.size() == x.statements.size());
+ for(unsigned int i = 0; i < statements.size(); i ++)
+ {
+ if( statements[i] && x.statements[i] )
+ return true;
+ }
+ return false;
+ }
+ void unify(const ValueLifetime& x) {
+ assert(statements.size() == x.statements.size());
+ for(unsigned int i = 0; i < statements.size(); i ++)
+ {
+ if( x.statements[i] )
+ statements[i] = true;
+ }
+ }
+};
+
+struct ValueLifetimes
+{
+ ::std::vector<ValueLifetime> m_temporaries;
+ ::std::vector<ValueLifetime> m_variables;
+};
+
} // namespace MIR
+
+extern ::MIR::ValueLifetimes MIR_Helper_GetLifetimes(::MIR::TypeResolve& state, ::MIR::Function& fcn, bool dump_debug);
diff --git a/src/mir/optimise.cpp b/src/mir/optimise.cpp
index 2e823821..390bd1d6 100644
--- a/src/mir/optimise.cpp
+++ b/src/mir/optimise.cpp
@@ -1006,6 +1006,7 @@ bool MIR_Optimise_Inlining(::MIR::TypeResolve& state, ::MIR::Function& fcn)
return inline_happened;
}
+
// --------------------------------------------------------------------
// If two temporaries don't overlap in lifetime (blocks in which they're valid), unify the two
// --------------------------------------------------------------------
@@ -1036,444 +1037,9 @@ bool MIR_Optimise_UnifyTemporaries(::MIR::TypeResolve& state, ::MIR::Function& f
return false;
}
- class VarLifetime
- {
- ::std::vector<bool> statements;
-
- public:
- VarLifetime(::std::vector<bool> stmts):
- statements( mv$(stmts) )
- {}
-
- // true if this value is used at any point
- bool is_used() const {
- for(auto v : statements)
- if( v )
- return true;
- return false;
- }
- bool overlaps(const VarLifetime& x) const {
- assert(statements.size() == x.statements.size());
- for(unsigned int i = 0; i < statements.size(); i ++)
- {
- if( statements[i] && x.statements[i] )
- return true;
- }
- return false;
- }
- void unify(const VarLifetime& x) {
- assert(statements.size() == x.statements.size());
- for(unsigned int i = 0; i < statements.size(); i ++)
- {
- if( x.statements[i] )
- statements[i] = true;
- }
- }
- };
- //::std::vector<VarLifetime> var_lifetimes;
- ::std::vector<VarLifetime> tmp_lifetimes;
-
-
- // New algorithm notes:
- // ---
- // The lifetime of a value starts when it is written, and ends the last time it is read
- // - When a variable is read, end any existing lifetime and start a new one.
- // - When the value is read, update the end of its lifetime.
- // ---
- // A lifetime is a range in the call graph (with a start and end, including list of blocks)
- // - Representation: Bitmap with a bit per statement.
- // - Record the current block path in general state, along with known active lifetimes
-
- // TODO: Move all this code to a helper to other passes can use it.
- {
- // Scan through all possible paths in the graph (with loopback detection using a memory of the path)
- // - If a loop is detected, determine if there were changes to the lifetime set during that pass
- // > Changes are noticed by recording in the state structure when it triggers a change in the lifetime
- // map.
- struct Position
- {
- size_t path_index = 0; // index into the block path.
- unsigned int stmt_idx = 0;
-
- bool operator==(const Position& x) const {
- return path_index == x.path_index && stmt_idx == x.stmt_idx;
- }
- };
- struct ProtoLifetime
- {
- Position start;
- Position end;
-
- bool is_empty() const {
- return start == end;
- }
- };
- static unsigned NEXT_INDEX = 0;
- struct State
- {
- unsigned int index = 0;
- ::std::vector<unsigned int> block_path;
- ::std::vector<unsigned int> block_change_idx;
- unsigned int cur_change_idx = 0;
-
- // if read, update. If set, save and update
- ::std::vector<ProtoLifetime> tmp_ends;
- //::std::vector<Position> var_ends;
-
- State clone() const {
- auto rv = *this;
- rv.index = ++NEXT_INDEX;
- return rv;
- }
- };
- NEXT_INDEX = 0;
-
- struct ValueLifetime
- {
- ::std::vector<bool> stmt_bitmap;
- ValueLifetime(size_t stmt_count):
- stmt_bitmap(stmt_count)
- {}
- };
-
- size_t statement_count = 0;
- ::std::vector<size_t> block_offsets;
- block_offsets.reserve( fcn.blocks.size() );
- for(const auto& bb : fcn.blocks)
- {
- block_offsets.push_back(statement_count);
- statement_count += bb.statements.size() + 1; // +1 for the terminator
- }
-
- ::std::vector<ValueLifetime> temporary_lifetimes( fcn.temporaries.size(), ValueLifetime(statement_count) );
-
- struct BlockSeenLifetimes {
- const ::std::vector<size_t>& block_offsets;
- ::std::vector< ::std::vector<unsigned int> > tmp;
-
- BlockSeenLifetimes(const ::std::vector<size_t>& block_offsets, const ::MIR::Function& fcn):
- block_offsets( block_offsets ),
- tmp( fcn.temporaries.size() )
- {}
-
- bool has_state() const
- {
- return ::std::any_of(tmp.begin(), tmp.end(), [&](const auto& x){ return !x.empty(); });
- }
-
- bool try_merge(const State& val_state) const
- {
- for(size_t i = 0; i < val_state.tmp_ends.size(); i++)
- {
- const auto& lft = val_state.tmp_ends[i];
- const auto& seen = this->tmp[i];
- if(lft.is_empty()) continue ;
- auto end_idx = block_offsets.at( val_state.block_path.at(lft.end.path_index) ) + lft.end.stmt_idx;
-
- auto it = ::std::find(seen.begin(), seen.end(), end_idx);
- if( it == seen.end() )
- {
- return true;
- }
- }
- return false;
- }
- bool merge(const State& val_state)
- {
- bool rv = false;
- for(size_t i = 0; i < val_state.tmp_ends.size(); i++)
- {
- const auto& lft = val_state.tmp_ends[i];
- auto& seen = this->tmp[i];
- if(lft.is_empty()) continue ;
- auto end_idx = block_offsets.at( val_state.block_path.at(lft.end.path_index) ) + lft.end.stmt_idx;
-
- auto it = ::std::find(seen.begin(), seen.end(), end_idx);
- if( it == seen.end() )
- {
- rv = true;
- seen.push_back( end_idx );
- }
- }
- return rv;
- }
- };
- ::std::vector<BlockSeenLifetimes> block_seen_lifetimes( fcn.blocks.size(), BlockSeenLifetimes(block_offsets, fcn) );
-
- State init_state;
- init_state.tmp_ends.resize( fcn.temporaries.size(), ProtoLifetime() );
-
- ::std::vector<::std::pair<unsigned int, State>> todo_queue;
- todo_queue.push_back(::std::make_pair( 0, mv$(init_state) ));
-
- while(!todo_queue.empty())
- {
- auto bb_idx = todo_queue.back().first;
- auto val_state = mv$(todo_queue.back().second);
- todo_queue.pop_back();
- state.set_cur_stmt(bb_idx, 0);
-
- // Fill alive time in the bitmap
- // TODO: Maybe also store the range (as a sequence of {block,start,end})
- auto add_lifetime_s = [&](State& val_state, const ::MIR::LValue& lv, const Position& start, const Position& end) {
- assert(start.path_index <= end.path_index);
- assert(start.path_index < end.path_index || start.stmt_idx <= end.stmt_idx);
- if(start.path_index == end.path_index && start.stmt_idx == end.stmt_idx)
- return;
- //DEBUG("[add_lifetime] " << lv << " (" << start.path_index << "," << start.stmt_idx << ") -- (" << end.path_index << "," << end.stmt_idx << ")");
- ValueLifetime* lft;
- if(const auto* e = lv.opt_Temporary())
- {
- lft = &temporary_lifetimes[e->idx];
- }
- else
- {
- MIR_TODO(state, "[add_lifetime] " << lv);
- return;
- }
-
- // Fill lifetime map for this temporary in the indicated range
- bool did_set = false;
- unsigned int j = start.stmt_idx;
- unsigned int i = start.path_index;
- while( i <= end.path_index )
- {
- auto bb_idx = val_state.block_path.at(i);
- const auto& bb = fcn.blocks[bb_idx];
- MIR_ASSERT(state, j <= bb.statements.size(), "");
- MIR_ASSERT(state, bb_idx < block_offsets.size(), "");
-
- auto block_base = block_offsets.at(bb_idx);
- auto idx = block_base + j;
- if( !lft->stmt_bitmap.at(idx) )
- {
- lft->stmt_bitmap[idx] = true;
- did_set = true;
- }
-
- if( i == end.path_index && j == end.stmt_idx )
- break;
-
- // If the current index is the terminator (one after the size)
- if(j == bb.statements.size())
- {
- j = 0;
- i++;
- }
- else
- {
- j ++;
- }
- }
-
- // - If the above set a new bit, increment `val_state.cur_change_idx`
- if( did_set )
- {
- DEBUG("[add_lifetime] " << lv << " (" << start.path_index << "," << start.stmt_idx << ") -- (" << end.path_index << "," << end.stmt_idx << ") - New information");
- val_state.cur_change_idx += 1;
- }
- };
- auto add_lifetime = [&](const ::MIR::LValue& lv, const Position& start, const Position& end) {
- add_lifetime_s(val_state, lv, start, end);
- };
-
- auto apply_state = [&](State& state) {
- // Apply all changes in this state, just in case there was new information
- for(unsigned i = 0; i < fcn.temporaries.size(); i++)
- add_lifetime_s( state, ::MIR::LValue::make_Temporary({i}), state.tmp_ends[i].start, state.tmp_ends[i].end );
- };
- auto add_to_visit = [&](unsigned int new_bb_idx, State new_state) {
- auto& bb_memory_ent = block_seen_lifetimes[new_bb_idx];
- /*if( !bb_memory_ent.has_state() )
- {
- // No recorded state, needs to be visited
- DEBUG(state << " " << new_state.index << " -> bb" << new_bb_idx << " (no existing state)");
- }
- else*/ if( bb_memory_ent.try_merge(new_state) )
- {
- // This state has new information, needs to be visited
- DEBUG(state << " " << new_state.index << " -> bb" << new_bb_idx << " (new info)");
- }
- else
- {
- // Skip
- DEBUG(state << " " << new_state.index << " No new state before push (to bb" << new_bb_idx << "), applying");
- apply_state(new_state);
- return ;
- }
- todo_queue.push_back(::std::make_pair( new_bb_idx, mv$(new_state) ));
- };
-
- // Compare this state to a composite list of lifetimes seen in this block
- // - Just compares the end of each proto lifetime
- {
- auto& bb_memory_ent = block_seen_lifetimes[bb_idx];
- bool has_new = bb_memory_ent.merge(val_state);
-
- if( !has_new && bb_memory_ent.has_state() )
- {
- DEBUG(state << " " << val_state.index << " No new entry state");
- apply_state(val_state);
-
- continue ;
- }
- }
-
- // Check if this state has visited this block before, and if anything changed since last time
- {
- auto it = ::std::find(val_state.block_path.rbegin(), val_state.block_path.rend(), bb_idx);
- if( it != val_state.block_path.rend() )
- {
- auto idx = &*it - &val_state.block_path.front();
- if( val_state.block_change_idx[idx] == val_state.cur_change_idx )
- {
- DEBUG(state << " " << val_state.index << " Loop and no change");
- continue ;
- }
- else
- {
- assert( val_state.block_change_idx[idx] < val_state.cur_change_idx );
- DEBUG(state << " " << val_state.index << " --- Loop, " << val_state.cur_change_idx - val_state.block_change_idx[idx] << " changes");
- }
- }
- else
- {
- DEBUG(state << " " << val_state.index << " ---");
- }
- val_state.block_path.push_back(bb_idx);
- val_state.block_change_idx.push_back( val_state.cur_change_idx );
- }
-
- Position cur_pos;
- cur_pos.path_index = val_state.block_path.size() - 1;
- cur_pos.stmt_idx = 0;
- auto lvalue_read = [&](const ::MIR::LValue& lv) {
- if(const auto* e = lv.opt_Temporary())
- {
- // Update the last read location
- val_state.tmp_ends.at(e->idx).end = cur_pos;
- }
- };
- auto lvalue_set = [&](const ::MIR::LValue& lv) {
- if(const auto* e = lv.opt_Temporary())
- {
- // End whatever value was originally there, and insert this new one
- val_state.tmp_ends.at(e->idx).end = cur_pos;
- add_lifetime(lv, val_state.tmp_ends.at(e->idx).start, val_state.tmp_ends.at(e->idx).end);
- val_state.tmp_ends.at(e->idx).start = cur_pos;
- }
- };
-
- // Run statements
- for(const auto& stmt : fcn.blocks[bb_idx].statements)
- {
- auto stmt_idx = &stmt - &fcn.blocks[bb_idx].statements.front();
- cur_pos.stmt_idx = stmt_idx;
- state.set_cur_stmt(bb_idx, stmt_idx);
- DEBUG(state << " " << stmt);
-
- if( const auto* e = stmt.opt_Drop() )
- {
- visit_mir_lvalues(stmt, [&](const auto& lv, ValUsage vu)->bool{
- if(vu == ValUsage::Read)
- lvalue_read(lv);
- return false;
- });
- lvalue_read(e->slot);
- lvalue_set(e->slot);
- }
- else
- {
- visit_mir_lvalues(stmt, [&](const auto& lv, ValUsage vu)->bool{
- if(vu == ValUsage::Read)
- lvalue_read(lv);
- if(vu == ValUsage::Write)
- lvalue_set(lv);
- return false;
- });
- }
- }
- cur_pos.stmt_idx = fcn.blocks[bb_idx].statements.size();
-
- state.set_cur_stmt_term(bb_idx);
- DEBUG(state << "TERM " << fcn.blocks[bb_idx].terminator);
- TU_MATCH(::MIR::Terminator, (fcn.blocks[bb_idx].terminator), (e),
- (Incomplete,
- // Should be impossible here.
- ),
- (Return,
- // End all active lifetimes at their previous location.
- apply_state(val_state);
- ),
- (Diverge,
- apply_state(val_state);
- ),
- (Goto,
- add_to_visit(e, mv$(val_state));
- ),
- (Panic,
- // What should be done here?
- ),
- (If,
- lvalue_read(e.cond);
-
- // Push blocks
- add_to_visit(e.bb0, val_state.clone());
- add_to_visit(e.bb1, mv$(val_state));
- ),
- (Switch,
- lvalue_read(e.val);
- ::std::set<unsigned int> tgts;
- for(const auto& tgt : e.targets)
- tgts.insert(tgt);
-
- for(const auto& tgt : tgts)
- {
- auto vs = (tgt == *tgts.rbegin() ? mv$(val_state) : val_state.clone());
- add_to_visit(tgt, mv$(vs));
- }
- ),
- (Call,
- if( const auto* f = e.fcn.opt_Value() )
- lvalue_read(*f);
- for(const auto& arg : e.args)
- if( const auto* e = arg.opt_LValue() )
- lvalue_read(*e);
-
- // Push blocks (with return valid only in one)
- add_to_visit(e.panic_block, val_state.clone());
-
- // TODO: If the function returns !, don't follow the ret_block
- lvalue_set(e.ret_val);
- add_to_visit(e.ret_block, mv$(val_state));
- )
- )
- }
-
- // Dump out variable lifetimes.
-#if 0
- for(unsigned int i = 0; i < temporary_lifetimes.size(); i ++)
- {
- const auto& lft = temporary_lifetimes[i];
- auto name = FMT("tmp$" << i);
- while(name.size() < 3+1+3)
- name += " ";
- DEBUG(name << " : " << FMT_CB(os,
- for(unsigned int j = 0; j < lft.stmt_bitmap.size(); j++)
- {
- if(j != 0 && ::std::find(block_offsets.begin(), block_offsets.end(), j) != block_offsets.end())
- os << "|";
- os << (lft.stmt_bitmap[j] ? "X" : " ");
- }
- ));
- }
-#endif
-
- // Move lifetime bitmaps into the variable for the below code
- tmp_lifetimes.reserve( temporary_lifetimes.size() );
- for(auto& lft : temporary_lifetimes)
- tmp_lifetimes.push_back( VarLifetime(mv$(lft.stmt_bitmap)) );
- }
+ auto lifetimes = MIR_Helper_GetLifetimes(state, fcn, /*dump_debug=*/false);
+ //::std::vector<::MIR::ValueLifetime> var_lifetimes = mv$(lifetimes.m_variables);
+ ::std::vector<::MIR::ValueLifetime> tmp_lifetimes = mv$(lifetimes.m_temporaries);
// 2. Unify variables of the same type with distinct non-overlapping lifetimes
::std::map<unsigned int, unsigned int> replacements;