488 lines
15 KiB
Rust
488 lines
15 KiB
Rust
// Section/Sections IR representation
|
|
//
|
|
// Copyright (C) 2014-2023 Ryan Specialty, 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 <http://www.gnu.org/licenses/>.
|
|
|
|
//! Sections of a linked [`xmle`](super) object file.
|
|
//!
|
|
//! An [`XmleSections`] object is responsible for placing provided
|
|
//! identifiers into the appropriate section,
|
|
//! but _it must be provided properly ordered data_.
|
|
//! This ordering is the result of [`sort`](super::lower::sort),
|
|
//! which places the relocatable object code fragments in the order
|
|
//! necessary for execution.
|
|
|
|
use crate::{
|
|
asg::{Ident, IdentKind, UnresolvedError},
|
|
diagnose::{Annotate, Diagnostic},
|
|
fmt::{DisplayWrapper, TtQuote},
|
|
parse::util::SPair,
|
|
sym::SymbolId,
|
|
};
|
|
use fxhash::FxHashSet;
|
|
use std::mem::take;
|
|
use std::result::Result;
|
|
|
|
pub type PushResult<T = ()> = Result<T, SectionsError>;
|
|
|
|
/// Sections of a linked `xmle` file.
|
|
///
|
|
/// For more information on these sections,
|
|
/// see the [parent module](super).
|
|
pub trait XmleSections<'a> {
|
|
/// Push an object into the appropriate section.
|
|
///
|
|
/// Objects are expected to be properly sorted relative to their order
|
|
/// of execution so that their text fragments are placed in the
|
|
/// correct order in the final program text.
|
|
fn push(&mut self, ident: &'a Ident) -> PushResult;
|
|
|
|
/// Take the list of objects present in the linked file.
|
|
///
|
|
/// The order of these objects does not matter.
|
|
fn take_deps(&mut self) -> Vec<&'a Ident>;
|
|
|
|
/// Take the ordered text fragments for the `map` section.
|
|
fn take_map(&mut self) -> Vec<SymbolId>;
|
|
|
|
/// Take the set of external identifiers mapped into this system.
|
|
fn take_map_froms(&mut self) -> FxHashSet<SymbolId>;
|
|
|
|
/// Take the ordered text fragments for the `retmap` section.
|
|
fn take_retmap(&mut self) -> Vec<SymbolId>;
|
|
|
|
/// Take the ordered text fragments for the `static` section.
|
|
fn take_static(&mut self) -> Vec<SymbolId>;
|
|
|
|
/// Take the ordered text fragments for the `exec` section.
|
|
fn take_exec(&mut self) -> Vec<SymbolId>;
|
|
}
|
|
|
|
/// Sections of a linked `xmle` file.
|
|
///
|
|
/// For more information on these sections,
|
|
/// see the [parent module](super).
|
|
#[derive(Debug, Default, PartialEq)]
|
|
pub struct Sections<'a> {
|
|
/// List of objects present in the linked file.
|
|
///
|
|
/// The order of these objects does not matter.
|
|
deps: Vec<&'a Ident>,
|
|
|
|
/// External identifiers mapped into this system.
|
|
map_froms: FxHashSet<SymbolId>,
|
|
|
|
/// Ordered text fragments of `map` section.
|
|
map: Vec<SymbolId>,
|
|
|
|
/// Order text fragments of `retmap` section.
|
|
retmap: Vec<SymbolId>,
|
|
|
|
/// Ordered text fragments of `static` section.
|
|
st: Vec<SymbolId>,
|
|
|
|
/// Ordered text fragments of `exec` section.
|
|
exec: Vec<SymbolId>,
|
|
}
|
|
|
|
impl<'a> Sections<'a> {
|
|
/// New collection of empty sections.
|
|
#[inline]
|
|
pub fn new() -> Self {
|
|
Self {
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> XmleSections<'a> for Sections<'a> {
|
|
fn push(&mut self, ident: &'a Ident) -> PushResult {
|
|
self.deps.push(ident);
|
|
|
|
let frag = ident.fragment();
|
|
let (resolved, name) = ident.resolved()?;
|
|
|
|
match resolved.kind() {
|
|
Some(kind) => match kind {
|
|
IdentKind::Cgen(..)
|
|
| IdentKind::Gen(..)
|
|
| IdentKind::Lparam(..) => {
|
|
// These types do not have fragments.
|
|
}
|
|
IdentKind::Meta
|
|
| IdentKind::Worksheet
|
|
| IdentKind::Param(..)
|
|
| IdentKind::Type(..)
|
|
| IdentKind::Func(..)
|
|
| IdentKind::Const(..) => {
|
|
self.st.push(expect_frag(name, frag)?)
|
|
}
|
|
IdentKind::MapHead | IdentKind::Map | IdentKind::MapTail => {
|
|
self.map.push(expect_frag(name, frag)?);
|
|
|
|
if let Some(from) =
|
|
ident.src().expect("missing map src").from
|
|
{
|
|
self.map_froms.insert(from);
|
|
}
|
|
}
|
|
IdentKind::RetMapHead
|
|
| IdentKind::RetMap
|
|
| IdentKind::RetMapTail => {
|
|
self.retmap.push(expect_frag(name, frag)?)
|
|
}
|
|
// TODO: Why do templates have fragments?
|
|
IdentKind::Class(..) | IdentKind::Rate(..) | IdentKind::Tpl => {
|
|
self.exec.push(expect_frag(name, frag)?)
|
|
}
|
|
},
|
|
None => {
|
|
// TODO: This should not be possible; ensure that with types
|
|
// or turn this into a panic. It would certainly be a
|
|
// compiler bug and there is no use in trying to be nice
|
|
// about a situation where something went terribly, horribly
|
|
// wrong.
|
|
return Err(SectionsError::MissingObjectKind(name));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn take_deps(&mut self) -> Vec<&'a Ident> {
|
|
take(&mut self.deps)
|
|
}
|
|
|
|
#[inline]
|
|
fn take_static(&mut self) -> Vec<SymbolId> {
|
|
take(&mut self.st)
|
|
}
|
|
|
|
#[inline]
|
|
fn take_map(&mut self) -> Vec<SymbolId> {
|
|
take(&mut self.map)
|
|
}
|
|
|
|
#[inline]
|
|
fn take_map_froms(&mut self) -> FxHashSet<SymbolId> {
|
|
take(&mut self.map_froms)
|
|
}
|
|
|
|
#[inline]
|
|
fn take_retmap(&mut self) -> Vec<SymbolId> {
|
|
take(&mut self.retmap)
|
|
}
|
|
|
|
#[inline]
|
|
fn take_exec(&mut self) -> Vec<SymbolId> {
|
|
take(&mut self.exec)
|
|
}
|
|
}
|
|
|
|
fn expect_frag(
|
|
ident_name: SPair,
|
|
frag: Option<SymbolId>,
|
|
) -> PushResult<SymbolId> {
|
|
frag.ok_or(SectionsError::MissingFragment(ident_name))
|
|
}
|
|
|
|
/// Error during [`Sections`] building.
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum SectionsError {
|
|
/// An unresolved object was encountered during sorting.
|
|
///
|
|
/// An unresolved object means that the graph has an incomplete picture
|
|
/// of the program,
|
|
/// and so sorting cannot be reliably performed.
|
|
/// Since all objects are supposed to be resolved prior to sorting,
|
|
/// this represents either a problem with the program being compiled
|
|
/// or a bug in the compiler itself.
|
|
UnresolvedObject(UnresolvedError),
|
|
|
|
/// Identifier is missing an expected text fragment.
|
|
MissingFragment(SPair),
|
|
|
|
/// The kind of an object encountered during sorting could not be
|
|
/// determined.
|
|
///
|
|
/// Sorting uses the object kind to place objects into their appropriate
|
|
/// sections.
|
|
/// It should never be the case that a resolved object has no kind,
|
|
/// so this likely represents a compiler bug.
|
|
MissingObjectKind(SPair),
|
|
}
|
|
|
|
impl From<UnresolvedError> for SectionsError {
|
|
fn from(err: UnresolvedError) -> Self {
|
|
Self::UnresolvedObject(err)
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for SectionsError {
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
match self {
|
|
Self::UnresolvedObject(err) => Some(err),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for SectionsError {
|
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
use SectionsError::*;
|
|
|
|
match self {
|
|
UnresolvedObject(err) => err.fmt(fmt),
|
|
MissingFragment(name) => write!(
|
|
fmt,
|
|
"missing text fragment for object identified by {}",
|
|
TtQuote::wrap(name),
|
|
),
|
|
MissingObjectKind(name) => write!(
|
|
fmt,
|
|
"missing object kind for object identified by {}",
|
|
TtQuote::wrap(name),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Diagnostic for SectionsError {
|
|
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
|
|
use SectionsError::*;
|
|
|
|
match self {
|
|
UnresolvedObject(e) => e.describe(),
|
|
MissingFragment(name) => vec![
|
|
name.internal_error(
|
|
"text fragment for this object cannot be found",
|
|
),
|
|
name.help("this means that the compiler failed to produce"),
|
|
name.help(" object code associated with this identifier."),
|
|
],
|
|
MissingObjectKind(name) => vec![
|
|
name.internal_error(
|
|
"the kind of object for this identifier is unknown",
|
|
),
|
|
name.help("this means that the compiler failed to output"),
|
|
name.help(" complete type information for this identifier."),
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use crate::{
|
|
asg::{Ident, IdentKind, Source},
|
|
num::{Dim, Dtype},
|
|
span::dummy::*,
|
|
sym::GlobalSymbolIntern,
|
|
};
|
|
|
|
type Sut<'a> = Sections<'a>;
|
|
|
|
#[test]
|
|
fn sections_empty() {
|
|
let mut sut = Sut::new();
|
|
|
|
assert!(sut.take_deps().is_empty());
|
|
assert!(sut.take_map_froms().is_empty());
|
|
assert!(sut.take_map().is_empty());
|
|
assert!(sut.take_retmap().is_empty());
|
|
assert!(sut.take_static().is_empty());
|
|
assert!(sut.take_exec().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn sections_push_adds_dep() -> PushResult {
|
|
let mut sut = Sut::new();
|
|
|
|
let a = Ident::IdentFragment(
|
|
SPair("a".into(), S1),
|
|
IdentKind::Const(Dim::Scalar, Dtype::Integer),
|
|
Default::default(),
|
|
"fraga".intern(),
|
|
);
|
|
|
|
// Different section than a, to be sure that we still add it.
|
|
let b = Ident::IdentFragment(
|
|
SPair("b".into(), S2),
|
|
IdentKind::MapHead,
|
|
Default::default(),
|
|
"fragb".intern(),
|
|
);
|
|
|
|
sut.push(&a)?;
|
|
sut.push(&b)?;
|
|
|
|
assert_eq!(sut.take_deps(), vec![&a, &b],);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Certain identifiers have no fragments because the code is associated
|
|
// with their parents (for now, anyway).
|
|
#[test]
|
|
fn idents_not_needing_fragments() -> PushResult {
|
|
let mut sut = Sut::new();
|
|
|
|
let cgen = Ident::Opaque(
|
|
SPair("cgen".into(), S1),
|
|
IdentKind::Cgen(Dim::Vector),
|
|
Default::default(),
|
|
);
|
|
|
|
let gen = Ident::Opaque(
|
|
SPair("gen".into(), S2),
|
|
IdentKind::Gen(Dim::Vector, Dtype::Integer),
|
|
Default::default(),
|
|
);
|
|
|
|
let lparam = Ident::Opaque(
|
|
SPair("lparam".into(), S3),
|
|
IdentKind::Lparam(Dim::Vector, Dtype::Integer),
|
|
Default::default(),
|
|
);
|
|
|
|
sut.push(&cgen)?;
|
|
sut.push(&gen)?;
|
|
sut.push(&lparam)?;
|
|
|
|
// They should be added as deps...
|
|
assert_eq!(sut.take_deps(), vec![&cgen, &gen, &lparam]);
|
|
|
|
// ...but not added to any sections.
|
|
assert!(sut.take_map_froms().is_empty());
|
|
assert!(sut.take_map().is_empty());
|
|
assert!(sut.take_retmap().is_empty());
|
|
assert!(sut.take_static().is_empty());
|
|
assert!(sut.take_exec().is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn sections_map_froms_is_uniq() -> PushResult {
|
|
let mut sut_a = Sections::new();
|
|
let mut sut_b = Sections::new();
|
|
|
|
let a = Ident::IdentFragment(
|
|
SPair("a".into(), S1),
|
|
IdentKind::Map,
|
|
Source {
|
|
from: Some("froma".intern()),
|
|
..Default::default()
|
|
},
|
|
"mapa".intern(),
|
|
);
|
|
|
|
let b = Ident::IdentFragment(
|
|
SPair("a".into(), S2),
|
|
IdentKind::Map,
|
|
Source {
|
|
from: Some("fromb".intern()),
|
|
..Default::default()
|
|
},
|
|
"mapb".intern(),
|
|
);
|
|
|
|
// A contains duplicates.
|
|
sut_a.push(&a)?;
|
|
sut_a.push(&a)?;
|
|
sut_a.push(&b)?;
|
|
|
|
// B does not.
|
|
sut_b.push(&a)?;
|
|
sut_b.push(&b)?;
|
|
|
|
let a_froms = sut_a.take_map_froms();
|
|
|
|
// They should compare the same.
|
|
assert_eq!(a_froms, sut_b.take_map_froms());
|
|
|
|
// And should use the proper ids.
|
|
assert!(a_froms.contains(&"froma".intern()));
|
|
assert!(a_froms.contains(&"fromb".intern()));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
macro_rules! add_syms {
|
|
($sut:ident, { $($name:ident: $kind:expr,)* }) => {
|
|
$(
|
|
let $name = Ident::IdentFragment(
|
|
SPair(stringify!($name).into(), S1),
|
|
$kind,
|
|
Default::default(),
|
|
stringify!($kind).intern(), // fragment
|
|
);
|
|
|
|
$sut.push(&$name)?;
|
|
)*
|
|
};
|
|
}
|
|
|
|
macro_rules! fragvec {
|
|
($($name:ident),*) => {
|
|
vec![
|
|
$(&$name),*
|
|
].into_iter().map(|x| x.fragment().unwrap()).collect::<Vec<SymbolId>>()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn push_sorts_fragments_into_sections() -> PushResult {
|
|
let mut sut = Sections::new();
|
|
|
|
add_syms!(sut, {
|
|
cgen: IdentKind::Cgen(Dim::Scalar),
|
|
class: IdentKind::Class(Dim::Matrix),
|
|
const_: IdentKind::Const(Dim::Scalar, Dtype::Boolean),
|
|
func: IdentKind::Func(Dim::Vector, Dtype::Integer),
|
|
gen: IdentKind::Gen(Dim::Vector, Dtype::Boolean),
|
|
lparam: IdentKind::Lparam(Dim::Matrix, Dtype::Float),
|
|
param: IdentKind::Param(Dim::Scalar, Dtype::Integer),
|
|
rate: IdentKind::Rate(Dtype::Integer),
|
|
tpl: IdentKind::Tpl,
|
|
ty: IdentKind::Type(Dtype::Integer),
|
|
maphead: IdentKind::MapHead,
|
|
map: IdentKind::Map,
|
|
maptail: IdentKind::MapTail,
|
|
retmaphead: IdentKind::RetMapHead,
|
|
retmap: IdentKind::RetMap,
|
|
retmaptail: IdentKind::RetMapTail,
|
|
meta: IdentKind::Meta,
|
|
worksheet: IdentKind::Worksheet,
|
|
});
|
|
|
|
assert_eq!(sut.take_map(), fragvec![maphead, map, maptail]);
|
|
assert_eq!(sut.take_retmap(), fragvec![retmaphead, retmap, retmaptail]);
|
|
|
|
assert_eq!(
|
|
sut.take_static(),
|
|
fragvec![const_, func, param, ty, meta, worksheet]
|
|
);
|
|
|
|
assert_eq!(sut.take_exec(), fragvec![class, rate, tpl]);
|
|
|
|
Ok(())
|
|
}
|
|
}
|