Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/salsa-macros/src/accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl AllowedOptions for Accumulator {
const DB: bool = false;
const CYCLE_FN: bool = false;
const CYCLE_INITIAL: bool = false;
const CYCLE_RESULT: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = false;
const ID: bool = false;
Expand Down
2 changes: 2 additions & 0 deletions components/salsa-macros/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ impl crate::options::AllowedOptions for InputStruct {

const CYCLE_INITIAL: bool = false;

const CYCLE_RESULT: bool = false;

const LRU: bool = false;

const CONSTRUCTOR_NAME: bool = true;
Expand Down
2 changes: 2 additions & 0 deletions components/salsa-macros/src/interned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ impl crate::options::AllowedOptions for InternedStruct {

const CYCLE_INITIAL: bool = false;

const CYCLE_RESULT: bool = false;

const LRU: bool = false;

const CONSTRUCTOR_NAME: bool = true;
Expand Down
23 changes: 23 additions & 0 deletions components/salsa-macros/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ pub(crate) struct Options<A: AllowedOptions> {
/// If this is `Some`, the value is the `<path>`.
pub cycle_initial: Option<syn::Path>,

/// The `cycle_result = <path>` option is the result for non-fixpoint cycle.
///
/// If this is `Some`, the value is the `<path>`.
pub cycle_result: Option<syn::Expr>,

/// The `data = <ident>` option is used to define the name of the data type for an interned
/// struct.
///
Expand Down Expand Up @@ -100,6 +105,7 @@ impl<A: AllowedOptions> Default for Options<A> {
db_path: Default::default(),
cycle_fn: Default::default(),
cycle_initial: Default::default(),
cycle_result: Default::default(),
data: Default::default(),
constructor_name: Default::default(),
phantom: Default::default(),
Expand All @@ -123,6 +129,7 @@ pub(crate) trait AllowedOptions {
const DB: bool;
const CYCLE_FN: bool;
const CYCLE_INITIAL: bool;
const CYCLE_RESULT: bool;
const LRU: bool;
const CONSTRUCTOR_NAME: bool;
const ID: bool;
Expand Down Expand Up @@ -274,6 +281,22 @@ impl<A: AllowedOptions> syn::parse::Parse for Options<A> {
"`cycle_initial` option not allowed here",
));
}
} else if ident == "cycle_result" {
if A::CYCLE_RESULT {
let _eq = Equals::parse(input)?;
let expr = syn::Expr::parse(input)?;
if let Some(old) = options.cycle_result.replace(expr) {
return Err(syn::Error::new(
old.span(),
"option `cycle_result` provided twice",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`cycle_result` option not allowed here",
));
}
} else if ident == "data" {
if A::DATA {
let _eq = Equals::parse(input)?;
Expand Down
25 changes: 20 additions & 5 deletions components/salsa-macros/src/tracked_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ impl crate::options::AllowedOptions for TrackedFn {

const CYCLE_INITIAL: bool = true;

const CYCLE_RESULT: bool = true;

const LRU: bool = true;

const CONSTRUCTOR_NAME: bool = false;
Expand Down Expand Up @@ -201,25 +203,38 @@ impl Macro {
fn cycle_recovery(&self) -> syn::Result<(TokenStream, TokenStream, TokenStream)> {
// TODO should we ask the user to specify a struct that impls a trait with two methods,
// rather than asking for two methods separately?
match (&self.args.cycle_fn, &self.args.cycle_initial) {
(Some(cycle_fn), Some(cycle_initial)) => Ok((
match (
&self.args.cycle_fn,
&self.args.cycle_initial,
&self.args.cycle_result,
) {
(Some(cycle_fn), Some(cycle_initial), None) => Ok((
quote!((#cycle_fn)),
quote!((#cycle_initial)),
quote!(Fixpoint),
)),
(None, None) => Ok((
(None, None, None) => Ok((
quote!((salsa::plumbing::unexpected_cycle_recovery!)),
quote!((salsa::plumbing::unexpected_cycle_initial!)),
quote!(Panic),
)),
(Some(_), None) => Err(syn::Error::new_spanned(
(Some(_), None, None) => Err(syn::Error::new_spanned(
self.args.cycle_fn.as_ref().unwrap(),
"must provide `cycle_initial` along with `cycle_fn`",
)),
(None, Some(_)) => Err(syn::Error::new_spanned(
(None, Some(_), None) => Err(syn::Error::new_spanned(
self.args.cycle_initial.as_ref().unwrap(),
"must provide `cycle_fn` along with `cycle_initial`",
)),
(None, None, Some(cycle_result)) => Ok((
quote!((salsa::plumbing::unexpected_cycle_recovery!)),
quote!((#cycle_result)),
quote!(FallbackImmediate),
)),
(_, _, Some(_)) => Err(syn::Error::new_spanned(
self.args.cycle_initial.as_ref().unwrap(),
"must provide either `cycle_result` or `cycle_fn` & `cycle_initial`, not both",
)),
}
}

Expand Down
2 changes: 2 additions & 0 deletions components/salsa-macros/src/tracked_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ impl crate::options::AllowedOptions for TrackedStruct {

const CYCLE_INITIAL: bool = false;

const CYCLE_RESULT: bool = false;

const LRU: bool = false;

const CONSTRUCTOR_NAME: bool = true;
Expand Down
16 changes: 14 additions & 2 deletions src/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ pub enum CycleRecoveryStrategy {
/// This choice is computed by the query's `cycle_recovery`
/// function and initial value.
Fixpoint,

/// Recovers from cycles by inserting a fallback value for all
/// queries that have a fallback, and ignoring any other query
/// in the cycle (as if they were not computed).
FallbackImmediate,
}

/// A "cycle head" is the query at which we encounter a cycle; that is, if A -> B -> C -> A, then A
Expand All @@ -91,8 +96,8 @@ pub enum CycleRecoveryStrategy {
/// cycle until it converges.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct CycleHead {
pub database_key_index: DatabaseKeyIndex,
pub iteration_count: u32,
pub(crate) database_key_index: DatabaseKeyIndex,
pub(crate) iteration_count: u32,
}

/// Any provisional value generated by any query in a cycle will track the cycle head(s) (can be
Expand Down Expand Up @@ -190,3 +195,10 @@ impl From<CycleHead> for CycleHeads {

pub(crate) static EMPTY_CYCLE_HEADS: std::sync::LazyLock<CycleHeads> =
std::sync::LazyLock::new(|| CycleHeads(ThinVec::new()));

#[derive(Debug, PartialEq, Eq)]
pub enum CycleHeadKind {
Provisional,
NotProvisional,
FallbackImmediate,
}
16 changes: 12 additions & 4 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::ptr::NonNull;
pub(crate) use maybe_changed_after::VerifyResult;

use crate::accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues};
use crate::cycle::{CycleRecoveryAction, CycleRecoveryStrategy};
use crate::cycle::{CycleHeadKind, CycleRecoveryAction, CycleRecoveryStrategy};
use crate::function::delete::DeletedEntries;
use crate::ingredient::{fmt_index, Ingredient};
use crate::key::DatabaseKeyIndex;
Expand Down Expand Up @@ -241,14 +241,22 @@ where

/// True if the input `input` contains a memo that cites itself as a cycle head.
/// This indicates an intermediate value for a cycle that has not yet reached a fixed point.
fn is_provisional_cycle_head<'db>(&'db self, db: &'db dyn Database, input: Id) -> bool {
fn cycle_head_kind<'db>(&'db self, db: &'db dyn Database, input: Id) -> CycleHeadKind {
let zalsa = db.zalsa();
self.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input))
let is_provisional = self
.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input))
.is_some_and(|memo| {
memo.cycle_heads()
.into_iter()
.any(|head| head.database_key_index == self.database_key_index(input))
})
});
if is_provisional {
CycleHeadKind::Provisional
} else if C::CYCLE_STRATEGY == CycleRecoveryStrategy::FallbackImmediate {
CycleHeadKind::FallbackImmediate
} else {
CycleHeadKind::NotProvisional
}
}

/// Attempts to claim `key_index`, returning `false` if a cycle occurs.
Expand Down
Loading