diff mbox series

[121/125] gccrs: borrowck: Fact collector

Message ID 20240801145809.366388-123-arthur.cohen@embecosm.com
State New
Headers show
Series [001/125] Rust: Make 'tree'-level 'MAIN_NAME_P' work | expand

Commit Message

Arthur Cohen Aug. 1, 2024, 2:57 p.m. UTC
From: Jakub Dupak <dev@jakubdupak.com>

This is the main Polonius based logic which creates the information
Polonius needs from BIR. It is largly guessed and rever engineered, so
some aspects are probably wrong.

gcc/rust/ChangeLog:

	* checks/errors/borrowck/rust-bir-fact-collector.h: New file.
	* checks/errors/borrowck/rust-borrow-checker.cc (BorrowChecker::go):
	Enable fact collection.

Signed-off-by: Jakub Dupak <dev@jakubdupak.com>
---
 .../errors/borrowck/rust-bir-fact-collector.h | 895 ++++++++++++++++++
 .../errors/borrowck/rust-borrow-checker.cc    |   3 +
 2 files changed, 898 insertions(+)
 create mode 100644 gcc/rust/checks/errors/borrowck/rust-bir-fact-collector.h
diff mbox series

Patch

diff --git a/gcc/rust/checks/errors/borrowck/rust-bir-fact-collector.h b/gcc/rust/checks/errors/borrowck/rust-bir-fact-collector.h
new file mode 100644
index 00000000000..17c198e0fa7
--- /dev/null
+++ b/gcc/rust/checks/errors/borrowck/rust-bir-fact-collector.h
@@ -0,0 +1,895 @@ 
+// Copyright (C) 2020-2023 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_FACT_COLLECTOR_H
+#define RUST_BIR_FACT_COLLECTOR_H
+
+#include "rust-bir-visitor.h"
+#include "rust-bir.h"
+#include "rust-bir-place.h"
+#include "polonius/rust-polonius.h"
+
+namespace Rust {
+namespace BIR {
+
+enum class PointPosition : uint8_t
+{
+  START,
+  MID
+};
+
+class FactCollector : public Visitor
+{
+  // Output.
+  Polonius::Facts facts;
+
+  // Read-only context.
+  const PlaceDB &place_db;
+  const std::vector<BasicBlock> &basic_blocks;
+  const PlaceId first_local;
+  const location_t location;
+
+  Resolver::TypeCheckContext &tyctx;
+
+  // Collector state.
+  BasicBlockId current_bb = 0;
+  uint32_t current_stmt = 0;
+  PlaceId lhs = INVALID_PLACE;
+
+  // PlaceDB is const in this phase, so this is used to generate fresh regions.
+  FreeRegion next_fresh_region;
+  RegionBinder region_binder{next_fresh_region};
+
+  std::vector<Polonius::Point> cfg_points_all;
+
+  FreeRegions bind_regions (std::vector<TyTy::Region> regions,
+			    FreeRegions parent_free_regions)
+  {
+    return region_binder.bind_regions (regions, parent_free_regions);
+  }
+
+  FreeRegions make_fresh_regions (size_t size)
+  {
+    std::vector<FreeRegion> free_regions;
+    for (size_t i = 0; i < size; i++)
+      {
+	free_regions.push_back (region_binder.get_next_free_region ());
+      }
+    return FreeRegions (std::move (free_regions));
+  }
+
+public:
+  static Polonius::Facts collect (Function &func)
+  {
+    FactCollector collector (func);
+    collector.init_universal_regions (func.universal_regions,
+				      func.universal_region_bounds);
+
+    collector.visit_statemensts ();
+    collector.visit_places (func.arguments);
+
+    return std::move (collector.facts);
+  }
+
+protected: // Constructor and destructor.
+  explicit FactCollector (Function &func)
+    : place_db (func.place_db), basic_blocks (func.basic_blocks),
+      first_local (func.arguments.empty () ? FIRST_VARIABLE_PLACE
+					   : *func.arguments.rbegin () + 1),
+      location (func.location), tyctx (*Resolver::TypeCheckContext::get ()),
+      next_fresh_region (place_db.peek_next_free_region ())
+  {}
+  ~FactCollector () = default;
+
+protected: // Main collection entry points (for different categories).
+  void init_universal_regions (
+    const FreeRegions &universal_regions,
+    const decltype (Function::universal_region_bounds) &universal_region_bounds)
+  {
+    size_t next_loan = place_db.get_loans ().size ();
+    facts.universal_region.emplace_back (0);
+    facts.placeholder.emplace_back (0, next_loan++);
+
+    for (auto &region : universal_regions)
+      {
+	facts.universal_region.emplace_back (region);
+	facts.placeholder.emplace_back (region, next_loan++);
+	facts.known_placeholder_subset.emplace_back (0, region);
+      }
+
+    // Copy already collected subset facts, that are universally valid.
+    for (auto &bound : universal_region_bounds)
+      facts.known_placeholder_subset.emplace_back (bound.first, bound.second);
+  }
+
+  void visit_places (const std::vector<PlaceId> &args)
+  {
+    for (PlaceId place_id = 0; place_id < place_db.size (); ++place_id)
+      {
+	auto &place = place_db[place_id];
+
+	switch (place.kind)
+	  {
+	  case Place::VARIABLE:
+	  case Place::TEMPORARY:
+	    facts.path_is_var.emplace_back (place_id, place_id);
+	    for (auto &region : place.regions)
+	      {
+		facts.use_of_var_derefs_origin.emplace_back (place_id, region);
+	      }
+	    // TODO: drop_of_var_derefs_origin
+	    break;
+	  case Place::FIELD:
+	    sanizite_field (place_id);
+	    facts.child_path.emplace_back (place_id, place.path.parent);
+	    break;
+	  case Place::INDEX:
+	    push_subset_all (place.tyty, place.regions,
+			     place_db[place.path.parent].regions);
+	    facts.child_path.emplace_back (place_id, place.path.parent);
+	    break;
+	  case Place::DEREF:
+	    sanitize_deref (place_id);
+	    facts.child_path.emplace_back (place_id, place.path.parent);
+	    break;
+	  case Place::CONSTANT:
+	  case Place::INVALID:
+	    break;
+	  }
+      }
+
+    for (PlaceId arg = FIRST_VARIABLE_PLACE + 1; arg < first_local; ++arg)
+      {
+	facts.path_assigned_at_base.emplace_back (
+	  arg, get_point (0, 0, PointPosition::START));
+      }
+    for (PlaceId place = first_local; place < place_db.size (); ++place)
+      {
+	if (place_db[place].is_var ())
+	  facts.path_moved_at_base.emplace_back (
+	    place, get_point (0, 0, PointPosition::START));
+      }
+  }
+
+  void sanitize_deref (PlaceId place_id)
+  {
+    auto &place = place_db[place_id];
+    auto &base = place_db[place.path.parent];
+
+    rust_debug ("\tSanitize deref of %s", base.tyty->as_string ().c_str ());
+
+    std::vector<Polonius::Origin> regions;
+    regions.insert (regions.end (), base.regions.begin () + 1,
+		    base.regions.end ());
+    FreeRegions r (std::move (regions));
+    push_subset_all (place.tyty, r, place.regions);
+  }
+  void sanizite_field (PlaceId place_id)
+  {
+    auto &place = place_db[place_id];
+    auto &base = place_db[place.path.parent];
+
+    rust_debug ("\tSanitize field .%d of %s", place.variable_or_field_index,
+		base.tyty->as_string ().c_str ());
+
+    if (base.tyty->is<TyTy::TupleType> ())
+      return;
+    auto r = Resolver::TypeCheckContext::get ()
+	       ->get_variance_analysis_ctx ()
+	       .query_field_regions (base.tyty->as<TyTy::ADTType> (), 0,
+				     place.variable_or_field_index,
+				     base.regions); // FIXME
+    FreeRegions f (std::move (r));
+    push_subset_all (place.tyty, f, place.regions);
+  }
+
+  void visit_statemensts ()
+  {
+    rust_debug ("visit_statemensts");
+
+    for (current_bb = 0; current_bb < basic_blocks.size (); ++current_bb)
+      {
+	auto &bb = basic_blocks[current_bb];
+	for (current_stmt = 0; current_stmt < bb.statements.size ();
+	     ++current_stmt)
+	  {
+	    cfg_points_all.push_back (get_current_point_start ());
+	    cfg_points_all.push_back (get_current_point_mid ());
+
+	    add_stmt_to_cfg (current_bb, current_stmt);
+
+	    visit (bb.statements[current_stmt]);
+	  }
+      }
+    current_bb = 0;
+    current_stmt = 0;
+  }
+
+  void visit (const Statement &stmt) override
+  {
+    switch (stmt.get_kind ())
+      {
+	case Statement::Kind::ASSIGNMENT: {
+	  // TODO: for unwind, must had hadning for non-panic-only assignements
+	  issue_write_deep (stmt.get_place ());
+	  visit_assignment_expr (stmt.get_place (), stmt.get_expr ());
+	  break;
+	}
+	case Statement::Kind::SWITCH: {
+	  issue_read_move (stmt.get_place ());
+	  issue_jumps ();
+	}
+	break;
+	case Statement::Kind::GOTO: {
+	  issue_jumps ();
+	}
+	break;
+	case Statement::Kind::RETURN: {
+	  issue_place_access (RETURN_VALUE_PLACE);
+	  issue_locals_dealloc ();
+	  break;
+	}
+	case Statement::Kind::STORAGE_DEAD: {
+	  facts.path_moved_at_base.emplace_back (stmt.get_place (),
+						 get_current_point_mid ());
+	  facts.var_defined_at.emplace_back (stmt.get_place (),
+					     get_current_point_mid ());
+	  break;
+	}
+	case Statement::Kind::STORAGE_LIVE: {
+	  issue_write_deep (stmt.get_place (), true);
+	  break;
+	}
+	case Statement::Kind::USER_TYPE_ASCRIPTION: {
+	  issue_user_type_constraints (stmt.get_place (), stmt.get_type ());
+	  break;
+	}
+	case Statement::Kind::FAKE_READ: {
+	  issue_place_access (stmt.get_place ());
+	  break;
+	}
+      }
+  }
+
+  void visit_assignment_expr (PlaceId lhs, AbstractExpr &expr)
+  {
+    this->lhs = lhs;
+    expr.accept_vis (*this);
+    this->lhs = INVALID_PLACE;
+  }
+
+  void visit (const InitializerExpr &expr) override
+  {
+    sanitize_constrains_at_init (lhs);
+
+    for (auto init_value : expr.get_values ())
+      issue_read_move (init_value);
+  }
+
+  void visit (const Operator<1> &expr) override
+  {
+    sanitize_constrains_at_init (lhs);
+    issue_read_move (expr.get_operand<0> ());
+  }
+
+  void visit (const Operator<2> &expr) override
+  {
+    sanitize_constrains_at_init (lhs);
+    issue_read_move (expr.get_operand<0> ());
+    issue_read_move (expr.get_operand<1> ());
+  }
+
+  void visit (const BorrowExpr &expr) override
+  {
+    rust_debug ("\t_%u = BorrowExpr(_%u)", lhs - 1, expr.get_place () - 1);
+
+    auto loan = place_db.get_loans ()[expr.get_loan ()];
+
+    auto &base_place = place_db[expr.get_place ()];
+    auto &ref_place = place_db[lhs];
+
+    issue_place_access (expr.get_place ());
+
+    // See compiler/rustc_borrowck/src/type_check/mod.rs:add_reborrow_constraint
+    if (base_place.kind == Place::DEREF)
+      {
+	// Reborrow
+
+	auto &main_loan_place = place_db[base_place.path.parent];
+	if (loan.mutability == Mutability::Mut)
+	  {
+	    if (!main_loan_place.tyty->as<TyTy::ReferenceType> ()
+		   ->is_mutable ())
+	      rust_error_at (location,
+			     "Cannot reborrow immutable borrow as mutable");
+	    issue_loan (expr.get_origin (), expr.get_loan ());
+	  }
+
+	push_subset (main_loan_place.regions[0], expr.get_origin ());
+      }
+    else
+      {
+	issue_loan (expr.get_origin (), expr.get_loan ());
+      }
+
+    auto loan_regions = base_place.regions.prepend (expr.get_origin ());
+    push_subset (ref_place.tyty, loan_regions, ref_place.regions);
+  }
+
+  void visit (const Assignment &expr) override
+  {
+    rust_debug ("\t_%u = Assignment(_%u) at %u:%u", lhs - 1,
+		expr.get_rhs () - 1, current_bb, current_stmt);
+
+    issue_read_move (expr.get_rhs ());
+    push_subset (lhs, expr.get_rhs ());
+  }
+
+  void visit (const CallExpr &expr) override
+  {
+    rust_debug ("\t_%u = CallExpr(_%u)", lhs - 1, expr.get_callable () - 1);
+
+    auto &return_place = place_db[lhs];
+    auto &callable_place = place_db[expr.get_callable ()];
+    auto callable_ty = callable_place.tyty->as<TyTy::CallableTypeInterface> ();
+
+    issue_read_move (expr.get_callable ());
+
+    // Each call needs unique regions.
+    auto call_regions = make_fresh_regions (callable_place.regions.size ());
+
+    for (size_t i = 0; i < expr.get_arguments ().size (); ++i)
+      {
+	auto arg = expr.get_arguments ().at (i);
+	auto arg_regions
+	  = bind_regions (Resolver::TypeCheckContext::get ()
+			    ->get_variance_analysis_ctx ()
+			    .query_type_regions (
+			      callable_ty->get_param_type_at (i)),
+			  call_regions);
+	issue_read_move (arg);
+	push_subset (place_db[arg].tyty, place_db[arg].regions, arg_regions);
+      }
+
+    // sanitize return regions
+    sanitize_constrains_at_init (lhs);
+
+    auto return_regions
+      = bind_regions (Resolver::TypeCheckContext::get ()
+			->get_variance_analysis_ctx ()
+			.query_type_regions (
+			  callable_ty->as<TyTy::FnType> ()->get_return_type ()),
+		      call_regions);
+    push_subset (return_place.tyty, return_regions, return_place.regions);
+
+    issue_jumps ();
+  }
+
+protected: // Statement visitor helpers
+  WARN_UNUSED_RESULT const BasicBlock &get_current_bb () const
+  {
+    return basic_blocks[current_bb];
+  }
+
+  WARN_UNUSED_RESULT static Polonius::Point
+  get_point (BasicBlockId bb, uint32_t stmt, PointPosition pos)
+  {
+    Polonius::Point point = 0;
+    point |= (bb << 16);
+    point |= (stmt << 1);
+    point |= (static_cast<uint8_t> (pos) & 1);
+    return point;
+  }
+
+  WARN_UNUSED_RESULT Polonius::Point get_current_point_start () const
+  {
+    return get_point (current_bb, current_stmt, PointPosition::START);
+  }
+
+  WARN_UNUSED_RESULT Polonius::Point get_current_point_mid () const
+  {
+    return get_point (current_bb, current_stmt, PointPosition::MID);
+  }
+
+  void add_stmt_to_cfg (BasicBlockId bb, uint32_t stmt)
+  {
+    if (stmt != 0)
+      {
+	facts.cfg_edge.emplace_back (get_point (bb, stmt - 1,
+						PointPosition::MID),
+				     get_point (bb, stmt,
+						PointPosition::START));
+      }
+
+    facts.cfg_edge.emplace_back (get_point (bb, stmt, PointPosition::START),
+				 get_point (bb, stmt, PointPosition::MID));
+  }
+
+protected: // Generic BIR operations.
+  void issue_jumps ()
+  {
+    for (auto succ : get_current_bb ().successors)
+      facts.cfg_edge.emplace_back (get_current_point_mid (),
+				   get_point (succ, 0, PointPosition::START));
+  }
+
+  /* Shallow r/w access */
+  void issue_place_access (PlaceId place_id)
+  {
+    auto &place = place_db[place_id];
+
+    if (place.is_constant ())
+      return;
+
+    if (place_id != RETURN_VALUE_PLACE)
+      facts.path_accessed_at_base.emplace_back (place_id,
+						get_current_point_mid ());
+
+    if (place.is_var ())
+      facts.var_used_at.emplace_back (place_id, get_current_point_mid ());
+    else if (place.is_path ())
+      {
+	facts.var_used_at.emplace_back (place_db.get_var (place_id),
+					get_current_point_mid ());
+      }
+  }
+
+  /** Deep read access, which consumes the place. */
+  void issue_read_move (PlaceId place_id)
+  {
+    auto &place = place_db[place_id];
+
+    issue_place_access (place_id);
+    if (place.should_be_moved ())
+      {
+	issue_move (place_id);
+      }
+    else
+      {
+	check_read_for_conflicts (place_id);
+      }
+  }
+
+  void issue_write_deep (PlaceId place_id, bool is_init = false)
+  {
+    auto &place = place_db[place_id];
+    rust_assert (place.is_lvalue () || place.is_rvalue ());
+
+    if (place.is_var ())
+      facts.var_defined_at.emplace_back (place_id, get_current_point_mid ());
+
+    if (!is_init)
+      {
+	facts.path_assigned_at_base.emplace_back (place_id,
+						  get_current_point_mid ());
+	check_write_for_conflict (place_id);
+	kill_borrows_for_place (place_id);
+      }
+  }
+
+  void issue_move (PlaceId place_id, bool initial = false)
+  {
+    if (!place_db[place_id].should_be_moved ())
+      return;
+
+    facts.path_moved_at_base.emplace_back (
+      place_id, initial ? get_point (0, 0, PointPosition::START)
+			: get_current_point_mid ());
+
+    check_move_behind_reference (place_id);
+
+    if (!initial)
+      {
+	check_write_for_conflict (place_id);
+	kill_borrows_for_place (place_id);
+      }
+  }
+
+  void issue_loan (Polonius::Origin origin, LoanId loan_id)
+  {
+    facts.loan_issued_at.emplace_back (origin, loan_id,
+				       get_current_point_mid ());
+
+    check_for_borrow_conficts (place_db.get_loans ()[loan_id].place, loan_id,
+			       place_db.get_loans ()[loan_id].mutability);
+  }
+
+  void issue_locals_dealloc ()
+  {
+    for (LoanId loan_id = 0; loan_id < place_db.get_loans ().size (); ++loan_id)
+      {
+	auto &loan = place_db.get_loans ()[loan_id];
+	auto loaned_var_id = place_db.get_var (loan.place);
+	if (place_db[loaned_var_id].tyty->is<TyTy::ReferenceType> ())
+	  continue;
+	if (loaned_var_id >= first_local)
+	  facts.loan_invalidated_at.emplace_back (get_current_point_start (),
+						  loan_id);
+      }
+  }
+
+  void issue_user_type_constraints (PlaceId place_id, TyTy::BaseType *type)
+  {
+    auto user_regions = Resolver::TypeCheckContext::get ()
+			  ->get_variance_analysis_ctx ()
+			  .query_type_regions (type);
+    push_subset_user (place_db[place_id].tyty, place_db[place_id].regions,
+		      user_regions);
+  }
+
+  void check_read_for_conflicts (PlaceId place_id)
+  {
+    place_db.for_each_path_segment (place_id, [&] (PlaceId id) {
+      for (auto loan : place_db[id].borrowed_by)
+	{
+	  if (place_db.get_loans ()[loan].mutability == Mutability::Mut)
+	    {
+	      facts.loan_invalidated_at.emplace_back (
+		get_current_point_start (), loan);
+	    }
+	}
+    });
+    place_db.for_each_path_from_root (place_id, [&] (PlaceId id) {
+      for (auto loan : place_db[id].borrowed_by)
+	{
+	  if (place_db.get_loans ()[loan].mutability == Mutability::Mut)
+	    {
+	      facts.loan_invalidated_at.emplace_back (
+		get_current_point_start (), loan);
+	    }
+	}
+    });
+  }
+
+  void check_write_for_conflict (PlaceId place_id)
+  {
+    place_db.for_each_path_segment (place_id, [&] (PlaceId id) {
+      for (auto loan : place_db[id].borrowed_by)
+	{
+	  facts.loan_invalidated_at.emplace_back (get_current_point_start (),
+						  loan);
+	}
+    });
+    place_db.for_each_path_from_root (place_id, [&] (PlaceId id) {
+      for (auto loan : place_db[id].borrowed_by)
+	{
+	  facts.loan_invalidated_at.emplace_back (get_current_point_start (),
+						  loan);
+	}
+    });
+  }
+
+  void check_for_borrow_conficts (PlaceId place_id, LoanId loan,
+				  Mutability mutability)
+  {
+    place_db.for_each_path_segment (place_id, [&] (PlaceId id) {
+      for (auto other_loan : place_db[id].borrowed_by)
+	{
+	  if (mutability == Mutability::Imm
+	      && place_db.get_loans ()[other_loan].mutability
+		   == Mutability::Imm)
+	    {
+	      continue;
+	    }
+	  else
+	    {
+	      facts.loan_invalidated_at.emplace_back (
+		get_current_point_start (), other_loan);
+	    }
+	}
+    });
+
+    place_db.for_each_path_from_root (place_id, [&] (PlaceId id) {
+      for (auto other_loan : place_db[id].borrowed_by)
+	{
+	  if (mutability == Mutability::Imm
+	      && place_db.get_loans ()[other_loan].mutability
+		   == Mutability::Imm)
+	    {
+	      continue;
+	    }
+	  else
+	    {
+	      facts.loan_invalidated_at.emplace_back (
+		get_current_point_start (), other_loan);
+	    }
+	}
+    });
+  }
+
+  void check_move_behind_reference (PlaceId place_id)
+  {
+    place_db.for_each_path_segment (place_id, [&] (PlaceId id) {
+      if (id == place_id)
+	return;
+      if (place_db[id].kind == Place::DEREF)
+	{
+	  rust_error_at (location, "Cannot move from behind a reference.");
+	}
+    });
+  }
+
+  void kill_borrows_for_place (PlaceId place_id)
+  {
+    auto &place = place_db[place_id];
+    for (auto loan : place.borrowed_by)
+      {
+	// TODO: this is more complicated, see
+	// compiler/rustc_borrowck/src/constraint_generation.rs:176
+	facts.loan_killed_at.emplace_back (loan, get_current_point_mid ());
+      }
+  }
+
+protected: // Subset helpers.
+  void push_subset (FreeRegion lhs, FreeRegion rhs)
+  {
+    rust_debug ("\t\tpush_subset: '?%lu: '?%lu", lhs, rhs);
+
+    facts.subset_base.emplace_back (lhs, rhs, get_current_point_mid ());
+  }
+
+  void push_subset_all (FreeRegion lhs, FreeRegion rhs)
+  {
+    rust_debug ("\t\tpush_subset_all: '?%lu: '?%lu", lhs, rhs);
+
+    for (auto point : cfg_points_all)
+      facts.subset_base.emplace_back (lhs, rhs, point);
+  }
+
+  void push_subset (Variance variance, FreeRegion lhs, FreeRegion rhs)
+  {
+    if (variance.is_covariant ())
+      {
+	push_subset (lhs, rhs);
+      }
+    else if (variance.is_contravariant ())
+      {
+	push_subset (rhs, lhs);
+      }
+    else if (variance.is_invariant ())
+      {
+	push_subset (lhs, rhs);
+	push_subset (rhs, lhs);
+      }
+  }
+
+  void push_subset_all (Variance variance, FreeRegion lhs, FreeRegion rhs)
+  {
+    if (variance.is_covariant ())
+      {
+	push_subset_all (lhs, rhs);
+      }
+    else if (variance.is_contravariant ())
+      {
+	push_subset_all (rhs, lhs);
+      }
+    else if (variance.is_invariant ())
+      {
+	push_subset_all (lhs, rhs);
+	push_subset_all (rhs, lhs);
+      }
+  }
+
+  void push_subset (PlaceId lhs, PlaceId rhs)
+  {
+    auto &lhs_place = place_db[lhs];
+    auto &rhs_place = place_db[rhs];
+
+    push_subset (lhs_place.tyty, rhs_place.regions, lhs_place.regions);
+  }
+
+  void push_subset (TyTy::BaseType *type, FreeRegions lhs, FreeRegions rhs)
+  {
+    auto variances = Resolver::TypeCheckContext::get ()
+		       ->get_variance_analysis_ctx ()
+		       .query_type_variances (type);
+    rust_assert (lhs.size () == rhs.size ());
+    rust_assert (lhs.size () == variances.size ());
+    for (size_t i = 0; i < lhs.size (); ++i)
+      {
+	push_subset (variances[i], lhs[i], rhs[i]);
+      }
+  }
+
+  void push_subset_all (TyTy::BaseType *type, FreeRegions lhs, FreeRegions rhs)
+  {
+    auto variances = Resolver::TypeCheckContext::get ()
+		       ->get_variance_analysis_ctx ()
+		       .query_type_variances (type);
+    rust_assert (lhs.size () == rhs.size ());
+    rust_assert (lhs.size () == variances.size ());
+    for (size_t i = 0; i < lhs.size (); ++i)
+      {
+	push_subset_all (variances[i], lhs[i], rhs[i]);
+      }
+  }
+
+  void push_subset_user (TyTy::BaseType *type, FreeRegions free_regions,
+			 std::vector<TyTy::Region> user_regions)
+  {
+    auto variances = Resolver::TypeCheckContext::get ()
+		       ->get_variance_analysis_ctx ()
+		       .query_type_variances (type);
+    rust_assert (free_regions.size () == user_regions.size ());
+    rust_assert (free_regions.size () == variances.size ());
+
+    for (size_t i = 0; i < free_regions.size (); ++i)
+      {
+	if (user_regions[i].is_named ())
+	  {
+	    push_subset (variances[i], free_regions[i],
+			 {Polonius::Origin (user_regions[i].get_index ())});
+	  }
+	else if (user_regions[i].is_anonymous ())
+	  {
+	    // IGNORE
+	  }
+	else
+	  {
+	    rust_internal_error_at (UNKNOWN_LOCATION, "Unexpected region type");
+	  }
+      }
+  }
+
+  /**
+   * Apply type and lifetime bounds
+   *
+   * For a place we have a list of fresh regions. We need to apply constraints
+   * from type definition to it. First `n` regions belong to the lifetime
+   * parameters of the type. The rest are flatten lifetime parameters of the
+   * type arguments. We walk the type arguments with a offset
+   */
+  void sanitize_constrains_at_init (PlaceId place_id)
+  {
+    auto &place = place_db[place_id];
+
+    rust_debug ("\tSanitize constraints of %s",
+		place.tyty->as_string ().c_str ());
+
+    if (auto generic = place.tyty->try_as<TyTy::SubstitutionRef> ())
+      {
+	auto &regions = place.regions;
+	auto region_end = sanitize_constraints (*generic, 0, regions);
+	rust_assert (region_end == regions.size ());
+      }
+    else if (place.tyty->is<TyTy::ReferenceType> ())
+      {
+	for (auto &region : place.regions)
+	  {
+	    if (region != place.regions[0])
+	      push_subset (region, place.regions[0]);
+	  }
+      }
+  }
+
+  size_t sanitize_constraints (const TyTy::BaseType *type, size_t region_start,
+			       const FreeRegions &regions)
+  {
+    switch (type->get_kind ())
+      {
+      case TyTy::ADT:
+	return sanitize_constraints (type->as<const TyTy::ADTType> (),
+				     region_start, regions);
+      case TyTy::STR:
+	return region_start;
+      case TyTy::REF:
+	return 1
+	       + sanitize_constraints (
+		 type->as<const TyTy::ReferenceType> ()->get_base (),
+		 region_start, regions);
+      case TyTy::POINTER:
+	return sanitize_constraints (
+	  type->as<const TyTy::PointerType> ()->get_base (), region_start,
+	  regions);
+      case TyTy::ARRAY:
+	return sanitize_constraints (
+	  type->as<const TyTy::ArrayType> ()->get_element_type (), region_start,
+	  regions);
+      case TyTy::SLICE:
+	return sanitize_constraints (
+	  type->as<const TyTy::SliceType> ()->get_element_type (), region_start,
+	  regions);
+      case TyTy::FNDEF:
+	case TyTy::TUPLE: {
+	  for (auto &field : type->as<const TyTy::TupleType> ()->get_fields ())
+	    sanitize_constraints (field.get_tyty (), region_start, regions);
+	}
+	break;
+      case TyTy::FNPTR:
+      case TyTy::PROJECTION:
+	return sanitize_constraints (*type->as<const TyTy::SubstitutionRef> (),
+				     region_start, regions);
+      case TyTy::BOOL:
+      case TyTy::CHAR:
+      case TyTy::INT:
+      case TyTy::UINT:
+      case TyTy::FLOAT:
+      case TyTy::USIZE:
+      case TyTy::ISIZE:
+      case TyTy::NEVER:
+      case TyTy::DYNAMIC:
+      case TyTy::CLOSURE:
+      case TyTy::ERROR:
+	return region_start;
+      case TyTy::PLACEHOLDER:
+      case TyTy::INFER:
+      case TyTy::PARAM:
+	rust_unreachable ();
+      }
+    rust_unreachable ();
+  }
+
+  size_t sanitize_constraints (const TyTy::SubstitutionRef &type,
+			       size_t region_start, const FreeRegions &regions)
+  {
+    for (auto constr : type.get_region_constraints ().region_region)
+      {
+	rust_assert (constr.first.is_early_bound ());
+	rust_assert (constr.second.is_early_bound ());
+	auto lhs = constr.first.get_index () + region_start;
+	auto rhs = constr.second.get_index () + region_start;
+	push_subset (regions[lhs], regions[rhs]);
+      }
+
+    size_t region_end = region_start + type.get_num_lifetime_params ();
+
+    /*
+     * For type `Foo<'a, T1, T2>`, where `T1 = &'b Vec<&'c i32>` and `T2 = &'d
+     * i32 the regions are `['a, 'b, 'c, 'd]`. The ranges
+     */
+    std::vector<size_t> type_param_region_ranges;
+    type_param_region_ranges.push_back (region_end);
+
+    for (auto type_param : type.get_substs ())
+      {
+	TyTy::SubstitutionArg arg = TyTy::SubstitutionArg::error ();
+	bool ok = type.get_used_arguments ().get_argument_for_symbol (
+	  type_param.get_param_ty (), &arg);
+	rust_assert (ok);
+	region_end
+	  = sanitize_constraints (arg.get_tyty (), region_end, regions);
+	type_param_region_ranges.push_back (region_end);
+      }
+
+    /*
+     * For constrain of form: `T: 'a` push outlives with all in range
+     * `indexof(T)..(indexof(T) + 1)`
+     */
+    for (auto constr : type.get_region_constraints ().type_region)
+      {
+	auto type_param_index_opt
+	  = type.get_used_arguments ().find_symbol (*constr.first);
+	rust_assert (type_param_index_opt.has_value ());
+	size_t type_param_index = type_param_index_opt.value ();
+
+	for (size_t i = type_param_region_ranges[type_param_index];
+	     i < type_param_region_ranges[type_param_index + 1]; ++i)
+	  {
+	    push_subset (regions[i],
+			 regions[constr.second.get_index () + region_start]);
+	  }
+      }
+
+    return region_end;
+  }
+}; // namespace BIR
+
+} // namespace BIR
+} // namespace Rust
+
+#endif // RUST_BIR_FACT_COLLECTOR_H
diff --git a/gcc/rust/checks/errors/borrowck/rust-borrow-checker.cc b/gcc/rust/checks/errors/borrowck/rust-borrow-checker.cc
index 5daa7eb8ded..77726eb1c26 100644
--- a/gcc/rust/checks/errors/borrowck/rust-borrow-checker.cc
+++ b/gcc/rust/checks/errors/borrowck/rust-borrow-checker.cc
@@ -20,6 +20,7 @@ 
 #include "rust-function-collector.h"
 #include "rust-bir-builder.h"
 #include "rust-bir-dump.h"
+#include "rust-bir-fact-collector.h"
 
 namespace Rust {
 namespace HIR {
@@ -86,6 +87,8 @@  BorrowChecker::go (HIR::Crate &crate)
 	  dump_function_bir (filename, bir,
 			     func->get_function_name ().as_string ());
 	}
+
+      auto facts = BIR::FactCollector::collect (bir);
     }
 
   for (auto closure ATTRIBUTE_UNUSED : collector.get_closures ())