https://gcc.gnu.org/g:9ff9808f3bc9c4cf20dc7eb2ed1ce929d2eeec9b

commit r17-4106-g9ff9808f3bc9c4cf20dc7eb2ed1ce929d2eeec9b
Author: Lishin <[email protected]>
Date:   Sat Jul 25 16:49:24 2026 +0000

    gccrs: Add straight-line BIR drop state analysis
    
    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 <[email protected]>

Diff:
---
 gcc/rust/Make-lang.in                              |   1 +
 .../errors/borrowck/rust-bir-builder-internal.h    |  34 +++++-
 gcc/rust/checks/errors/borrowck/rust-bir-builder.h |   2 +-
 .../errors/borrowck/rust-bir-drop-analysis.cc      | 114 +++++++++++++++++++++
 .../errors/borrowck/rust-bir-drop-analysis.h       |  42 ++++++++
 gcc/rust/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 +++-
 .../checks/errors/borrowck/rust-borrow-checker.cc  |   3 +
 .../rust/borrowck/drop_analysis_whole_move.rs      |  34 ++++++
 10 files changed, 278 insertions(+), 4 deletions(-)

diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in
index 9103fbe234af..f50e7d0c0184 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 85f6c6300cdf..eabfdc6af4cb 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 e95e3efe7bd6..d26645a2466e 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 000000000000..c0de04fac2d3
--- /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 000000000000..eb63f1e1eda2
--- /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 9d1336e84f1a..2465b6fe4b2d 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 b73e5fc7dc06..069095985441 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 725e4d2828db..581f661655dc 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 219986fb012d..f93c5595ea8e 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 000000000000..ff296fb9b1b5
--- /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) {}

Reply via email to