[gccrs COMMIT 2/3] gccrs: Add straight-line BIR drop state analysis

gerris.rs@gmail.com gerris.rs@gmail.com
Thu Aug 13 12:11:41 GMT 2026


From: Lishin <lishin1008@gmail.com>

Add BIR Drop statements at scope exits and classify them as static or
dead by tracking whether each local is initialized or moved.

Treat function arguments as initialized at function entry and schedule
their value drops before returning. Arguments do not receive
StorageLive or StorageDead statements.

Dump the classification and add tests for static local, whole-local
move, a copy, and a function argument.

gcc/rust/ChangeLog:

	* Make-lang.in: Add rust-bir-drop-analysis.o.
	* checks/errors/borrowck/rust-bir.h:
	(Statement::DropStyle): New enum.
	(Statement::Kind): Add DROP.
	(Statement::make_drop): New function.
	(Statement::get_drop_style): Likewise.
	(Statement::set_drop_style): Likewise.
	* checks/errors/borrowck/rust-bir-builder-internal.h:
	(AbstractBuilder::declare_argument): New function.
	(AbstractBuilder::push_drop): Likewise.
	(AbstractBuilder::push_function_argument_drops): Likewise.
	(AbstractBuilder::pop_scope): Schedule Drop statements.
	(AbstractBuilder::unwind_until): Likewise.
	(AbstractBuilder::push_return): Schedule function argument drops.
	* checks/errors/borrowck/rust-bir-builder.h
	(Builder::handle_param): Use declare_argument for function
	parameters.
	* checks/errors/borrowck/rust-bir-dump.cc (Dump::visit): Dump
	Drop classifications.
	* checks/errors/borrowck/rust-bir-fact-collector.h
	(FactCollector::visit): Handle Drop statements.
	* checks/errors/borrowck/rust-borrow-checker.cc
	(BorrowChecker::go): Run BIR Drop analysis.
	* checks/errors/borrowck/rust-bir-drop-analysis.cc: New file.
	* checks/errors/borrowck/rust-bir-drop-analysis.h: New file.

gcc/testsuite/ChangeLog:

	* rust/borrowck/drop_analysis_whole_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/ca45ab7beb13f3d0ab6c39113e428622ddbb7583

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/4730

 gcc/rust/Make-lang.in                         |   1 +
 .../borrowck/rust-bir-builder-internal.h      |  34 +++++-
 .../checks/errors/borrowck/rust-bir-builder.h |   2 +-
 .../errors/borrowck/rust-bir-drop-analysis.cc | 114 ++++++++++++++++++
 .../errors/borrowck/rust-bir-drop-analysis.h  |  42 +++++++
 .../checks/errors/borrowck/rust-bir-dump.cc   |  26 ++++
 .../errors/borrowck/rust-bir-fact-collector.h |   7 ++
 gcc/rust/checks/errors/borrowck/rust-bir.h    |  19 ++-
 .../errors/borrowck/rust-borrow-checker.cc    |   3 +
 .../rust/borrowck/drop_analysis_whole_move.rs |  34 ++++++
 10 files changed, 278 insertions(+), 4 deletions(-)
 create mode 100644 gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.cc
 create mode 100644 gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.h
 create mode 100644 gcc/testsuite/rust/borrowck/drop_analysis_whole_move.rs

diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in
index 9103fbe23..f50e7d0c0 100644
--- a/gcc/rust/Make-lang.in
+++ b/gcc/rust/Make-lang.in
@@ -182,6 +182,7 @@ GRS_OBJS = \
     rust/rust-borrow-checker-diagnostics.o\
     rust/rust-bir-builder-expr-stmt.o \
     rust/rust-bir-builder-pattern.o \
+    rust/rust-bir-drop-analysis.o \
     rust/rust-bir-dump.o \
     rust/rust-polonius.o\
     rust/rust-hir-dot-operator.o \
diff --git a/gcc/rust/checks/errors/borrowck/rust-bir-builder-internal.h b/gcc/rust/checks/errors/borrowck/rust-bir-builder-internal.h
index 85f6c6300..eabfdc6af 100644
--- a/gcc/rust/checks/errors/borrowck/rust-bir-builder-internal.h
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-builder-internal.h
@@ -167,15 +167,40 @@ protected:
     return place_id;
   }
 
+  PlaceId declare_argument (const Analysis::NodeMapping &node,
+			    TyTy::BaseType *ty)
+  {
+    const NodeId nodeid = node.get_nodeid ();
+
+    // In debug mode, check that the argument is not already declared.
+    rust_assert (ctx.place_db.lookup_variable (nodeid) == INVALID_PLACE);
+
+    return ctx.place_db.add_variable (nodeid, ty);
+  }
+
   void push_new_scope () { ctx.place_db.push_new_scope (); }
 
+  void push_drop (PlaceId place)
+  {
+    ctx.get_current_bb ().statements.push_back (Statement::make_drop (place));
+  }
+
+  void push_function_argument_drops ()
+  {
+    std::for_each (ctx.arguments.rbegin (), ctx.arguments.rend (),
+		   [&] (PlaceId argument) { push_drop (argument); });
+  }
+
   void pop_scope ()
   {
     auto &scope = ctx.place_db.get_current_scope ();
     if (ctx.place_db.get_current_scope_id () != INVALID_SCOPE)
       {
 	std::for_each (scope.locals.rbegin (), scope.locals.rend (),
-		       [&] (PlaceId place) { push_storage_dead (place); });
+		       [&] (PlaceId place) {
+			 push_drop (place);
+			 push_storage_dead (place);
+		       });
       }
     ctx.place_db.pop_scope ();
   }
@@ -200,7 +225,10 @@ protected:
 	// TODO: Perform stable toposort based on `borrowed_by`.
 
 	std::for_each (scope.locals.rbegin (), scope.locals.rend (),
-		       [&] (PlaceId place) { push_storage_dead (place); });
+		       [&] (PlaceId place) {
+			 push_drop (place);
+			 push_storage_dead (place);
+		       });
 	current_scope_id = scope.parent;
       }
   }
@@ -305,6 +333,8 @@ protected: // Helpers to add BIR statements
 
   void push_return (location_t location)
   {
+    push_function_argument_drops ();
+
     ctx.get_current_bb ().statements.push_back (
       Statement::make_return (location));
   }
diff --git a/gcc/rust/checks/errors/borrowck/rust-bir-builder.h b/gcc/rust/checks/errors/borrowck/rust-bir-builder.h
index e95e3efe7..d26645a24 100644
--- a/gcc/rust/checks/errors/borrowck/rust-bir-builder.h
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-builder.h
@@ -125,7 +125,7 @@ private:
 	&& !static_cast<HIR::IdentifierPattern &> (pattern).get_is_ref ())
       {
 	// Avoid useless temporary variable for parameter to look like MIR.
-	translated = declare_variable (pattern.get_mappings ());
+	translated = declare_argument (pattern.get_mappings (), param_type);
 	ctx.arguments.push_back (translated);
       }
     else
diff --git a/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.cc b/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.cc
new file mode 100644
index 000000000..c0de04fac
--- /dev/null
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.cc
@@ -0,0 +1,114 @@
+// Copyright (C) 2026 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-bir-drop-analysis.h"
+#include "rust-bir.h"
+
+namespace Rust {
+namespace BIR {
+
+void
+DropAnalysis::analyze (Function &function)
+{
+  std::vector<BasicBlockId> block_order;
+  std::set<BasicBlockId> visited;
+
+  BasicBlockId current = ENTRY_BASIC_BLOCK;
+
+  while (current != INVALID_BB)
+    {
+      // A repeated block indicates a cycle in straight-line control flow.
+      if (!visited.insert (current).second)
+	return;
+
+      block_order.push_back (current);
+
+      const BasicBlock &block = function.basic_blocks[current];
+
+      if (block.successors.empty ())
+	break;
+
+      if (block.successors.size () != 1)
+	return;
+
+      current = block.successors.front ();
+    }
+
+  std::vector<bool> initialized (function.place_db.size (), false);
+
+  for (PlaceId argument : function.arguments)
+    initialized[argument.value] = true;
+
+  for (BasicBlockId block_id : block_order)
+    {
+      BasicBlock &block = function.basic_blocks[block_id];
+
+      for (Statement &statement : block.statements)
+	{
+	  PlaceId place = statement.get_place ();
+
+	  switch (statement.get_kind ())
+	    {
+	    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);
+
+	      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;
+	    }
+	}
+    }
+}
+
+} // 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
new file mode 100644
index 000000000..eb63f1e1e
--- /dev/null
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-drop-analysis.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2026 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_BIR_DROP_ANALYSIS_H
+#define RUST_BIR_DROP_ANALYSIS_H
+
+namespace Rust {
+namespace BIR {
+
+struct Function;
+
+/*
+  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.
+*/
+class DropAnalysis
+{
+public:
+  static void analyze (Function &function);
+};
+
+} // namespace BIR
+} // namespace Rust
+
+#endif // RUST_BIR_DROP_ANALYSIS_H
diff --git a/gcc/rust/checks/errors/borrowck/rust-bir-dump.cc b/gcc/rust/checks/errors/borrowck/rust-bir-dump.cc
index 9d1336e84..2465b6fe4 100644
--- a/gcc/rust/checks/errors/borrowck/rust-bir-dump.cc
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-dump.cc
@@ -210,6 +210,32 @@ Dump::visit (const Statement &stmt)
 	<< bb_fold_map[func.basic_blocks[statement_bb].successors.at (0)].value;
       bb_terminated = true;
       break;
+
+    case Statement::Kind::DROP:
+      stream << "Drop(";
+      visit_place (stmt.get_place ());
+      stream << "): ";
+
+      switch (stmt.get_drop_style ())
+	{
+	case Statement::DropStyle::UNCLASSIFIED:
+	  stream << "Unclassified";
+	  break;
+
+	case Statement::DropStyle::STATIC:
+	  stream << "Static";
+	  break;
+
+	case Statement::DropStyle::DEAD:
+	  stream << "Dead";
+	  break;
+
+	case Statement::DropStyle::CONDITIONAL:
+	  stream << "Conditional";
+	  break;
+	}
+      break;
+
     case Statement::Kind::STORAGE_DEAD:
       stream << "StorageDead(";
       visit_place (stmt.get_place ());
diff --git a/gcc/rust/checks/errors/borrowck/rust-bir-fact-collector.h b/gcc/rust/checks/errors/borrowck/rust-bir-fact-collector.h
index b73e5fc7d..069095985 100644
--- a/gcc/rust/checks/errors/borrowck/rust-bir-fact-collector.h
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-fact-collector.h
@@ -250,6 +250,13 @@ protected: // Main collection entry points (for different categories).
 	  issue_jumps ();
 	}
 	break;
+
+      case Statement::Kind::DROP:
+	{
+	  // Drop statements are currently used only by BIR drop analysis.
+	  break;
+	}
+
       case Statement::Kind::RETURN:
 	{
 	  issue_place_access (RETURN_VALUE_PLACE);
diff --git a/gcc/rust/checks/errors/borrowck/rust-bir.h b/gcc/rust/checks/errors/borrowck/rust-bir.h
index 725e4d282..581f66165 100644
--- a/gcc/rust/checks/errors/borrowck/rust-bir.h
+++ b/gcc/rust/checks/errors/borrowck/rust-bir.h
@@ -75,12 +75,21 @@ struct Function
 class Statement
 {
 public:
+  enum class DropStyle
+  {
+    UNCLASSIFIED,
+    STATIC,
+    DEAD,
+    CONDITIONAL,
+  };
+
   enum class Kind
   {
     ASSIGNMENT,		  // <place> = <expr>
     SWITCH,		  // switch <place>
     RETURN,		  // return
     GOTO,		  // goto
+    DROP,		  // Drop(<place>)
     STORAGE_DEAD,	  // StorageDead(<place>)
     STORAGE_LIVE,	  // StorageLive(<place>)
     USER_TYPE_ASCRIPTION, // UserTypeAscription(<place>, <tyty>)
@@ -91,9 +100,11 @@ private:
   Kind kind;
   // ASSIGNMENT: lhs
   // SWITCH: switch_val
-  // StorageDead/StorageLive: place
+  // DROP/StorageDead/StorageLive: place
   // otherwise: <unused>
   PlaceId place;
+  // DROP: drop classification
+  DropStyle drop_style = DropStyle::UNCLASSIFIED;
   // ASSIGNMENT: rhs
   // otherwise: <unused>
   std::unique_ptr<AbstractExpr> expr;
@@ -118,6 +129,10 @@ public:
     return Statement (Kind::RETURN, INVALID_PLACE, nullptr, nullptr, location);
   }
   static Statement make_goto () { return Statement (Kind::GOTO); }
+  static Statement make_drop (PlaceId place)
+  {
+    return Statement (Kind::DROP, place);
+  }
   static Statement make_storage_dead (PlaceId place)
   {
     return Statement (Kind::STORAGE_DEAD, place);
@@ -147,6 +162,8 @@ private:
 public:
   WARN_UNUSED_RESULT Kind get_kind () const { return kind; }
   WARN_UNUSED_RESULT PlaceId get_place () const { return place; }
+  WARN_UNUSED_RESULT DropStyle get_drop_style () const { return drop_style; }
+  void set_drop_style (DropStyle style) { drop_style = style; }
   WARN_UNUSED_RESULT AbstractExpr &get_expr () const { return *expr; }
   WARN_UNUSED_RESULT TyTy::BaseType *get_type () const { return type; }
   WARN_UNUSED_RESULT location_t get_location () const { return location; }
diff --git a/gcc/rust/checks/errors/borrowck/rust-borrow-checker.cc b/gcc/rust/checks/errors/borrowck/rust-borrow-checker.cc
index 219986fb0..f93c5595e 100644
--- a/gcc/rust/checks/errors/borrowck/rust-borrow-checker.cc
+++ b/gcc/rust/checks/errors/borrowck/rust-borrow-checker.cc
@@ -21,6 +21,7 @@
 #include "rust-function-collector.h"
 #include "rust-bir-fact-collector.h"
 #include "rust-bir-builder.h"
+#include "rust-bir-drop-analysis.h"
 #include "rust-bir-dump.h"
 #include "polonius/rust-polonius.h"
 
@@ -69,6 +70,8 @@ BorrowChecker::go (HIR::Crate &crate)
       BIR::Builder builder (ctx);
       auto bir = builder.build (*func);
 
+      BIR::DropAnalysis::analyze (bir);
+
       if (enable_dump_bir)
 	{
 	  std::string filename = "bir_dump/" + crate_name + "."
diff --git a/gcc/testsuite/rust/borrowck/drop_analysis_whole_move.rs b/gcc/testsuite/rust/borrowck/drop_analysis_whole_move.rs
new file mode 100644
index 000000000..ff296fb9b
--- /dev/null
+++ b/gcc/testsuite/rust/borrowck/drop_analysis_whole_move.rs
@@ -0,0 +1,34 @@
+// { dg-additional-options "-frust-compile-until=compilation -frust-borrowcheck -frust-dump-bir" }
+// { dg-final { scan-file bir_dump/drop_analysis_whole_move.static_local.bir.dump "Drop\\(_2\\): Static" } }
+// { dg-final { scan-file bir_dump/drop_analysis_whole_move.whole_move.bir.dump "Drop\\(_4\\): Static" } }
+// { dg-final { scan-file bir_dump/drop_analysis_whole_move.whole_move.bir.dump "Drop\\(_2\\): Dead" } }
+// { dg-final { scan-file bir_dump/drop_analysis_whole_move.copy.bir.dump "Drop\\(_4\\): Static" } }
+// { dg-final { scan-file bir_dump/drop_analysis_whole_move.copy.bir.dump "Drop\\(_2\\): Static" } }
+// { dg-final { scan-file bir_dump/drop_analysis_whole_move.function_argument.bir.dump "Drop\\(_2\\): Static" } }
+
+#![feature(no_core)]
+#![no_core]
+
+fn static_local() {
+    struct A {
+        i: i32,
+    }
+
+    let x = A { i: 1 };
+}
+
+fn whole_move() {
+    struct A {
+        i: i32,
+    }
+
+    let x = A { i: 1 };
+    let y = x;
+}
+
+fn copy() {
+    let x = 1;
+    let y = x;
+}
+
+fn function_argument(x: i32) {}
-- 
2.54.0



More information about the Gcc-rust mailing list