diff options
author | John Hodge <tpg@ucc.asn.au> | 2017-12-20 18:08:43 +0800 |
---|---|---|
committer | John Hodge <tpg@ucc.asn.au> | 2017-12-20 18:08:43 +0800 |
commit | 8d23dfdf26fa2d61731018743737efd36ec6ed1a (patch) | |
tree | 10980b21c355278142a47fcdb5bc8fa6ee672456 | |
parent | 398ff88581fbb3c33ff851a044faa35f327d5185 (diff) | |
download | mrust-8d23dfdf26fa2d61731018743737efd36ec6ed1a.tar.gz |
MIR Optimise - Redo DeTemporary pass, works for build rustc/cargo, rustc tested
-rw-r--r-- | Notes/MIR-Optimisations.md | 99 | ||||
-rw-r--r-- | src/mir/optimise.cpp | 246 |
2 files changed, 199 insertions, 146 deletions
diff --git a/Notes/MIR-Optimisations.md b/Notes/MIR-Optimisations.md index 759bfa2e..e83f7cd3 100644 --- a/Notes/MIR-Optimisations.md +++ b/Notes/MIR-Optimisations.md @@ -19,8 +19,8 @@ At the end of the block, remove original tuple assignment and propagate the valu -De-Temporary -============ +De-Temporary (version 1) +======================== Purpose: Eliminate useless temporaries ``` @@ -31,20 +31,19 @@ _2 = foo(_1) Algorithm --------- -Where the pattern `Local(N) = Use(...)` is seen, record that statement as the assignment for `Local(N)`. +- Where the pattern `Local(N) = Use(...)` is seen, record that statement as the assignment for `Local(N)`. +- Detect any uses of `Local(N)` in mutation context (e.g. `Field(Local(N), 0) = ...`) or as a borrow and remove from the + list (as these cause the local to diverge from its source). +- Detect any by-value or mutation uses of the origin value that would mean that moving the assignment forwards would + lead to a use-after-free or incorrect value. -Detect any uses of `Local(N)` in mutation context (e.g. `Field(Local(N), 0) = ...`) or as a borrow and remove from the -list (as these cause the local to diverge from its source). - -Detect any by-value or mutation uses of the origin value that would mean that moving the assignment forwards would -lead to a use-after-free or incorrect value. - -If a recorded local is used, replace that usage by the original source. If the value was !Copy, and the use is a move, - delete the original assignment. -- CATCH: Detecting a by-move of !Copy is hard when a Copy value is used from a !Copy struct, needs knowledge of the - types involved. -- CATCH: Since partial moves are valid, the !Copy rule above is invalid if it would have been a partial move. - - Only allow use of a !Copy result when it's just the slot. +- Find any uses of locals recorded locals + - If this is of a Copy value, the replacement + - Copy values can be reused freely + - If the value is used at the top level of the source RValue, do the replacemnet and remove the record + - !Copy value moved, so can't be moved again. + - Otherwise, remove the record + - The value was used, so then assignment can't be replaced with later and deleted. Pseudocode ---------- @@ -67,15 +66,22 @@ for stmt in block.statements fn do_replacements(stmt) { visit_lvalues(stmt, |lv, usage| { - // 1. Check if Copy (if it is, don't delete any referenced values) - delete = (usage == Move && !lv_is_copy(lv)); - // 2. Search for replacements + top_level = true; + // 1. Search for replacements visit_lvalues(lv, |ilv, usage2| { if Local(i) == ilv { - ilv = replacement; - if delete { - remove_replacement; - } + if replacement = find_replacement(i) { + if is_copy(ilv) { + ilv = replacement + } + else if top_level { + ilv = replacement + remove_replacement(i) + } + else { + remove_replacement(i) + } + } } }); // Early-return (don't recuse) @@ -95,6 +101,13 @@ Locate `RETURN = Local(N)` and replace use of Local(N) as assignment destination Note: Only replace assignments that are setting the value that will become the return value. +Algorithm +--------- +TODO: + +Pseudocode +---------- +TODO: Dead Assignment Elimination @@ -104,14 +117,56 @@ Purpose: Remove dead code from previous passes Remove assignments where the assigned value isn't read/borrowed before next assign +Algorithm +--------- + +Pseudocode +--------- + +Dead Drop Flag Elimination +========================== + +Purpose: Remove unused (or unchanged) drop flags + +Algorithm +--------- +- Enumerate all changes to drop flags +- If a flag is never set, replace all uses with its default and delete +- If a flag is never used, delete + + Constant Propagation ==================== Purpose: Allow deletion of useless code (`if false`) and expansion of `size_of` (and relevant optimisations) +Algorithm +--------- +- Track assignments of slots with constants +- Erase record if the slot is invalidated +- Replace usage of the slot where possible with the constant +- Evaluate BinOps with both arguments a known slot + Value Forward Propagation ========================= Purpose: Determine when a value has the same value in all paths to a BB and move the assignment into that BB +Inlining +======== + +Purpose: Inline simple methods + +Algorithm +--------- +- For each function call: +- Check if the called function is simple enough to be inlined + - A single BB - inline + - Three bbs (first being a function call) - inline + - First bb ends with a switch of a constant parameter, and all arms point to return or a return bb + +CFG Simplification +================== +Purpose: Remove gotos to blocks that only have a single use + diff --git a/src/mir/optimise.cpp b/src/mir/optimise.cpp index 08e246df..11cbe45e 100644 --- a/src/mir/optimise.cpp +++ b/src/mir/optimise.cpp @@ -79,7 +79,7 @@ namespace { return visit_mir_lvalue_mut(*e.val, u == ValUsage::Move ? ValUsage::Read : u, cb); ), (Deref, - return visit_mir_lvalue_mut(*e.val, ValUsage::Read, cb); + return visit_mir_lvalue_mut(*e.val, u == ValUsage::Borrow ? u : ValUsage::Read, cb); ), (Index, bool rv = false; @@ -241,6 +241,10 @@ namespace { ) ) } + void 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); }); + } void visit_mir_lvalues_mut(::MIR::TypeResolve& state, ::MIR::Function& fcn, ::std::function<bool(::MIR::LValue& , ValUsage)> cb) { @@ -555,15 +559,12 @@ void MIR_Optimise(const StaticTraitResolve& resolve, const ::HIR::ItemPath& path MIR_Validate(resolve, path, fcn, args, ret_type); #endif - // Disabled: Slightly buggy. -#if 0 // Attempt to remove useless temporaries while( MIR_Optimise_DeTemporary(state, fcn) ) change_happened = true; #if CHECK_AFTER_ALL MIR_Validate(resolve, path, fcn, args, ret_type); #endif -#endif // >> Replace values from composites if they're known // - Undoes the inefficiencies from the `match (a, b) { ... }` pattern @@ -1254,161 +1255,158 @@ bool MIR_Optimise_DeTemporary(::MIR::TypeResolve& state, ::MIR::Function& fcn) for(unsigned int bb_idx = 0; bb_idx < fcn.blocks.size(); bb_idx ++) { auto& bb = fcn.blocks[bb_idx]; - // TODO: Store statement index, to allow deleting the statement if the value is !Copy - ::std::vector<unsigned> to_delete; - ::std::map<unsigned,unsigned> local_assignments; // Local index to statement - // Since the passed lvalue was mutated (or borrowed), check if that invalidates part of the above cache. - auto check_invalidates = [&](const ::MIR::LValue& lv) { - for(auto it = local_assignments.begin(); it != local_assignments.end(); ) - { - const auto& new_lv = bb.statements.at( it->second ).as_Assign().src.as_Use(); + ::std::map<unsigned,unsigned> local_assignments; // Local number -> statement index + ::std::vector<unsigned> statements_to_remove; // List of statements that have to be removed + + // ----- Helper closures ----- + // > Check if a recorded assignment is no longer valid. + auto cb_check_invalidate = [&](const ::MIR::LValue& lv, ValUsage vu) { + for(auto it = local_assignments.begin(); it != local_assignments.end(); ) + { + bool invalidated = false; + const auto& src_rvalue = bb.statements[it->second].as_Assign().src; - bool new_invalidated = visit_mir_lvalue(new_lv, ValUsage::Read, [&](const auto& ilv2, auto) { - return visit_mir_lvalue(lv, ValUsage::Write, [&](const auto& ilv, auto vu) { - if( ilv == ilv2 ) + // Destination invalidated? + if( lv.is_Local() && it->first == lv.as_Local() ) + { + switch(vu) { - if( vu != ValUsage::Read ) - return true; - if( !state.lvalue_is_copy(ilv) ) - return true; - DEBUG(state << it->first << " referenced, but not invalidated"); + case ValUsage::Borrow: + case ValUsage::Write: + DEBUG(state << "> Mutate/Borrowed " << lv); + invalidated = true; + break; + default: + break; } - return false; - }); - }); - bool old_used = visit_mir_lvalue(lv, ValUsage::Write, [&](const auto& ilv, auto vu) { - if( ilv == ::MIR::LValue::make_Local(it->first) ) + } + // Source invalidated? + else { - DEBUG(state << it->first << " used, remove"); - return true; + switch(vu) + { + case ValUsage::Borrow: // Borrows are annoying, assume they invalidate anything used + case ValUsage::Write: // Mutated? It's invalidated + case ValUsage::Move: // Moved? Now invalid + visit_mir_lvalues(src_rvalue, [&](const auto& s_lv, auto /*s_vu*/) { + if( s_lv == lv ) + { + DEBUG(state << "> Invalidates source of Local(" << it->first << ") - " << src_rvalue); + invalidated = true; + return true; + } + return false; + }); + break; + case ValUsage::Read: // Read is Ok + break; + } } - return false; - }); - if( new_invalidated || old_used ) - { - DEBUG(state << it->first << " from " << bb_idx << "/" << it->second << " - Invalidated"); - it = local_assignments.erase(it); - } - else - { - ++ it; + if( invalidated ) + { + it = local_assignments.erase(it); + } + else + { + ++ it; + } } - } + return false; }; - auto replace_cb = [&](auto& lv, auto use) { - if( lv.is_Local() && (use == ValUsage::Read || use == ValUsage::Move) ) - { - auto it = local_assignments.find(lv.as_Local()); - if( it != local_assignments.end() ) + // ^^^ Check for invalidations + auto cb_apply_replacements = [&](auto& top_lv, auto top_usage) { + // NOTE: Visits only the top-level LValues + // - The inner `visit_mir_lvalue_mut` handles sub-values + + // TODO: Handle partial moves (only delete assignment if the value is fully used) + // > For now, don't do the replacement if it would delete the assignment UNLESS it's directly being used) + + // 2. Search for replacements + bool top_level = true; + visit_mir_lvalue_mut(top_lv, top_usage, [&](auto& ilv, auto /*i_usage*/) { + if( ilv.is_Local() ) { - auto new_lv = bb.statements.at( it->second ).as_Assign().src.as_Use().clone(); - if( !state.m_resolve.type_is_copy(state.sp, fcn.locals[lv.as_Local()]) ) + auto it = local_assignments.find(ilv.as_Local()); + if( it != local_assignments.end() ) { - local_assignments.erase(it); - return false; - // TODO: ::Move is used for field accesses, even if the value is !Copy - if( use == ValUsage::Move ) + // - Copy? All is good. + if( state.lvalue_is_copy(ilv) ) { - to_delete.push_back(it->second); + ilv = bb.statements[it->second].as_Assign().src.as_Use().clone(); + DEBUG(state << "> Replace (and keep) Local(" << it->first << ") with " << ilv); + } + // - Top-level (directly used) also good. + else if( top_level ) + { + // TODO: DstMeta/DstPtr _doesn't_ move, so shouldn't trigger this. + ilv = bb.statements[it->second].as_Assign().src.as_Use().clone(); + DEBUG(state << "> Replace (and remove) Local(" << it->first << ") with " << ilv); + statements_to_remove.push_back( it->second ); local_assignments.erase(it); - DEBUG(state << lv << " -> " << new_lv << " (and erase)"); } + // - Otherwise, remove the record. else { + DEBUG(state << "> Non-copy value used within a LValue, remove record of Local(" << it->first << ")"); local_assignments.erase(it); - DEBUG(state << lv << " kept, !Copy and not moved"); - return false; } } - else - { - DEBUG(state << lv << " -> " << new_lv); - } - lv = mv$(new_lv); - changed = true; - return true; } - } - return false; + top_level = false; + return false; + }); + // Return true to prevent recursion + return true; }; + + // ----- Top-level algorithm ------ + // - Find expressions matching the pattern `Local(N) = Use(...)` + // > Delete entry when destination is mutated + // > Delete entry when source is mutated or invalidated (moved) for(unsigned int stmt_idx = 0; stmt_idx < bb.statements.size(); stmt_idx ++ ) { auto& stmt = bb.statements[stmt_idx]; state.set_cur_stmt(bb_idx, stmt_idx); DEBUG(state << stmt); - // 2. If it's an assignment of the form Local(N) = LValue, keep a record of that. - // > If a local is borrowed, wipe that local's record - bool something_to_add = false; - ::std::pair<unsigned,unsigned> to_add; - if(const auto* e = stmt.opt_Assign()) - { - if( const auto* se = e->src.opt_Borrow() ) - { - if( se->val.is_Local() ) { - local_assignments.erase( se->val.as_Local() ); - } - check_invalidates(se->val); - } - if( e->dst.is_Local() ) - { - if( e->src.is_Use() ) - { - to_add = ::std::make_pair(e->dst.as_Local(), stmt_idx); - something_to_add = true; + // - Check if this statement mutates or borrows a recorded local + // > (meaning that the slot isn't a temporary) + // - Check if this statement mutates or moves the source + // > (thus making it invalid to move the source forwards) + visit_mir_lvalues(stmt, cb_check_invalidate); - if( visit_mir_lvalues(e->src, [&](const auto& lv, auto ){ return lv == e->dst; }) ) - { - DEBUG(state << "Assignment to slot used in source, can't use"); - something_to_add = false; - } - } - local_assignments.erase( e->dst.as_Local() ); - } - check_invalidates(e->dst); + // - Apply known relacements + visit_mir_lvalues_mut(stmt, cb_apply_replacements); - // Remove no-op assignents - if( TU_TEST1(e->src, Use, == e->dst) ) { - bb.statements.erase(bb.statements.begin() + stmt_idx); - stmt_idx -= 1; - continue ; - } - } - else if( stmt.is_Drop() ) - { - check_invalidates(stmt.as_Drop().slot); - } - else + // - Check if this is a new assignment + if( stmt.is_Assign() && stmt.as_Assign().dst.is_Local() && stmt.as_Assign().src.is_Use() ) { + local_assignments.insert(::std::make_pair( stmt.as_Assign().dst.as_Local(), stmt_idx )); + DEBUG(state << "> Record assignment"); } - if( !something_to_add ) - { - // 1. Replace any referenced locals with contents of a record of the local values. - // > Don't do the replacement if the source could have changed - visit_mir_lvalues_mut(stmt, replace_cb); - } - - visit_mir_lvalues(stmt, [&](const auto& lv, auto vu){ - if(vu == ValUsage::Move) - check_invalidates(lv); - return true; - }); + } // for(stmt in bb.statements) - if( something_to_add ) { - DEBUG(state << "Record Local(" << to_add.first << ") set from here"); - local_assignments.insert(to_add); - } - } + // TERMINATOR state.set_cur_stmt_term(bb_idx); DEBUG(state << bb.terminator); - visit_mir_lvalues_mut(bb.terminator, replace_cb); + // > Check for invalidations (e.g. move of a source value) + visit_mir_lvalues(bb.terminator, cb_check_invalidate); + // > THEN check for replacements + if( ! bb.terminator.is_Switch() ) + { + visit_mir_lvalues_mut(bb.terminator, cb_apply_replacements); + } - ::std::sort(to_delete.begin(), to_delete.end()); - while(!to_delete.empty()) + // Remove assignments + ::std::sort(statements_to_remove.begin(), statements_to_remove.end()); + while(!statements_to_remove.empty()) { - bb.statements.erase( bb.statements.begin() + to_delete.back() ); - to_delete.pop_back(); + // TODO: Handle partial moves here? + // TODO: Is there some edge case I'm missing where the assignment shouldn't be removed? + // > It isn't removed if it's used as a Copy, so that's not a problem. + bb.statements.erase( bb.statements.begin() + statements_to_remove.back() ); + statements_to_remove.pop_back(); } } |