Skip to content

Commit

Permalink
Merge pull request #24 from frengor/derive-macros
Browse files Browse the repository at this point in the history
Add derive macros for Trace and Finalize
  • Loading branch information
frengor authored Nov 9, 2023
2 parents 115dbe5 + 3b3a9c5 commit ccbacc9
Show file tree
Hide file tree
Showing 23 changed files with 643 additions and 67 deletions.
17 changes: 7 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,30 @@ edition.workspace = true
members = ["derive"]

[workspace.package]
version = "0.2.0"
version = "0.2.0" # Also update in [dependencies.rust-cc-derive.version]
authors = ["fren_gor <[email protected]>"]
repository = "https://github.com/frengor/rust-cc"
categories = ["memory-management"]
license = "MIT OR Apache-2.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["auto-collect", "finalization"]
nightly = []
default = ["auto-collect", "finalization", "derive"]
nightly = ["rust-cc-derive?/nightly"]
derive = ["dep:rust-cc-derive"]
auto-collect = []
finalization = []
pedantic-debug-assertions = []

[dependencies]
rust-cc-derive = { path = "./derive", version = "0.2.0", optional = true }
thiserror = "1.0.37"

[dev-dependencies]
iai-callgrind = "0.7.3"
iai-callgrind = "0.7.3" # Also update IAI_CALLGRIND_VERSION in .github/workflows/bench.yml
rand = "0.8.3"
trybuild = "1.0.85"

[[bench]]
name = "bench"
harness = false

[package.metadata.cargo-all-features]
# Exclude certain features from the build matrix
denylist = ["full"]
12 changes: 1 addition & 11 deletions benches/benches/binary_trees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub fn count_binary_trees(max_size: usize) -> Vec<usize> {
res
}

#[derive(Trace, Finalize)]
enum TreeNode {
Nested {
left: Cc<TreeNode>,
Expand All @@ -32,17 +33,6 @@ enum TreeNode {
End,
}

unsafe impl Trace for TreeNode {
fn trace(&self, ctx: &mut Context<'_>) {
if let Self::Nested { left, right } = self {
left.trace(ctx);
right.trace(ctx);
}
}
}

impl Finalize for TreeNode {}

impl TreeNode {
fn new(depth: usize) -> Self {
if depth == 0 {
Expand Down
20 changes: 1 addition & 19 deletions benches/benches/binary_trees_with_parent_pointers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub fn count_binary_trees_with_parent(max_size: usize) -> Vec<usize> {
res
}

#[derive(Trace, Finalize)]
enum TreeNodeWithParent {
Root {
left: Cc<TreeNodeWithParent>,
Expand All @@ -39,25 +40,6 @@ enum TreeNodeWithParent {
End,
}

unsafe impl Trace for TreeNodeWithParent {
fn trace(&self, ctx: &mut Context<'_>) {
match self {
Self::Root { left, right } => {
left.trace(ctx);
right.trace(ctx);
}
Self::Nested { parent, left, right } => {
parent.trace(ctx);
left.trace(ctx);
right.trace(ctx);
}
Self::End => {},
}
}
}

impl Finalize for TreeNodeWithParent {}

impl TreeNodeWithParent {
fn new(depth: usize) -> Cc<Self> {
if depth == 0 {
Expand Down
15 changes: 1 addition & 14 deletions benches/benches/large_linked_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ impl List {
}
}

#[derive(Trace, Finalize)]
enum Node {
Cons { next: Cc<Node>, previous: RefCell<Option<Cc<Node>>> },
Nil,
Expand All @@ -57,17 +58,3 @@ impl Node {
}
}
}

unsafe impl Trace for Node {
fn trace(&self, ctx: &mut Context<'_>) {
match self {
Self::Cons { next, previous } => {
next.trace(ctx);
previous.trace(ctx);
},
Self::Nil => {},
}
}
}

impl Finalize for Node {}
9 changes: 1 addition & 8 deletions benches/benches/stress_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,12 @@ use rust_cc::*;

// BENCHMARK 1: My janky stress test
// (It basically creates a graph where every node is rooted, then de-roots some nodes a few at a time)
#[derive(Trace, Finalize)]
struct DirectedGraphNode {
_label: String,
edges: Vec<Cc<RefCell<DirectedGraphNode>>>,
}

unsafe impl Trace for DirectedGraphNode {
fn trace(&self, ctx: &mut Context<'_>) {
self.edges.iter().for_each(|elem| elem.trace(ctx));
}
}

impl Finalize for DirectedGraphNode {}

const NODE_COUNT: usize = 1 << 15;
const EDGE_COUNT: usize = 1 << 15;
const SHRINK_DIV: usize = 1 << 10;
Expand Down
9 changes: 6 additions & 3 deletions derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "rust-cc-derive"
description = "Derive macro for rust-cc"
version = "0.0.0"
version.workspace = true
authors.workspace = true
readme = "README.md"
repository.workspace = true
Expand All @@ -10,12 +10,15 @@ keywords = ["cycle", "cycles", "collector", "memory", "macro"]
license.workspace = true
edition.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
nightly = []

[lib]
proc-macro = true

[dependencies]
syn = { version = "2.0", default-features = false, features = ["derive", "parsing", "printing"] }
proc-macro2 = "1.0"
quote = "1.0"
proc-macro-error = "1.0"
synstructure = "0.13.0"
17 changes: 16 additions & 1 deletion derive/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,19 @@

Derive macro for the `rust-cc` crate.

Not implemented yet!!! (currently it's a no-op)
## Example

```rust
#[derive(Trace, Finalize)]
struct A<T: Trace> {
a: Cc<T>,
#[rust_cc(ignore)] // The b field won't be traced, safe to use!
b: i32,
}

#[derive(Trace, Finalize)]
#[rust_cc(unsafe_no_drop)] // Allows to implement Drop for B, unsafe to use! (see Trace docs)
struct B {
// fields
}
```
160 changes: 159 additions & 1 deletion derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,159 @@
// TODO
#![forbid(unsafe_code)]

use proc_macro_error::{abort_if_dirty, emit_error, proc_macro_error};
use quote::quote;
use syn::{Attribute, Data, Meta, MetaList, Token};
use syn::punctuated::Punctuated;
use synstructure::{AddBounds, decl_derive, Structure};

const IGNORE: &str = "ignore";
const UNSAFE_NO_DROP: &str = "unsafe_no_drop";
const ALLOWED_ATTR_META_ITEMS: [&str; 2] = [IGNORE, UNSAFE_NO_DROP];

decl_derive!([Trace, attributes(rust_cc)] => #[proc_macro_error] derive_trace_trait);

fn derive_trace_trait(mut s: Structure<'_>) -> proc_macro2::TokenStream {
// Check if the struct is annotated with #[rust_cc(unsafe_no_drop)]
let no_drop = s.ast().attrs
.iter()
.any(|attr| attr_contains(attr, UNSAFE_NO_DROP));

// Ignore every field and variant annotated with #[rust_cc(ignore)]
// Filter fields before variants to be able to emit all the errors in case of wrong attributes in ignored variants
s.filter(|bi| {
!bi.ast().attrs
.iter()
.any(|attr| attr_contains(attr, IGNORE))
});

// Filter variants only in case of enums
if let Data::Enum(_) = s.ast().data {
s.filter_variants(|vi| {
!vi.ast().attrs
.iter()
.any(|attr| attr_contains(attr, IGNORE))
});
}

// Abort if errors has been emitted
abort_if_dirty();

// Identifier for the ctx parameter of Trace::trace(...)
// Shouldn't clash with any other identifier
let ctx = quote::format_ident!("__rust_cc__Trace__ctx__");

// There's no .len() method, so this is the only way to know if there are any traced fields
let mut has_no_variants = true; // See inline_attr below

let body = s.each(|bi| {
has_no_variants = false;

let ty = &bi.ast().ty;
quote! {
<#ty as rust_cc::Trace>::trace(#bi, #ctx);
}
});

// Generate an #[inline(always)] if no field is being traced
let inline_attr = if has_no_variants {
quote! { #[inline(always)] }
} else {
quote! { #[inline] }
};

s.underscore_const(true);

s.add_bounds(AddBounds::Fields);
let trace_impl = s.gen_impl(quote! {
extern crate rust_cc;

gen unsafe impl rust_cc::Trace for @Self {
#inline_attr
#[allow(non_snake_case)]
fn trace(&self, #ctx: &mut rust_cc::Context<'_>) {
match *self { #body }
}
}
});

if no_drop {
return trace_impl;
}

s.add_bounds(AddBounds::None); // Don't generate bounds for Drop
let drop_impl = s.gen_impl(quote! {
extern crate core;

gen impl core::ops::Drop for @Self {
#[inline(always)]
fn drop(&mut self) {
}
}
});

quote! {
#trace_impl
#drop_impl
}
}

fn get_meta_items(attr: &Attribute) -> Option<&MetaList> {
if attr.path().is_ident("rust_cc") {
match &attr.meta {
Meta::List(meta) => Some(meta),
err => {
emit_error!(err, "Invalid attribute");
None
},
}
} else {
None
}
}

fn attr_contains(attr: &Attribute, ident: &str) -> bool {
let Some(meta_list) = get_meta_items(attr) else {
return false;
};

let nested = match meta_list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) {
Ok(nested) => nested,
Err(err) => {
emit_error!(meta_list, "Invalid attribute: {}", err);
return false;
},
};

for meta in nested {
match meta {
Meta::Path(path) if path.is_ident(ident) => {
return true;
},
Meta::Path(path) if ALLOWED_ATTR_META_ITEMS.iter().any(|id| path.is_ident(id)) => {
emit_error!(path, "Invalid attribute position");
},
Meta::Path(path) => {
emit_error!(path, "Unrecognized attribute");
},
err => {
emit_error!(err, "Invalid attribute");
},
}
}

false
}

decl_derive!([Finalize] => derive_finalize_trait);

fn derive_finalize_trait(mut s: Structure<'_>) -> proc_macro2::TokenStream {
s.underscore_const(true);
s.add_bounds(AddBounds::None); // Don't generate bounds for Finalize
s.gen_impl(quote! {
extern crate rust_cc;
use rust_cc::Finalize as __rust_cc__Finalize__;

gen impl __rust_cc__Finalize__ for @Self {
}
})
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ mod utils;
#[cfg(feature = "auto-collect")]
pub mod config;

#[cfg(feature = "derive")]
pub use rust_cc_derive::{Finalize, Trace};

pub use cc::Cc;
pub use trace::{Context, Finalize, Trace};

Expand Down
23 changes: 23 additions & 0 deletions tests/derive_macro_tests/derive_finalize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use rust_cc::*;

#[derive(Finalize)]
struct MyStruct {
a: (),
}

#[derive(Finalize)] // Finalize is required by Trace
enum MyEnum {
A(),
B(),
}

fn main() {
fn test<T: Finalize>(_t: T) {
}

test(MyStruct {
a: (),
});

test(MyEnum::A());
}
Loading

0 comments on commit ccbacc9

Please sign in to comment.