Skip to content

Commit

Permalink
Start work on zero copy support (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog authored Oct 9, 2023
1 parent d665158 commit ddb844b
Show file tree
Hide file tree
Showing 44 changed files with 2,958 additions and 37 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: ['1.66', stable]
rust: ['1.73', stable]
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@1.68
- uses: dtolnay/rust-toolchain@1.73
with:
components: clippy
- run: cargo clippy --all-targets -- -D warnings
Expand Down
2 changes: 1 addition & 1 deletion crates/musli-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "musli-common"
version = "0.0.49"
authors = ["John-John Tedro <udoprog@tedro.se>"]
edition = "2021"
rust-version = "1.66"
rust-version = "1.73"
description = """
Common utilities shared among Müsli encodings.
"""
Expand Down
4 changes: 2 additions & 2 deletions crates/musli-common/src/allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ pub use self::no_std::NoStd;
mod default_alloc {
#![allow(missing_docs)]

#[cfg(all(feature = "alloc"))]
#[cfg(feature = "alloc")]
pub type Default = super::Alloc;
#[cfg(all(not(feature = "alloc")))]
#[cfg(not(feature = "alloc"))]
pub type Default = super::NoStd<1024>;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/musli-common/src/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub fn from_utf8_owned(bytes: Vec<u8>) -> Result<String, Utf8Error> {
#[inline(always)]
#[cfg(all(feature = "alloc", feature = "simdutf8"))]
pub fn from_utf8_owned(bytes: Vec<u8>) -> Result<String, Utf8Error> {
if let Err(..) = from_utf8(&bytes) {
if from_utf8(&bytes).is_err() {
return Err(Utf8Error);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/musli-descriptive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "musli-descriptive"
version = "0.0.49"
authors = ["John-John Tedro <udoprog@tedro.se>"]
edition = "2021"
rust-version = "1.66"
rust-version = "1.73"
description = """
A fully self-descriptive format for Müsli.
"""
Expand Down
4 changes: 1 addition & 3 deletions crates/musli-descriptive/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@ where
{
#[inline]
fn clone(&self) -> Self {
Self {
_marker: marker::PhantomData,
}
*self
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/musli-json/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "musli-json"
version = "0.0.49"
authors = ["John-John Tedro <udoprog@tedro.se>"]
edition = "2021"
rust-version = "1.66"
rust-version = "1.73"
description = """
JSON support for Müsli.
"""
Expand Down
4 changes: 1 addition & 3 deletions crates/musli-json/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,7 @@ where
impl<M> Clone for Encoding<M> {
#[inline]
fn clone(&self) -> Self {
Self {
_marker: marker::PhantomData,
}
*self
}
}

Expand Down
5 changes: 4 additions & 1 deletion crates/musli-json/tests/trace_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ fn trace_collection() {

let Ok(..) = encoding.from_slice_with::<_, Collection>(&mut cx, &bytes) else {
if let Some(error) = cx.iter().next() {
assert_eq!(error.to_string(), ".values[Hello]: Invalid numeric (at bytes 15-16)");
assert_eq!(
error.to_string(),
".values[Hello]: Invalid numeric (at bytes 15-16)"
);
return;
}

Expand Down
5 changes: 4 additions & 1 deletion crates/musli-json/tests/trace_no_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ fn trace_no_std() {

let Ok(..) = encoding.from_slice_with::<_, Collection>(&mut cx, &bytes) else {
if let Some(error) = cx.iter().next() {
assert_eq!(error.to_string(), ".values[Hello]: Invalid numeric (at bytes 15-16)");
assert_eq!(
error.to_string(),
".values[Hello]: Invalid numeric (at bytes 15-16)"
);
return;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/musli-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "musli-macros"
version = "0.0.49"
authors = ["John-John Tedro <udoprog@tedro.se>"]
edition = "2021"
rust-version = "1.66"
rust-version = "1.73"
description = """
Macros for Müsli.
"""
Expand Down
12 changes: 8 additions & 4 deletions crates/musli-macros/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,16 @@ fn decode_enum(
})
});

let enter = trace.then(|| quote! {
#context_t::enter_enum(#ctx_var, #type_name);
let enter = trace.then(|| {
quote! {
#context_t::enter_enum(#ctx_var, #type_name);
}
});

let leave = trace.then(|| quote! {
#context_t::leave_enum(#ctx_var);
let leave = trace.then(|| {
quote! {
#context_t::leave_enum(#ctx_var);
}
});

return Ok(quote! {{
Expand Down
12 changes: 12 additions & 0 deletions crates/musli-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod internals;
#[cfg(feature = "test")]
mod test;
mod types;
mod zero_copy;

/// Please refer to the main [musli documentation](https://docs.rs/musli).
#[proc_macro_derive(Encode, attributes(musli))]
Expand Down Expand Up @@ -133,6 +134,17 @@ pub fn visitor(attr: TokenStream, input: TokenStream) -> TokenStream {
}
}

#[proc_macro_derive(ZeroCopy, attributes(zero_copy))]
pub fn zero_copy(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
let expander = zero_copy::Expander::new(&input);

match expander.expand() {
Ok(stream) => stream.into(),
Err(errors) => to_compile_errors(errors).into(),
}
}

fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
let mut output = proc_macro2::TokenStream::new();

Expand Down
13 changes: 10 additions & 3 deletions crates/musli-macros/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,12 @@ fn never_type<const N: usize>(
if let Extra::Visitor(ty) = extra {
let mut it = generics.params.iter();

let Some(syn::GenericParam::Type(syn::TypeParam { ident: c_param, .. })) = it.next() else {
return Err(syn::Error::new_spanned(generics, "Missing generic parameter in associated type (usually `C`)"));
let Some(syn::GenericParam::Type(syn::TypeParam { ident: c_param, .. })) = it.next()
else {
return Err(syn::Error::new_spanned(
generics,
"Missing generic parameter in associated type (usually `C`)",
));
};

if let Some(ty) = ty {
Expand All @@ -343,7 +347,10 @@ fn never_type<const N: usize>(
args.push(syn::GenericArgument::Type(syn::Type::Slice(
syn::TypeSlice {
bracket_token: syn::token::Bracket::default(),
elem: Box::new(syn::Type::Path(syn::TypePath { qself: None, path })),
elem: Box::new(syn::Type::Path(syn::TypePath {
qself: None,
path,
})),
},
)));
}
Expand Down
178 changes: 178 additions & 0 deletions crates/musli-macros/src/zero_copy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use std::cell::RefCell;

use proc_macro2::TokenStream;
use quote::quote;
use syn::meta::ParseNestedMeta;
use syn::parse::ParseStream;
use syn::punctuated::Punctuated;
use syn::{DeriveInput, Token};

#[derive(Default)]
struct Ctxt {
errors: RefCell<Vec<syn::Error>>,
}

impl Ctxt {
fn error(&self, error: syn::Error) {
self.errors.borrow_mut().push(error);
}
}

pub struct Expander<'a> {
input: &'a DeriveInput,
}

impl<'a> Expander<'a> {
pub fn new(input: &'a DeriveInput) -> Self {
Self { input }
}
}

impl<'a> Expander<'a> {
pub fn expand(&self) -> Result<TokenStream, Vec<syn::Error>> {
let cx = Ctxt::default();

let Ok(output) = expand(&cx, self.input) else {
return Err(cx.errors.into_inner());
};

let errors = cx.errors.into_inner();

if !errors.is_empty() {
return Err(errors);
}

Ok(output)
}
}

fn expand(cx: &Ctxt, input: &DeriveInput) -> Result<TokenStream, ()> {
let st = match &input.data {
syn::Data::Struct(st) => st,
syn::Data::Enum(data) => {
cx.error(syn::Error::new_spanned(
data.enum_token,
"ZeroCopy: not supported for enums",
));
return Err(());
}
syn::Data::Union(data) => {
cx.error(syn::Error::new_spanned(
data.union_token,
"ZeroCopy: not supported for unions",
));
return Err(());
}
};

let mut generics = input.generics.clone();

let mut is_repr_c = false;

for attr in &input.attrs {
if attr.path().is_ident("repr") {
let result = attr.parse_args_with(|input: ParseStream| {
let ident: syn::Ident = input.parse()?;

if ident == "C" {
is_repr_c = true;
}

Ok(())
});

if let Err(error) = result {
cx.error(error);
}
}

if attr.path().is_ident("zero_copy") {
let result = attr.parse_nested_meta(|meta: ParseNestedMeta| {
if meta.path.is_ident("bounds") {
meta.input.parse::<Token![=]>()?;
let content;
syn::braced!(content in meta.input);
generics.make_where_clause().predicates.extend(Punctuated::<
syn::WherePredicate,
Token![,],
>::parse_terminated(
&content
)?);
return Ok(());
}

Ok(())
});

if let Err(error) = result {
cx.error(error);
}
}
}

if !is_repr_c {
cx.error(syn::Error::new_spanned(
input,
"ZeroCopy: struct must be marked with repr(C)",
));
return Err(());
}

let buf_mut: syn::Path = syn::parse_quote!(musli_zerocopy::BufMut);
let buf: syn::Path = syn::parse_quote!(musli_zerocopy::Buf);
let error: syn::Path = syn::parse_quote!(musli_zerocopy::Error);
let validator: syn::Path = syn::parse_quote!(musli_zerocopy::Validator);
let zero_copy: syn::Path = syn::parse_quote!(musli_zerocopy::ZeroCopy);

let mut writes = Vec::new();
let mut validates = Vec::new();

for (index, field) in st.fields.iter().enumerate() {
let member = match &field.ident {
Some(ident) => syn::Member::Named(ident.clone()),
None => syn::Member::Unnamed(syn::Index::from(index)),
};

writes.push(quote! {
#buf_mut::write(buf, &self.#member)?;
});

let ty = &field.ty;

validates.push(quote! {
#validator::field::<#ty>(&mut validator)?;
});
}

let name = &input.ident;

let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

let validates = &validates[..];

Ok(quote::quote! {
unsafe impl #impl_generics #zero_copy for #name #ty_generics #where_clause {
fn write_to<__B: ?Sized>(&self, buf: &mut __B) -> Result<(), #error>
where
__B: #buf_mut
{
#(#writes)*
Ok(())
}

fn read_from(buf: &#buf) -> Result<&Self, #error> {
let mut validator = #buf::validate::<Self>(buf)?;
#(#validates)*
#validator::finalize(validator)?;
Ok(unsafe { #buf::cast(buf) })
}

unsafe fn validate_aligned(buf: &#buf) -> Result<(), #error> {
let mut validator = #buf::validate_aligned(buf)?;
#(#validates)*
#validator::finalize(validator)?;
Ok(())
}
}
})
}
2 changes: 1 addition & 1 deletion crates/musli-storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "musli-storage"
version = "0.0.49"
authors = ["John-John Tedro <udoprog@tedro.se>"]
edition = "2021"
rust-version = "1.66"
rust-version = "1.73"
description = """
Partially upgrade stable format for Müsli suitable for storage.
"""
Expand Down
4 changes: 1 addition & 3 deletions crates/musli-storage/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,7 @@ where
{
#[inline]
fn clone(&self) -> Self {
Self {
_marker: marker::PhantomData,
}
*self
}
}

Expand Down
Loading

0 comments on commit ddb844b

Please sign in to comment.