Skip to content

Commit efd0b0e

Browse files
committed
Tracking prototype
1 parent 67a7569 commit efd0b0e

File tree

6 files changed

+365
-104
lines changed

6 files changed

+365
-104
lines changed

examples/image.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use comemo::{Track, Tracked};
2+
3+
fn main() {
4+
let mut image = Image::new(20, 40);
5+
6+
// [Miss] The cache is empty.
7+
describe(image.track());
8+
9+
// [Hit] Everything stayed the same.
10+
describe(image.track());
11+
12+
image.resize(80, 30);
13+
14+
// [Miss] The image's width and height are different.
15+
describe(image.track());
16+
17+
image.resize(80, 70);
18+
image.pixels.fill(255);
19+
20+
// [Hit] The last call only read the width and it stayed the same.
21+
describe(image.track());
22+
}
23+
24+
/// Format the image's size humanly readable.
25+
#[comemo::memoize]
26+
fn describe(image: Tracked<Image>) -> &'static str {
27+
if image.width() > 50 || image.height() > 50 {
28+
"The image is big!"
29+
} else {
30+
"The image is small!"
31+
}
32+
}
33+
34+
/// A raster image.
35+
struct Image {
36+
width: u32,
37+
height: u32,
38+
pixels: Vec<u8>,
39+
}
40+
41+
impl Image {
42+
fn new(width: u32, height: u32) -> Self {
43+
Self {
44+
width,
45+
height,
46+
pixels: vec![0; (width * height) as usize],
47+
}
48+
}
49+
50+
fn resize(&mut self, width: u32, height: u32) {
51+
self.width = width;
52+
self.height = height;
53+
// Resize the actual image ...
54+
}
55+
}
56+
57+
#[comemo::track]
58+
impl Image {
59+
fn width(&self) -> u32 {
60+
self.width
61+
}
62+
63+
fn height(&self) -> u32 {
64+
self.height
65+
}
66+
}

macros/src/lib.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ mod track;
1414

1515
use proc_macro::TokenStream;
1616
use proc_macro2::Span;
17-
use quote::{quote, quote_spanned};
18-
use syn::spanned::Spanned;
17+
use quote::quote;
1918
use syn::{parse_quote, Error, Result};
2019

2120
/// Memoize a pure function.
@@ -31,7 +30,7 @@ use syn::{parse_quote, Error, Result};
3130
#[proc_macro_attribute]
3231
pub fn memoize(_: TokenStream, stream: TokenStream) -> TokenStream {
3332
let func = syn::parse_macro_input!(stream as syn::ItemFn);
34-
memoize::expand(func)
33+
memoize::expand(&func)
3534
.unwrap_or_else(|err| err.to_compile_error())
3635
.into()
3736
}
@@ -62,7 +61,7 @@ pub fn memoize(_: TokenStream, stream: TokenStream) -> TokenStream {
6261
#[proc_macro_attribute]
6362
pub fn track(_: TokenStream, stream: TokenStream) -> TokenStream {
6463
let block = syn::parse_macro_input!(stream as syn::ItemImpl);
65-
track::expand(block)
64+
track::expand(&block)
6665
.unwrap_or_else(|err| err.to_compile_error())
6766
.into()
6867
}

macros/src/memoize.rs

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::*;
22

33
/// Make a type trackable.
4-
pub fn expand(mut func: syn::ItemFn) -> Result<proc_macro2::TokenStream> {
4+
pub fn expand(func: &syn::ItemFn) -> Result<proc_macro2::TokenStream> {
55
let name = func.sig.ident.to_string();
66

77
let mut args = vec![];
@@ -24,51 +24,25 @@ pub fn expand(mut func: syn::ItemFn) -> Result<proc_macro2::TokenStream> {
2424
let mut inner = func.clone();
2525
inner.sig.ident = syn::Ident::new("inner", Span::call_site());
2626

27-
let asserts = args.iter().map(|ident| {
28-
quote_spanned! {
29-
ident.span() =>
30-
argument_must_be_hash_or_tracked(&#ident);
31-
}
32-
});
33-
34-
let hashes = args.iter().map(|ident| {
27+
let cts = args.iter().map(|arg| {
3528
quote! {
36-
#ident.hash(&mut state);
29+
Validate::constraint(&#arg)
3730
}
3831
});
3932

40-
func.block = parse_quote! { { #inner {
41-
fn argument_must_be_hash_or_tracked<T: std::hash::Hash>(_: &T) {}
42-
#(#asserts)*
43-
44-
use std::collections::HashMap;
45-
use std::hash::Hash;
33+
let mut outer = func.clone();
34+
outer.block = parse_quote! { { #inner {
4635
use std::sync::atomic::{AtomicUsize, Ordering};
47-
use std::sync::Mutex;
48-
use comemo::once_cell::sync::Lazy;
49-
use comemo::siphasher::sip128::{Hasher128, SipHasher};
36+
use comemo::Validate;
5037

5138
static NR: AtomicUsize = AtomicUsize::new(1);
52-
static MAP: Lazy<Mutex<HashMap<u128, String>>> =
53-
Lazy::new(|| Mutex::new(HashMap::new()));
54-
5539
let nr = NR.fetch_add(1, Ordering::SeqCst);
56-
let hash = {
57-
let mut state = SipHasher::new();
58-
#(#hashes)*
59-
state.finish128().as_u128()
60-
};
40+
let cts = (#(#cts,)*);
41+
42+
println!("{:?}", cts);
6143

62-
let mut hit = true;
63-
let result = MAP
64-
.lock()
65-
.unwrap()
66-
.entry(hash)
67-
.or_insert_with(|| {
68-
hit = false;
69-
inner(#(#args),*)
70-
})
71-
.clone();
44+
let mut hit = false;
45+
let result = inner(#(#args),*);
7246

7347
println!(
7448
"{} {} {} {}",
@@ -81,5 +55,5 @@ pub fn expand(mut func: syn::ItemFn) -> Result<proc_macro2::TokenStream> {
8155
result
8256
} } };
8357

84-
Ok(quote! { #func })
58+
Ok(quote! { #outer })
8559
}

macros/src/track.rs

Lines changed: 80 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,67 @@
11
use super::*;
22

33
/// Make a type trackable.
4-
pub fn expand(block: syn::ItemImpl) -> Result<proc_macro2::TokenStream> {
4+
pub fn expand(block: &syn::ItemImpl) -> Result<proc_macro2::TokenStream> {
55
let ty = &block.self_ty;
66

7-
let mut tracked_methods = vec![];
7+
// Extract and validate the methods.
8+
let mut methods = vec![];
89
for item in &block.items {
9-
let method = match item {
10-
syn::ImplItem::Method(method) => method,
11-
_ => bail!(item, "only methods are supported"),
12-
};
10+
methods.push(method(&item)?);
11+
}
1312

14-
let mut tracked = method.clone();
15-
let name = &tracked.sig.ident;
13+
let tracked_methods = methods.iter().map(|method| {
14+
let mut method = (*method).clone();
15+
let name = &method.sig.ident;
16+
method.block = parse_quote! { {
17+
let output = self.0.#name();
18+
let slot = &mut self.constraint().#name;
19+
let ct = Validate::constraint(&output);
20+
if slot.is_none() {
21+
assert_eq!(*slot, Some(ct), "comemo: method is not pure");
22+
}
23+
*slot = Some(ct);
24+
output
25+
} };
26+
method
27+
});
1628

17-
let mut inputs = tracked.sig.inputs.iter();
18-
let receiver = match inputs.next() {
19-
Some(syn::FnArg::Receiver(recv)) => recv,
20-
_ => bail!(tracked, "method must take self"),
29+
let tracked_fields = methods.iter().map(|method| {
30+
let name = &method.sig.ident;
31+
let ty = match &method.sig.output {
32+
syn::ReturnType::Default => unreachable!(),
33+
syn::ReturnType::Type(_, ty) => ty.as_ref(),
2134
};
35+
quote! { #name: Option<<#ty as Validate>::Constraint>, }
36+
});
2237

23-
if receiver.reference.is_none() || receiver.mutability.is_some() {
24-
bail!(receiver, "must take self by shared reference");
25-
}
38+
let track_impl = quote! {
39+
use comemo::Validate;
2640

27-
let mut args = vec![];
28-
for input in inputs {
29-
let pat = match input {
30-
syn::FnArg::Typed(typed) => &*typed.pat,
31-
syn::FnArg::Receiver(_) => unreachable!("unexpected second self"),
32-
};
41+
struct Surface<'a>(&'a #ty);
3342

34-
let ident = match pat {
35-
syn::Pat::Ident(ident) => ident,
36-
_ => bail!(pat, "only simple identifiers are supported"),
37-
};
43+
impl Surface<'_> {
44+
#(#tracked_methods)*
3845

39-
args.push(ident);
46+
fn constraint(&self) -> &mut Constraint {
47+
todo!()
48+
}
4049
}
4150

42-
tracked.block = parse_quote! { { self.0.#name(#(#args),*) } };
43-
tracked_methods.push(tracked);
44-
}
45-
46-
let track_impl = quote! {
47-
impl comemo::Track for #ty {
48-
type Surface = Surface;
49-
50-
fn surface(&self) -> &Surface {
51-
unsafe { &*(self as *const Self as *const Surface) }
51+
impl<'a> From<&'a #ty> for Surface<'a> {
52+
fn from(val: &'a #ty) -> Self {
53+
Self(val)
5254
}
5355
}
5456

55-
#[repr(transparent)]
56-
struct Surface(#ty);
57+
#[derive(Debug, Default)]
58+
struct Constraint {
59+
#(#tracked_fields)*
60+
}
5761

58-
impl Surface {
59-
#(#tracked_methods)*
62+
impl<'a> comemo::Track<'a> for #ty {
63+
type Surface = Surface<'a>;
64+
type Constraint = Constraint;
6065
}
6166
};
6267

@@ -65,3 +70,38 @@ pub fn expand(block: syn::ItemImpl) -> Result<proc_macro2::TokenStream> {
6570
const _: () = { #track_impl };
6671
})
6772
}
73+
74+
/// Extract and validate a method.
75+
fn method(item: &syn::ImplItem) -> Result<&syn::ImplItemMethod> {
76+
let method = match item {
77+
syn::ImplItem::Method(method) => method,
78+
_ => bail!(item, "only methods are supported"),
79+
};
80+
81+
let mut inputs = method.sig.inputs.iter();
82+
let receiver = match inputs.next() {
83+
Some(syn::FnArg::Receiver(recv)) => recv,
84+
_ => bail!(method, "method must take self"),
85+
};
86+
87+
if receiver.reference.is_none() || receiver.mutability.is_some() {
88+
bail!(receiver, "must take self by shared reference");
89+
}
90+
91+
if inputs.next().is_some() {
92+
bail!(
93+
method.sig,
94+
"currently, only methods without extra arguments are supported"
95+
);
96+
}
97+
98+
let output = &method.sig.output;
99+
match output {
100+
syn::ReturnType::Default => {
101+
bail!(method.sig, "method must have a return type")
102+
}
103+
syn::ReturnType::Type(..) => {}
104+
}
105+
106+
Ok(method)
107+
}

0 commit comments

Comments
 (0)