From cae379fd06704e4a2125634016931ff1b7369616 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 28 Sep 2018 11:04:52 -0400 Subject: [PATCH] import the code --- Cargo.toml | 2 + src/dyn_descriptor.rs | 46 ++++++++++++ src/lib.rs | 162 ++++++++++++++++++++++++++++++++++++++++-- src/storage.rs | 74 +++++++++++++++++++ 4 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 src/dyn_descriptor.rs create mode 100644 src/storage.rs diff --git a/Cargo.toml b/Cargo.toml index d494441..8ca8c9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ edition = "2018" license = "Apache-2.0/MIT" [dependencies] +derive-new = "0.5.5" +rustc-hash = "1.0" diff --git a/src/dyn_descriptor.rs b/src/dyn_descriptor.rs new file mode 100644 index 0000000..9e2c2e6 --- /dev/null +++ b/src/dyn_descriptor.rs @@ -0,0 +1,46 @@ +use crate::BaseQueryContext; +use crate::Query; +use crate::QueryTable; +use rustc_hash::FxHashMap; +use std::any::{Any, TypeId}; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::fmt::Debug; +use std::fmt::Display; +use std::fmt::Write; +use std::hash::Hash; + +// Total hack for now: assume that the Debug string +// for the key, combined with the type-id of the query, +// is sufficient for an equality comparison. + +/// A simple-to-use query descriptor that is meant only for dumping +/// out cycle stack errors and not for any real recovery; also, not +/// especially efficient. +#[derive(PartialEq, Eq)] +crate struct DynDescriptor { + type_id: TypeId, + debug_string: String, +} + +impl DynDescriptor { + crate fn from_key(_query: &QC, key: &Q::Key) -> DynDescriptor + where + QC: BaseQueryContext, + Q: Query, + { + let type_id = TypeId::of::(); + let query = Q::default(); + let debug_string = format!("Query `{:?}` applied to `{:?}`", query, key); + DynDescriptor { + type_id, + debug_string, + } + } +} + +impl std::fmt::Debug for DynDescriptor { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(fmt, "{}", self.debug_string) + } +} diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..515d329 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,159 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); +#![deny(rust_2018_idioms)] +#![feature(in_band_lifetimes)] +#![feature(box_patterns)] +#![feature(crate_visibility_modifier)] +#![feature(nll)] +#![feature(min_const_fn)] +#![feature(const_fn)] +#![feature(const_let)] +#![feature(try_from)] +#![feature(macro_at_most_once_rep)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use derive_new::new; +use rustc_hash::FxHashMap; +use std::any::Any; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::fmt::Debug; +use std::fmt::Display; +use std::fmt::Write; +use std::hash::Hash; + +pub mod dyn_descriptor; +pub mod storage; + +pub trait BaseQueryContext: Sized { + /// A "query descriptor" packages up all the possible queries and a key. + /// It is used to store information about (e.g.) the stack. + /// + /// At runtime, it can be implemented in various ways: a monster enum + /// works for a fixed set of queries, but a boxed trait object is good + /// for a more open-ended option. + type QueryDescriptor: Debug + Eq; + + fn execute_query_implementation( + &self, + descriptor: Self::QueryDescriptor, + key: &Q::Key, + ) -> Q::Value + where + Q: Query; + + /// Reports an unexpected cycle attempting to access the query Q with the given key. + fn report_unexpected_cycle(&self, descriptor: Self::QueryDescriptor) -> !; +} + +pub trait Query: Debug + Default + Sized + 'static { + type Key: Clone + Debug + Hash + Eq; + type Value: Clone + Debug + Hash + Eq; + type Storage: QueryStorageOps; + + fn execute(query: &QC, key: Self::Key) -> Self::Value; +} + +pub trait QueryStorageOps +where + QC: BaseQueryContext, + Q: Query, +{ + fn try_fetch<'q>( + &self, + query: &'q QC, + key: &Q::Key, + descriptor: impl FnOnce() -> QC::QueryDescriptor, + ) -> Result; +} + +#[derive(new)] +pub struct QueryTable<'me, QC, Q> +where + QC: BaseQueryContext, + Q: Query, +{ + pub query: &'me QC, + pub storage: &'me Q::Storage, + pub descriptor_fn: fn(&QC, &Q::Key) -> QC::QueryDescriptor, +} + +#[derive(Debug)] +pub enum QueryState { + InProgress, + Memoized(V), +} + +pub struct CycleDetected; + +impl QueryTable<'me, QC, Q> +where + QC: BaseQueryContext, + Q: Query, +{ + pub fn of(&self, key: Q::Key) -> Q::Value { + self.storage + .try_fetch(self.query, &key, || self.descriptor(&key)) + .unwrap_or_else(|CycleDetected| { + self.query.report_unexpected_cycle(self.descriptor(&key)) + }) + } + + fn descriptor(&self, key: &Q::Key) -> QC::QueryDescriptor { + (self.descriptor_fn)(self.query, key) + } +} + +/// A macro helper for writing the query contexts in traits that helps +/// you avoid repeating information. +/// +/// Example: +/// +/// ``` +/// trait TypeckQueryContext { +/// query_prototype!(fn () for ); +/// } +/// ``` +#[macro_export] +macro_rules! query_prototype { + ( + $(#[$attr:meta])* + fn $method_name:ident() for $query_type:ty + ) => { + $(#[$attr])* + fn $method_name(&self) -> $crate::query::QueryTable<'_, Self, $query_type>; + } +} + +/// Example: +/// +/// ``` +/// query_definition! { +/// QueryName(query: &impl TypeckQueryContext, key: DefId) -> Arc> { +/// ... +/// } +/// } +#[macro_export] +macro_rules! query_definition { + ( + $(#[$attr:meta])* + $v:vis $name:ident($query:tt : &impl $query_trait:path, $key:tt : $key_ty:ty) -> $value_ty:ty { + $($body:tt)* + } + ) => { + #[derive(Default, Debug)] + $v struct $name; + + impl $crate::query::Query for $name + where + QC: $query_trait, + { + type Key = $key_ty; + type Value = $value_ty; + type Storage = $crate::query::storage::MemoizedStorage; + + fn execute($query: &QC, $key: $key_ty) -> $value_ty { + $($body)* + } + } } } diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000..37bb88e --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,74 @@ +use crate::BaseQueryContext; +use crate::CycleDetected; +use crate::Query; +use crate::QueryState; +use crate::QueryStorageOps; +use crate::QueryTable; +use rustc_hash::FxHashMap; +use std::any::Any; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::fmt::Debug; +use std::fmt::Display; +use std::fmt::Write; +use std::hash::Hash; + +// The master implementation that knits together all the queries +// contains a certain amount of boilerplate. This file aims to +// reduce that. + +crate struct MemoizedStorage +where + Q: Query, + QC: BaseQueryContext, +{ + map: RefCell>>, +} + +impl QueryStorageOps for MemoizedStorage +where + Q: Query, + QC: BaseQueryContext, +{ + fn try_fetch<'q>( + &self, + query: &'q QC, + key: &Q::Key, + descriptor: impl FnOnce() -> QC::QueryDescriptor, + ) -> Result { + { + let mut map = self.map.borrow_mut(); + match map.entry(key.clone()) { + Entry::Occupied(entry) => { + return match entry.get() { + QueryState::InProgress => Err(CycleDetected), + QueryState::Memoized(value) => Ok(value.clone()), + }; + } + Entry::Vacant(entry) => { + entry.insert(QueryState::InProgress); + } + } + } + + // If we get here, the query is in progress, and we are the + // ones tasked with finding its final value. + let descriptor = descriptor(); + let value = query.execute_query_implementation::(descriptor, key); + + { + let mut map = self.map.borrow_mut(); + let old_value = map.insert(key.clone(), QueryState::Memoized(value.clone())); + assert!( + match old_value { + Some(QueryState::InProgress) => true, + _ => false, + }, + "expected in-progress state, not {:?}", + old_value + ); + } + + Ok(value) + } +}