[gccrs COMMIT] gccrs: Handle conditional moves in BIR drop analysis
gerris.rs@gmail.com
gerris.rs@gmail.com
Sun Aug 23 21:38:40 GMT 2026
From: Lishin <lishin1008@gmail.com>
Track whole-local initialization across BIR control-flow branches.
When a local is moved on only some paths, mark its Drop as conditional.
Keep the existing backend handling for straight-line CFGs and add tests
for static, dead and conditional Drops.
gcc/rust/ChangeLog:
* checks/errors/borrowck/rust-bir-drop-analysis.cc
(struct BlockInitializationState): New struct.
(is_straight_line): New function.
(set_initialized): Likewise.
(set_uninitialized): Likewise.
(merge_state): Likewise.
(update_state_for_statement): Likewise.
(classify_drop): Likewise.
(compute_entry_states): Likewise.
(record_drop_for_straight_line_backend): Likewise.
(annotate_drop_statements): Likewise.
(DropAnalysis::analyze): Propagate initialization state across the
CFG and classify Drop statements.
* checks/errors/borrowck/rust-bir-drop-analysis.h: Update class
comment.
gcc/testsuite/ChangeLog:
* rust/borrowck/drop_analysis_conditional_move.rs: New test.
Signed-off-by: Lishin <lishin1008@gmail.com>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.
Commit on github: https://github.com/Rust-GCC/gccrs/commit/00a99e715512791f52ad68522e38d149c6373310
The commit has NOT been mentioned in any issue.
The commit has been mentioned in the following pull-request(s):
- https://github.com/Rust-GCC/gccrs/pull/4777
.../errors/borrowck/rust-bir-drop-analysis.cc | 391 +++++++++++++-----
.../errors/borrowck/rust-bir-drop-analysis.h | 2 +-
.../drop_analysis_conditional_move.rs | 46 +++
3 files changed, 340 insertions(+), 99 deletions(-)
create mode 100644 gcc/testsuite/rust/borrowck/drop_analysis_conditional_move.rs
diff --git a/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.cc b/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.cc
index 09301d86e..2bdf5e5e4 100644
--- a/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.cc
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.cc
@@ -20,145 +20,340 @@
#include "rust-bir.h"
#include "rust-hir-map.h"
-#include <unordered_set>
-
namespace Rust {
namespace BIR {
-
namespace {
-struct BasicBlockIdHash
+struct BlockInitializationState
{
- size_t operator() (BasicBlockId id) const
- {
- return std::hash<uint32_t> () (id.value);
- }
-};
+ explicit BlockInitializationState (size_t place_count)
+ : maybe_initialized (place_count, false),
+ maybe_uninitialized (place_count, false), reachable (false)
+ {}
-} // namespace
+ std::vector<bool> maybe_initialized;
+ std::vector<bool> maybe_uninitialized;
-DropAnalysis &
-DropAnalysis::get ()
+ // Whether this block is reached or not.
+ bool reachable;
+};
+
+static bool
+is_straight_line (const Function &function)
{
- static DropAnalysis instance;
- return instance;
+ std::set<BasicBlockId> visited;
+ BasicBlockId current = ENTRY_BASIC_BLOCK;
+
+ while (current != INVALID_BB)
+ {
+ // Revisiting a block means that the CFG contains a cycle.
+ if (!visited.insert (current).second)
+ return false;
+
+ const BasicBlock &block = function.basic_blocks[current];
+
+ if (block.successors.empty ())
+ return true;
+
+ if (block.successors.size () != 1)
+ return false;
+
+ current = block.successors.front ();
+ }
+
+ return true;
}
-void
-DropAnalysis::clear ()
+static void
+set_initialized (BlockInitializationState &state, PlaceId place)
{
- definitely_dead.clear ();
+ state.maybe_initialized[place.value] = true;
+ state.maybe_uninitialized[place.value] = false;
}
-bool
-DropAnalysis::is_definitely_dead (HirId id) const
+static void
+set_uninitialized (BlockInitializationState &state, PlaceId place)
{
- return definitely_dead.find (id) != definitely_dead.end ();
+ state.maybe_initialized[place.value] = false;
+ state.maybe_uninitialized[place.value] = true;
}
-void
-DropAnalysis::analyze (Function &function)
+// This function combines states from incoming blocks.
+// Take conditional move as example,
+//
+// BB0
+// initialize x
+// / |
+// v v
+// BB1 BB2
+// move x no change
+// \ /
+// v v
+// BB3
+// Drop(x)
+// BB1 -> BB3
+// (BB1): maybe_initialized(x) = false
+// (BB1): maybe_uninitialized(x) = true
+//
+// (BB3): maybe_initialized(x) = true || (BB1): maybe_initialized(x) -> true
+// (BB3): maybe_uninitialized(x) = false || (BB1): maybe_uninitialized(x) ->
+// true
+static bool
+merge_state (BlockInitializationState &into,
+ const BlockInitializationState &from)
{
- std::vector<BasicBlockId> block_order;
- std::unordered_set<BasicBlockId, BasicBlockIdHash> visited;
+ bool changed = false;
- BasicBlockId current = ENTRY_BASIC_BLOCK;
+ if (!into.reachable)
+ {
+ into = from;
+ return !changed;
+ }
- while (current != INVALID_BB)
+ for (size_t i = 0; i < into.maybe_initialized.size (); i++)
{
- // A repeated block indicates a cycle in straight-line control flow.
- if (!visited.insert (current).second)
- return;
+ bool maybe_initialized
+ = into.maybe_initialized[i] || from.maybe_initialized[i];
- block_order.push_back (current);
+ bool maybe_uninitialized
+ = into.maybe_uninitialized[i] || from.maybe_uninitialized[i];
- const BasicBlock &block = function.basic_blocks[current];
+ changed |= maybe_initialized != into.maybe_initialized[i];
+ changed |= maybe_uninitialized != into.maybe_uninitialized[i];
- if (block.successors.empty ())
- break;
+ into.maybe_initialized[i] = maybe_initialized;
+ into.maybe_uninitialized[i] = maybe_uninitialized;
+ }
- if (block.successors.size () != 1)
- return;
+ return changed;
+}
- current = block.successors.front ();
+static void
+update_state_for_statement (Function &function, Statement &statement,
+ BlockInitializationState &state)
+{
+ PlaceId place = statement.get_place ();
+
+ switch (statement.get_kind ())
+ {
+ case Statement::Kind::STORAGE_LIVE:
+ set_uninitialized (state, place);
+ break;
+
+ case Statement::Kind::ASSIGNMENT:
+ {
+ PlaceId lhs = place;
+ AbstractExpr &expr = statement.get_expr ();
+
+ if (expr.get_kind () == ExprKind::ASSIGNMENT)
+ {
+ PlaceId rhs = static_cast<Assignment &> (expr).get_rhs ();
+ const Place &rhs_place = function.place_db[rhs];
+
+ if (rhs_place.kind == Place::VARIABLE
+ && rhs_place.should_be_moved ())
+ set_uninitialized (state, rhs);
+ }
+
+ set_initialized (state, lhs);
+ break;
+ }
+
+ case Statement::Kind::DROP:
+ case Statement::Kind::STORAGE_DEAD:
+ set_uninitialized (state, place);
+ break;
+
+ case Statement::Kind::SWITCH:
+ case Statement::Kind::RETURN:
+ case Statement::Kind::GOTO:
+ case Statement::Kind::USER_TYPE_ASCRIPTION:
+ case Statement::Kind::FAKE_READ:
+ break;
}
+}
+
+static Statement::DropStyle
+classify_drop (const BlockInitializationState &state, PlaceId place)
+{
+ bool maybe_initialized = state.maybe_initialized[place.value];
+ bool maybe_uninitialized = state.maybe_uninitialized[place.value];
+
+ if (!maybe_initialized)
+ return Statement::DropStyle::DEAD;
+
+ if (!maybe_uninitialized)
+ return Statement::DropStyle::STATIC;
+
+ return Statement::DropStyle::CONDITIONAL;
+}
+
+// Compute the initialization state at the entry of every reachable block.
+static std::vector<BlockInitializationState>
+compute_entry_states (Function &function)
+{
+ size_t place_count = function.place_db.size ();
+ size_t block_count = function.basic_blocks.size ();
+
+ std::vector<BlockInitializationState> entry_states;
+ entry_states.reserve (block_count);
+
+ for (size_t i = 0; i < block_count; i++)
+ entry_states.emplace_back (place_count);
+
+ BlockInitializationState &entry_state = entry_states[ENTRY_BASIC_BLOCK.value];
- std::vector<bool> initialized (function.place_db.size (), false);
+ entry_state.reachable = true;
+
+ // All places start uninitialized, except function arguments.
+ for (size_t i = 0; i < place_count; i++)
+ entry_state.maybe_uninitialized[i] = true;
for (PlaceId argument : function.arguments)
- initialized[argument.value] = true;
+ set_initialized (entry_state, argument);
+
+ std::vector<BasicBlockId> worklist;
+ std::vector<bool> queued (block_count, false);
- for (BasicBlockId block_id : block_order)
+ worklist.push_back (ENTRY_BASIC_BLOCK);
+ queued[ENTRY_BASIC_BLOCK.value] = true;
+
+ // Propagate block states until the last block.
+ while (!worklist.empty ())
{
+ BasicBlockId block_id = worklist.back ();
+ worklist.pop_back ();
+ queued[block_id.value] = false;
+
+ BlockInitializationState state = entry_states[block_id.value];
BasicBlock &block = function.basic_blocks[block_id];
for (Statement &statement : block.statements)
+ update_state_for_statement (function, statement, state);
+
+ for (BasicBlockId successor : block.successors)
{
- PlaceId place = statement.get_place ();
+ bool state_changed
+ = merge_state (entry_states[successor.value], state);
+
+ if (state_changed && !queued[successor.value])
+ {
+ worklist.push_back (successor);
+ queued[successor.value] = true;
+ }
+ }
+ }
+ return entry_states;
+}
+
+static void
+record_drop_for_straight_line_backend (const Function &function, PlaceId place,
+ Statement::DropStyle drop_style,
+ std::set<HirId> &dead_drop_hir_ids,
+ std::set<HirId> &non_dead_drop_hir_ids)
+{
+ const Place &dropped_place = function.place_db[place];
+
+ if (dropped_place.kind != Place::VARIABLE)
+ return;
+
+ auto hir_id = Analysis::Mappings::get ().lookup_node_to_hir (
+ static_cast<NodeId> (dropped_place.variable_or_field_index));
- switch (statement.get_kind ())
+ if (!hir_id.has_value ())
+ return;
+
+ if (drop_style == Statement::DropStyle::DEAD)
+ dead_drop_hir_ids.insert (hir_id.value ());
+ else
+ non_dead_drop_hir_ids.insert (hir_id.value ());
+}
+
+// Walk each reachable block forward from its stable entry state and classify
+// its Drop statements.
+static void
+annotate_drop_statements (
+ Function &function, const std::vector<BlockInitializationState> &entry_states,
+ bool record_straight_line_backend_drops, std::set<HirId> &dead_drop_hir_ids,
+ std::set<HirId> &non_dead_drop_hir_ids)
+{
+ const size_t block_count = function.basic_blocks.size ();
+
+ for (size_t i = 0; i < block_count; i++)
+ {
+ BlockInitializationState state = entry_states[i];
+
+ if (!state.reachable)
+ continue;
+
+ BasicBlockId block_id = {static_cast<uint32_t> (i)};
+ BasicBlock &block = function.basic_blocks[block_id];
+
+ for (Statement &statement : block.statements)
+ {
+ // A Drop is classified using the state before it executes.
+ if (statement.get_kind () == Statement::Kind::DROP)
{
- case Statement::Kind::STORAGE_LIVE:
- initialized[place.value] = false;
- break;
-
- case Statement::Kind::ASSIGNMENT:
- {
- PlaceId lhs = place;
- AbstractExpr &expr = statement.get_expr ();
-
- if (expr.get_kind () == ExprKind::ASSIGNMENT)
- {
- PlaceId rhs = static_cast<Assignment &> (expr).get_rhs ();
- const Place &rhs_place = function.place_db[rhs];
-
- if (rhs_place.kind == Place::VARIABLE
- && rhs_place.should_be_moved ())
- initialized[rhs.value] = false;
- }
-
- initialized[lhs.value] = true;
- break;
- }
-
- case Statement::Kind::DROP:
- statement.set_drop_style (initialized[place.value]
- ? Statement::DropStyle::STATIC
- : Statement::DropStyle::DEAD);
-
- if (statement.get_drop_style () == Statement::DropStyle::DEAD)
- {
- const Place &dropped_place = function.place_db[place];
-
- if (dropped_place.kind == Place::VARIABLE)
- {
- auto hir_id
- = Analysis::Mappings::get ().lookup_node_to_hir (
- static_cast<NodeId> (
- dropped_place.variable_or_field_index));
-
- if (hir_id.has_value ())
- definitely_dead.insert (hir_id.value ());
- }
- }
-
- initialized[place.value] = false;
- break;
-
- case Statement::Kind::STORAGE_DEAD:
- initialized[place.value] = false;
- break;
-
- case Statement::Kind::SWITCH:
- case Statement::Kind::RETURN:
- case Statement::Kind::GOTO:
- case Statement::Kind::USER_TYPE_ASCRIPTION:
- case Statement::Kind::FAKE_READ:
- break;
+ PlaceId place = statement.get_place ();
+ Statement::DropStyle drop_style = classify_drop (state, place);
+
+ statement.set_drop_style (drop_style);
+
+ if (record_straight_line_backend_drops)
+ record_drop_for_straight_line_backend (function, place,
+ drop_style,
+ dead_drop_hir_ids,
+ non_dead_drop_hir_ids);
}
+
+ // Update the state for the following statement.
+ update_state_for_statement (function, statement, state);
}
}
}
+} // namespace
+
+DropAnalysis &
+DropAnalysis::get ()
+{
+ static DropAnalysis instance;
+ return instance;
+}
+
+void
+DropAnalysis::clear ()
+{
+ definitely_dead.clear ();
+}
+
+bool
+DropAnalysis::is_definitely_dead (HirId id) const
+{
+ return definitely_dead.find (id) != definitely_dead.end ();
+}
+
+void
+DropAnalysis::analyze (Function &function)
+{
+ std::vector<BlockInitializationState> entry_states
+ = compute_entry_states (function);
+
+ // Keep the existing backend handling for straight-line CFGs.
+ const bool record_straight_line_backend_drops = is_straight_line (function);
+
+ std::set<HirId> dead_drop_hir_ids;
+ std::set<HirId> non_dead_drop_hir_ids;
+
+ annotate_drop_statements (function, entry_states,
+ record_straight_line_backend_drops,
+ dead_drop_hir_ids, non_dead_drop_hir_ids);
+
+ // A local is definitely dead only when all of its Drops are dead.
+ for (HirId hir_id : dead_drop_hir_ids)
+ if (non_dead_drop_hir_ids.find (hir_id) == non_dead_drop_hir_ids.end ())
+ definitely_dead.insert (hir_id);
+}
+
} // namespace BIR
} // namespace Rust
diff --git a/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.h b/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.h
index 2a52c1355..17a175e08 100644
--- a/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.h
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.h
@@ -28,7 +28,7 @@ namespace BIR {
Classifies scheduled whole-local BIR Drop statements according to
whether their place is initialized at the drop point.
- This initial implementation only handles straight-line control flow.
+ This analysis tracks initialization state across the BIR control-flow graph.
*/
class DropAnalysis
{
diff --git a/gcc/testsuite/rust/borrowck/drop_analysis_conditional_move.rs b/gcc/testsuite/rust/borrowck/drop_analysis_conditional_move.rs
new file mode 100644
index 000000000..0f58de44c
--- /dev/null
+++ b/gcc/testsuite/rust/borrowck/drop_analysis_conditional_move.rs
@@ -0,0 +1,46 @@
+// { dg-additional-options "-frust-compile-until=compilation -frust-borrowcheck -frust-dump-bir" }
+// { dg-final { scan-file bir_dump/drop_analysis_conditional_move.conditional_move.bir.dump "Drop\\(_3\\): Conditional" } }
+// { dg-final { scan-file bir_dump/drop_analysis_conditional_move.static_after_join.bir.dump "Drop\\(_3\\): Static" } }
+// { dg-final { scan-file bir_dump/drop_analysis_conditional_move.dead_after_join.bir.dump "Drop\\(_3\\): Dead" } }
+
+#![feature(no_core)]
+#![no_core]
+
+fn conditional_move(condition: bool) {
+ struct A {
+ i: i32,
+ }
+
+ let x = A { i: 1 };
+
+ if condition {
+ let y = x;
+ }
+}
+
+
+fn static_after_join(condition: bool) {
+ struct A {
+ i: i32,
+ }
+
+ let x = A { i: 1 };
+
+ if condition {
+ let y = 1;
+ }
+}
+
+fn dead_after_join(condition: bool) {
+ struct A {
+ i: i32,
+ }
+
+ let x = A { i: 1 };
+
+ if condition {
+ let y = x;
+ } else {
+ let z = x;
+ }
+}
\ No newline at end of file
base-commit: 0fd9c68665c73458385978ac698b7d0b644c7d87
--
2.55.0
More information about the Gcc-rust
mailing list