// Copyright (C) 2020-2025 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
// .
#ifndef RUST_BIR_BUILDER_INTERNAL_H
#define RUST_BIR_BUILDER_INTERNAL_H
#include "rust-bir-place.h"
#include "rust-hir-expr.h"
#include "rust-hir-item.h"
#include "rust-hir-type-check.h"
#include "rust-hir-visitor.h"
#include "rust-name-resolver.h"
#include "rust-bir.h"
#include "rust-bir-free-region.h"
namespace Rust {
namespace TyTy {
using Variance = VarianceAnalysis::Variance;
class RenumberCtx
{
Polonius::Origin next_region = 0;
public:
Polonius::Origin get_next_region () { return next_region++; }
};
} // namespace TyTy
namespace BIR {
/** Holds the context of BIR building so that it can be shared/passed between
* different builders. */
struct BuilderContext
{
struct LoopAndLabelCtx
{
bool is_loop; // Loop or labelled block
NodeId label; // UNKNOWN_NODEID if no label (loop)
PlaceId label_var; // For break with value.
BasicBlockId break_bb;
BasicBlockId continue_bb; // Only valid for loops
ScopeId continue_scope;
// Break scope is the parent of the `continue_scope`.
LoopAndLabelCtx (bool is_loop = false, NodeId label = UNKNOWN_NODEID,
PlaceId label_var = INVALID_PLACE,
BasicBlockId break_bb = INVALID_BB,
BasicBlockId continue_bb = INVALID_BB,
ScopeId continue_scope = INVALID_SCOPE)
: is_loop (is_loop), label (label), label_var (label_var),
break_bb (break_bb), continue_bb (continue_bb),
continue_scope (continue_scope)
{}
};
// External context.
Resolver::TypeCheckContext &tyctx;
Resolver::Resolver &resolver;
// BIR output
BasicBlocks basic_blocks;
BasicBlockId current_bb = ENTRY_BASIC_BLOCK;
/**
* Allocation and lookup of places (variables, temporaries, paths, and
* constants)
*/
PlaceDB place_db;
RegionBinder region_binder{place_db.expose_next_free_region ()};
// Used for cleaner dump.
std::vector arguments;
/**
* Since labels can be used to return values, we need to reserve a place for
* them. This map associates labels with their respective places.
*/
std::unordered_map label_place_map;
/** Context for current situation (loop, label, etc.) */
std::vector loop_and_label_stack;
FreeRegions fn_free_regions{{}};
public:
BuilderContext ()
: tyctx (*Resolver::TypeCheckContext::get ()),
resolver (*Resolver::Resolver::get ())
{
basic_blocks.emplace_back (); // StartBB
}
BasicBlock &get_current_bb () { return basic_blocks[current_bb]; }
const LoopAndLabelCtx &lookup_label (NodeId label)
{
auto label_match = [label] (const LoopAndLabelCtx &info) {
return info.label != UNKNOWN_NODEID && info.label == label;
};
auto found = std::find_if (loop_and_label_stack.rbegin (),
loop_and_label_stack.rend (), label_match);
rust_assert (found != loop_and_label_stack.rend ());
return *found;
}
};
/** Common infrastructure for building BIR from HIR. */
class AbstractBuilder
{
protected:
BuilderContext &ctx;
/**
* This emulates the return value of the visitor, to be able to use the
* current visitor infrastructure, where the return value is forced to be
* void.
*/
PlaceId translated = INVALID_PLACE;
protected:
explicit AbstractBuilder (BuilderContext &ctx) : ctx (ctx) {}
PlaceId declare_variable (const Analysis::NodeMapping &node,
bool user_type_annotation = false)
{
return declare_variable (node, lookup_type (node.get_hirid ()),
user_type_annotation);
}
PlaceId declare_variable (const Analysis::NodeMapping &node,
TyTy::BaseType *ty,
bool user_type_annotation = false)
{
const NodeId nodeid = node.get_nodeid ();
// In debug mode, check that the variable is not already declared.
rust_assert (ctx.place_db.lookup_variable (nodeid) == INVALID_PLACE);
auto place_id = ctx.place_db.add_variable (nodeid, ty);
if (ctx.place_db.get_current_scope_id () != INVALID_SCOPE)
push_storage_live (place_id);
if (user_type_annotation)
push_user_type_ascription (place_id, ty);
return place_id;
}
void push_new_scope () { ctx.place_db.push_new_scope (); }
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); });
}
ctx.place_db.pop_scope ();
}
bool intersection_empty (std::vector &a, std::vector &b)
{
for (auto &place : a)
{
if (std::find (b.begin (), b.end (), place) != b.end ())
return false;
}
return true;
}
void unwind_until (ScopeId final_scope)
{
auto current_scope_id = ctx.place_db.get_current_scope_id ();
while (current_scope_id != final_scope)
{
auto &scope = ctx.place_db.get_scope (current_scope_id);
// TODO: Perform stable toposort based on `borrowed_by`.
std::for_each (scope.locals.rbegin (), scope.locals.rend (),
[&] (PlaceId place) { push_storage_dead (place); });
current_scope_id = scope.parent;
}
}
FreeRegions bind_regions (std::vector regions,
FreeRegions parent_free_regions)
{
FreeRegions free_regions;
for (auto ®ion : regions)
{
if (region.is_early_bound ())
{
free_regions.push_back (parent_free_regions[region.get_index ()]);
}
else if (region.is_static ())
{
free_regions.push_back (STATIC_FREE_REGION);
}
else if (region.is_anonymous ())
{
free_regions.push_back (ctx.place_db.get_next_free_region ());
}
else if (region.is_named ())
{
rust_unreachable (); // FIXME
}
else
{
rust_sorry_at (UNKNOWN_LOCATION, "Unimplemented");
rust_unreachable ();
}
}
return free_regions;
}
protected: // Helpers to add BIR statements
void push_assignment (PlaceId lhs, AbstractExpr *rhs, location_t location)
{
ctx.get_current_bb ().statements.push_back (
Statement::make_assignment (lhs, rhs, location));
translated = lhs;
}
void push_assignment (PlaceId lhs, PlaceId rhs, location_t location)
{
push_assignment (lhs, new Assignment (rhs), location);
}
void push_tmp_assignment (AbstractExpr *rhs, TyTy::BaseType *tyty,
location_t location)
{
PlaceId tmp = ctx.place_db.add_temporary (tyty);
push_storage_live (tmp);
push_assignment (tmp, rhs, location);
}
void push_tmp_assignment (PlaceId rhs, location_t location)
{
push_tmp_assignment (new Assignment (rhs), ctx.place_db[rhs].tyty,
location);
}
void push_switch (PlaceId switch_val, location_t location,
std::initializer_list destinations = {})
{
auto copy = move_place (switch_val, location);
ctx.get_current_bb ().statements.push_back (Statement::make_switch (copy));
ctx.get_current_bb ().successors.insert (
ctx.get_current_bb ().successors.end (), destinations);
}
void push_goto (BasicBlockId bb)
{
ctx.get_current_bb ().statements.push_back (Statement::make_goto ());
if (bb != INVALID_BB) // INVALID_BB means the goto will be resolved later.
ctx.get_current_bb ().successors.push_back (bb);
}
void push_storage_live (PlaceId place)
{
ctx.get_current_bb ().statements.push_back (
Statement::make_storage_live (place));
}
void push_storage_dead (PlaceId place)
{
ctx.get_current_bb ().statements.push_back (
Statement::make_storage_dead (place));
}
void push_user_type_ascription (PlaceId place, TyTy::BaseType *ty)
{
ctx.get_current_bb ().statements.push_back (
Statement::make_user_type_ascription (place, ty));
}
void push_fake_read (PlaceId place)
{
ctx.get_current_bb ().statements.push_back (
Statement::make_fake_read (place));
}
void push_return (location_t location)
{
ctx.get_current_bb ().statements.push_back (
Statement::make_return (location));
}
PlaceId borrow_place (PlaceId place_id, TyTy::BaseType *ty,
location_t location)
{
auto mutability = ty->as ()->mutability ();
auto loan = ctx.place_db.add_loan ({mutability, place_id, location});
push_tmp_assignment (
new BorrowExpr (place_id, loan,
ctx.place_db.get_next_free_region ().value),
ty, location);
return translated;
}
PlaceId move_place (PlaceId arg, location_t location)
{
auto &place = ctx.place_db[arg];
if (place.is_constant ())
return arg;
if (place.tyty->is ())
return reborrow_place (arg, location);
if (place.is_rvalue ())
return arg;
push_tmp_assignment (arg, location);
return translated;
}
PlaceId reborrow_place (PlaceId arg, location_t location)
{
auto ty = ctx.place_db[arg].tyty->as ();
return borrow_place (ctx.place_db.lookup_or_add_path (Place::DEREF,
ty->get_base (), arg),
ty, location);
}
template
void move_all (T &args, std::vector locations)
{
rust_assert (args.size () == locations.size ());
std::transform (args.begin (), args.end (), locations.begin (),
args.begin (), [this] (PlaceId arg, location_t location) {
return move_place (arg, location);
});
}
protected: // CFG helpers
BasicBlockId new_bb ()
{
ctx.basic_blocks.emplace_back ();
return {ctx.basic_blocks.size () - 1};
}
BasicBlockId start_new_consecutive_bb ()
{
BasicBlockId bb = new_bb ();
if (!ctx.get_current_bb ().is_terminated ())
{
push_goto (bb);
}
else
{
add_jump_to (bb);
}
ctx.current_bb = bb;
return bb;
}
void add_jump (BasicBlockId from, BasicBlockId to)
{
ctx.basic_blocks[from].successors.emplace_back (to);
}
void add_jump_to (BasicBlockId bb) { add_jump (ctx.current_bb, bb); }
protected: // HIR resolution helpers
template
WARN_UNUSED_RESULT TyTy::BaseType *lookup_type (T &hir_node) const
{
return lookup_type (hir_node.get_mappings ().get_hirid ());
}
WARN_UNUSED_RESULT TyTy::BaseType *lookup_type (HirId hirid) const
{
TyTy::BaseType *type = nullptr;
bool ok = ctx.tyctx.lookup_type (hirid, &type);
rust_assert (ok);
rust_assert (type != nullptr);
return type;
}
template NodeId resolve_label (T &expr)
{
NodeId resolved_label;
bool ok
= ctx.resolver.lookup_resolved_label (expr.get_mappings ().get_nodeid (),
&resolved_label);
rust_assert (ok);
return resolved_label;
}
template PlaceId resolve_variable (T &variable)
{
NodeId variable_id;
bool ok = ctx.resolver.lookup_resolved_name (
variable.get_mappings ().get_nodeid (), &variable_id);
rust_assert (ok);
return ctx.place_db.lookup_variable (variable_id);
}
template
PlaceId resolve_variable_or_fn (T &variable, TyTy::BaseType *ty)
{
ty = (ty) ? ty : lookup_type (variable);
// Unlike variables,
// functions do not have to be declared in PlaceDB before use.
NodeId variable_id;
bool ok = ctx.resolver.lookup_resolved_name (
variable.get_mappings ().get_nodeid (), &variable_id);
rust_assert (ok);
if (ty->is ())
return ctx.place_db.get_constant (ty);
else
return ctx.place_db.lookup_or_add_variable (variable_id, ty);
}
protected: // Implicit conversions.
/**
* Performs implicit coercions on the `translated` place defined for a
* coercion site.
*
* Reference: https://doc.rust-lang.org/reference/type-coercions.html
*
* The only coercion relevant to BIR is the autoderef. All other coercions
* will be taken in account because the type is extracted from each node and
* not derived from operations in HIR/BIR. The borrowck does not care about
* type transitions. Lifetimes are not coerced, rather new are created with
* defined bounds to the original ones.
*/
void coercion_site (PlaceId &place, TyTy::BaseType *expected_ty)
{
auto count_ref_levels = [] (TyTy::BaseType *ty) {
size_t count = 0;
while (auto r = ty->try_as ())
{
ty = r->get_base ();
count++;
}
return count;
};
auto actual_ty = ctx.place_db[place].tyty;
auto deref_count
= count_ref_levels (actual_ty) - count_ref_levels (expected_ty);
for (size_t i = 0; i < deref_count; ++i)
{
actual_ty = actual_ty->as ()->get_base ();
place
= ctx.place_db.lookup_or_add_path (Place::DEREF, actual_ty, place);
}
}
/** Dereferences the `translated` place until it is at most one reference
* and return the base type. */
TyTy::BaseType *autoderef (PlaceId &place)
{
auto ty = ctx.place_db[place].tyty;
while (auto ref_ty = ty->try_as ())
{
ty = ref_ty->get_base ();
place = ctx.place_db.lookup_or_add_path (Place::DEREF, ty, place);
}
return ty;
}
void autoref ()
{
if (ctx.place_db[translated].tyty->get_kind () != TyTy::REF)
{
// FIXME: not sure how to fetch correct location for this
// this function is unused yet, so can ignore for now
auto ty = ctx.place_db[translated].tyty;
translated
= borrow_place (translated,
new TyTy::ReferenceType (ty->get_ref (),
TyTy::TyVar (ty->get_ref ()),
Mutability::Imm),
UNKNOWN_LOCATION);
}
}
};
class AbstractExprBuilder : public AbstractBuilder,
public HIR::HIRExpressionVisitor
{
protected:
/**
* Optional place for the result of the evaluated expression.
* Valid if value is not `INVALID_PLACE`.
* Used when return place must be created by caller (return for if-else).
*/
PlaceId expr_return_place = INVALID_PLACE;
protected:
explicit AbstractExprBuilder (BuilderContext &ctx,
PlaceId expr_return_place = INVALID_PLACE)
: AbstractBuilder (ctx), expr_return_place (expr_return_place)
{}
/**
* Wrapper that provides return value based API inside a visitor which has
* to use global state to pass the data around.
* @param dst_place Place to assign the produced value to, optionally
* allocated by the caller.
* */
PlaceId visit_expr (HIR::Expr &expr, PlaceId dst_place = INVALID_PLACE)
{
// Save to support proper recursion.
auto saved = expr_return_place;
expr_return_place = dst_place;
translated = INVALID_PLACE;
expr.accept_vis (*this);
expr_return_place = saved;
auto result = translated;
translated = INVALID_PLACE;
return result;
}
/**
* Create a return value of a subexpression, which produces an expression.
* Use `return_place` for subexpression that only produce a place (look it
* up) to avoid needless assignments.
*
* @param can_panic mark that expression can panic to insert jump to
* cleanup.
*/
void return_expr (AbstractExpr *expr, TyTy::BaseType *ty, location_t location,
bool can_panic = false)
{
if (expr_return_place != INVALID_PLACE)
{
push_assignment (expr_return_place, expr, location);
}
else
{
push_tmp_assignment (expr, ty, location);
}
if (can_panic)
{
start_new_consecutive_bb ();
}
if (ty->is ()
|| ctx.place_db[translated].is_constant ())
{
push_fake_read (translated);
}
}
/** Mark place to be a result of processed subexpression. */
void return_place (PlaceId place, location_t location, bool can_panic = false)
{
if (expr_return_place != INVALID_PLACE)
{
// Return place is already allocated, no need to defer assignment.
push_assignment (expr_return_place, place, location);
}
else
{
translated = place;
}
if (can_panic)
{
start_new_consecutive_bb ();
}
if (ctx.place_db[place].is_constant ())
{
push_fake_read (translated);
}
}
/** Explicitly return a unit value. Expression produces no value. */
void return_unit (HIR::Expr &expr)
{
translated = ctx.place_db.get_constant (lookup_type (expr));
}
PlaceId return_borrowed (PlaceId place_id, TyTy::BaseType *ty,
location_t location)
{
// TODO: deduplicate with borrow_place
auto loan = ctx.place_db.add_loan (
{ty->as ()->mutability (), place_id,
location});
return_expr (new BorrowExpr (place_id, loan,
ctx.place_db.get_next_free_region ().value),
ty, location);
return translated;
}
PlaceId take_or_create_return_place (TyTy::BaseType *type)
{
PlaceId result = INVALID_PLACE;
if (expr_return_place != INVALID_PLACE)
{
result = expr_return_place;
expr_return_place = INVALID_PLACE;
}
else
{
result = ctx.place_db.add_temporary (type);
push_storage_live (result);
}
return result;
}
};
/**
* Helper to convert a pointer to an optional. Maps nullptr to nullopt.
* Optionals are mainly used here to provide monadic operations (map) over
* possibly null pointers.
*/
template
tl::optional
optional_from_ptr (T ptr)
{
if (ptr != nullptr)
return {ptr};
else
return tl::nullopt;
}
} // namespace BIR
} // namespace Rust
#endif // RUST_BIR_BUILDER_INTERNAL_H