(almost) encansulate Runtime into Zalsa

The distinction is dumb and should go away.
But we still need it for a bit.
This commit is contained in:
Niko Matsakis 2024-07-27 10:53:15 +00:00
parent 596461c213
commit 1842b1dfbb
21 changed files with 136 additions and 111 deletions

View file

@ -21,6 +21,7 @@ rustc-hash = "2.0.0"
salsa-macro-rules = { version = "0.1.0", path = "components/salsa-macro-rules" }
salsa-macros = { path = "components/salsa-macros" }
smallvec = "1.0.0"
lazy_static = "1.5.0"
[dev-dependencies]
annotate-snippets = "0.11.4"

View file

@ -86,12 +86,12 @@ macro_rules! setup_input_struct {
})
}
pub fn ingredient_mut(db: &mut dyn $zalsa::Database) -> (&mut $zalsa_struct::IngredientImpl<Self>, &mut $zalsa::Runtime) {
pub fn ingredient_mut(db: &mut dyn $zalsa::Database) -> (&mut $zalsa_struct::IngredientImpl<Self>, $zalsa::Revision) {
let zalsa_mut = db.zalsa_mut();
let index = zalsa_mut.add_or_lookup_jar_by_type(&<$zalsa_struct::JarImpl<$Configuration>>::default());
let (ingredient, runtime) = zalsa_mut.lookup_ingredient_mut(index);
let (ingredient, current_revision) = zalsa_mut.lookup_ingredient_mut(index);
let ingredient = ingredient.assert_type_mut::<$zalsa_struct::IngredientImpl<Self>>();
(ingredient, runtime)
(ingredient, current_revision)
}
}

View file

@ -81,8 +81,7 @@ impl<A: Accumulator> IngredientImpl<A> {
pub fn push(&self, db: &dyn crate::Database, value: A) {
local_state::attach(db, |state| {
let runtime = db.zalsa().runtime();
let current_revision = runtime.current_revision();
let current_revision = db.zalsa().current_revision();
let (active_query, _) = match state.active_query() {
Some(pair) => pair,
None => {
@ -163,7 +162,7 @@ impl<A: Accumulator> Ingredient for IngredientImpl<A> {
output_key: Option<crate::Id>,
) {
assert!(output_key.is_none());
let current_revision = db.zalsa().runtime().current_revision();
let current_revision = db.zalsa().current_revision();
if let Some(mut v) = self.map.get_mut(&executor) {
// The value is still valid in the new revision.
v.produced_at = current_revision;

View file

@ -2,8 +2,9 @@ use crate::{
durability::Durability,
hash::{FxIndexMap, FxIndexSet},
key::{DatabaseKeyIndex, DependencyIndex},
local_state::EMPTY_DEPENDENCIES,
tracked_struct::Disambiguator,
Cycle, Revision, Runtime,
Cycle, Revision,
};
use super::local_state::{EdgeKind, QueryEdges, QueryOrigin, QueryRevisions};
@ -86,9 +87,9 @@ impl ActiveQuery {
self.input_outputs.contains(&(EdgeKind::Output, key))
}
pub(crate) fn revisions(&self, runtime: &Runtime) -> QueryRevisions {
pub(crate) fn revisions(&self) -> QueryRevisions {
let input_outputs = if self.input_outputs.is_empty() {
runtime.empty_dependencies()
EMPTY_DEPENDENCIES.clone()
} else {
self.input_outputs.iter().copied().collect()
};

View file

@ -21,9 +21,9 @@ pub trait Database: ZalsaDatabase + AsDynDatabase {
/// will block until that snapshot is dropped -- if that snapshot
/// is owned by the current thread, this could trigger deadlock.
fn synthetic_write(&mut self, durability: Durability) {
let runtime = self.zalsa_mut().runtime_mut();
runtime.new_revision();
runtime.report_tracked_write(durability);
let zalsa_mut = self.zalsa_mut();
zalsa_mut.new_revision();
zalsa_mut.report_tracked_write(durability);
}
/// Reports that the query depends on some state unknown to salsa.
@ -33,7 +33,7 @@ pub trait Database: ZalsaDatabase + AsDynDatabase {
fn report_untracked_read(&self) {
let db = self.as_dyn_database();
local_state::attach(db, |state| {
state.report_untracked_read(db.zalsa().runtime().current_revision())
state.report_untracked_read(db.zalsa().current_revision())
})
}
@ -65,7 +65,7 @@ impl<T: Database> AsDynDatabase for T {
}
pub fn current_revision<Db: ?Sized + Database>(db: &Db) -> Revision {
db.zalsa().runtime().current_revision()
db.zalsa().current_revision()
}
impl dyn Database {

View file

@ -16,7 +16,7 @@ where
{
local_state::attach(db, |local_state| {
let zalsa = db.zalsa();
let current_revision = zalsa.runtime().current_revision();
let current_revision = zalsa.current_revision();
let Some(accumulator) = <accumulator::IngredientImpl<A>>::from_db(db) else {
return vec![];

View file

@ -27,8 +27,7 @@ where
opt_old_memo: Option<Arc<Memo<C::Output<'_>>>>,
) -> StampedValue<&C::Output<'db>> {
let zalsa = db.zalsa();
let runtime = zalsa.runtime();
let revision_now = runtime.current_revision();
let revision_now = zalsa.current_revision();
let database_key_index = active_query.database_key_index;
tracing::info!("{:?}: executing query", database_key_index);
@ -68,16 +67,7 @@ where
}
}
};
let mut revisions = active_query.pop(runtime);
// We assume that query is side-effect free -- that is, does
// not mutate the "inputs" to the query system. Sanity check
// that assumption here, at least to the best of our ability.
assert_eq!(
runtime.current_revision(),
revision_now,
"revision altered during query execution",
);
let mut revisions = active_query.pop();
// If the new value is equal to the old one, then it didn't
// really change, even if some of its inputs have. So we can

View file

@ -63,8 +63,8 @@ where
let memo_guard = self.memo_map.get(key);
if let Some(memo) = &memo_guard {
if memo.value.is_some() {
let runtime = db.zalsa().runtime();
if self.shallow_verify_memo(db, runtime, self.database_key_index(key), memo) {
let zalsa = db.zalsa();
if self.shallow_verify_memo(db, zalsa, self.database_key_index(key), memo) {
let value = unsafe {
// Unsafety invariant: memo is present in memo_map
self.extend_memo_lifetime(memo).unwrap()

View file

@ -4,8 +4,8 @@ use crate::{
key::DatabaseKeyIndex,
local_state::{self, ActiveQueryGuard, EdgeKind, LocalState, QueryOrigin},
runtime::StampedValue,
storage::ZalsaDatabase as _,
AsDynDatabase as _, Id, Revision, Runtime,
storage::{Zalsa, ZalsaDatabase as _},
AsDynDatabase as _, Id, Revision,
};
use super::{memo::Memo, Configuration, IngredientImpl};
@ -21,7 +21,7 @@ where
revision: Revision,
) -> bool {
local_state::attach(db.as_dyn_database(), |local_state| {
let runtime = db.zalsa().runtime();
let zalsa = db.zalsa();
local_state.unwind_if_revision_cancelled(db.as_dyn_database());
loop {
@ -34,7 +34,7 @@ where
// Check if we have a verified version: this is the hot path.
let memo_guard = self.memo_map.get(key);
if let Some(memo) = &memo_guard {
if self.shallow_verify_memo(db, runtime, database_key_index, memo) {
if self.shallow_verify_memo(db, zalsa, database_key_index, memo) {
return memo.revisions.changed_at > revision;
}
drop(memo_guard); // release the arc-swap guard before cold path
@ -102,12 +102,12 @@ where
pub(super) fn shallow_verify_memo(
&self,
db: &C::DbView,
runtime: &Runtime,
zalsa: &dyn Zalsa,
database_key_index: DatabaseKeyIndex,
memo: &Memo<C::Output<'_>>,
) -> bool {
let verified_at = memo.verified_at.load();
let revision_now = runtime.current_revision();
let revision_now = zalsa.current_revision();
tracing::debug!("{database_key_index:?}: shallow_verify_memo(memo = {memo:#?})",);
@ -116,10 +116,10 @@ where
return true;
}
if memo.check_durability(runtime) {
if memo.check_durability(zalsa) {
// No input of the suitable durability has changed since last verified.
let db = db.as_dyn_database();
memo.mark_as_verified(db, runtime, database_key_index);
memo.mark_as_verified(db, revision_now, database_key_index);
memo.mark_outputs_as_verified(db, database_key_index);
return true;
}
@ -141,12 +141,12 @@ where
old_memo: &Memo<C::Output<'_>>,
active_query: &ActiveQueryGuard<'_>,
) -> bool {
let runtime = db.zalsa().runtime();
let zalsa = db.zalsa();
let database_key_index = active_query.database_key_index;
tracing::debug!("{database_key_index:?}: deep_verify_memo(old_memo = {old_memo:#?})",);
if self.shallow_verify_memo(db, runtime, database_key_index, old_memo) {
if self.shallow_verify_memo(db, zalsa, database_key_index, old_memo) {
return true;
}
@ -215,7 +215,11 @@ where
}
}
old_memo.mark_as_verified(db.as_dyn_database(), runtime, database_key_index);
old_memo.mark_as_verified(
db.as_dyn_database(),
zalsa.current_revision(),
database_key_index,
);
true
}
}

View file

@ -4,8 +4,8 @@ use arc_swap::{ArcSwap, Guard};
use crossbeam::atomic::AtomicCell;
use crate::{
hash::FxDashMap, key::DatabaseKeyIndex, local_state::QueryRevisions, Event, EventKind, Id,
Revision, Runtime,
hash::FxDashMap, key::DatabaseKeyIndex, local_state::QueryRevisions, storage::Zalsa, Event,
EventKind, Id, Revision,
};
use super::Configuration;
@ -129,8 +129,8 @@ impl<V> Memo<V> {
}
}
/// True if this memo is known not to have changed based on its durability.
pub(super) fn check_durability(&self, runtime: &Runtime) -> bool {
let last_changed = runtime.last_changed_revision(self.revisions.durability);
pub(super) fn check_durability(&self, zalsa: &dyn Zalsa) -> bool {
let last_changed = zalsa.last_changed_revision(self.revisions.durability);
let verified_at = self.verified_at.load();
tracing::debug!(
"check_durability(last_changed={:?} <= verified_at={:?}) = {:?}",
@ -146,7 +146,7 @@ impl<V> Memo<V> {
pub(super) fn mark_as_verified(
&self,
db: &dyn crate::Database,
runtime: &crate::Runtime,
revision_now: Revision,
database_key_index: DatabaseKeyIndex,
) {
db.salsa_event(Event {
@ -156,7 +156,7 @@ impl<V> Memo<V> {
},
});
self.verified_at.store(runtime.current_revision());
self.verified_at.store(revision_now);
}
pub(super) fn mark_outputs_as_verified(

View file

@ -64,7 +64,7 @@ where
// - a result that is verified in the current revision, because it was set, which will use the set value
// - a result that is NOT verified and has untracked inputs, which will re-execute (and likely panic)
let revision = db.zalsa().runtime().current_revision();
let revision = db.zalsa().current_revision();
let mut revisions = QueryRevisions {
changed_at: current_deps.changed_at,
durability: current_deps.durability,
@ -101,7 +101,7 @@ where
executor: DatabaseKeyIndex,
key: Id,
) {
let runtime = db.zalsa().runtime();
let zalsa = db.zalsa();
let memo = match self.memo_map.get(key) {
Some(m) => m,
@ -119,6 +119,10 @@ where
}
let database_key_index = self.database_key_index(key);
memo.mark_as_verified(db.as_dyn_database(), runtime, database_key_index);
memo.mark_as_verified(
db.as_dyn_database(),
zalsa.current_revision(),
database_key_index,
);
}
}

View file

@ -28,7 +28,7 @@ impl SyncMap {
local_state: &LocalState,
database_key_index: DatabaseKeyIndex,
) -> Option<ClaimGuard<'me>> {
let runtime = db.zalsa().runtime();
let runtime = db.zalsa().runtimex();
let thread_id = std::thread::current().id();
match self.sync_map.entry(database_key_index.key_index) {
dashmap::mapref::entry::Entry::Vacant(entry) => {

View file

@ -78,7 +78,7 @@ impl<Db: Database> Handle<Db> {
/// same database!
fn cancel_others(&mut self) {
let zalsa = self.db().zalsa();
zalsa.runtime().set_cancellation_flag();
zalsa.set_cancellation_flag();
self.db().salsa_event(Event {
thread_id: std::thread::current().id(),

View file

@ -19,7 +19,6 @@ use crate::{
key::{DatabaseKeyIndex, DependencyIndex},
local_state::{self, QueryOrigin},
plumbing::{Jar, Stamp},
runtime::Runtime,
storage::IngredientIndex,
Database, Durability, Id, Revision,
};
@ -121,18 +120,17 @@ impl<C: Configuration> IngredientImpl<C> {
/// * `setter`, function that modifies the fields tuple; should only modify the element for `field_index`
pub fn set_field<R>(
&mut self,
runtime: &mut Runtime,
current_revision: Revision,
id: C::Struct,
field_index: usize,
durability: Durability,
setter: impl FnOnce(&mut C::Fields) -> R,
) -> R {
let revision = runtime.current_revision();
let id: Id = id.as_id();
let mut r = self.struct_map.update(id);
let stamp = &mut r.stamps[field_index];
stamp.durability = durability;
stamp.changed_at = revision;
stamp.changed_at = current_revision;
setter(&mut r.fields)
}

View file

@ -1,7 +1,7 @@
use std::marker::PhantomData;
use crate::input::{Configuration, IngredientImpl};
use crate::{Durability, Runtime};
use crate::{Durability, Revision};
/// Setter for a field of an input.
pub trait Setter: Sized {
@ -12,7 +12,7 @@ pub trait Setter: Sized {
#[must_use]
pub struct SetterImpl<'setter, C: Configuration, S, F> {
runtime: &'setter mut Runtime,
current_revision: Revision,
id: C::Struct,
ingredient: &'setter mut IngredientImpl<C>,
durability: Durability,
@ -27,14 +27,14 @@ where
S: FnOnce(&mut C::Fields, F) -> F,
{
pub fn new(
runtime: &'setter mut Runtime,
current_revision: Revision,
id: C::Struct,
field_index: usize,
ingredient: &'setter mut IngredientImpl<C>,
setter: S,
) -> Self {
SetterImpl {
runtime,
current_revision,
id,
field_index,
ingredient,
@ -59,7 +59,7 @@ where
fn to(self, value: F) -> F {
let Self {
runtime,
current_revision,
id,
ingredient,
durability,
@ -68,7 +68,7 @@ where
phantom: _,
} = self;
ingredient.set_field(runtime, id, field_index, durability, |tuple| {
ingredient.set_field(current_revision, id, field_index, durability, |tuple| {
setter(tuple, value)
})
}

View file

@ -13,7 +13,6 @@ use crate::Database;
use crate::Event;
use crate::EventKind;
use crate::Revision;
use crate::Runtime;
use std::cell::Cell;
use std::cell::RefCell;
use std::ptr::NonNull;
@ -321,21 +320,20 @@ impl LocalState {
/// `salsa_event` is emitted when this method is called, so that should be
/// used instead.
pub(crate) fn unwind_if_revision_cancelled(&self, db: &dyn Database) {
let runtime = db.zalsa().runtime();
let thread_id = std::thread::current().id();
db.salsa_event(Event {
thread_id,
kind: EventKind::WillCheckCancellation,
});
if runtime.load_cancellation_flag() {
self.unwind_cancelled(runtime);
let zalsa = db.zalsa();
if zalsa.load_cancellation_flag() {
self.unwind_cancelled(zalsa.current_revision());
}
}
#[cold]
pub(crate) fn unwind_cancelled(&self, runtime: &Runtime) {
let current_revision = runtime.current_revision();
pub(crate) fn unwind_cancelled(&self, current_revision: Revision) {
self.report_untracked_read(current_revision);
Cancelled::PendingWrite.throw();
}
@ -414,6 +412,10 @@ pub enum EdgeKind {
Output,
}
lazy_static::lazy_static! {
pub(crate) static ref EMPTY_DEPENDENCIES: Arc<[(EdgeKind, DependencyIndex)]> = Arc::new([]);
}
/// The edges between a memoized value and other queries in the dependency graph.
/// These edges include both dependency edges
/// e.g., when creating the memoized value for Q0 executed another function Q1)
@ -497,14 +499,14 @@ impl ActiveQueryGuard<'_> {
/// which summarizes the other queries that were accessed during this
/// query's execution.
#[inline]
pub(crate) fn pop(self, runtime: &Runtime) -> QueryRevisions {
pub(crate) fn pop(self) -> QueryRevisions {
// Extract accumulated inputs.
let popped_query = self.complete();
// If this frame were a cycle participant, it would have unwound.
assert!(popped_query.cycle.is_none());
popped_query.revisions(runtime)
popped_query.revisions()
}
/// If the active query is registered as a cycle participant, remove and

View file

@ -8,13 +8,9 @@ use crossbeam::atomic::AtomicCell;
use parking_lot::Mutex;
use crate::{
active_query::ActiveQuery,
cycle::CycleRecoveryStrategy,
durability::Durability,
key::{DatabaseKeyIndex, DependencyIndex},
local_state::{EdgeKind, LocalState},
revision::AtomicRevision,
Cancelled, Cycle, Database, Event, EventKind, Revision,
active_query::ActiveQuery, cycle::CycleRecoveryStrategy, durability::Durability,
key::DatabaseKeyIndex, local_state::LocalState, revision::AtomicRevision, Cancelled, Cycle,
Database, Event, EventKind, Revision,
};
use self::dependency_graph::DependencyGraph;
@ -25,9 +21,6 @@ pub struct Runtime {
/// Stores the next id to use for a snapshotted runtime (starts at 1).
next_id: AtomicUsize,
/// Vector we can clone
empty_dependencies: Arc<[(EdgeKind, DependencyIndex)]>,
/// Set to true when the current revision has been canceled.
/// This is done when we an input is being changed. The flag
/// is set back to false once the input has been changed.
@ -89,7 +82,6 @@ impl Default for Runtime {
.map(|_| AtomicRevision::start())
.collect(),
next_id: AtomicUsize::new(1),
empty_dependencies: None.into_iter().collect(),
revision_canceled: Default::default(),
dependency_graph: Default::default(),
}
@ -112,10 +104,6 @@ impl Runtime {
self.revisions[0].load()
}
pub(crate) fn empty_dependencies(&self) -> Arc<[(EdgeKind, DependencyIndex)]> {
self.empty_dependencies.clone()
}
/// Reports that an input with durability `durability` changed.
/// This will update the 'last changed at' values for every durability
/// less than or equal to `durability` to the current revision.

View file

@ -9,7 +9,7 @@ use crate::ingredient::{Ingredient, Jar};
use crate::nonce::{Nonce, NonceGenerator};
use crate::runtime::Runtime;
use crate::views::{Views, ViewsOf};
use crate::Database;
use crate::{Database, Durability, Revision};
pub fn views<Db: ?Sized + Database>(db: &Db) -> &Views {
db.zalsa().views()
@ -55,17 +55,36 @@ pub trait Zalsa {
/// Gets an `&`-ref to an ingredient by index
fn lookup_ingredient(&self, index: IngredientIndex) -> &dyn Ingredient;
/// Gets an `&mut`-ref to an ingredient by index; also returns the runtime for further use
/// Gets an `&mut`-ref to an ingredient by index.
///
/// **Triggers a new revision.** Returns the `&mut` reference
/// along with the new revision index.
fn lookup_ingredient_mut(
&mut self,
index: IngredientIndex,
) -> (&mut dyn Ingredient, &mut Runtime);
) -> (&mut dyn Ingredient, Revision);
/// Gets the salsa runtime
fn runtime(&self) -> &Runtime;
fn runtimex(&self) -> &Runtime;
/// Gets the salsa runtime
fn runtime_mut(&mut self) -> &mut Runtime;
/// Return the current revision
fn current_revision(&self) -> Revision;
/// Increment revision counter.
///
/// **Triggers a new revision.**
fn new_revision(&mut self) -> Revision;
/// Return the time when an input of durability `durability` last changed
fn last_changed_revision(&self, durability: Durability) -> Revision;
/// True if any threads have signalled for cancellation
fn load_cancellation_flag(&self) -> bool;
/// Signal for cancellation, indicating current thread is trying to get unique access.
fn set_cancellation_flag(&self);
/// Reports a (synthetic) tracked write to "some input of the given durability".
fn report_tracked_write(&mut self, durability: Durability);
}
impl<Db: Database> Zalsa for Storage<Db> {
@ -119,19 +138,11 @@ impl<Db: Database> Zalsa for Storage<Db> {
&**self.ingredients_vec.get(index.as_usize()).unwrap()
}
fn runtime(&self) -> &Runtime {
&self.runtime
}
fn runtime_mut(&mut self) -> &mut Runtime {
&mut self.runtime
}
fn lookup_ingredient_mut(
&mut self,
index: IngredientIndex,
) -> (&mut dyn Ingredient, &mut Runtime) {
self.runtime.new_revision();
) -> (&mut dyn Ingredient, Revision) {
let new_revision = self.runtime.new_revision();
for index in self.ingredients_requiring_reset.iter() {
self.ingredients_vec
@ -142,9 +153,37 @@ impl<Db: Database> Zalsa for Storage<Db> {
(
&mut **self.ingredients_vec.get_mut(index.as_usize()).unwrap(),
&mut self.runtime,
new_revision,
)
}
fn current_revision(&self) -> Revision {
self.runtime.current_revision()
}
fn load_cancellation_flag(&self) -> bool {
self.runtime.load_cancellation_flag()
}
fn report_tracked_write(&mut self, durability: Durability) {
self.runtime.report_tracked_write(durability)
}
fn runtimex(&self) -> &Runtime {
&self.runtime
}
fn last_changed_revision(&self, durability: Durability) -> Revision {
self.runtime.last_changed_revision(durability)
}
fn set_cancellation_flag(&self) {
self.runtime.set_cancellation_flag()
}
fn new_revision(&mut self) -> Revision {
self.runtime.new_revision()
}
}
/// Nonce type representing the underlying database storage.

View file

@ -307,7 +307,7 @@ where
let (id, new_id) = self.intern(entity_key);
local_state.add_output(self.database_key_index(id).into());
let current_revision = zalsa.runtime().current_revision();
let current_revision = zalsa.current_revision();
if new_id {
// This is a new tracked struct, so create an entry in the struct map.
@ -379,7 +379,7 @@ where
///
/// If the struct has not been created in this revision.
pub fn lookup_struct<'db>(&'db self, db: &'db dyn Database, id: Id) -> C::Struct<'db> {
let current_revision = db.zalsa().runtime().current_revision();
let current_revision = db.zalsa().current_revision();
self.struct_map.get(current_revision, id)
}
@ -458,9 +458,9 @@ where
_executor: DatabaseKeyIndex,
output_key: Option<crate::Id>,
) {
let runtime = db.zalsa().runtime();
let current_revision = db.zalsa().current_revision();
let output_key = output_key.unwrap();
self.struct_map.validate(runtime, output_key);
self.struct_map.validate(current_revision, output_key);
}
fn remove_stale_output(

View file

@ -6,7 +6,7 @@ use std::{
use crossbeam::queue::SegQueue;
use dashmap::mapref::one::RefMut;
use crate::{alloc::Alloc, hash::FxDashMap, Id, Revision, Runtime};
use crate::{alloc::Alloc, hash::FxDashMap, Id, Revision};
use super::{Configuration, KeyStruct, Value};
@ -99,7 +99,7 @@ where
unsafe { C::struct_from_raw(pointer) }
}
pub fn validate<'db>(&'db self, runtime: &'db Runtime, id: Id) {
pub fn validate<'db>(&'db self, current_revision: Revision, id: Id) {
let mut data = self.map.get_mut(&id).unwrap();
// UNSAFE: We never permit `&`-access in the current revision until data.created_at
@ -107,7 +107,6 @@ where
let data = unsafe { data.as_mut() };
// Never update a struct twice in the same revision.
let current_revision = runtime.current_revision();
assert!(data.created_at < current_revision);
data.created_at = current_revision;
}

View file

@ -48,7 +48,7 @@ where
/// The caller is responible for selecting the appropriate element.
pub fn field<'db>(&'db self, db: &'db dyn Database, id: Id) -> &'db C::Fields<'db> {
local_state::attach(db, |local_state| {
let current_revision = db.zalsa().runtime().current_revision();
let current_revision = db.zalsa().current_revision();
let data = self.struct_map.get(current_revision, id);
let data = C::deref_struct(data);
let changed_at = data.revisions[self.field_index];
@ -85,7 +85,7 @@ where
input: Option<Id>,
revision: crate::Revision,
) -> bool {
let current_revision = db.zalsa().runtime().current_revision();
let current_revision = db.zalsa().current_revision();
let id = input.unwrap();
let data = self.struct_map.get(current_revision, id);
let data = C::deref_struct(data);