summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mir/optimise.cpp357
1 files changed, 348 insertions, 9 deletions
diff --git a/src/mir/optimise.cpp b/src/mir/optimise.cpp
index a1db0ddb..2987cf39 100644
--- a/src/mir/optimise.cpp
+++ b/src/mir/optimise.cpp
@@ -310,8 +310,14 @@ namespace {
for(const auto& w : lv.m_wrappers)
{
if(w.is_Index())
+ {
if( cb(::MIR::LValue::new_Local(w.as_Index()), ValUsage::Read) )
return true;
+ }
+ else if(w.is_Deref())
+ {
+ //u = ValUsage::Read;
+ }
}
return cb(lv, u);
}
@@ -477,8 +483,9 @@ namespace {
return visit_mir_lvalues_mut(const_cast<::MIR::Statement&>(stmt), [&](auto& lv, auto im){ return cb(lv, im); });
}
- void visit_mir_lvalues_mut(::MIR::Terminator& term, ::std::function<bool(::MIR::LValue& , ValUsage)> cb)
+ bool visit_mir_lvalues_mut(::MIR::Terminator& term, ::std::function<bool(::MIR::LValue& , ValUsage)> cb)
{
+ bool rv = false;
TU_MATCHA( (term), (e),
(Incomplete,
),
@@ -491,27 +498,28 @@ namespace {
(Panic,
),
(If,
- visit_mir_lvalue_raw_mut(e.cond, ValUsage::Read, cb);
+ rv |= visit_mir_lvalue_raw_mut(e.cond, ValUsage::Read, cb);
),
(Switch,
- visit_mir_lvalue_raw_mut(e.val, ValUsage::Read, cb);
+ rv |= visit_mir_lvalue_raw_mut(e.val, ValUsage::Read, cb);
),
(SwitchValue,
- visit_mir_lvalue_raw_mut(e.val, ValUsage::Read, cb);
+ rv |= visit_mir_lvalue_raw_mut(e.val, ValUsage::Read, cb);
),
(Call,
if( e.fcn.is_Value() ) {
- visit_mir_lvalue_raw_mut(e.fcn.as_Value(), ValUsage::Read, cb);
+ rv |= visit_mir_lvalue_raw_mut(e.fcn.as_Value(), ValUsage::Read, cb);
}
for(auto& v : e.args)
- visit_mir_lvalue_mut(v, ValUsage::Move, cb);
- visit_mir_lvalue_raw_mut(e.ret_val, ValUsage::Write, cb);
+ rv |= visit_mir_lvalue_mut(v, ValUsage::Move, cb);
+ rv |= visit_mir_lvalue_raw_mut(e.ret_val, ValUsage::Write, cb);
)
)
+ return rv;
}
- void visit_mir_lvalues(const ::MIR::Terminator& term, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb)
+ bool visit_mir_lvalues(const ::MIR::Terminator& term, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb)
{
- visit_mir_lvalues_mut(const_cast<::MIR::Terminator&>(term), [&](auto& lv, auto im){ return cb(lv, im); });
+ return visit_mir_lvalues_mut(const_cast<::MIR::Terminator&>(term), [&](auto& lv, auto im){ return cb(lv, im); });
}
void visit_mir_lvalues_mut(::MIR::TypeResolve& state, ::MIR::Function& fcn, ::std::function<bool(::MIR::LValue& , ValUsage)> cb)
@@ -1427,6 +1435,325 @@ bool MIR_Optimise_Inlining(::MIR::TypeResolve& state, ::MIR::Function& fcn, bool
// (*_$1).1 = 0x0;
// ```
+namespace {
+ struct StmtRef {
+ unsigned bb_idx;
+ unsigned stmt_idx;
+ StmtRef(): bb_idx(0), stmt_idx(0) {}
+ StmtRef(unsigned b, unsigned s): bb_idx(b), stmt_idx(s) {}
+ };
+ ::std::ostream& operator<<(::std::ostream& os, const StmtRef& x) {
+ return os << "BB" << x.bb_idx << "/" << x.stmt_idx;
+ }
+
+ // Iterates the path between two positions, NOT visiting entry specified by `end`
+ bool iter_path(
+ const ::MIR::Function& fcn, const StmtRef& start, const StmtRef& end,
+ ::std::function<bool(StmtRef, const ::MIR::Statement&)> cb_stmt,
+ ::std::function<bool(StmtRef, const ::MIR::Terminator&)> cb_term
+ )
+ {
+ assert(start.bb_idx == end.bb_idx); // TODO: Support chaining.
+ assert(start.stmt_idx <= end.stmt_idx);
+
+ size_t bb_idx = start.bb_idx;
+ for(auto ref = start; ref.stmt_idx < end.stmt_idx; ref.stmt_idx ++)
+ {
+ const auto& bb = fcn.blocks.at(bb_idx);
+ if( ref.stmt_idx < bb.statements.size() )
+ {
+ if( cb_stmt(ref, bb.statements.at(ref.stmt_idx)) )
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if( cb_term(ref, bb.terminator) )
+ {
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ ::std::function<bool(const ::MIR::LValue& , ValUsage)> check_invalidates_lvalue_cb(const ::MIR::LValue& val)
+ {
+ bool has_index = ::std::any_of(val.m_wrappers.begin(), val.m_wrappers.end(), [&](const auto& w){ return w.is_Index(); });
+ // Value is invalidated if it's used with ValUsage::Write or ValUsage::Borrow
+ // - Same applies to any component of the lvalue
+ return [&val,has_index](const ::MIR::LValue& lv, ValUsage vu) {
+ switch(vu)
+ {
+ case ValUsage::Move: // A move can invalidate
+ // - Ideally this would check if it DOES invalidate
+ case ValUsage::Write:
+ case ValUsage::Borrow:
+ // (Possibly) mutating use, check if it impacts the root or one of the indexes
+ if( lv.m_root == val.m_root ) {
+ return true;
+ }
+ if( has_index && lv.m_root.is_Local() )
+ {
+ for(const auto& w : val.m_wrappers)
+ {
+ if( w.is_Index() )
+ {
+ if( w.as_Index() == lv.m_root.as_Local() )
+ {
+ return true;
+ }
+ }
+ }
+ }
+ break;
+ case ValUsage::Read:
+ break;
+ }
+ return false;
+ };
+ }
+ bool check_invalidates_lvalue(const ::MIR::Statement& stmt, const ::MIR::LValue& val)
+ {
+ return visit_mir_lvalues(stmt, check_invalidates_lvalue_cb(val));
+ }
+ bool check_invalidates_lvalue(const ::MIR::Terminator& term, const ::MIR::LValue& val)
+ {
+ return visit_mir_lvalues(term, check_invalidates_lvalue_cb(val));
+ }
+}
+
+bool MIR_Optimise_DeTemporary_SingleSetAndUse(::MIR::TypeResolve& state, ::MIR::Function& fcn)
+{
+ bool changed = false;
+ TRACE_FUNCTION_FR("", changed);
+
+ // TODO: New algorithm
+ // Find all single-use/single-write locals
+ // - IF the usage is a RValue::Use, AND the usage destination is not invalidated between set/use
+ // - Replace initialisation destination with usage destination (delete usage statement)
+ // - IF the source a Use/Constant, AND is not invalidated between set/use
+ // - Replace usage with the original source
+ struct LocalUsage {
+ unsigned n_write;
+ unsigned n_read;
+ unsigned n_borrow;
+ StmtRef set_loc;
+ StmtRef use_loc;
+ LocalUsage():
+ n_write(0),
+ n_read(0),
+ n_borrow(0)
+ {
+ }
+ };
+ auto usage_info = ::std::vector<LocalUsage>(fcn.locals.size());
+ for(const auto& bb : fcn.blocks)
+ {
+ StmtRef cur_loc;
+ auto visit_cb = [&](const ::MIR::LValue& lv, auto vu) {
+ if( !lv.m_wrappers.empty() ) {
+ vu = ValUsage::Read;
+ }
+ for(const auto& w : lv.m_wrappers)
+ {
+ if(w.is_Index())
+ {
+ auto& slot = usage_info[w.as_Index()];
+ slot.n_read += 1;
+ slot.use_loc = cur_loc;
+ //DEBUG(lv << " index use");
+ }
+ }
+ if( lv.m_root.is_Local() )
+ {
+ auto& slot = usage_info[lv.m_root.as_Local()];
+ switch(vu)
+ {
+ case ValUsage::Write:
+ slot.n_write += 1;
+ slot.set_loc = cur_loc;
+ //DEBUG(lv << " set");
+ break;
+ case ValUsage::Move:
+ slot.n_read += 1;
+ slot.use_loc = cur_loc;
+ //DEBUG(lv << " use");
+ break;
+ case ValUsage::Read:
+ case ValUsage::Borrow:
+ slot.n_borrow += 1;
+ //DEBUG(lv << " borrow");
+ break;
+ }
+ }
+ return false;
+ };
+ for(const auto& stmt : bb.statements)
+ {
+ cur_loc = StmtRef(&bb - &fcn.blocks.front(), &stmt - &bb.statements.front());
+ //DEBUG(cur_loc << ":" << stmt);
+ visit_mir_lvalues(stmt, visit_cb);
+ }
+ cur_loc = StmtRef(&bb - &fcn.blocks.front(), bb.statements.size());
+ //DEBUG(cur_loc << ":" << bb.terminator);
+ visit_mir_lvalues(bb.terminator, visit_cb);
+ }
+
+ for(size_t var_idx = 0; var_idx < fcn.locals.size(); var_idx ++)
+ {
+ const auto& slot = usage_info[var_idx];
+ auto this_var = ::MIR::LValue::new_Local(var_idx);
+ //ASSERT_BUG(Span(), slot.n_write > 0, "Variable " << var_idx << " not written?");
+ if( slot.n_write == 1 && slot.n_read == 1 && slot.n_borrow == 0 )
+ {
+ // Single-use variable, now check how we can eliminate it
+ DEBUG("Single-use: _" << var_idx << " - Set " << slot.set_loc << ", Use " << slot.use_loc);
+
+ auto& use_bb = fcn.blocks[slot.use_loc.bb_idx];
+ auto& set_bb = fcn.blocks[slot.set_loc.bb_idx];
+ // If usage is direct assignment of the original value.
+ // - In this case, we can move the usage upwards
+ if( slot.use_loc.stmt_idx < use_bb.statements.size() && TU_TEST2(use_bb.statements[slot.use_loc.stmt_idx], Assign, .src, Use, == this_var) )
+ {
+ // Move the usage up to original assignment (if destination isn't invalidated)
+ const auto& dst = use_bb.statements[slot.use_loc.stmt_idx].as_Assign().dst;
+
+ // - Iterate the path(s) between the two statements to check if the destination would be invalidated
+ // > The iterate function doesn't (yet) support following BB chains, so assume invalidated if over a jump.
+ bool invalidated = (slot.set_loc.bb_idx != slot.use_loc.bb_idx) || iter_path(fcn, slot.set_loc, slot.use_loc,
+ [&](auto loc, const auto& stmt)->bool{ return check_invalidates_lvalue(stmt, dst); },
+ [&](auto loc, const auto& term)->bool{ return check_invalidates_lvalue(term, dst); }
+ );
+ if( !invalidated )
+ {
+ // destination not dependent on any statements between the two, move.
+ if( slot.set_loc.stmt_idx < set_bb.statements.size() )
+ {
+ auto& set_stmt = set_bb.statements[slot.set_loc.stmt_idx];
+ TU_MATCH_HDRA( (set_stmt), {)
+ TU_ARMA(Assign, se) {
+ MIR_ASSERT(state, se.dst == ::MIR::LValue::new_Local(var_idx), "");
+ DEBUG("Move destination " << dst << " from " << use_bb.statements[slot.use_loc.stmt_idx] << " to " << set_stmt);
+ se.dst = dst.clone();
+ use_bb.statements[slot.use_loc.stmt_idx] = ::MIR::Statement();
+ }
+ TU_ARMA(Asm, se) {
+ // Initialised from an ASM statement, find the variable in the output parameters
+ }
+ break;
+ default:
+ MIR_BUG(state, "Impossibility: Value set in " << set_stmt);
+ }
+ }
+ else
+ {
+ auto& set_term = set_bb.terminator;
+ MIR_ASSERT(state, set_term.is_Call(), "Impossibility: Value set using non-call");
+ // TODO: Update call destination.
+ MIR_TODO(state, "Update call destination when moving assignment up");
+ }
+ }
+ else
+ {
+ DEBUG("Destination invalidated");
+ }
+ continue ;
+ }
+ // Can't move up, can we move down?
+ // - If the source is an Assign(Use) then we can move down
+ if( slot.set_loc.stmt_idx < set_bb.statements.size() && TU_TEST1(set_bb.statements[slot.set_loc.stmt_idx], Assign, .src.is_Use()) )
+ {
+ auto& set_stmt = set_bb.statements[slot.set_loc.stmt_idx];
+ const auto& src = set_stmt.as_Assign().src.as_Use();
+
+ // Check if the source of initial assignment is invalidated in the meantime.
+ bool invalidated = (slot.set_loc.bb_idx != slot.use_loc.bb_idx) || iter_path(fcn, slot.set_loc, slot.use_loc,
+ [&](auto loc, const auto& stmt)->bool{ return check_invalidates_lvalue(stmt, src); },
+ [&](auto loc, const auto& term)->bool{ return check_invalidates_lvalue(term, src); }
+ );
+ if( !invalidated )
+ {
+ // Update the usage site and replace.
+ auto replace_cb = [&](::MIR::LValue& slot, ValUsage vu)->bool {
+ if( slot.m_root == this_var.m_root )
+ {
+ if( src.m_wrappers.empty() ) {
+ slot.m_root = src.m_root.clone();
+ }
+ else if( slot.m_wrappers.empty() ) {
+ slot = src.clone();
+ }
+ else {
+ MIR_TODO(state, "Replace inner of " << slot << " with " << src);
+ }
+ return true;
+ }
+ return false;
+ };
+ if( slot.use_loc.stmt_idx < use_bb.statements.size() )
+ {
+ auto& use_stmt = use_bb.statements[slot.use_loc.stmt_idx];
+ DEBUG("Replace " << this_var << " with " << src << " in " << use_stmt);
+ bool found = visit_mir_lvalues_mut(use_stmt, replace_cb);
+ if( !found )
+ {
+ DEBUG("Can't find use of " << this_var << " in " << use_stmt);
+ }
+ else
+ {
+ set_stmt = ::MIR::Statement();
+ }
+ }
+ else
+ {
+ auto& use_term = use_bb.terminator;
+ DEBUG("Replace " << this_var << " with " << src << " in " << use_term);
+ bool found = visit_mir_lvalues_mut(use_term, replace_cb);
+ if( !found )
+ {
+ DEBUG("Can't find use of " << this_var << " in " << use_term);
+ }
+ else
+ {
+ set_stmt = ::MIR::Statement();
+ }
+ }
+ }
+ else
+ {
+ DEBUG("Source invalidated");
+ }
+ continue;
+ }
+
+ // TODO: If the source is a Borrow and the use is a Deref, then propagate forwards
+
+ DEBUG("Can't replace:");
+ if( slot.set_loc.stmt_idx < set_bb.statements.size() )
+ {
+ DEBUG("Set: " << set_bb.statements[slot.set_loc.stmt_idx]);
+ }
+ else
+ {
+ DEBUG("Set: " << set_bb.terminator);
+ }
+ if( slot.use_loc.stmt_idx < use_bb.statements.size() )
+ {
+ DEBUG("Use: " << use_bb.statements[slot.use_loc.stmt_idx]);
+ }
+ else
+ {
+ DEBUG("Use: " << use_bb.terminator);
+ }
+ }
+ }
+
+ return changed;
+}
+
// --------------------------------------------------------------------
// Replaces uses of stack slots with what they were assigned with (when
// possible)
@@ -1436,6 +1763,10 @@ bool MIR_Optimise_DeTemporary(::MIR::TypeResolve& state, ::MIR::Function& fcn)
bool changed = false;
TRACE_FUNCTION_FR("", changed);
+ changed |= MIR_Optimise_DeTemporary_SingleSetAndUse(state, fcn);
+
+
+ // OLD ALGORITHM.
for(unsigned int bb_idx = 0; bb_idx < fcn.blocks.size(); bb_idx ++)
{
auto& bb = fcn.blocks[bb_idx];
@@ -3801,6 +4132,14 @@ bool MIR_Optimise_GarbageCollect(::MIR::TypeResolve& state, ::MIR::Function& fcn
{
auto stmt_idx = &stmt - &it->statements.front();
state.set_cur_stmt(i, stmt_idx);
+
+ if( stmt == ::MIR::Statement() )
+ {
+ DEBUG(state << "Remove " << stmt << " - Pure default");
+ to_remove_statements[stmt_idx] = true;
+ continue ;
+ }
+
if( auto* se = stmt.opt_Drop() )
{
// If the drop flag was unset, either remove the drop or remove the drop flag reference