Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <mike.gerwitz@ryansg.com>2020-04-06 10:30:33 -0400
committerMike Gerwitz <mike.gerwitz@ryansg.com>2020-04-06 10:30:33 -0400
commit587241bf9b1e390b28935b80f969d953d8e1bafe (patch)
treeee5dea75cfa211bb838b1871255bdea6e2a1d834
parent7c65d729aad05963313128404ef0e6378151e40a (diff)
parent8385b64e1d65d6e1317018d7c940d3f14b88268c (diff)
downloadtame-587241bf9b1e390b28935b80f969d953d8e1bafe.tar.gz
tame-587241bf9b1e390b28935b80f969d953d8e1bafe.tar.bz2
tame-587241bf9b1e390b28935b80f969d953d8e1bafe.zip
TAMER: Finalize object state transitions
In particular, this finalizes overrides and redeclarations. The linker should now be feature-complete.
-rw-r--r--.gitignore5
-rw-r--r--build-aux/Makefile.am1
-rw-r--r--tamer/benches/asg.rs550
-rw-r--r--tamer/src/ir/asg/base.rs105
-rw-r--r--tamer/src/ir/asg/mod.rs6
-rw-r--r--tamer/src/ir/asg/object.rs667
-rw-r--r--tamer/src/ld/poc.rs2
7 files changed, 1156 insertions, 180 deletions
diff --git a/.gitignore b/.gitignore
index 847752f..7a432f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,8 @@ configure
# generated by TAME build
suppliers.mk
run-[0-9].log
+
+# binary data and profiling
+a.out
+perf.data
+
diff --git a/build-aux/Makefile.am b/build-aux/Makefile.am
index 762c949..ba9256e 100644
--- a/build-aux/Makefile.am
+++ b/build-aux/Makefile.am
@@ -132,7 +132,6 @@ standalones: $(dest_standalone)
strip: $(dest_standalone_strip) ui/package.strip.js
%.xmle: %.xmlo $(path_tame)/.rev-xmle
$(TAME_TS)
- @echo "WARNING: using WIP proof-of-concept linker!"
$(path_tame)/tamer/target/release/tameld -o $@ $<
%.js: %.xmle
$(TAME_TS)
diff --git a/tamer/benches/asg.rs b/tamer/benches/asg.rs
new file mode 100644
index 0000000..f3862c1
--- /dev/null
+++ b/tamer/benches/asg.rs
@@ -0,0 +1,550 @@
+// Abstract semantic graph benchmarks
+//
+// Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>.
+//
+// Note that the baseline tests have a _suffix_ rather than a prefix so that
+// they are still grouped with the associated test in the output, since it's
+// sorted lexically by function name.
+
+#![feature(test)]
+
+extern crate tamer;
+extern crate test;
+
+use test::Bencher;
+
+mod base {
+ use super::*;
+ use tamer::global;
+ use tamer::ir::asg::{
+ Asg, DataType, DefaultAsg, IdentKind, IdentObject, SortableAsg, Source,
+ };
+ use tamer::sym::{DefaultInterner, Interner, Symbol};
+
+ type Sut<'i> = DefaultAsg<'i, IdentObject<'i>, global::PkgIdentSize>;
+ type SutProg<'i> = DefaultAsg<'i, IdentObject<'i>, global::ProgIdentSize>;
+
+ fn interned_n<'i>(
+ interner: &'i DefaultInterner<'i>,
+ n: u16,
+ ) -> Vec<&'i Symbol<'i>> {
+ (0..n).map(|i| interner.intern(&i.to_string())).collect()
+ }
+
+ #[bench]
+ fn declare_1_000(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ bench.iter(|| {
+ xs.iter()
+ .map(|i| sut.declare(i, IdentKind::Meta, Source::default()))
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn declare_1_000_full_inital_capacity(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(1024, 1024);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ bench.iter(|| {
+ xs.iter()
+ .map(|i| sut.declare(i, IdentKind::Meta, Source::default()))
+ .for_each(drop);
+ });
+ }
+
+ // The Ix size affects memory, but how about performance?
+ #[bench]
+ fn declare_1_000_prog_ident_size(bench: &mut Bencher) {
+ let mut sut = SutProg::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ bench.iter(|| {
+ xs.iter()
+ .map(|i| sut.declare(i, IdentKind::Meta, Source::default()))
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn declare_extern_1_000(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ bench.iter(|| {
+ xs.iter()
+ .map(|i| {
+ sut.declare_extern(i, IdentKind::Meta, Source::default())
+ })
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn resolve_extern_1_000(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ xs.iter().for_each(|sym| {
+ let _ = sut.declare_extern(sym, IdentKind::Meta, Source::default());
+ });
+
+ // Bench only the resolution, not initial declare.
+ bench.iter(|| {
+ xs.iter()
+ .map(|sym| sut.declare(sym, IdentKind::Meta, Source::default()))
+ .for_each(drop);
+ });
+ }
+
+ // N.B.: This benchmark isn't easily comparable to the others because
+ // `set_fragment` takes ownership over a string, and so we have to clone
+ // strings for each call.
+ #[bench]
+ fn set_fragment_1_000_with_new_str(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ let orefs = xs
+ .iter()
+ .map(|sym| {
+ sut.declare(sym, IdentKind::Meta, Source::default())
+ .unwrap()
+ })
+ .collect::<Vec<_>>();
+
+ // Bench only the resolution, not initial declare.
+ bench.iter(|| {
+ orefs
+ .iter()
+ .map(|oref| sut.set_fragment(*oref, "".into())) // see N.B.
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn lookup_1_000(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ xs.iter().for_each(|sym| {
+ let _ = sut.declare(&sym, IdentKind::Meta, Source::default());
+ });
+
+ bench.iter(|| {
+ xs.iter().map(|sym| sut.lookup(sym).unwrap()).for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn get_1_000(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ let orefs = xs
+ .iter()
+ .map(|sym| {
+ sut.declare(sym, IdentKind::Meta, Source::default())
+ .unwrap()
+ })
+ .collect::<Vec<_>>();
+
+ bench.iter(|| {
+ orefs
+ .iter()
+ .map(|oref| sut.get(*oref).unwrap())
+ .for_each(drop);
+ });
+ }
+
+ // All dependencies on a single node. Petgraph does poorly with
+ // supernodes at the time of writing, relatively speaking.
+ #[bench]
+ fn add_dep_1_000_to_single_node(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ let orefs = xs
+ .iter()
+ .map(|sym| {
+ sut.declare(sym, IdentKind::Meta, Source::default())
+ .unwrap()
+ })
+ .collect::<Vec<_>>();
+
+ let root = orefs[0];
+
+ // Note that this adds all edges to one node
+ bench.iter(|| {
+ orefs
+ .iter()
+ .map(|oref| sut.add_dep(root, *oref))
+ .for_each(drop);
+ });
+ }
+
+ // Same as above but only one edge per node.
+ #[bench]
+ fn add_dep_1_000_one_edge_per_node(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ let orefs = xs
+ .iter()
+ .map(|sym| {
+ sut.declare(sym, IdentKind::Meta, Source::default())
+ .unwrap()
+ })
+ .collect::<Vec<_>>();
+
+ bench.iter(|| {
+ orefs
+ .iter()
+ .zip(orefs.iter().cycle().skip(1))
+ .map(|(from, to)| sut.add_dep(*from, *to))
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn has_dep_1_000_single_node(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ let orefs = xs
+ .iter()
+ .map(|sym| {
+ sut.declare(sym, IdentKind::Meta, Source::default())
+ .unwrap()
+ })
+ .collect::<Vec<_>>();
+
+ let root = orefs[0];
+
+ orefs.iter().for_each(|oref| {
+ sut.add_dep(root, *oref);
+ });
+
+ bench.iter(|| {
+ orefs
+ .iter()
+ .map(|oref| sut.has_dep(root, *oref))
+ .for_each(drop);
+ });
+ }
+
+ // Same as above but only one edge per node.
+ #[bench]
+ fn has_dep_1_000_one_edge_per_node(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ let orefs = xs
+ .iter()
+ .map(|sym| {
+ sut.declare(sym, IdentKind::Meta, Source::default())
+ .unwrap()
+ })
+ .collect::<Vec<_>>();
+
+ orefs.iter().zip(orefs.iter().cycle().skip(1)).for_each(
+ |(from, to)| {
+ sut.add_dep(*from, *to);
+ },
+ );
+
+ bench.iter(|| {
+ orefs
+ .iter()
+ .zip(orefs.iter().cycle().skip(1))
+ .map(|(from, to)| sut.has_dep(*from, *to))
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn add_dep_lookup_1_000_missing_one_edge_per_node(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ bench.iter(|| {
+ xs.iter()
+ .zip(xs.iter().cycle().skip(1))
+ .map(|(from, to)| sut.add_dep_lookup(from, to))
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn add_dep_lookup_1_000_existing_one_edge_per_node(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ xs.iter().for_each(|sym| {
+ let _ = sut.declare(sym, IdentKind::Meta, Source::default());
+ });
+
+ bench.iter(|| {
+ xs.iter()
+ .zip(xs.iter().cycle().skip(1))
+ .map(|(from, to)| sut.add_dep_lookup(from, to))
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn sort_1_with_1_000_existing_supernode(bench: &mut Bencher) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ let orefs = xs
+ .iter()
+ .map(|sym| {
+ sut.declare(
+ sym,
+ IdentKind::Rate(DataType::Integer),
+ Source::default(),
+ )
+ .unwrap()
+ })
+ .collect::<Vec<_>>();
+
+ let root = orefs[0];
+
+ // All edges from a single node.
+ orefs.iter().skip(1).for_each(|to| {
+ sut.add_dep(root, *to);
+ });
+
+ bench.iter(|| {
+ drop(sut.sort(&[root]));
+ });
+ }
+
+ #[bench]
+ fn sort_1_with_1_000_existing_one_edge_per_node_one_path(
+ bench: &mut Bencher,
+ ) {
+ let mut sut = Sut::with_capacity(0, 0);
+ let interner = DefaultInterner::new();
+ let xs = interned_n(&interner, 1_000);
+
+ let orefs = xs
+ .iter()
+ .map(|sym| {
+ sut.declare(
+ sym,
+ IdentKind::Rate(DataType::Integer),
+ Source::default(),
+ )
+ .unwrap()
+ })
+ .collect::<Vec<_>>();
+
+ // Note that there's no `cycle` call on the iterator, like the
+ // above tests, to make sure we don't create a cycle on the
+ // graph.
+ orefs
+ .iter()
+ .zip(orefs.iter().skip(1))
+ .for_each(|(from, to)| {
+ sut.add_dep(*from, *to);
+ });
+
+ let root = orefs[0];
+
+ bench.iter(|| {
+ drop(sut.sort(&[root]));
+ });
+ }
+}
+
+mod object {
+ use super::*;
+
+ mod ident {
+ use super::*;
+ use tamer::ir::asg::{
+ IdentKind, IdentObject, IdentObjectData, IdentObjectState, Source,
+ };
+ use tamer::sym::{DefaultInterner, Interner};
+
+ type Sut<'i> = IdentObject<'i>;
+
+ #[bench]
+ fn declare_1_000(bench: &mut Bencher) {
+ let interner = DefaultInterner::new();
+ let sym = interner.intern("sym");
+
+ bench.iter(|| {
+ (0..1000).map(|_| Sut::declare(&sym)).for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn resolve_1_000_missing(bench: &mut Bencher) {
+ let interner = DefaultInterner::new();
+ let sym = interner.intern("sym");
+
+ bench.iter(|| {
+ (0..1000)
+ .map(|_| {
+ Sut::declare(&sym)
+ .resolve(IdentKind::Meta, Source::default())
+ })
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn extern_1_000_missing(bench: &mut Bencher) {
+ let interner = DefaultInterner::new();
+ let sym = interner.intern("sym");
+
+ bench.iter(|| {
+ (0..1000)
+ .map(|_| {
+ Sut::declare(&sym)
+ .extern_(IdentKind::Meta, Source::default())
+ })
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn resolve_1_000_extern(bench: &mut Bencher) {
+ let interner = DefaultInterner::new();
+ let sym = interner.intern("sym");
+
+ bench.iter(|| {
+ (0..1000)
+ .map(|_| {
+ Sut::declare(&sym)
+ .extern_(IdentKind::Meta, Source::default())
+ .unwrap()
+ .resolve(IdentKind::Meta, Source::default())
+ })
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn resolve_1_000_override(bench: &mut Bencher) {
+ let interner = DefaultInterner::new();
+ let sym = interner.intern("sym");
+
+ bench.iter(|| {
+ (0..1000)
+ .map(|_| {
+ Sut::declare(&sym)
+ .resolve(
+ IdentKind::Meta,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap()
+ .resolve(
+ IdentKind::Meta,
+ Source {
+ override_: true,
+ ..Default::default()
+ },
+ )
+ })
+ .for_each(drop);
+ });
+ }
+
+ // Override encountered before virtual
+ #[bench]
+ fn resolve_1_000_override_virt_after_override(bench: &mut Bencher) {
+ let interner = DefaultInterner::new();
+ let sym = interner.intern("sym");
+
+ bench.iter(|| {
+ (0..1000)
+ .map(|_| {
+ Sut::declare(&sym)
+ .resolve(
+ IdentKind::Meta,
+ Source {
+ override_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap()
+ .resolve(
+ IdentKind::Meta,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ })
+ .for_each(drop);
+ });
+ }
+
+ #[bench]
+ fn set_fragment_1_000_resolved_with_new_str(bench: &mut Bencher) {
+ let interner = DefaultInterner::new();
+ let sym = interner.intern("sym");
+
+ bench.iter(|| {
+ (0..1000)
+ .map(|_| {
+ Sut::declare(&sym)
+ .resolve(IdentKind::Meta, Source::default())
+ .unwrap()
+ .set_fragment("".into())
+ })
+ .for_each(drop);
+ });
+ }
+
+ // No need to do all of the others, since they're all the same thing.
+ #[bench]
+ fn declared_name_1_000(bench: &mut Bencher) {
+ let interner = DefaultInterner::new();
+ let sym = interner.intern("sym");
+
+ bench.iter(|| {
+ (0..1000).map(|_| Sut::declare(&sym).name()).for_each(drop);
+ });
+ }
+ }
+}
diff --git a/tamer/src/ir/asg/base.rs b/tamer/src/ir/asg/base.rs
index 5e3a2a7..637c892 100644
--- a/tamer/src/ir/asg/base.rs
+++ b/tamer/src/ir/asg/base.rs
@@ -140,7 +140,7 @@ where
})
}
- /// Perform a state transition on an identifier.
+ /// 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`).
@@ -149,7 +149,7 @@ where
///
/// This will safely restore graph state to the original identifier
/// value on transition failure.
- fn with_ident<F>(
+ fn with_ident_lookup<F>(
&mut self,
name: &'i Symbol<'i>,
f: F,
@@ -158,11 +158,29 @@ where
F: FnOnce(O) -> TransitionResult<O>,
{
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<F>(
+ &mut self,
+ identi: ObjectRef<Ix>,
+ f: F,
+ ) -> AsgResult<ObjectRef<Ix>, Ix>
+ where
+ F: FnOnce(O) -> TransitionResult<O>,
+ {
let node = self.graph.node_weight_mut(identi.0).unwrap();
let obj = node
.take()
- .expect(&format!("internal error: missing object for {}", name));
+ .expect(&format!("internal error: missing object"));
f(obj)
.and_then(|obj| {
@@ -237,7 +255,7 @@ where
kind: IdentKind,
src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix> {
- self.with_ident(name, |obj| obj.resolve(kind, src))
+ self.with_ident_lookup(name, |obj| obj.resolve(kind, src))
}
fn declare_extern(
@@ -246,7 +264,7 @@ where
kind: IdentKind,
src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix> {
- self.with_ident(name, |obj| obj.extern_(kind, src))
+ self.with_ident_lookup(name, |obj| obj.extern_(kind, src))
}
fn set_fragment(
@@ -254,30 +272,7 @@ where
identi: ObjectRef<Ix>,
text: FragmentText,
) -> AsgResult<ObjectRef<Ix>, Ix> {
- // This should _never_ happen as long as you're only using ObjectRef
- // values produced by these methods.
- let node = self
- .graph
- .node_weight_mut(identi.0)
- .expect("internal error: BaseAsg::set_fragment bogus identi");
-
- // This should also never happen, since we immediately repopulate
- // the node below.
- let ty = node
- .take()
- .expect("internal error: BaseAsg::set_fragment missing Node data");
-
- // Be sure to restore the previous node if the transition fails,
- // otherwise we'll be left in an inconsistent internal state.
- ty.set_fragment(text)
- .and_then(|obj| {
- node.replace(obj);
- Ok(identi)
- })
- .or_else(|(orig, err)| {
- node.replace(orig);
- Err(err.into())
- })
+ self.with_ident(identi, |obj| obj.set_fragment(text))
}
#[inline]
@@ -424,6 +419,7 @@ mod test {
given_set_fragment: Option<FragmentText>,
fail_redeclare: RefCell<Option<TransitionError>>,
fail_extern: RefCell<Option<TransitionError>>,
+ fail_set_fragment: RefCell<Option<TransitionError>>,
}
impl<'i> IdentObjectData<'i> for StubIdentObject<'i> {
@@ -488,6 +484,11 @@ mod test {
mut self,
text: FragmentText,
) -> TransitionResult<StubIdentObject<'i>> {
+ if self.fail_set_fragment.borrow().is_some() {
+ let err = self.fail_set_fragment.replace(None).unwrap();
+ return Err((self, err));
+ }
+
self.given_set_fragment.replace(text);
Ok(self)
}
@@ -626,7 +627,11 @@ mod test {
// Set up an object to fail redeclaration.
let node = sut.declare(&sym, IdentKind::Meta, src.clone())?;
let obj = sut.get(node).unwrap();
- let terr = TransitionError::Incompatible(String::from("test fail"));
+ let terr = TransitionError::ExternResolution {
+ name: String::from("test fail"),
+ expected: IdentKind::Meta,
+ given: IdentKind::Meta,
+ };
obj.fail_redeclare.replace(Some(terr.clone()));
// Should invoke StubIdentObject::redeclare on the above `obj`.
@@ -687,7 +692,11 @@ mod test {
// It doesn't matter that this isn't the error that'll actually be
// returned, as long as it's some sort of TransitionError.
- let terr = TransitionError::Incompatible(String::from("test fail"));
+ let terr = TransitionError::ExternResolution {
+ name: String::from("test fail"),
+ expected: IdentKind::Meta,
+ given: IdentKind::Meta,
+ };
obj.fail_extern.replace(Some(terr.clone()));
// Should invoke StubIdentObject::extern_ on the above `obj`.
@@ -737,7 +746,39 @@ mod test {
Ok(())
}
- // TODO: fragment fail
+ #[test]
+ fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<(), u8> {
+ let mut sut = Sut::with_capacity(0, 0);
+
+ let sym = symbol_dummy!(1, "failfrag");
+ let src = Source {
+ generated: true,
+ ..Default::default()
+ };
+
+ // The failure will come from terr below, not this.
+ let node = sut.declare(&sym, IdentKind::Meta, src.clone())?;
+ let obj = sut.get(node).unwrap();
+
+ // It doesn't matter that this isn't the error that'll actually be
+ // returned, as long as it's some sort of TransitionError.
+ let terr = TransitionError::BadFragmentDest {
+ name: String::from("test fail"),
+ };
+ obj.fail_set_fragment.replace(Some(terr.clone()));
+
+ let result = sut
+ .set_fragment(node, "".into())
+ .expect_err("error expected");
+
+ // The node should have been restored.
+ let obj = sut.get(node).unwrap();
+
+ assert_eq!(&sym, *obj.given_declare.as_ref().unwrap());
+ assert_eq!(AsgError::ObjectTransition(terr), result);
+
+ Ok(())
+ }
#[test]
fn add_ident_dep_to_ident() -> AsgResult<(), u8> {
diff --git a/tamer/src/ir/asg/mod.rs b/tamer/src/ir/asg/mod.rs
index 5176eae..d36d4c3 100644
--- a/tamer/src/ir/asg/mod.rs
+++ b/tamer/src/ir/asg/mod.rs
@@ -197,10 +197,10 @@ mod object;
mod section;
pub use graph::{Asg, AsgError, AsgResult, ObjectRef, SortableAsg};
-pub use ident::{Dim, IdentKind};
+pub use ident::{DataType, Dim, IdentKind};
pub use object::{
- FragmentText, IdentObject, IdentObjectData, Source, TransitionError,
- TransitionResult,
+ FragmentText, IdentObject, IdentObjectData, IdentObjectState, Source,
+ TransitionError, TransitionResult,
};
pub use section::{Section, SectionIterator, Sections};
diff --git a/tamer/src/ir/asg/object.rs b/tamer/src/ir/asg/object.rs
index 0bf75e9..628521e 100644
--- a/tamer/src/ir/asg/object.rs
+++ b/tamer/src/ir/asg/object.rs
@@ -251,22 +251,82 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
/// (it returns to a [`IdentObject::Ident`])
/// to make way for the fragment of the override.
///
+ /// Overrides will always have their virtual flag cleared,
+ /// even if set.
+ /// The compiler will hopefully have done this for us,
+ /// since the user may be confused with subsequent
+ /// [`TransitionError::NonVirtualOverride`] errors if they try to
+ /// override an override.
+ ///
/// The kind of identifier cannot change,
/// but the argument is provided here for convenience so that the
/// caller does not need to perform such a check itself.
+ ///
+ /// If no extern or virtual override is possible,
+ /// an identifier cannot be redeclared and this operation will fail.
fn resolve(
- mut self,
+ self,
kind: IdentKind,
- src: Source<'i>,
+ mut src: Source<'i>,
) -> TransitionResult<IdentObject<'i>> {
match self {
- IdentObject::Ident(_, _, ref mut orig_src)
- if orig_src.virtual_ && src.override_ =>
+ IdentObject::Ident(name, ref orig_kind, ref orig_src)
+ | IdentObject::IdentFragment(
+ name,
+ ref orig_kind,
+ ref orig_src,
+ _,
+ ) if src.override_ => {
+ if !orig_src.virtual_ {
+ let err = TransitionError::NonVirtualOverride {
+ name: name.to_string(),
+ };
+
+ return Err((self, err));
+ }
+
+ if orig_kind != &kind {
+ let err = TransitionError::VirtualOverrideKind {
+ name: name.to_string(),
+ existing: orig_kind.clone(),
+ given: kind.clone(),
+ };
+
+ return Err((self, err));
+ }
+
+ // Ensure that virtual flags are cleared to prohibit
+ // override-overrides. The compiler should do this; this is
+ // just an extra layer of defense.
+ src.virtual_ = false;
+
+ // Note that this has the effect of clearing fragments if we
+ // originally were in state `IdentObject::IdentFragment`.
+ Ok(IdentObject::Ident(name, kind, src))
+ }
+
+ // If we encountered the override _first_, flip the context by
+ // declaring a new identifier and trying to override that.
+ IdentObject::Ident(name, orig_kind, orig_src)
+ if orig_src.override_ =>
{
- *orig_src = src;
- Ok(self)
+ Self::declare(name)
+ .resolve(kind, src)?
+ .resolve(orig_kind, orig_src)
}
+ // Same as above, but for fragments, we want to keep the
+ // _original override_ fragment.
+ IdentObject::IdentFragment(
+ name,
+ orig_kind,
+ orig_src,
+ orig_text,
+ ) if orig_src.override_ => Self::declare(name)
+ .resolve(kind, src)?
+ .resolve(orig_kind, orig_src)?
+ .set_fragment(orig_text),
+
IdentObject::Extern(name, ref orig_kind, _) => {
if orig_kind != &kind {
let err = TransitionError::ExternResolution {
@@ -281,20 +341,26 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(IdentObject::Ident(name, kind, src))
}
- // TODO: no override-override
- IdentObject::IdentFragment(name, _, orig_src, _)
- if orig_src.virtual_ && src.override_ =>
- {
- // clears fragment, which is no longer applicable
- Ok(IdentObject::Ident(name, kind, src))
+ // These represent the prolog and epilogue of maps. This
+ // situation will be resolved in the future.
+ IdentObject::IdentFragment(_, IdentKind::MapHead, _, _)
+ | IdentObject::IdentFragment(_, IdentKind::MapTail, _, _)
+ | IdentObject::IdentFragment(_, IdentKind::RetMapHead, _, _)
+ | IdentObject::IdentFragment(_, IdentKind::RetMapTail, _, _) => {
+ Ok(self)
}
- IdentObject::Missing(name) | IdentObject::Ident(name, _, _) => {
+ IdentObject::Missing(name) => {
Ok(IdentObject::Ident(name, kind, src))
}
- // TODO
- _ => Ok(self),
+ _ => {
+ let err = TransitionError::Redeclare {
+ name: self.name().unwrap().to_string(),
+ };
+
+ Err((self, err))
+ }
}
}
@@ -331,6 +397,21 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(IdentObject::IdentFragment(sym, kind, src, text))
}
+ // If we get to this point in a properly functioning program (at
+ // least as of the time of writing), then we have encountered a
+ // fragment for a virtual identifier _after_ we have already
+ // encountered the fragment for its _override_. We therefore
+ // want to keep the override.
+ //
+ // If this is not permissable, then we should have already
+ // prevented the `resolve` transition before this fragment was
+ // encountered.
+ IdentObject::IdentFragment(_, _, ref src, _) if src.override_ => {
+ Ok(self)
+ }
+
+ // These represent the prolog and epilogue of maps. This
+ // situation will be resolved in the future.
IdentObject::IdentFragment(_, IdentKind::MapHead, _, _)
| IdentObject::IdentFragment(_, IdentKind::MapTail, _, _)
| IdentObject::IdentFragment(_, IdentKind::RetMapHead, _, _)
@@ -338,47 +419,13 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(self)
}
- // TODO remove these ignores when fixed
- IdentObject::IdentFragment(
- sym,
- IdentKind::Map,
- Source {
- virtual_: true,
- override_: true,
- ..
- },
- _,
- ) => {
- eprintln!(
- "ignoring virtual and overridden map object: {}",
- sym
- );
- Ok(self)
- }
- IdentObject::IdentFragment(
- sym,
- IdentKind::RetMap,
- Source {
- virtual_: true,
- override_: true,
- ..
- },
- _,
- ) => {
- eprintln!(
- "ignoring virtual and overridden retmap object: {}",
- sym
- );
- Ok(self)
- }
-
_ => {
let msg = format!(
"identifier is not a IdentObject::Ident): {:?}",
self,
);
- Err((self, TransitionError::BadFragmentDest(msg)))
+ Err((self, TransitionError::BadFragmentDest { name: msg }))
}
}
}
@@ -386,16 +433,11 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
/// An error attempting to transition from one [`IdentObject`] state to
/// another.
-///
-/// TODO: Provide enough information to construct a useful message.
#[derive(Clone, Debug, PartialEq)]
pub enum TransitionError {
- /// An attempt to redeclare an identifier with additional information
- /// has failed because the provided information was not compatible
- /// with the original declaration.
- ///
- /// See [`IdentObjectState::resolve`].
- Incompatible(String),
+ /// Attempted to redeclare a concrete, non-virtual identifier without an
+ /// override.
+ Redeclare { name: String },
/// Extern resolution failure.
///
@@ -407,19 +449,32 @@ pub enum TransitionError {
given: IdentKind,
},
+ /// Attempt to override a non-virtual identifier.
+ NonVirtualOverride { name: String },
+
+ /// Overriding a virtual identifier failed due to an incompatible
+ /// [`IdentKind`].
+ VirtualOverrideKind {
+ name: String,
+ existing: IdentKind,
+ given: IdentKind,
+ },
+
/// The provided identifier is not in a state that is permitted to
/// receive a fragment.
///
/// See [`IdentObjectState::set_fragment`].
- BadFragmentDest(String),
+ BadFragmentDest { name: String },
}
impl std::fmt::Display for TransitionError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
- Self::Incompatible(msg) => {
- write!(fmt, "object incompatible: {}", msg)
- }
+ Self::Redeclare { name } => write!(
+ fmt,
+ "cannot redeclare identifier `{}`",
+ name,
+ ),
Self::ExternResolution {
name,
@@ -431,7 +486,23 @@ impl std::fmt::Display for TransitionError {
name, expected, given,
),
- Self::BadFragmentDest(msg) => {
+ Self::NonVirtualOverride { name } => write!(
+ fmt,
+ "non-virtual identifier `{}` cannot be overridden",
+ name,
+ ),
+
+ Self::VirtualOverrideKind {
+ name,
+ existing,
+ given,
+ } => write!(
+ fmt,
+ "virtual identifier `{}` of type `{}` cannot be overridden with type `{}`",
+ name, existing, given,
+ ),
+
+ Self::BadFragmentDest{name: msg} => {
write!(fmt, "bad fragment destination: {}", msg)
}
}
@@ -718,6 +789,37 @@ mod test {
);
}
+ // Note that we don't care about similar sources. It's expected
+ // that the system populating the ASG will only resolve local
+ // symbols, and so redeclarations should represent that multiple
+ // packages have the same local symbol.
+ #[test]
+ fn ident_object_redeclare_same_src() {
+ let sym = symbol_dummy!(1, "redecl");
+ let kind = IdentKind::Meta;
+ let src = Source::default();
+
+ let first = IdentObject::declare(&sym)
+ .resolve(kind.clone(), src.clone())
+ .unwrap();
+
+ // Resolve twice, as if we encountered two local symbols.
+ let result = first
+ .clone()
+ .resolve(kind.clone(), src.clone())
+ .expect_err("expected error redeclaring identifier");
+
+ match result {
+ (orig, TransitionError::Redeclare { name }) => {
+ assert_eq!(first, orig);
+ assert_eq!(*sym, name);
+ }
+ _ => {
+ panic!("expected TransitionError::Redeclare: {:?}", result)
+ }
+ }
+ }
+
mod extern_ {
use super::*;
@@ -888,25 +990,6 @@ mod test {
}
}
- // TODO: incompatible
- #[test]
- fn redeclare_returns_existing_compatible() {
- let sym = symbol_dummy!(1, "symdup");
-
- let first = IdentObject::declare(&sym)
- .resolve(IdentKind::Meta, Source::default())
- .unwrap();
-
- // Same declaration a second time
- assert_eq!(
- Ok(first.clone()),
- first.clone().resolve(
- first.kind().unwrap().clone(),
- first.src().unwrap().clone(),
- )
- );
- }
-
#[test]
fn add_fragment_to_ident() {
let sym = symbol_dummy!(1, "tofrag");
@@ -946,7 +1029,7 @@ mod test {
.expect_err("Expected failure");
match err {
- (orig, TransitionError::BadFragmentDest(str))
+ (orig, TransitionError::BadFragmentDest { name: str })
if str.contains("badsym") =>
{
assert_eq!(ident_with_frag, orig);
@@ -958,76 +1041,376 @@ mod test {
}
}
- // TODO: incompatible
- #[test]
- fn declare_override_virtual_ident() {
- let sym = symbol_dummy!(1, "virtual");
- let over_src = symbol_dummy!(2, "src");
- let kind = IdentKind::Meta;
+ mod override_ {
+ use super::*;
- let virt = IdentObject::declare(&sym)
- .resolve(
- kind.clone(),
- Source {
- virtual_: true,
- ..Default::default()
- },
- )
- .unwrap();
+ #[test]
+ fn declare_virtual_ident_first() {
+ let sym = symbol_dummy!(1, "virtual");
+ let over_src = symbol_dummy!(2, "src");
+ let kind = IdentKind::Meta;
+
+ let virt = IdentObject::declare(&sym)
+ .resolve(
+ kind.clone(),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let over_src = Source {
- override_: true,
- src: Some(&over_src),
- ..Default::default()
- };
+ let over_src = Source {
+ virtual_: true, // this needn't be set, but see below
+ override_: true,
+ src: Some(&over_src),
+ ..Default::default()
+ };
- let result = virt.resolve(kind.clone(), over_src.clone());
+ let result = virt.resolve(kind.clone(), over_src.clone());
- assert_eq!(Ok(IdentObject::Ident(&sym, kind, over_src)), result);
- }
+ // Overriding should clear any virtual flag that may have
+ // been set to prevent override-overrides.
+ let expected_src = Source {
+ virtual_: false,
+ ..over_src
+ };
- // TODO: incompatible
- #[test]
- fn declare_override_virtual_ident_fragment() {
- let sym = symbol_dummy!(1, "virtual");
- let over_src = symbol_dummy!(2, "src");
- let kind = IdentKind::Meta;
+ assert_eq!(
+ Ok(IdentObject::Ident(&sym, kind, expected_src)),
+ result
+ );
+ }
- let virt_src = Source {
- virtual_: true,
- ..Default::default()
- };
+ // Override is encountered before the virtual
+ #[test]
+ fn declare_virtual_ident_after_override() {
+ let sym = symbol_dummy!(1, "virtual_second");
+ let virt_src = symbol_dummy!(2, "virt_src");
+ let kind = IdentKind::Meta;
- let virt = IdentObject::declare(&sym)
- .resolve(kind.clone(), virt_src.clone())
- .unwrap();
- let text = FragmentText::from("remove me");
- let virt_frag = virt.set_fragment(text.clone());
+ let over_src = Source {
+ virtual_: true, // this needn't be set, but see below
+ override_: true,
+ ..Default::default()
+ };
- assert_eq!(
- Ok(IdentObject::IdentFragment(
- &sym,
- kind.clone(),
- virt_src,
- text
- )),
- virt_frag,
- );
+ let over = IdentObject::declare(&sym)
+ .resolve(kind.clone(), over_src.clone())
+ .unwrap();
- let over_src = Source {
- override_: true,
- src: Some(&over_src),
- ..Default::default()
- };
+ let virt_src = Source {
+ virtual_: true,
+ src: Some(&virt_src),
+ ..Default::default()
+ };
+
+ let result = over.resolve(kind.clone(), virt_src.clone());
+
+ // Overriding should clear any virtual flag that may have
+ // been set to prevent override-overrides. We should also
+ // take the override source even though virtual was second.
+ let expected_src = Source {
+ virtual_: false,
+ ..over_src
+ };
+
+ assert_eq!(
+ Ok(IdentObject::Ident(&sym, kind, expected_src)),
+ result
+ );
+ }
+
+ #[test]
+ fn declare_override_non_virtual() {
+ let sym = symbol_dummy!(1, "non_virtual");
+ let kind = IdentKind::Meta;
+
+ let non_virt = IdentObject::declare(&sym)
+ .resolve(
+ kind.clone(),
+ Source {
+ virtual_: false,
+ ..Default::default()
+ },
+ )
+ .unwrap();
+
+ let over_src = Source {
+ override_: true,
+ ..Default::default()
+ };
+
+ // This isn't the purpose of the test, but we want to make
+ // sure that the non-virtual override error occurs before
+ // the kind error.
+ let bad_kind = IdentKind::Cgen(Dim::from_u8(1));
+
+ let result = non_virt
+ .clone()
+ .resolve(bad_kind, over_src.clone())
+ .expect_err("expected error");
+
+ match result {
+ (
+ ref orig,
+ TransitionError::NonVirtualOverride { ref name },
+ ) => {
+ assert_eq!(orig, &non_virt);
+ assert_eq!(*sym, *name);
+
+ // Formatted error
+ let (_, err) = result;
+ let msg = format!("{}", err);
+
+ assert!(msg.contains(&format!("{}", sym)));
+ }
+ (_, TransitionError::VirtualOverrideKind { .. }) => {
+ panic!("kind check must happen _after_ virtual check")
+ }
+ _ => panic!(
+ "expected TransitionError::VirtualOverrideKind {:?}",
+ result
+ ),
+ }
+ }
+
+ #[test]
+ fn declare_virtual_ident_incompatible_kind() {
+ let sym = symbol_dummy!(1, "virtual");
+ let src_sym = symbol_dummy!(2, "src");
+ let kind = IdentKind::Meta;
+
+ let virt = IdentObject::declare(&sym)
+ .resolve(
+ kind.clone(),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
+
+ let over_src = Source {
+ override_: true,
+ src: Some(&src_sym),
+ ..Default::default()
+ };
+
+ let bad_kind = IdentKind::Cgen(Dim::from_u8(1));
+ let result = virt
+ .clone()
+ .resolve(bad_kind.clone(), over_src.clone())
+ .expect_err("expected error");
+
+ match result {
+ (
+ ref orig,
+ TransitionError::VirtualOverrideKind {
+ ref name,
+ ref existing,
+ ref given,
+ },
+ ) => {
+ assert_eq!(orig, &virt);
+
+ assert_eq!(*sym, *name);
+ assert_eq!(&kind, existing);
+ assert_eq!(&bad_kind, given);
+
+ // Formatted error
+ let (_, err) = result;
+ let msg = format!("{}", err);
+
+ assert!(msg.contains(&format!("{}", sym)));
+ assert!(msg.contains(&format!("{}", kind)));
+ assert!(msg.contains(&format!("{}", bad_kind)));
+ }
+ _ => panic!(
+ "expected TransitionError::VirtualOverrideKind {:?}",
+ result
+ ),
+ }
+ }
+
+ // Encounter virtual first and override second should cause the
+ // fragment to be cleared to make way for the new fragment.
+ #[test]
+ fn declare_override_virtual_ident_fragment_virtual_first() {
+ let sym = symbol_dummy!(1, "virtual");
+ let over_src = symbol_dummy!(2, "src");
+ let kind = IdentKind::Meta;
+
+ // Remember: override is going to come first...
+ let over_src = Source {
+ override_: true,
+ src: Some(&over_src),
+ ..Default::default()
+ };
+
+ // ...and virt second.
+ let virt_src = Source {
+ virtual_: true,
+ ..Default::default()
+ };
+
+ let over = IdentObject::declare(&sym)
+ .resolve(kind.clone(), over_src.clone())
+ .unwrap();
+
+ // So we should _keep_ this fragment, since it represent the
+ // override, even though it's appearing first.
+ let text = FragmentText::from("keep me");
+ let over_frag = over.set_fragment(text.clone());
+
+ assert_eq!(
+ Ok(IdentObject::IdentFragment(
+ &sym,
+ kind.clone(),
+ over_src.clone(),
+ text.clone(),
+ )),
+ over_frag,
+ );
+
+ let result =
+ over_frag.unwrap().resolve(kind.clone(), virt_src.clone());
+
+ // Overriding should _not_ have cleared the fragment since
+ // the override was encountered _first_, so we want to keep
+ // its fragment.
+ assert_eq!(
+ Ok(IdentObject::IdentFragment(
+ &sym,
+ kind.clone(),
+ over_src.clone(),
+ text.clone()
+ )),
+ result
+ );
+
+ // Finally, after performing this transition, we will
+ // inevitably encounter the fragment for the virtual
+ // identifier, which we must ignore. So we must make sure
+ // that encountering it will not cause an error, because we
+ // still have an IdentFragment at this point.
+ assert_eq!(
+ Ok(IdentObject::IdentFragment(
+ &sym,
+ kind,
+ over_src.clone(),
+ text.clone()
+ )),
+ result.unwrap().set_fragment("virt fragment".into()),
+ );
+ }
+
+ // Encountering _override_ first and virtual second should _not_
+ // clear the fragment, otherwise the virtual fragment will take
+ // precedence over the override.
+ #[test]
+ fn declare_override_virtual_ident_fragment_override_first() {
+ let sym = symbol_dummy!(1, "virtual");
+ let over_src = symbol_dummy!(2, "src");
+ let kind = IdentKind::Meta;
+
+ let virt_src = Source {
+ virtual_: true,
+ ..Default::default()
+ };
+
+ let virt = IdentObject::declare(&sym)
+ .resolve(kind.clone(), virt_src.clone())
+ .unwrap();
+ let text = FragmentText::from("remove me");
+ let virt_frag = virt.set_fragment(text.clone());
+
+ assert_eq!(
+ Ok(IdentObject::IdentFragment(
+ &sym,
+ kind.clone(),
+ virt_src,
+ text
+ )),
+ virt_frag,
+ );
- let result =
- virt_frag.unwrap().resolve(kind.clone(), over_src.clone());
+ let over_src = Source {
+ override_: true,
+ src: Some(&over_src),
+ ..Default::default()
+ };
+
+ let result =
+ virt_frag.unwrap().resolve(kind.clone(), over_src.clone());
+
+ // The act of overriding the object should have cleared any
+ // existing fragment, making way for a new fragment to take its
+ // place as soon as it is discovered. (So, back to an
+ // IdentObject::Ident.)
+ assert_eq!(
+ Ok(IdentObject::Ident(&sym, kind, over_src)),
+ result
+ );
+ }
+
+ #[test]
+ fn declare_override_virtual_ident_fragment_incompatible_type() {
+ let sym = symbol_dummy!(1, "virtual");
+ let over_src = symbol_dummy!(2, "src");
+ let kind = IdentKind::Meta;
+
+ let virt_src = Source {
+ virtual_: true,
+ ..Default::default()
+ };
+
+ let virt = IdentObject::declare(&sym)
+ .resolve(kind.clone(), virt_src.clone())
+ .unwrap();
+ let virt_frag = virt.set_fragment("".into()).unwrap();
- // The act of overriding the object should have cleared any
- // existing fragment, making way for a new fragment to take its
- // place as soon as it is discovered. (So, back to an
- // IdentObject::Ident.)
- assert_eq!(Ok(IdentObject::Ident(&sym, kind, over_src)), result);
+ let over_src = Source {
+ override_: true,
+ src: Some(&over_src),
+ ..Default::default()
+ };
+
+ let bad_kind = IdentKind::Cgen(Dim::from_u8(1));
+ let result = virt_frag
+ .clone()
+ .resolve(bad_kind.clone(), over_src.clone())
+ .expect_err("expected error");
+
+ match result {
+ (
+ ref orig,
+ TransitionError::VirtualOverrideKind {
+ ref name,
+ ref existing,
+ ref given,
+ },
+ ) => {
+ assert_eq!(orig, &virt_frag);
+
+ assert_eq!(*sym, *name);
+ assert_eq!(&kind, existing);
+ assert_eq!(&bad_kind, given);
+
+ // Formatted error
+ let (_, err) = result;
+ let msg = format!("{}", err);
+
+ assert!(msg.contains(&format!("{}", sym)));
+ assert!(msg.contains(&format!("{}", kind)));
+ assert!(msg.contains(&format!("{}", bad_kind)));
+ }
+ _ => panic!(
+ "expected TransitionError::VirtualOverrideKind {:?}",
+ result
+ ),
+ }
+ }
}
fn add_ident_kind_ignores(given: IdentKind, expected: IdentKind) {
diff --git a/tamer/src/ld/poc.rs b/tamer/src/ld/poc.rs
index 975ca35..c75aacb 100644
--- a/tamer/src/ld/poc.rs
+++ b/tamer/src/ld/poc.rs
@@ -49,8 +49,6 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
let abs_path = fs::canonicalize(package_path)?;
- println!("WARNING: This is proof-of-concept; do not use!");
-
let (name, relroot) = load_xmlo(
&abs_path.to_str().unwrap().to_string(),
&mut pkgs_seen,