Skip to content

Commit

Permalink
Eliminate the Value wrapper around DDlog values.
Browse files Browse the repository at this point in the history
This commit eliminates the `Value` wrapper around DDlog types stored in
relations.  The wrapper was necessary in order to implement the
`DDValConvert` trait for such types in order to convert them between
normal and type-erased representation.  This design was ugly and
expensive.  Most importantly, the wrapper was visible via the Rust API
and required the user to type auto-generated struct names like
`__Tuple2__internment_Intern____Stringval_internment_Intern____Stringval`.
In addition, auto-derived trait implementations for the wrapper types
(especially serde traits) increased compilation time.  Finally, all of
this junk lived in a separate `value` crate that had to be imported by
clients.

An alternative to the wrapper is to implement `trait DDlogConvert` for
the actual DDlog types rather than wrapping them, which is tricky due to
Rust's orphan rules: a trait must be implemented either in the crate
that declares the trait (in this case, `differential_datalog`) or in the
crate that declares the implementing type.  This does not work for
tuples: since `DDlogConvert` can not be implemented for a generic type,
we cannot provide generic implementations for all tuples in
`differential_datalog`.  On the other hand, we also cannot implement it
in the `types` crate, since tuples are a builtin Rust type.

The workaround is to replace tuples with custom tuple structs
`tuple2<T1,T2>`, `tuple3<T1,T2,T3>`.  We already declare those in the
`types` crate, but previously only used them for tuples with >12 fields,
for which Rust does not implement the various crates we care about.  So
the only change required was to map all DDlog tuple types to `tupleN`
instead of Rust tuples.  We also has to change a bunch of Rust libraries
to work with this convention.

With this change, we can eliminate the `Value` wrapper and place all
`DDlogConvert` implementations either in the `differential_datalog`
crate (for Rust types like `uNN`, `iNN`, `String`, `bool`, ...) or
in the `types` crate.

We more remaining contents of the `value` crate to the top-level
generated crate and get rid of `value` altogether.
  • Loading branch information
ryzhyk committed Oct 27, 2020
1 parent 2b91ed7 commit 56b0da1
Show file tree
Hide file tree
Showing 30 changed files with 482 additions and 598 deletions.
6 changes: 1 addition & 5 deletions doc/tutorial/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,7 @@ When type annotations are missing, the compiler relies on
expression. DDlog allows writing lambda expressions using the `function` keyword
instead of vertical bars (`||`):
```
```
v.map(function(x: s64):s64 { x * n })
```
Expand Down Expand Up @@ -2666,10 +2666,6 @@ prog_ddlog
| | +--+submod2.rs |
| +--+lib.rs |
| +-+
+----+value +-+
| +--+Cargo.toml | value crate:
| +--+lib.rs | wrapper types
| +-+
| +-+
+----+Cargo.toml |
+----+src | main crate:
Expand Down
31 changes: 18 additions & 13 deletions lib/allocate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub fn allocate<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Copy>(
toallocate: &crate::ddlog_std::Vec<B>,
min_val: &N,
max_val: &N,
) -> crate::ddlog_std::Vec<(B, N)> {
) -> crate::ddlog_std::Vec<crate::ddlog_std::tuple2<B, N>> {
assert!(*max_val >= *min_val);
let range = *max_val - *min_val;

Expand All @@ -34,7 +34,7 @@ pub fn allocate<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Copy>(
offset = offset + N::one();
continue;
} else {
res.x.push(((*b).clone(), next));
res.x.push(crate::ddlog_std::tuple2((*b).clone(), next));
if offset == range {
return res;
};
Expand All @@ -48,10 +48,10 @@ pub fn allocate<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Copy>(

pub fn allocate_with_hint<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Copy>(
allocated: &crate::ddlog_std::Set<N>,
toallocate: &crate::ddlog_std::Vec<(B, crate::ddlog_std::Option<N>)>,
toallocate: &crate::ddlog_std::Vec<crate::ddlog_std::tuple2<B, crate::ddlog_std::Option<N>>>,
min_val: &N,
max_val: &N,
) -> crate::ddlog_std::Vec<(B, N)> {
) -> crate::ddlog_std::Vec<crate::ddlog_std::tuple2<B, N>> {
assert!(*max_val >= *min_val);
let range = *max_val - *min_val;
let mut new_allocations: BTreeSet<N> = BTreeSet::new();
Expand All @@ -63,7 +63,7 @@ pub fn allocate_with_hint<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Co
next = *min_val;
};
let mut res = crate::ddlog_std::Vec::new();
for (b, hint) in toallocate.x.iter() {
for crate::ddlog_std::tuple2(b, hint) in toallocate.x.iter() {
let mut offset = N::zero();
next = match hint {
crate::ddlog_std::Option::None => next,
Expand All @@ -82,7 +82,7 @@ pub fn allocate_with_hint<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Co
};
continue;
} else {
res.x.push(((*b).clone(), next));
res.x.push(crate::ddlog_std::tuple2((*b).clone(), next));
new_allocations.insert(next);
if offset == range {
return res;
Expand All @@ -104,7 +104,7 @@ pub fn allocate_opt<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Copy>(
toallocate: &crate::ddlog_std::Vec<B>,
min_val: &N,
max_val: &N,
) -> crate::ddlog_std::Vec<(B, crate::ddlog_std::Option<N>)> {
) -> crate::ddlog_std::Vec<crate::ddlog_std::tuple2<B, crate::ddlog_std::Option<N>>> {
assert!(*max_val >= *min_val);
let range = *max_val - *min_val;

Expand All @@ -122,7 +122,10 @@ pub fn allocate_opt<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Copy>(
for b in toallocate.x.iter() {
loop {
if exhausted {
res.x.push(((*b).clone(), crate::ddlog_std::Option::None));
res.x.push(crate::ddlog_std::tuple2(
(*b).clone(),
crate::ddlog_std::Option::None,
));
break;
};
if offset == range {
Expand All @@ -138,8 +141,10 @@ pub fn allocate_opt<B: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Copy>(
if allocated.x.contains(&next) {
continue;
} else {
res.x
.push(((*b).clone(), crate::ddlog_std::Option::Some { x: next }));
res.x.push(crate::ddlog_std::tuple2(
(*b).clone(),
crate::ddlog_std::Option::Some { x: next },
));
break;
}
}
Expand All @@ -152,7 +157,7 @@ pub fn adjust_allocation<A: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Cop
toallocate: &crate::ddlog_std::Vec<A>,
min_val: &N,
max_val: &N,
) -> crate::ddlog_std::Vec<(A, N)> {
) -> crate::ddlog_std::Vec<crate::ddlog_std::tuple2<A, N>> {
assert!(*max_val >= *min_val);
let range = *max_val - *min_val;

Expand All @@ -170,7 +175,7 @@ pub fn adjust_allocation<A: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Cop
for b in toallocate.x.iter() {
match allocated.x.get(b) {
Some(x) => {
res.push(((*b).clone(), x.clone()));
res.push(crate::ddlog_std::tuple2((*b).clone(), x.clone()));
}
None => loop {
if exhausted {
Expand All @@ -189,7 +194,7 @@ pub fn adjust_allocation<A: Ord + Clone, N: num::Num + ops::Add + cmp::Ord + Cop
if allocated_ids.contains(&next) {
continue;
} else {
res.x.push(((*b).clone(), next));
res.x.push(crate::ddlog_std::tuple2((*b).clone(), next));
break;
}
},
Expand Down
166 changes: 147 additions & 19 deletions lib/ddlog_std.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/// Rust implementation of DDlog standard library functions and types.
use differential_datalog::arcval;
use differential_datalog::ddval::DDValue;
use differential_datalog::decl_record_mutator_struct;
use differential_datalog::decl_struct_from_record;
Expand Down Expand Up @@ -30,6 +29,7 @@ use std::ops::Deref;
use std::option;
use std::result;
use std::slice;
use std::sync::Arc;
use std::vec;

#[cfg(feature = "flatbuf")]
Expand Down Expand Up @@ -68,10 +68,111 @@ pub fn result_unwrap_or_default<T: Default + Clone, E>(res: &Result<T, E>) -> T
}

// Ref
pub type Ref<A> = arcval::ArcVal<A>;

#[derive(Eq, PartialOrd, PartialEq, Ord, Clone, Hash)]
pub struct Ref<T> {
x: Arc<T>,
}

impl<T: Default> Default for Ref<T> {
fn default() -> Self {
Self {
x: Arc::new(T::default()),
}
}
}

impl<T> Deref for Ref<T> {
type Target = T;

fn deref(&self) -> &T {
&*self.x
}
}

impl<T> From<T> for Ref<T> {
fn from(x: T) -> Self {
Self { x: Arc::new(x) }
}
}

impl<T: abomonation::Abomonation> abomonation::Abomonation for Ref<T> {
unsafe fn entomb<W: ::std::io::Write>(&self, write: &mut W) -> ::std::io::Result<()> {
self.deref().entomb(write)
}
unsafe fn exhume<'a, 'b>(
&'a mut self,
bytes: &'b mut [u8],
) -> ::std::option::Option<&'b mut [u8]> {
Arc::get_mut(&mut self.x).unwrap().exhume(bytes)
}
fn extent(&self) -> usize {
self.deref().extent()
}
}

impl<T> Ref<T> {
pub fn get_mut(this: &mut Self) -> ::std::option::Option<&mut T> {
Arc::get_mut(&mut this.x)
}
}

impl<T: fmt::Display> fmt::Display for Ref<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.deref().fmt(f)
}
}

impl<T: fmt::Debug> fmt::Debug for Ref<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.deref().fmt(f)
}
}

impl<T: Serialize> Serialize for Ref<T> {
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
self.deref().serialize(serializer)
}
}

impl<'de, T: Deserialize<'de>> Deserialize<'de> for Ref<T> {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Ref<T>, D::Error>
where
D: Deserializer<'de>,
{
T::deserialize(deserializer).map(Self::from)
}
}

impl<T: FromRecord> FromRecord for Ref<T> {
fn from_record(val: &Record) -> ::std::result::Result<Self, String> {
T::from_record(val).map(Self::from)
}
}

impl<T: IntoRecord + Clone> IntoRecord for Ref<T> {
fn into_record(self) -> Record {
(*self.x).clone().into_record()
}
}

impl<T: Clone> Mutator<Ref<T>> for Record
where
Record: Mutator<T>,
{
fn mutate(&self, arc: &mut Ref<T>) -> ::std::result::Result<(), String> {
let mut copy: T = (*arc).deref().clone();
self.mutate(&mut copy)?;
*arc = Ref::from(copy);
Ok(())
}
}

pub fn ref_new<A: Clone>(x: &A) -> Ref<A> {
arcval::ArcVal::from(x.clone())
Ref::from(x.clone())
}

pub fn deref<A: Clone>(x: &Ref<A>) -> &A {
Expand Down Expand Up @@ -583,13 +684,13 @@ pub fn vec_update_nth<X: Clone>(v: &mut Vec<X>, idx: &std_usize, value: &X) -> b
return false;
}

pub fn vec_zip<X: Clone, Y: Clone>(v1: &Vec<X>, v2: &Vec<Y>) -> Vec<(X, Y)> {
pub fn vec_zip<X: Clone, Y: Clone>(v1: &Vec<X>, v2: &Vec<Y>) -> Vec<tuple2<X, Y>> {
Vec {
x: v1
.x
.iter()
.zip(v2.x.iter())
.map(|(x, y)| (x.clone(), y.clone()))
.map(|(x, y)| tuple2(x.clone(), y.clone()))
.collect(),
}
}
Expand Down Expand Up @@ -877,10 +978,10 @@ impl<'a, K: Ord, V> MapIter<'a, K, V> {
}

impl<'a, K: Clone, V: Clone> Iterator for MapIter<'a, K, V> {
type Item = (K, V);
type Item = tuple2<K, V>;

fn next(&mut self) -> ::std::option::Option<Self::Item> {
self.iter.next().map(|(k, v)| (k.clone(), v.clone()))
self.iter.next().map(|(k, v)| tuple2(k.clone(), v.clone()))
}

fn size_hint(&self) -> (usize, ::std::option::Option<usize>) {
Expand Down Expand Up @@ -921,11 +1022,35 @@ impl<K: FromRecord + Ord, V: FromRecord + PartialEq> Mutator<Map<K, V>> for Reco
}
}

pub struct MapIntoIter<K, V> {
iter: btree_map::IntoIter<K, V>,
}

impl<K: Ord, V> MapIntoIter<K, V> {
pub fn new(map: Map<K, V>) -> MapIntoIter<K, V> {
MapIntoIter {
iter: map.x.into_iter(),
}
}
}

impl<K, V> Iterator for MapIntoIter<K, V> {
type Item = tuple2<K, V>;

fn next(&mut self) -> ::std::option::Option<Self::Item> {
self.iter.next().map(|(k, v)| tuple2(k, v))
}

fn size_hint(&self) -> (usize, ::std::option::Option<usize>) {
self.iter.size_hint()
}
}

impl<K: Ord, V> IntoIterator for Map<K, V> {
type Item = (K, V);
type IntoIter = btree_map::IntoIter<K, V>;
type Item = tuple2<K, V>;
type IntoIter = MapIntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
self.x.into_iter()
Self::IntoIter::new(self)
}
}

Expand Down Expand Up @@ -975,12 +1100,12 @@ impl<'a, K, V, F> FromFlatBuffer<fbrt::Vector<'a, F>> for Map<K, V>
where
F: fbrt::Follow<'a> + 'a,
K: Ord,
(K, V): FromFlatBuffer<F::Inner>,
tuple2<K, V>: FromFlatBuffer<F::Inner>,
{
fn from_flatbuf(fb: fbrt::Vector<'a, F>) -> ::std::result::Result<Self, String> {
let mut m = Map::new();
for x in FBIter::from_vector(fb) {
let (k, v) = <(K, V)>::from_flatbuf(x)?;
let tuple2(k, v) = <tuple2<K, V>>::from_flatbuf(x)?;
m.insert(k, v);
}
Ok(m)
Expand All @@ -992,15 +1117,15 @@ impl<'b, K, V, T> ToFlatBuffer<'b> for Map<K, V>
where
K: Ord + Clone,
V: Clone,
(K, V): ToFlatBufferVectorElement<'b, Target = T>,
tuple2<K, V>: ToFlatBufferVectorElement<'b, Target = T>,
T: 'b + fbrt::Push + Copy,
{
type Target = fbrt::WIPOffset<fbrt::Vector<'b, <T as fbrt::Push>::Output>>;

fn to_flatbuf(&self, fbb: &mut fbrt::FlatBufferBuilder<'b>) -> Self::Target {
let vec: ::std::vec::Vec<<(K, V) as ToFlatBufferVectorElement<'b>>::Target> = self
let vec: ::std::vec::Vec<<tuple2<K, V> as ToFlatBufferVectorElement<'b>>::Target> = self
.iter()
.map(|(k, v)| (k, v).to_flatbuf_vector_element(fbb))
.map(|tuple2(k, v)| tuple2(k, v).to_flatbuf_vector_element(fbb))
.collect();
fbb.create_vector(vec.as_slice())
}
Expand Down Expand Up @@ -1547,19 +1672,19 @@ pub fn group_to_vec<K, V: Ord + Clone>(g: &Group<K, V>) -> Vec<V> {
res
}

pub fn group_to_map<K1, K2: Ord + Clone, V: Clone>(g: &Group<K1, (K2, V)>) -> Map<K2, V> {
pub fn group_to_map<K1, K2: Ord + Clone, V: Clone>(g: &Group<K1, tuple2<K2, V>>) -> Map<K2, V> {
let mut res = Map::new();
for (k, v) in g.iter() {
for tuple2(k, v) in g.iter() {
map_insert(&mut res, &k, &v);
}
res
}

pub fn group_to_setmap<K1, K2: Ord + Clone, V: Clone + Ord>(
g: &Group<K1, (K2, V)>,
g: &Group<K1, tuple2<K2, V>>,
) -> Map<K2, Set<V>> {
let mut res = Map::new();
for (k, v) in g.iter() {
for tuple2(k, v) in g.iter() {
match res.x.entry(k) {
btree_map::Entry::Vacant(ve) => {
ve.insert(set_singleton(&v));
Expand Down Expand Up @@ -1608,6 +1733,9 @@ macro_rules! decl_tuple {
( $name:ident, $( $t:tt ),+ ) => {
#[derive(Default, Eq, Ord, Clone, Hash, PartialEq, PartialOrd, Serialize, Deserialize, Debug)]
pub struct $name< $($t),* >($(pub $t),*);

impl <$($t),*> abomonation::Abomonation for $name<$($t),*>{}

impl <$($t: FromRecord),*> FromRecord for $name<$($t),*> {
fn from_record(val: &Record) -> ::std::result::Result<Self, String> {
<($($t),*)>::from_record(val).map(|($($t),*)|$name($($t),*))
Expand Down
Loading

0 comments on commit 56b0da1

Please sign in to comment.