mirror of
https://github.com/salsa-rs/salsa.git
synced 2024-10-22 20:36:40 +00:00
port input to use Table
This commit is contained in:
parent
2f8e78f431
commit
703f312eae
16 changed files with 247 additions and 207 deletions
|
@ -203,7 +203,7 @@ macro_rules! setup_input_struct {
|
|||
/// Default debug formatting for this struct (may be useful if you define your own `Debug` impl)
|
||||
pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
$zalsa::with_attached_database(|db| {
|
||||
let fields = $Configuration::ingredient(db).leak_fields(this);
|
||||
let fields = $Configuration::ingredient(db).leak_fields(db, this);
|
||||
let mut f = f.debug_struct(stringify!($Struct));
|
||||
let f = f.field("[salsa id]", &$zalsa::AsId::as_id(&this));
|
||||
$(
|
||||
|
@ -235,7 +235,7 @@ macro_rules! setup_input_struct {
|
|||
let current_revision = $zalsa::current_revision(db);
|
||||
let ingredient = $Configuration::ingredient(db.as_dyn_database());
|
||||
let (fields, stamps) = builder::builder_into_inner(self, current_revision);
|
||||
ingredient.new_input(fields, stamps)
|
||||
ingredient.new_input(db.as_dyn_database(), fields, stamps)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -378,25 +378,25 @@ fn parse_print() {
|
|||
let expected = expect_test::expect![[r#"
|
||||
(
|
||||
Program {
|
||||
[salsa id]: Id(400),
|
||||
[salsa id]: Id(800),
|
||||
statements: [
|
||||
Statement {
|
||||
span: Span {
|
||||
[salsa id]: Id(4),
|
||||
[salsa id]: Id(404),
|
||||
start: 0,
|
||||
end: 11,
|
||||
},
|
||||
data: Print(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(3),
|
||||
[salsa id]: Id(403),
|
||||
start: 6,
|
||||
end: 11,
|
||||
},
|
||||
data: Op(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(0),
|
||||
[salsa id]: Id(400),
|
||||
start: 6,
|
||||
end: 7,
|
||||
},
|
||||
|
@ -407,7 +407,7 @@ fn parse_print() {
|
|||
Add,
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(2),
|
||||
[salsa id]: Id(402),
|
||||
start: 10,
|
||||
end: 11,
|
||||
},
|
||||
|
@ -441,22 +441,22 @@ fn parse_example() {
|
|||
let expected = expect_test::expect![[r#"
|
||||
(
|
||||
Program {
|
||||
[salsa id]: Id(1000),
|
||||
[salsa id]: Id(1400),
|
||||
statements: [
|
||||
Statement {
|
||||
span: Span {
|
||||
[salsa id]: Id(9),
|
||||
[salsa id]: Id(409),
|
||||
start: 13,
|
||||
end: 57,
|
||||
},
|
||||
data: Function(
|
||||
Function {
|
||||
[salsa id]: Id(c00),
|
||||
[salsa id]: Id(1000),
|
||||
name: FunctionId {
|
||||
text: "area_rectangle",
|
||||
},
|
||||
name_span: Span {
|
||||
[salsa id]: Id(0),
|
||||
[salsa id]: Id(400),
|
||||
start: 16,
|
||||
end: 30,
|
||||
},
|
||||
|
@ -470,14 +470,14 @@ fn parse_example() {
|
|||
],
|
||||
body: Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(8),
|
||||
[salsa id]: Id(408),
|
||||
start: 39,
|
||||
end: 57,
|
||||
},
|
||||
data: Op(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(5),
|
||||
[salsa id]: Id(405),
|
||||
start: 39,
|
||||
end: 41,
|
||||
},
|
||||
|
@ -490,7 +490,7 @@ fn parse_example() {
|
|||
Multiply,
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(7),
|
||||
[salsa id]: Id(407),
|
||||
start: 43,
|
||||
end: 57,
|
||||
},
|
||||
|
@ -507,18 +507,18 @@ fn parse_example() {
|
|||
},
|
||||
Statement {
|
||||
span: Span {
|
||||
[salsa id]: Id(15),
|
||||
[salsa id]: Id(415),
|
||||
start: 57,
|
||||
end: 102,
|
||||
},
|
||||
data: Function(
|
||||
Function {
|
||||
[salsa id]: Id(c01),
|
||||
[salsa id]: Id(1001),
|
||||
name: FunctionId {
|
||||
text: "area_circle",
|
||||
},
|
||||
name_span: Span {
|
||||
[salsa id]: Id(a),
|
||||
[salsa id]: Id(40a),
|
||||
start: 60,
|
||||
end: 71,
|
||||
},
|
||||
|
@ -529,21 +529,21 @@ fn parse_example() {
|
|||
],
|
||||
body: Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(14),
|
||||
[salsa id]: Id(414),
|
||||
start: 77,
|
||||
end: 102,
|
||||
},
|
||||
data: Op(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(11),
|
||||
[salsa id]: Id(411),
|
||||
start: 77,
|
||||
end: 86,
|
||||
},
|
||||
data: Op(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(e),
|
||||
[salsa id]: Id(40e),
|
||||
start: 77,
|
||||
end: 81,
|
||||
},
|
||||
|
@ -554,7 +554,7 @@ fn parse_example() {
|
|||
Multiply,
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(10),
|
||||
[salsa id]: Id(410),
|
||||
start: 84,
|
||||
end: 86,
|
||||
},
|
||||
|
@ -569,7 +569,7 @@ fn parse_example() {
|
|||
Multiply,
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(13),
|
||||
[salsa id]: Id(413),
|
||||
start: 88,
|
||||
end: 102,
|
||||
},
|
||||
|
@ -586,14 +586,14 @@ fn parse_example() {
|
|||
},
|
||||
Statement {
|
||||
span: Span {
|
||||
[salsa id]: Id(1c),
|
||||
[salsa id]: Id(41c),
|
||||
start: 102,
|
||||
end: 141,
|
||||
},
|
||||
data: Print(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(1b),
|
||||
[salsa id]: Id(41b),
|
||||
start: 108,
|
||||
end: 128,
|
||||
},
|
||||
|
@ -604,7 +604,7 @@ fn parse_example() {
|
|||
[
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(17),
|
||||
[salsa id]: Id(417),
|
||||
start: 123,
|
||||
end: 124,
|
||||
},
|
||||
|
@ -614,7 +614,7 @@ fn parse_example() {
|
|||
},
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(19),
|
||||
[salsa id]: Id(419),
|
||||
start: 126,
|
||||
end: 127,
|
||||
},
|
||||
|
@ -629,14 +629,14 @@ fn parse_example() {
|
|||
},
|
||||
Statement {
|
||||
span: Span {
|
||||
[salsa id]: Id(21),
|
||||
[salsa id]: Id(421),
|
||||
start: 141,
|
||||
end: 174,
|
||||
},
|
||||
data: Print(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(20),
|
||||
[salsa id]: Id(420),
|
||||
start: 147,
|
||||
end: 161,
|
||||
},
|
||||
|
@ -647,7 +647,7 @@ fn parse_example() {
|
|||
[
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(1e),
|
||||
[salsa id]: Id(41e),
|
||||
start: 159,
|
||||
end: 160,
|
||||
},
|
||||
|
@ -662,21 +662,21 @@ fn parse_example() {
|
|||
},
|
||||
Statement {
|
||||
span: Span {
|
||||
[salsa id]: Id(26),
|
||||
[salsa id]: Id(426),
|
||||
start: 174,
|
||||
end: 195,
|
||||
},
|
||||
data: Print(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(25),
|
||||
[salsa id]: Id(425),
|
||||
start: 180,
|
||||
end: 186,
|
||||
},
|
||||
data: Op(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(22),
|
||||
[salsa id]: Id(422),
|
||||
start: 180,
|
||||
end: 182,
|
||||
},
|
||||
|
@ -687,7 +687,7 @@ fn parse_example() {
|
|||
Multiply,
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(24),
|
||||
[salsa id]: Id(424),
|
||||
start: 185,
|
||||
end: 186,
|
||||
},
|
||||
|
@ -714,7 +714,7 @@ fn parse_error() {
|
|||
let expected = expect_test::expect![[r#"
|
||||
(
|
||||
Program {
|
||||
[salsa id]: Id(400),
|
||||
[salsa id]: Id(800),
|
||||
statements: [],
|
||||
},
|
||||
[
|
||||
|
@ -736,32 +736,32 @@ fn parse_precedence() {
|
|||
let expected = expect_test::expect![[r#"
|
||||
(
|
||||
Program {
|
||||
[salsa id]: Id(400),
|
||||
[salsa id]: Id(800),
|
||||
statements: [
|
||||
Statement {
|
||||
span: Span {
|
||||
[salsa id]: Id(a),
|
||||
[salsa id]: Id(40a),
|
||||
start: 0,
|
||||
end: 19,
|
||||
},
|
||||
data: Print(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(9),
|
||||
[salsa id]: Id(409),
|
||||
start: 6,
|
||||
end: 19,
|
||||
},
|
||||
data: Op(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(6),
|
||||
[salsa id]: Id(406),
|
||||
start: 6,
|
||||
end: 16,
|
||||
},
|
||||
data: Op(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(0),
|
||||
[salsa id]: Id(400),
|
||||
start: 6,
|
||||
end: 7,
|
||||
},
|
||||
|
@ -772,14 +772,14 @@ fn parse_precedence() {
|
|||
Add,
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(5),
|
||||
[salsa id]: Id(405),
|
||||
start: 10,
|
||||
end: 15,
|
||||
},
|
||||
data: Op(
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(2),
|
||||
[salsa id]: Id(402),
|
||||
start: 10,
|
||||
end: 11,
|
||||
},
|
||||
|
@ -790,7 +790,7 @@ fn parse_precedence() {
|
|||
Multiply,
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(4),
|
||||
[salsa id]: Id(404),
|
||||
start: 14,
|
||||
end: 15,
|
||||
},
|
||||
|
@ -805,7 +805,7 @@ fn parse_precedence() {
|
|||
Add,
|
||||
Expression {
|
||||
span: Span {
|
||||
[salsa id]: Id(8),
|
||||
[salsa id]: Id(408),
|
||||
start: 18,
|
||||
end: 19,
|
||||
},
|
||||
|
|
|
@ -268,7 +268,7 @@ fn fix_bad_variable_in_function() {
|
|||
expect![[r#"
|
||||
[
|
||||
"Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: parse_statements(Id(0)) } }",
|
||||
"Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: type_check_function(Id(1400)) } }",
|
||||
"Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: type_check_function(Id(1800)) } }",
|
||||
]
|
||||
"#]],
|
||||
)],
|
||||
|
|
107
src/input.rs
107
src/input.rs
|
@ -1,16 +1,11 @@
|
|||
use std::{
|
||||
any::Any,
|
||||
fmt,
|
||||
ops::DerefMut,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
use std::{any::Any, fmt, ops::DerefMut};
|
||||
|
||||
pub mod input_field;
|
||||
pub mod setter;
|
||||
mod struct_map;
|
||||
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use input_field::FieldIngredientImpl;
|
||||
use struct_map::StructMap;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
|
@ -18,7 +13,8 @@ use crate::{
|
|||
ingredient::{fmt_index, Ingredient},
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
plumbing::{Jar, JarAux, Stamp},
|
||||
zalsa::IngredientIndex,
|
||||
table::{memo::MemoTable, Slot, Table},
|
||||
zalsa::{IngredientIndex, Zalsa},
|
||||
zalsa_local::QueryOrigin,
|
||||
Database, Durability, Id, Revision, Runtime,
|
||||
};
|
||||
|
@ -57,15 +53,10 @@ impl<C: Configuration> Jar for JarImpl<C> {
|
|||
struct_index: crate::zalsa::IngredientIndex,
|
||||
) -> Vec<Box<dyn Ingredient>> {
|
||||
let struct_ingredient: IngredientImpl<C> = IngredientImpl::new(struct_index);
|
||||
let struct_map = struct_ingredient.struct_map.clone();
|
||||
|
||||
std::iter::once(Box::new(struct_ingredient) as _)
|
||||
.chain((0..C::FIELD_DEBUG_NAMES.len()).map(|field_index| {
|
||||
Box::new(FieldIngredientImpl::new(
|
||||
struct_index,
|
||||
field_index,
|
||||
struct_map.clone(),
|
||||
)) as _
|
||||
Box::new(<FieldIngredientImpl<C>>::new(struct_index, field_index)) as _
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
|
@ -73,8 +64,8 @@ impl<C: Configuration> Jar for JarImpl<C> {
|
|||
|
||||
pub struct IngredientImpl<C: Configuration> {
|
||||
ingredient_index: IngredientIndex,
|
||||
counter: AtomicU32,
|
||||
struct_map: StructMap<C>,
|
||||
singleton_index: AtomicCell<Option<Id>>,
|
||||
singleton_lock: Mutex<()>,
|
||||
_phantom: std::marker::PhantomData<C::Struct>,
|
||||
}
|
||||
|
||||
|
@ -82,12 +73,20 @@ impl<C: Configuration> IngredientImpl<C> {
|
|||
pub fn new(index: IngredientIndex) -> Self {
|
||||
Self {
|
||||
ingredient_index: index,
|
||||
counter: Default::default(),
|
||||
struct_map: StructMap::new(),
|
||||
singleton_index: AtomicCell::new(None),
|
||||
singleton_lock: Default::default(),
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn data<'db>(zalsa: &'db Zalsa, id: Id) -> &'db Value<C> {
|
||||
zalsa.table().get(id)
|
||||
}
|
||||
|
||||
fn data_raw<'db>(table: &'db Table, id: Id) -> *mut Value<C> {
|
||||
table.get_raw(id)
|
||||
}
|
||||
|
||||
pub fn database_key_index(&self, id: C::Struct) -> DatabaseKeyIndex {
|
||||
DatabaseKeyIndex {
|
||||
ingredient_index: self.ingredient_index,
|
||||
|
@ -95,19 +94,36 @@ impl<C: Configuration> IngredientImpl<C> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_input(&self, fields: C::Fields, stamps: C::Stamps) -> C::Struct {
|
||||
pub fn new_input(&self, db: &dyn Database, fields: C::Fields, stamps: C::Stamps) -> C::Struct {
|
||||
let (zalsa, zalsa_local) = db.zalsas();
|
||||
|
||||
// If declared as a singleton, only allow a single instance
|
||||
if C::IS_SINGLETON && self.counter.load(Ordering::Relaxed) >= 1 {
|
||||
panic!("singleton struct may not be duplicated");
|
||||
let guard = if C::IS_SINGLETON {
|
||||
let guard = self.singleton_lock.lock();
|
||||
if self.singleton_index.load().is_some() {
|
||||
panic!("singleton struct may not be duplicated");
|
||||
}
|
||||
Some(guard)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let id = zalsa_local.allocate(
|
||||
zalsa.table(),
|
||||
self.ingredient_index,
|
||||
Value::<C> {
|
||||
fields,
|
||||
stamps,
|
||||
memos: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
if C::IS_SINGLETON {
|
||||
self.singleton_index.store(Some(id));
|
||||
drop(guard);
|
||||
}
|
||||
|
||||
let next_id = Id::from_u32(self.counter.fetch_add(1, Ordering::Relaxed));
|
||||
let value = Value {
|
||||
id: next_id,
|
||||
fields,
|
||||
stamps,
|
||||
};
|
||||
self.struct_map.insert(value)
|
||||
FromId::from_id(id)
|
||||
}
|
||||
|
||||
/// Change the value of the field `field_index` to a new value.
|
||||
|
@ -128,7 +144,12 @@ impl<C: Configuration> IngredientImpl<C> {
|
|||
setter: impl FnOnce(&mut C::Fields) -> R,
|
||||
) -> R {
|
||||
let id: Id = id.as_id();
|
||||
let mut r = self.struct_map.update(id);
|
||||
let r = Self::data_raw(runtime.table(), id);
|
||||
|
||||
// SAFETY: We hold `&mut` on the runtime so no `&`-references can be active.
|
||||
// Also, we don't access any other data from the table while `r` is active.
|
||||
let r = unsafe { &mut *r };
|
||||
|
||||
let stamp = &mut r.stamps[field_index];
|
||||
|
||||
if stamp.durability != Durability::LOW {
|
||||
|
@ -146,7 +167,7 @@ impl<C: Configuration> IngredientImpl<C> {
|
|||
C::IS_SINGLETON,
|
||||
"get_singleton_input invoked on a non-singleton"
|
||||
);
|
||||
(self.counter.load(Ordering::Relaxed) > 0).then(|| C::Struct::from_id(Id::from_u32(0)))
|
||||
self.singleton_index.load().map(FromId::from_id)
|
||||
}
|
||||
|
||||
/// Access field of an input.
|
||||
|
@ -158,10 +179,10 @@ impl<C: Configuration> IngredientImpl<C> {
|
|||
id: C::Struct,
|
||||
field_index: usize,
|
||||
) -> &'db C::Fields {
|
||||
let zalsa_local = db.zalsa_local();
|
||||
let (zalsa, zalsa_local) = db.zalsas();
|
||||
let field_ingredient_index = self.ingredient_index.successor(field_index);
|
||||
let id = id.as_id();
|
||||
let value = self.struct_map.get(id);
|
||||
let value = Self::data(zalsa, id);
|
||||
let stamp = &value.stamps[field_index];
|
||||
zalsa_local.report_tracked_read(
|
||||
DependencyIndex {
|
||||
|
@ -176,9 +197,10 @@ impl<C: Configuration> IngredientImpl<C> {
|
|||
|
||||
/// Peek at the field values without recording any read dependency.
|
||||
/// Used for debug printouts.
|
||||
pub fn leak_fields(&self, id: C::Struct) -> &C::Fields {
|
||||
pub fn leak_fields<'db>(&'db self, db: &'db dyn Database, id: C::Struct) -> &'db C::Fields {
|
||||
let zalsa = db.zalsa();
|
||||
let id = id.as_id();
|
||||
let value = self.struct_map.get(id);
|
||||
let value = Self::data(zalsa, id);
|
||||
&value.fields
|
||||
}
|
||||
}
|
||||
|
@ -267,17 +289,26 @@ pub struct Value<C>
|
|||
where
|
||||
C: Configuration,
|
||||
{
|
||||
/// The id of this struct in the ingredient.
|
||||
id: Id,
|
||||
|
||||
/// Fields of this input struct. They can change across revisions,
|
||||
/// but they do not change within a particular revision.
|
||||
fields: C::Fields,
|
||||
|
||||
/// The revision and durability information for each field: when did this field last change.
|
||||
stamps: C::Stamps,
|
||||
|
||||
/// Memos
|
||||
memos: MemoTable,
|
||||
}
|
||||
|
||||
pub trait HasBuilder {
|
||||
type Builder;
|
||||
}
|
||||
|
||||
impl<C> Slot for Value<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
fn memos(&self) -> Option<&crate::table::memo::MemoTable> {
|
||||
Some(&self.memos)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,9 @@ use crate::zalsa::IngredientIndex;
|
|||
use crate::zalsa_local::QueryOrigin;
|
||||
use crate::{Database, DatabaseKeyIndex, Id, Revision};
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::struct_map::StructMap;
|
||||
use super::{IngredientImpl, Value};
|
||||
|
||||
/// Ingredient used to represent the fields of a `#[salsa::input]`.
|
||||
///
|
||||
|
@ -20,22 +21,18 @@ use super::struct_map::StructMap;
|
|||
pub struct FieldIngredientImpl<C: Configuration> {
|
||||
index: IngredientIndex,
|
||||
field_index: usize,
|
||||
struct_map: StructMap<C>,
|
||||
phantom: PhantomData<fn() -> Value<C>>,
|
||||
}
|
||||
|
||||
impl<C> FieldIngredientImpl<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
pub(super) fn new(
|
||||
struct_index: IngredientIndex,
|
||||
field_index: usize,
|
||||
struct_map: StructMap<C>,
|
||||
) -> Self {
|
||||
pub(super) fn new(struct_index: IngredientIndex, field_index: usize) -> Self {
|
||||
Self {
|
||||
index: struct_index.successor(field_index),
|
||||
field_index,
|
||||
struct_map,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,12 +51,14 @@ where
|
|||
|
||||
fn maybe_changed_after(
|
||||
&self,
|
||||
_db: &dyn Database,
|
||||
db: &dyn Database,
|
||||
input: Option<Id>,
|
||||
revision: Revision,
|
||||
) -> bool {
|
||||
let zalsa = db.zalsa();
|
||||
let input = input.unwrap();
|
||||
self.struct_map.get(input).stamps[self.field_index].changed_at > revision
|
||||
let value = <IngredientImpl<C>>::data(zalsa, input);
|
||||
value.stamps[self.field_index].changed_at > revision
|
||||
}
|
||||
|
||||
fn origin(&self, _db: &dyn Database, _key_index: Id) -> Option<QueryOrigin> {
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
use std::{ops::DerefMut, sync::Arc};
|
||||
|
||||
use dashmap::mapref::one::RefMut;
|
||||
|
||||
use crate::{alloc::Alloc, hash::FxDashMap, id::FromId, Id};
|
||||
|
||||
use super::{Configuration, Value};
|
||||
|
||||
/// Maps an input id to its data.
|
||||
///
|
||||
/// Input structs can only be mutated by a call to a setter with an `&mut`
|
||||
/// reference to the database, and therefore cannot be mutated during a tracked
|
||||
/// function or in parallel.
|
||||
///
|
||||
/// However for on-demand inputs to work the fields must be able to be set via
|
||||
/// a shared reference, so some locking is required.
|
||||
///
|
||||
/// Altogether this makes the implementation somewhat simpler than tracked
|
||||
/// structs.
|
||||
pub(crate) struct StructMap<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
map: Arc<FxDashMap<Id, Alloc<Value<C>>>>,
|
||||
}
|
||||
|
||||
impl<C: Configuration> Clone for StructMap<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
map: self.map.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> StructMap<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: Arc::new(FxDashMap::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert the given tracked struct value into the map.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// * If value with same `value.id` is already present in the map.
|
||||
/// * If value not created in current revision.
|
||||
pub fn insert(&self, value: Value<C>) -> C::Struct {
|
||||
let id = value.id;
|
||||
let boxed_value = Alloc::new(value);
|
||||
let old_value = self.map.insert(id, boxed_value);
|
||||
assert!(old_value.is_none()); // ...strictly speaking we probably need to abort here
|
||||
C::Struct::from_id(id)
|
||||
}
|
||||
|
||||
/// Get immutable access to the data for `id` -- this holds a write lock for the duration
|
||||
/// of the returned value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// * If the value is not present in the map.
|
||||
/// * If the value is already updated in this revision.
|
||||
pub fn get(&self, id: Id) -> &Value<C> {
|
||||
/// More limited wrapper around transmute that copies lifetime from `a` to `b`.
|
||||
///
|
||||
/// # Safety condition
|
||||
///
|
||||
/// `b` must be owned by `a`
|
||||
unsafe fn transmute_lifetime<'a, A, B>(_a: &'a A, b: &B) -> &'a B {
|
||||
std::mem::transmute(b)
|
||||
}
|
||||
unsafe { transmute_lifetime(self, self.map.get(&id).unwrap().as_ref()) }
|
||||
}
|
||||
|
||||
/// Get mutable access to the data for `id` -- this holds a write lock for the duration
|
||||
/// of the returned value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// * If the value is not present in the map.
|
||||
/// * If the value is already updated in this revision.
|
||||
pub fn update(&mut self, id: Id) -> impl DerefMut<Target = Value<C>> + '_ {
|
||||
RefMut::map(self.map.get_mut(&id).unwrap(), |v| unsafe { v.as_mut() })
|
||||
}
|
||||
}
|
|
@ -9,8 +9,8 @@ use parking_lot::Mutex;
|
|||
|
||||
use crate::{
|
||||
active_query::ActiveQuery, cycle::CycleRecoveryStrategy, durability::Durability,
|
||||
key::DatabaseKeyIndex, revision::AtomicRevision, zalsa_local::ZalsaLocal, Cancelled, Cycle,
|
||||
Database, Event, EventKind, Revision,
|
||||
key::DatabaseKeyIndex, revision::AtomicRevision, table::Table, zalsa_local::ZalsaLocal,
|
||||
Cancelled, Cycle, Database, Event, EventKind, Revision,
|
||||
};
|
||||
|
||||
use self::dependency_graph::DependencyGraph;
|
||||
|
@ -40,6 +40,9 @@ pub struct Runtime {
|
|||
/// The dependency graph tracks which runtimes are blocked on one
|
||||
/// another, waiting for queries to terminate.
|
||||
dependency_graph: Mutex<DependencyGraph>,
|
||||
|
||||
/// Data for instances
|
||||
table: Table,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -84,6 +87,7 @@ impl Default for Runtime {
|
|||
next_id: AtomicUsize::new(1),
|
||||
revision_canceled: Default::default(),
|
||||
dependency_graph: Default::default(),
|
||||
table: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +138,14 @@ impl Runtime {
|
|||
self.revision_canceled.store(true);
|
||||
}
|
||||
|
||||
pub(crate) fn table(&self) -> &Table {
|
||||
&self.table
|
||||
}
|
||||
|
||||
pub(crate) fn table_mut(&mut self) -> &mut Table {
|
||||
&mut self.table
|
||||
}
|
||||
|
||||
/// Increments the "current revision" counter and clears
|
||||
/// the cancellation flag.
|
||||
///
|
||||
|
|
85
src/table.rs
85
src/table.rs
|
@ -9,7 +9,10 @@ use crossbeam::atomic::AtomicCell;
|
|||
use memo::MemoTable;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{zalsa::transmute_data_ptr, Id, IngredientIndex};
|
||||
use crate::{
|
||||
zalsa::{transmute_data_ptr, transmute_data_ptr_mut},
|
||||
Id, IngredientIndex,
|
||||
};
|
||||
|
||||
pub(crate) mod memo;
|
||||
|
||||
|
@ -75,22 +78,62 @@ impl Default for Table {
|
|||
}
|
||||
|
||||
impl Table {
|
||||
/// Get a reference to the data for `id`, which must have been allocated from this table with type `T`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `id` is out of bounds or the does not have the type `T`.
|
||||
pub fn get<T: Slot>(&self, id: Id) -> &T {
|
||||
let (page, slot) = split_id(id);
|
||||
let page_ref = self.page::<T>(page);
|
||||
page_ref.get(slot)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the data for `id`, which must have been allocated from this table with type `T`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `id` is out of bounds or the does not have the type `T`.
|
||||
pub fn get_mut<T: Slot>(&mut self, id: Id) -> &mut T {
|
||||
let (page, slot) = split_id(id);
|
||||
let page_ref = self.page_mut::<T>(page);
|
||||
page_ref.get_mut(slot)
|
||||
}
|
||||
|
||||
/// Get a raw pointer to the data for `id`, which must have been allocated from this table.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `id` is out of bounds or the does not have the type `T`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See [`Page::get_raw`][].
|
||||
pub fn get_raw<T: Slot>(&self, id: Id) -> *mut T {
|
||||
let (page, slot) = split_id(id);
|
||||
let page_ref = self.page::<T>(page);
|
||||
page_ref.get_raw(slot)
|
||||
}
|
||||
|
||||
/// Gets a reference to the page which has slots of type `T`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `page` is out of bounds or the type `T` is incorrect.
|
||||
pub fn page<T: Slot>(&self, page: PageIndex) -> &Page<T> {
|
||||
self.pages[page.0].assert_type::<Page<T>>()
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the page which has slots of type `T`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `page` is out of bounds or the type `T` is incorrect.
|
||||
fn page_mut<T: Slot>(&mut self, page: PageIndex) -> &mut Page<T> {
|
||||
self.pages[page.0].assert_type_mut::<Page<T>>()
|
||||
}
|
||||
|
||||
/// Allocate a new page for the given ingredient and with slots of type `T`
|
||||
pub fn push_page<T: Slot>(&self, ingredient: IngredientIndex) -> PageIndex {
|
||||
let page = Box::new(<Page<T>>::new(ingredient));
|
||||
PageIndex(self.pages.push(page))
|
||||
|
@ -125,13 +168,36 @@ impl<T: Slot> Page<T> {
|
|||
);
|
||||
}
|
||||
|
||||
/// Returns a reference to the given slot.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If slot is out of bounds
|
||||
pub(crate) fn get(&self, slot: SlotIndex) -> &T {
|
||||
self.check_bounds(slot);
|
||||
unsafe { &*self.data[slot.0].get() }
|
||||
}
|
||||
|
||||
/// Returns a mut reference to the given slot.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If slot is out of bounds
|
||||
pub(crate) fn get_mut(&mut self, slot: SlotIndex) -> &mut T {
|
||||
self.check_bounds(slot);
|
||||
self.data[slot.0].get_mut()
|
||||
}
|
||||
|
||||
/// Returns a raw pointer to the given slot.
|
||||
/// Reads/writes must be coordinated properly with calls to `get`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If slot is out of bounds
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Safe to call, but reads/writes through this pointer must be coordinated
|
||||
/// properly with calls to [`get`](`Self::get`) and [`get_mut`](`Self::get_mut`).
|
||||
pub(crate) fn get_raw(&self, slot: SlotIndex) -> *mut T {
|
||||
self.check_bounds(slot);
|
||||
self.data[slot.0].get()
|
||||
|
@ -183,7 +249,7 @@ impl<T: Slot> Drop for Page<T> {
|
|||
impl dyn TablePage {
|
||||
fn assert_type<T: Any>(&self) -> &T {
|
||||
assert_eq!(
|
||||
self.type_id(),
|
||||
Any::type_id(self),
|
||||
TypeId::of::<T>(),
|
||||
"page has hidden type `{:?}` but `{:?}` was expected",
|
||||
self.hidden_type_name(),
|
||||
|
@ -193,6 +259,19 @@ impl dyn TablePage {
|
|||
// SAFETY: Assertion above
|
||||
unsafe { transmute_data_ptr::<dyn TablePage, T>(self) }
|
||||
}
|
||||
|
||||
fn assert_type_mut<T: Any>(&mut self) -> &mut T {
|
||||
assert_eq!(
|
||||
Any::type_id(self),
|
||||
TypeId::of::<T>(),
|
||||
"page has hidden type `{:?}` but `{:?}` was expected",
|
||||
self.hidden_type_name(),
|
||||
std::any::type_name::<T>(),
|
||||
);
|
||||
|
||||
// SAFETY: Assertion above
|
||||
unsafe { transmute_data_ptr_mut::<dyn TablePage, T>(self) }
|
||||
}
|
||||
}
|
||||
|
||||
fn make_id(page: PageIndex, slot: SlotIndex) -> Id {
|
||||
|
|
17
src/zalsa.rs
17
src/zalsa.rs
|
@ -132,9 +132,6 @@ pub struct Zalsa {
|
|||
/// The runtime for this particular salsa database handle.
|
||||
/// Each handle gets its own runtime, but the runtimes have shared state between them.
|
||||
runtime: Runtime,
|
||||
|
||||
/// Data for instances
|
||||
table: Table,
|
||||
}
|
||||
|
||||
impl Zalsa {
|
||||
|
@ -146,7 +143,6 @@ impl Zalsa {
|
|||
ingredients_vec: AppendOnlyVec::new(),
|
||||
ingredients_requiring_reset: AppendOnlyVec::new(),
|
||||
runtime: Runtime::default(),
|
||||
table: Table::default(),
|
||||
memo_ingredient_count: AtomicCell::new(0),
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +156,7 @@ impl Zalsa {
|
|||
}
|
||||
|
||||
pub(crate) fn table(&self) -> &Table {
|
||||
&self.table
|
||||
self.runtime.table()
|
||||
}
|
||||
|
||||
/// **NOT SEMVER STABLE**
|
||||
|
@ -349,3 +345,14 @@ pub(crate) unsafe fn transmute_data_ptr<T: ?Sized, U>(t: &T) -> &U {
|
|||
let u: *const U = t as *const U;
|
||||
unsafe { &*u }
|
||||
}
|
||||
|
||||
/// Given a wide pointer `T`, extracts the data pointer (typed as `U`).
|
||||
///
|
||||
/// # Safety requirement
|
||||
///
|
||||
/// `U` must be correct type for the data pointer.
|
||||
pub(crate) unsafe fn transmute_data_ptr_mut<T: ?Sized, U>(t: &mut T) -> &mut U {
|
||||
let t: *mut T = t;
|
||||
let u: *mut U = t as *mut U;
|
||||
unsafe { &mut *u }
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ fn input() {
|
|||
|
||||
// debug includes all fields
|
||||
let actual = format!("{complex_struct:?}");
|
||||
let expected = expect![[r#"ComplexStruct { [salsa id]: Id(0), my_input: MyInput { [salsa id]: Id(0), field: 22 }, not_salsa: NotSalsa { field: "it's salsa time" } }"#]];
|
||||
let expected = expect![[r#"ComplexStruct { [salsa id]: Id(400), my_input: MyInput { [salsa id]: Id(0), field: 22 }, not_salsa: NotSalsa { field: "it's salsa time" } }"#]];
|
||||
expected.assert_eq(&actual);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -80,11 +80,11 @@ fn basic() {
|
|||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"intermediate_result(MyInput { [salsa id]: Id(0), field: 2 })",
|
||||
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(Id(0)), output_key: MyTracked(Id(2)) })",
|
||||
"salsa_event(DidDiscard { key: MyTracked(Id(2)) })",
|
||||
"salsa_event(DidDiscard { key: contribution_from_struct(Id(2)) })",
|
||||
"salsa_event(DidDiscard { key: MyTracked(Id(5)) })",
|
||||
"salsa_event(DidDiscard { key: copy_field(Id(5)) })",
|
||||
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(Id(0)), output_key: MyTracked(Id(402)) })",
|
||||
"salsa_event(DidDiscard { key: MyTracked(Id(402)) })",
|
||||
"salsa_event(DidDiscard { key: contribution_from_struct(Id(402)) })",
|
||||
"salsa_event(DidDiscard { key: MyTracked(Id(405)) })",
|
||||
"salsa_event(DidDiscard { key: copy_field(Id(405)) })",
|
||||
"final_result(MyInput { [salsa id]: Id(0), field: 2 })",
|
||||
]"#]]);
|
||||
}
|
||||
|
|
|
@ -67,9 +67,9 @@ fn basic() {
|
|||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"intermediate_result(MyInput { [salsa id]: Id(0), field: 2 })",
|
||||
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(Id(0)), output_key: MyTracked(Id(2)) })",
|
||||
"salsa_event(DidDiscard { key: MyTracked(Id(2)) })",
|
||||
"salsa_event(DidDiscard { key: contribution_from_struct(Id(2)) })",
|
||||
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(Id(0)), output_key: MyTracked(Id(402)) })",
|
||||
"salsa_event(DidDiscard { key: MyTracked(Id(402)) })",
|
||||
"salsa_event(DidDiscard { key: contribution_from_struct(Id(402)) })",
|
||||
"final_result(MyInput { [salsa id]: Id(0), field: 2 })",
|
||||
]"#]]);
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ fn test_leaked_inputs_ignored() {
|
|||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: function(Id(0)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: counter_field(Id(400)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: counter_field(Id(800)) } }",
|
||||
]"#]]);
|
||||
|
||||
assert_eq!(result_in_rev_1, (0, 0));
|
||||
|
@ -82,7 +82,7 @@ fn test_leaked_inputs_ignored() {
|
|||
"Event { thread_id: ThreadId(2), kind: DidSetCancellationFlag }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: counter_field(Id(400)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: counter_field(Id(800)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: function(Id(0)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
]"#]]);
|
||||
|
|
|
@ -61,7 +61,7 @@ fn test_leaked_inputs_ignored() {
|
|||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: function(Id(0)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: counter_field(Id(0)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: counter_field(Id(400)) } }",
|
||||
]"#]]);
|
||||
|
||||
assert_eq!(result_in_rev_1, (0, 0));
|
||||
|
@ -79,7 +79,7 @@ fn test_leaked_inputs_ignored() {
|
|||
"Event { thread_id: ThreadId(2), kind: DidSetCancellationFlag }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
"Event { thread_id: ThreadId(2), kind: DidValidateMemoizedValue { database_key: counter_field(Id(0)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: DidValidateMemoizedValue { database_key: counter_field(Id(400)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillExecute { database_key: function(Id(0)) } }",
|
||||
"Event { thread_id: ThreadId(2), kind: WillCheckCancellation }",
|
||||
]"#]]);
|
||||
|
|
|
@ -64,7 +64,7 @@ fn execute() {
|
|||
[
|
||||
"salsa_event(WillExecute { database_key: the_fn(Id(0)) })",
|
||||
"salsa_event(WillExecute { database_key: make_tracked_struct(Id(0)) })",
|
||||
"salsa_event(WillExecute { database_key: read_tracked_struct(Id(0)) })",
|
||||
"salsa_event(WillExecute { database_key: read_tracked_struct(Id(400)) })",
|
||||
]"#]]);
|
||||
|
||||
// Update the input to `false` and re-execute.
|
||||
|
@ -79,7 +79,7 @@ fn execute() {
|
|||
db.assert_logs(expect![[r#"
|
||||
[
|
||||
"salsa_event(WillExecute { database_key: make_tracked_struct(Id(0)) })",
|
||||
"salsa_event(DidValidateMemoizedValue { database_key: read_tracked_struct(Id(0)) })",
|
||||
"salsa_event(DidValidateMemoizedValue { database_key: read_tracked_struct(Id(400)) })",
|
||||
"salsa_event(DidValidateMemoizedValue { database_key: the_fn(Id(0)) })",
|
||||
]"#]]);
|
||||
}
|
||||
|
|
|
@ -36,14 +36,14 @@ fn execute() {
|
|||
let t1 = create_tracked_list(db, input);
|
||||
expect_test::expect![[r#"
|
||||
MyTracked {
|
||||
[salsa id]: Id(1),
|
||||
[salsa id]: Id(401),
|
||||
data: MyInput {
|
||||
[salsa id]: Id(0),
|
||||
field: "foo",
|
||||
},
|
||||
next: Next(
|
||||
MyTracked {
|
||||
[salsa id]: Id(0),
|
||||
[salsa id]: Id(400),
|
||||
data: MyInput {
|
||||
[salsa id]: Id(0),
|
||||
field: "foo",
|
||||
|
|
Loading…
Reference in a new issue