// 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_PLACE_H #define RUST_BIR_PLACE_H #include "rust-mapping-common.h" #include "rust-system.h" #include "rust-tyty.h" #include "rust-bir-free-region.h" #include "rust-tyty-variance-analysis.h" #include "polonius/rust-polonius-ffi.h" #include "rust-hir-type-check.h" namespace Rust { namespace BIR { /** A unique identifier for a place in the BIR. */ struct PlaceId { uint32_t value; // some overloads for comparision bool operator== (const PlaceId &rhs) const { return value == rhs.value; } bool operator!= (const PlaceId &rhs) const { return !(operator== (rhs)); } bool operator< (const PlaceId &rhs) const { return value < rhs.value; } bool operator> (const PlaceId &rhs) const { return value > rhs.value; } bool operator<= (const PlaceId &rhs) const { return !(operator> (rhs)); } bool operator>= (const PlaceId &rhs) const { return !(operator< (rhs)); } }; static constexpr PlaceId INVALID_PLACE = {0}; static constexpr PlaceId RETURN_VALUE_PLACE = {1}; static constexpr PlaceId FIRST_VARIABLE_PLACE = RETURN_VALUE_PLACE; using Variance = TyTy::VarianceAnalysis::Variance; /** A unique identifier for a loan in the BIR. */ struct LoanId { uint32_t value; // some overloads for comparision bool operator== (const LoanId &rhs) const { return value == rhs.value; } bool operator!= (const LoanId &rhs) const { return !(operator== (rhs)); } bool operator< (const LoanId &rhs) const { return value < rhs.value; } bool operator> (const LoanId &rhs) const { return value > rhs.value; } bool operator<= (const LoanId &rhs) const { return !(operator> (rhs)); } bool operator>= (const LoanId &rhs) const { return !(operator< (rhs)); } }; /** * Representation of lvalues and constants in BIR. * See bir bir design notes (in this directory) and the Polonius book. */ struct Place { enum Kind { INVALID, VARIABLE, TEMPORARY, CONSTANT, FIELD, INDEX, DEREF, }; Kind kind; uint32_t variable_or_field_index; // NodeId for VARIABLE /** Data for traversing paths in the PlaceDB. */ struct Path { PlaceId parent = INVALID_PLACE; PlaceId first_child = INVALID_PLACE; PlaceId next_sibling = INVALID_PLACE; Path (PlaceId parent, PlaceId first_child, PlaceId next_sibling) : parent (parent), first_child (first_child), next_sibling (next_sibling) {} Path () = default; } path; /** Copy trait */ bool is_copy; bool has_drop = false; TyTy::BaseType *tyty; FreeRegions regions{{}}; std::vector borrowed_by{}; public: Place (Kind kind, uint32_t variable_or_field_index, const Path &path, bool is_copy, TyTy::BaseType *tyty) : kind (kind), variable_or_field_index (variable_or_field_index), path (path), is_copy (is_copy), tyty (tyty) {} // Place can only be stored in PlaceDB and used via reference. Turn all // accidental copies into errors. Place (const Place &) = delete; Place (Place &&) = default; public: WARN_UNUSED_RESULT bool is_lvalue () const { return kind == VARIABLE || is_path (); } WARN_UNUSED_RESULT bool is_rvalue () const { return kind == TEMPORARY; } bool is_constant () const { return kind == CONSTANT; } WARN_UNUSED_RESULT bool is_var () const { return kind == VARIABLE || kind == TEMPORARY; } WARN_UNUSED_RESULT bool is_path () const { return kind == FIELD || kind == INDEX || kind == DEREF; } WARN_UNUSED_RESULT TyTy::BaseType *get_fn_return_ty () const { switch (tyty->get_kind ()) { case TyTy::FNPTR: return tyty->as ()->get_return_type (); case TyTy::FNDEF: return tyty->as ()->get_return_type (); default: rust_assert (false); } } WARN_UNUSED_RESULT bool is_indirect () const { // TODO: probably incomplete, check other projections switch (tyty->get_kind ()) { case TyTy::REF: case TyTy::POINTER: return true; default: return false; } } WARN_UNUSED_RESULT bool should_be_moved () const { return kind == TEMPORARY || (!is_copy && kind != CONSTANT); } }; struct ScopeId { uint32_t value; ScopeId next_scope_id () const { return {value + 1}; } // some overloads for comparision bool operator== (const ScopeId &rhs) const { return value == rhs.value; } bool operator!= (const ScopeId &rhs) const { return !(operator== (rhs)); } bool operator< (const ScopeId &rhs) const { return value < rhs.value; } bool operator> (const ScopeId &rhs) const { return value > rhs.value; } bool operator<= (const ScopeId &rhs) const { return !(operator> (rhs)); } bool operator>= (const ScopeId &rhs) const { return !(operator< (rhs)); } }; static constexpr ScopeId INVALID_SCOPE = {std::numeric_limits::max ()}; /** Arguments and return value are in the root scope. */ static constexpr ScopeId ROOT_SCOPE = {0}; /** Top-level local variables are in the top-level scope. */ static constexpr ScopeId TOP_LEVEL_SCOPE = {1}; struct Scope { ScopeId parent = INVALID_SCOPE; std::vector children; std::vector locals; }; struct Loan { Mutability mutability; PlaceId place; location_t location; }; // I is the index type, T is the contained type template class IndexVec { std::vector internal_vector; public: IndexVec () = default; IndexVec (size_t size) { internal_vector.reserve (size); } T &at (I pid) { return internal_vector[pid.value]; } const T &at (I pid) const { return internal_vector[pid.value]; } T &operator[] (I pid) { return internal_vector[pid.value]; } const T &operator[] (I pid) const { return internal_vector[pid.value]; } void push_back (T &¶m) { internal_vector.push_back (std::move (param)); } template void emplace_back (Args &&... args) { internal_vector.emplace_back (std::forward (args)...); } size_t size () const { return internal_vector.size (); } std::vector &get_vector () { return internal_vector; } }; using Scopes = IndexVec; using Loans = IndexVec; using Places = IndexVec; /** Allocated places and keeps track of paths. */ class PlaceDB { private: // Possible optimizations: separate variables to speedup lookup. Places places; std::unordered_map constants_lookup; Scopes scopes; ScopeId current_scope = ROOT_SCOPE; Loans loans; FreeRegion next_free_region = {1}; public: PlaceDB () { // Reserved index for invalid place. places.push_back ({Place::INVALID, 0, {}, false, nullptr}); scopes.emplace_back (); // Root scope. } Place &operator[] (PlaceId id) { return places.at (id); } const Place &operator[] (PlaceId id) const { return places.at (id); } size_t size () const { return places.size (); } const Loans &get_loans () const { return loans; } const Loan &get_loan (LoanId loan_id) const { return loans.at (loan_id); } ScopeId get_current_scope_id () const { return current_scope; } const Scopes &get_scopes () const { return scopes; } const Scope &get_current_scope () const { return scopes[current_scope]; } const Scope &get_scope (ScopeId id) const { return scopes[id]; } FreeRegion get_next_free_region () { ++next_free_region.value; return {next_free_region.value - 1}; } FreeRegion peek_next_free_region () const { return next_free_region; } FreeRegion &expose_next_free_region () { return next_free_region; } ScopeId push_new_scope () { ScopeId new_scope = {scopes.size ()}; scopes.emplace_back (); scopes[new_scope].parent = current_scope; scopes[current_scope].children.push_back (new_scope); current_scope = new_scope; return new_scope; } ScopeId pop_scope () { current_scope = scopes[current_scope].parent; return current_scope; } PlaceId add_place (Place &&place, PlaceId last_sibling = INVALID_PLACE) { places.emplace_back (std::forward (place)); PlaceId new_place = {places.size () - 1}; Place &new_place_ref = places[new_place]; // Intentional shadowing. if (last_sibling == INVALID_PLACE) places[new_place_ref.path.parent].path.first_child = new_place; else places[last_sibling].path.next_sibling = new_place; if (new_place_ref.kind == Place::VARIABLE || new_place_ref.kind == Place::TEMPORARY) scopes[current_scope].locals.push_back (new_place); auto variances = Resolver::TypeCheckContext::get () ->get_variance_analysis_ctx () .query_type_variances (new_place_ref.tyty); FreeRegions regions; for (size_t i = 0; i < variances.size (); ++i) { regions.push_back (next_free_region); ++next_free_region.value; } new_place_ref.regions = regions; return new_place; } PlaceId add_variable (NodeId id, TyTy::BaseType *tyty) { return add_place ({Place::VARIABLE, id, {}, is_type_copy (tyty), tyty}, INVALID_PLACE); } WARN_UNUSED_RESULT PlaceId lookup_or_add_path (Place::Kind kind, TyTy::BaseType *tyty, PlaceId parent, size_t id = 0) { PlaceId current = INVALID_PLACE; if (parent.value < places.size ()) { current = places[parent].path.first_child; while (current != INVALID_PLACE) { if (places[current].kind == kind && places[current].variable_or_field_index == id) { rust_assert (places[current].tyty->is_equal (*tyty)); return current; } current = places[current].path.next_sibling; } } return add_place ({kind, (uint32_t) id, Place::Path{parent, INVALID_PLACE, INVALID_PLACE}, is_type_copy (tyty), tyty}, current); } PlaceId add_temporary (TyTy::BaseType *tyty) { return add_place ({Place::TEMPORARY, 0, {}, is_type_copy (tyty), tyty}, INVALID_PLACE); } PlaceId get_constant (TyTy::BaseType *tyty) { auto lookup = constants_lookup.find (tyty); if (lookup != constants_lookup.end ()) return lookup->second; return add_place ({Place::CONSTANT, 0, {}, is_type_copy (tyty), tyty}); } PlaceId lookup_variable (NodeId id) { PlaceId current = FIRST_VARIABLE_PLACE; while (current.value != places.size ()) { if (places[current].kind == Place::VARIABLE && places[current].variable_or_field_index == id) return current; ++current.value; } return INVALID_PLACE; } LoanId add_loan (Loan &&loan) { LoanId id = {loans.size ()}; loans.push_back (std::forward (loan)); PlaceId borrowed_place = loans.get_vector ().rbegin ()->place; places[loans.get_vector ().rbegin ()->place].borrowed_by.push_back (id); if (places[borrowed_place].kind == Place::DEREF) { places[places[borrowed_place].path.parent].borrowed_by.push_back (id); } return id; } PlaceId get_var (PlaceId id) const { if (places[id].is_var ()) return id; rust_assert (places[id].is_path ()); PlaceId current = id; while (!places[current].is_var ()) { current = places[current].path.parent; } return current; } void set_next_free_region (Polonius::Origin next_free_region) { this->next_free_region.value = next_free_region; } PlaceId lookup_or_add_variable (NodeId id, TyTy::BaseType *tyty) { auto lookup = lookup_variable (id); if (lookup != INVALID_PLACE) return lookup; add_place ({Place::VARIABLE, id, {}, is_type_copy (tyty), tyty}); return {places.size () - 1}; }; template void for_each_path_from_root (PlaceId var, FN fn) const { PlaceId current = var; current = places[current].path.first_child; while (current != INVALID_PLACE) { fn (current); for_each_path_from_root (current, fn); current = places[current].path.next_sibling; } } template void for_each_path_segment (PlaceId place_id, FN fn) const { PlaceId current = place_id; while (current != INVALID_PLACE) { fn (current); current = places[current].path.parent; } } private: static bool is_type_copy (TyTy::BaseType *ty) { switch (ty->get_kind ()) { case TyTy::REF: return ty->as ()->mutability () == Mutability::Imm; case TyTy::POINTER: case TyTy::SLICE: case TyTy::BOOL: case TyTy::CHAR: case TyTy::INT: case TyTy::UINT: case TyTy::FLOAT: case TyTy::USIZE: case TyTy::ISIZE: case TyTy::FNPTR: case TyTy::FNDEF: case TyTy::NEVER: return true; case TyTy::TUPLE: { auto &fields = ty->as ()->get_fields (); return std::all_of (fields.begin (), fields.end (), [] (const TyTy::TyVar &field) { return is_type_copy (field.get_tyty ()); }); } case TyTy::ARRAY: { return is_type_copy (ty->as ()->get_element_type ()); } case TyTy::INFER: case TyTy::PARAM: case TyTy::ERROR: case TyTy::STR: case TyTy::PLACEHOLDER: rust_unreachable (); case TyTy::ADT: // TODO: check trait case TyTy::PROJECTION: // TODO: DUNNO case TyTy::CLOSURE: // TODO: DUNNO case TyTy::DYNAMIC: // TODO: dunno case TyTy::OPAQUE: return false; } rust_unreachable (); } /** Check whether given place is not out-of-scope. */ WARN_UNUSED_RESULT bool is_in_scope (PlaceId place) const { for (ScopeId scope = current_scope; scope != INVALID_SCOPE; scope = scopes[scope].parent) { auto &scope_ref = scopes[scope]; if (std::find (scope_ref.locals.begin (), scope_ref.locals.end (), place) != scope_ref.locals.end ()) return true; } return false; } }; } // namespace BIR } // namespace Rust #endif // RUST_BIR_PLACE_H