// Graph abstraction
//
// Copyright (C) 2014-2022 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// This program 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 of the License, or
// (at your option) any later version.
//
// This program 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 this program. If not, see .
//! Abstract graph as the basis for concrete ASGs.
use super::ident::IdentKind;
use super::object::{
FragmentText, IdentObjectData, IdentObjectState, Source, TransitionResult,
};
use super::AsgError;
use crate::global;
use crate::sym::SymbolId;
use petgraph::graph::{DiGraph, Graph, NodeIndex};
use std::fmt::Debug;
use std::result::Result;
/// Datatype representing node and edge indexes.
pub trait IndexType = petgraph::graph::IndexType;
/// A [`Result`] with a hard-coded [`AsgError`] error type.
///
/// This is the result of every [`Asg`] operation that could potentially
/// fail in error.
pub type AsgResult = Result;
/// There are currently no data stored on edges ("edge weights").
pub type AsgEdge = ();
/// Each node of the graph represents an object.
///
/// Enclosed in an [`Option`] to permit moving owned values out of the
/// graph.
pub type Node = Option;
/// Index size for Graph nodes and edges.
type Ix = global::ProgSymSize;
/// An abstract semantic graph (ASG) of [objects][super::object].
///
/// This implementation is currently based on [`petgraph`].
///
/// Identifiers are cached by name for `O(1)` lookup.
/// Since [`SymbolId`][crate::sym::SymbolId] is used for this purpose,
/// the index may contain more entries than nodes and may contain gaps.
///
/// This IR focuses on the definition and manipulation of objects and their
/// dependencies.
/// See [`IdentObject`](super::object::IdentObject) for a summary of valid
/// identifier object state transitions.
///
/// Objects are never deleted from the graph,
/// so [`ObjectRef`]s will remain valid for the lifetime of the ASG.
///
/// For more information,
/// see the [module-level documentation][self].
#[derive(Debug, Default)]
pub struct Asg {
// TODO: private; see `ld::xmle::lower`.
/// Directed graph on which objects are stored.
pub graph: DiGraph, AsgEdge, Ix>,
/// Map of [`SymbolId`][crate::sym::SymbolId] to node indexes.
///
/// This allows for `O(1)` lookup of identifiers in the graph.
/// Note that,
/// while we store [`NodeIndex`] internally,
/// the public API encapsulates it within an [`ObjectRef`].
index: Vec>,
/// Empty node indicating that no object exists for a given index.
empty_node: NodeIndex,
}
impl Asg
where
O: IdentObjectState + IdentObjectData,
{
/// Create a new ASG.
///
/// See also [`with_capacity`](Asg::with_capacity).
pub fn new() -> Self {
Self::with_capacity(0, 0)
}
/// Create an ASG with the provided initial capacity.
///
/// The value for `objects` will be used as the capacity for the nodes
/// in the graph,
/// as well as the initial index capacity.
/// The value for `edges` may be more difficult to consider,
/// since edges are used to represent various relationships between
/// different types of objects,
/// but it's safe to say that each object will have at least one
/// edge to another object.
pub fn with_capacity(objects: usize, edges: usize) -> Self {
let mut graph = Graph::with_capacity(objects, edges);
let mut index = Vec::with_capacity(objects);
// Exhaust the first index to be used as a placeholder.
let empty_node = graph.add_node(None);
index.push(empty_node);
Self {
graph,
index,
empty_node,
}
}
/// Get the underlying Graph
pub fn into_inner(self) -> DiGraph, AsgEdge, Ix> {
self.graph
}
/// Index the provided symbol `name` as representing the identifier `node`.
///
/// This index permits `O(1)` identifier lookups.
///
/// After an identifier is indexed it is not expected to be reassigned
/// to another node.
/// Debug builds contain an assertion that will panic in this instance.
///
/// Panics
/// ======
/// Will panic if unable to allocate more space for the index.
fn index_identifier(&mut self, name: SymbolId, node: NodeIndex) {
let i = name.as_usize();
if i >= self.index.len() {
// If this is ever a problem we can fall back to usize max and
// re-compare before panicing
let new_size = (i + 1)
.checked_next_power_of_two()
.expect("internal error: cannot allocate space for ASG index");
self.index.resize(new_size, self.empty_node);
}
// We should never overwrite indexes
debug_assert!(self.index[i] == self.empty_node);
self.index[i] = node;
}
/// Lookup `ident` or add a missing identifier to the graph and return a
/// reference to it.
///
/// See [`IdentObjectState::declare`] for more information.
fn lookup_or_missing(&mut self, ident: SymbolId) -> ObjectRef {
self.lookup(ident).unwrap_or_else(|| {
let index = self.graph.add_node(Some(O::declare(ident)));
self.index_identifier(ident, index);
ObjectRef::new(index)
})
}
/// Perform a state transition on an identifier by name.
///
/// Look up `ident` or add a missing identifier if it does not yet exist
/// (see `lookup_or_missing`).
/// Then invoke `f` with the located identifier and replace the
/// identifier on the graph with the result.
///
/// This will safely restore graph state to the original identifier
/// value on transition failure.
fn with_ident_lookup(
&mut self,
name: SymbolId,
f: F,
) -> AsgResult
where
F: FnOnce(O) -> TransitionResult,
{
let identi = self.lookup_or_missing(name);
self.with_ident(identi, f)
}
/// Perform a state transition on an identifier by [`ObjectRef`].
///
/// Invoke `f` with the located identifier and replace the identifier on
/// the graph with the result.
///
/// This will safely restore graph state to the original identifier
/// value on transition failure.
fn with_ident(&mut self, identi: ObjectRef, f: F) -> AsgResult
where
F: FnOnce(O) -> TransitionResult,
{
let node = self.graph.node_weight_mut(identi.into()).unwrap();
let obj = node
.take()
.expect(&format!("internal error: missing object"));
f(obj)
.and_then(|obj| {
node.replace(obj);
Ok(identi)
})
.or_else(|(orig, err)| {
node.replace(orig);
Err(err.into())
})
}
/// Declare a concrete identifier.
///
/// An identifier declaration is similar to a declaration in a header
/// file in a language like C,
/// describing the structure of the identifier.
/// Once declared,
/// this information cannot be changed.
///
/// Identifiers are uniquely identified by a [`SymbolId`] `name`.
/// If an identifier of the same `name` already exists,
/// then the provided declaration is compared against the existing
/// declaration---should
/// they be incompatible,
/// then the operation will fail;
/// otherwise,
/// the existing identifier will be returned.
///
/// If a concrete identifier has already been declared (see
/// [`Asg::declare`]),
/// then extern declarations will be compared and,
/// if compatible,
/// the identifier will be immediately _resolved_ and the object
/// on the graph will not be altered.
/// Resolution will otherwise fail in error.
///
/// For more information on state transitions that can occur when
/// redeclaring an identifier that already exists,
/// see [`IdentObjectState::resolve`].
///
/// A successful declaration will add an identifier to the graph
/// and return an [`ObjectRef`] reference.
pub fn declare(
&mut self,
name: SymbolId,
kind: IdentKind,
src: Source,
) -> AsgResult {
self.with_ident_lookup(name, |obj| obj.resolve(kind, src))
}
/// Declare an abstract identifier.
///
/// An _extern_ declaration declares an identifier the same as
/// [`Asg::declare`],
/// but omits source information.
/// Externs are identifiers that are expected to be defined somewhere
/// else ("externally"),
/// and are resolved at [link-time][crate::ld].
///
/// If a concrete identifier has already been declared (see
/// [`Asg::declare`]),
/// then the declarations will be compared and,
/// if compatible,
/// the identifier will be immediately _resolved_ and the object
/// on the graph will not be altered.
/// Resolution will otherwise fail in error.
///
/// See [`IdentObjectState::extern_`] and
/// [`IdentObjectState::resolve`] for more information on
/// compatibility related to extern resolution.
pub fn declare_extern(
&mut self,
name: SymbolId,
kind: IdentKind,
src: Source,
) -> AsgResult {
self.with_ident_lookup(name, |obj| obj.extern_(kind, src))
}
/// Set the fragment associated with a concrete identifier.
///
/// Fragments are intended for use by the [linker][crate::ld].
/// For more information,
/// see [`IdentObjectState::set_fragment`].
pub fn set_fragment(
&mut self,
identi: ObjectRef,
text: FragmentText,
) -> AsgResult {
self.with_ident(identi, |obj| obj.set_fragment(text))
}
/// Retrieve an object from the graph by [`ObjectRef`].
///
/// Since an [`ObjectRef`] should only be produced by an [`Asg`],
/// and since objects are never deleted from the graph,
/// this should never fail so long as references are not shared
/// between multiple graphs.
/// It is nevertheless wrapped in an [`Option`] just in case.
#[inline]
pub fn get>(&self, index: I) -> Option<&O> {
self.graph.node_weight(index.into().into()).map(|node| {
node.as_ref()
.expect("internal error: Asg::get missing Node data")
})
}
/// Attempt to retrieve an identifier from the graph by name.
///
/// Since only identifiers carry a name,
/// this method cannot be used to retrieve all possible objects on the
/// graph---for
/// that, see [`Asg::get`].
#[inline]
pub fn lookup(&self, name: SymbolId) -> Option {
let i = name.as_usize();
self.index
.get(i)
.filter(|ni| ni.index() > 0)
.map(|ni| ObjectRef::new(*ni))
}
/// Declare that `dep` is a dependency of `ident`.
///
/// An object must be declared as a dependency if its value must be
/// computed before computing the value of `ident`.
/// The [linker][crate::ld] will ensure this ordering.
///
/// See [`add_dep_lookup`][Asg::add_dep_lookup] if identifiers have to
/// be looked up by [`SymbolId`] or if they may not yet have been
/// declared.
pub fn add_dep(&mut self, identi: ObjectRef, depi: ObjectRef) {
self.graph
.update_edge(identi.into(), depi.into(), Default::default());
}
/// Check whether `dep` is a dependency of `ident`.
#[inline]
pub fn has_dep(&self, ident: ObjectRef, dep: ObjectRef) -> bool {
self.graph.contains_edge(ident.into(), dep.into())
}
/// Declare that `dep` is a dependency of `ident`,
/// regardless of whether they are known.
///
/// In contrast to [`add_dep`][Asg::add_dep],
/// this method will add the dependency even if one or both of `ident`
/// or `dep` have not yet been declared.
/// In such a case,
/// a missing identifier will be added as a placeholder,
/// allowing the ASG to be built with partial information as
/// identifiers continue to be discovered.
/// See [`IdentObjectState::declare`] for more information.
///
/// References to both identifiers are returned in argument order.
pub fn add_dep_lookup(
&mut self,
ident: SymbolId,
dep: SymbolId,
) -> (ObjectRef, ObjectRef) {
let identi = self.lookup_or_missing(ident);
let depi = self.lookup_or_missing(dep);
self.graph
.update_edge(identi.into(), depi.into(), Default::default());
(identi, depi)
}
}
/// Reference to an [object][super::object] stored within the [`Asg`].
///
/// IdentObject references are integer offsets,
/// not pointers.
/// See the [module-level documentation][self] for more information.
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct ObjectRef(NodeIndex);
impl ObjectRef {
pub fn new(index: NodeIndex) -> Self {
Self(index)
}
}
impl From for ObjectRef {
fn from(index: NodeIndex) -> Self {
Self(index)
}
}
impl From for NodeIndex {
fn from(objref: ObjectRef) -> Self {
objref.0
}
}
#[cfg(test)]
mod test {
use super::super::error::AsgError;
use super::*;
use crate::asg::{
Dim, IdentObject, TransitionError, TransitionResult, UnresolvedError,
};
use crate::sym::{GlobalSymbolIntern, SymbolId};
use std::cell::RefCell;
#[derive(Debug, Default, PartialEq)]
struct StubIdentObject {
given_declare: Option,
given_extern: Option<(IdentKind, Source)>,
given_resolve: Option<(IdentKind, Source)>,
given_set_fragment: Option,
fail_redeclare: RefCell