[COMMITTED 19/43] gccrs: Add straight-line BIR drop state analysis

arthur.cohen@opensrcsec.com arthur.cohen@opensrcsec.com
Thu Sep 10 08:19:32 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>
---
 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 9103fbe234a..f50e7d0c018 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 85f6c6300cd..eabfdc6af4c 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 e95e3efe7bd..d26645a2466 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 00000000000..c0de04fac2d
--- /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 00000000000..eb63f1e1eda
--- /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 9d1336e84f1..2465b6fe4b2 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 b73e5fc7dc0..06909598544 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 725e4d2828d..581f661655d 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 219986fb012..f93c5595ea8 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 00000000000..ff296fb9b1b
--- /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.50.1



More information about the Gcc-rust mailing list