From d2174103f70405c1ec7e1f1f5482071af23a084d Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Sat, 20 Jan 2018 16:23:15 +0100 Subject: [PATCH 1/8] Add cpp_class! A macro which allow to wrap C++ class into rust types with and call their destructor and copy constructor appropriatly --- cpp/src/lib.rs | 51 ++++++++++++++++++ cpp_build/src/lib.rs | 66 ++++++++++++++++++++++-- cpp_common/src/lib.rs | 41 +++++++++++++-- cpp_macros/src/lib.rs | 117 +++++++++++++++++++++++++++++++++++++++--- test/src/header.h | 30 ++++++++++- test/src/lib.rs | 85 ++++++++++++++++++++++++++++-- 6 files changed, 371 insertions(+), 19 deletions(-) diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index 4e476f6..14cfb2e 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -109,3 +109,54 @@ macro_rules! cpp { } }; } + +#[doc(hidden)] +pub trait CppTrait { + type BaseType; + const ARRAY_SIZE: usize; + const CPP_TYPE: &'static str; +} + +/// This macro allow to wrap a relocatable C++ struct or class that might +/// have destructor or copy constructor, and instantiate the Drop and Clone +/// trait appropriately. +/// +/// ```ignore +/// cpp_class!(pub struct MyClass, "MyClass"); +/// impl MyClass { +/// fn new() -> Self { +/// unsafe { cpp!([] -> MyClass as "MyClass" { return MyClass(); }) } +/// } +/// fn member_function(&self, param : i32) -> i32 { +/// unsafe { cpp!([self as "const MyClass*", param as "int"] -> i32 as "int" { +/// return self->member_function(param); +/// }) } +/// } +/// } +/// ``` +/// +/// This will create a rust struct MyClass, which has the same size and +/// alignement as the the C++ class "MyClass". It will also call the destructor +/// of MyClass on drop, and its copy constructor on clone. +/// +/// Warning: This only work if the C++ class can be moved in memory (using +/// memcpy). This disallow most classes from the standard library. +/// +#[macro_export] +macro_rules! cpp_class { + (struct $name:ident, $type:expr) => { + #[derive(__cpp_internal_class)] + #[repr(C)] + struct $name { + _opaque : [<$name as $crate::CppTrait>::BaseType ; <$name as $crate::CppTrait>::ARRAY_SIZE + (stringify!(struct $name, $type), 0).1] + } + }; + (pub struct $name:ident, $type:expr) => { + #[derive(__cpp_internal_class)] + #[repr(C)] + pub struct $name { + _opaque : [<$name as $crate::CppTrait>::BaseType ; <$name as $crate::CppTrait>::ARRAY_SIZE + (stringify!(pub struct $name, $type), 0).1] + } + }; +} + diff --git a/cpp_build/src/lib.rs b/cpp_build/src/lib.rs index 3f5b799..bc3af9d 100644 --- a/cpp_build/src/lib.rs +++ b/cpp_build/src/lib.rs @@ -23,7 +23,7 @@ use std::fs::{create_dir, remove_dir_all, File}; use std::io::prelude::*; use syn::visit::Visitor; use syn::{Mac, Span, Spanned, DUMMY_SPAN}; -use cpp_common::{parsing, Capture, Closure, ClosureSig, Macro, LIB_NAME, STRUCT_METADATA_MAGIC, +use cpp_common::{parsing, Capture, Closure, ClosureSig, Macro, Class, LIB_NAME, STRUCT_METADATA_MAGIC, VERSION}; use cpp_synmap::SourceMap; @@ -89,21 +89,23 @@ fn gen_cpp_lib(visitor: &Handle) -> PathBuf { // Generate the sizes array with the sizes of each of the argument types if is_void { sizealign.push(format!( - "{{{hash}ull, 0, 1}}", + "{{{hash}ull, 0, 1, 0}}", hash = hash )); } else { sizealign.push(format!("{{ {hash}ull, sizeof({type}), - rustcpp::AlignOf<{type}>::value + rustcpp::AlignOf<{type}>::value, + rustcpp::Flags<{type}>::value }}", hash=hash, type=cpp)); } for &Capture { ref cpp, .. } in captures { sizealign.push(format!("{{ {hash}ull, sizeof({type}), - rustcpp::AlignOf<{type}>::value + rustcpp::AlignOf<{type}>::value, + rustcpp::Flags<{type}>::value }}", hash=hash, type=cpp)); } @@ -167,6 +169,36 @@ void {name}({params}{comma} void* __result) {{ } } + for class in &visitor.classes { + let hash = class.name_hash(); + + // Generate the sizes array + sizealign.push(format!("{{ + {hash}ull, + sizeof({type}), + rustcpp::AlignOf<{type}>::value, + rustcpp::Flags<{type}>::value + }}", hash=hash, type=class.cpp)); + + // Generate constructor + write!( + output, + r#" +extern "C" {{ +void __cpp_destructor_{hash}(void *ptr) {{ + typedef {cpp_name} T; + static_cast< {cpp_name} *>(ptr)->~T(); +}} +void __cpp_copy_{hash}(const void *src, void *dest) {{ + new (dest) {cpp_name} (*static_cast<{cpp_name} const*>(src)); +}} +}} +"#, + hash = hash, + cpp_name = class.cpp + ).unwrap(); + } + let mut magic = vec![]; for mag in STRUCT_METADATA_MAGIC.iter() { magic.push(format!("{}", mag)); @@ -187,10 +219,22 @@ struct AlignOf {{ static const uintptr_t value = sizeof(Inner) - sizeof(T); }}; +template +struct Flags {{ + static const uintptr_t value = +#if __cplusplus > 199711L + (std::is_trivially_destructible::value << 0) | + (std::is_trivially_copyable::value << 1) | +#endif + 0; +}}; + + struct SizeAlign {{ uint64_t hash; uint64_t size; uint64_t align; + uint64_t flags; }}; struct MetaData {{ @@ -472,6 +516,7 @@ successfully, such that rustc can provide an error message."#, // Parse the macro definitions let mut visitor = Handle { closures: Vec::new(), + classes: Vec::new(), snippets: String::new(), sm: &sm, }; @@ -493,6 +538,7 @@ pub fn build>(path: P) { struct Handle<'a> { closures: Vec, + classes: Vec, snippets: String, sm: &'a SourceMap, } @@ -538,5 +584,17 @@ impl<'a> Visitor for Handle<'a> { } } } + if mac.path.segments[0].ident.as_ref() == "cpp_class" { + let tts = &mac.tts; + assert!(tts.len() >= 1); + let span = Span { + lo: tts[0].span().lo, + hi: tts[tts.len() - 1].span().hi, + }; + let src = self.sm.source_text(span).unwrap(); + let input = synom::ParseState::new(&src); + let class = parsing::class_macro(input).expect("cpp_class! macro"); + self.classes.push(class); + } } } diff --git a/cpp_common/src/lib.rs b/cpp_common/src/lib.rs index 9b51b20..e820125 100644 --- a/cpp_common/src/lib.rs +++ b/cpp_common/src/lib.rs @@ -28,7 +28,7 @@ pub const MSVC_LIB_NAME: &'static str = "rust_cpp_generated.lib"; pub const STRUCT_METADATA_MAGIC: [u8; 128] = [ b'r', b'u', b's', b't', b'c', b'p', b'p', b'~', b'm', b'e', b't', b'a', b'd', b'a', b't', b'a', - 91, 74, 112, 213, 165, 185, 214, 120, 179, 17, 185, 25, 182, 253, 82, 118, + 92, 74, 112, 213, 165, 185, 214, 120, 179, 17, 185, 25, 182, 253, 82, 118, 148, 29, 139, 208, 59, 153, 78, 137, 230, 54, 26, 177, 232, 121, 132, 166, 44, 106, 218, 57, 158, 33, 69, 32, 54, 204, 123, 226, 99, 117, 60, 173, 112, 61, 56, 174, 117, 141, 126, 249, 79, 159, 6, 119, 2, 129, 147, 66, @@ -71,6 +71,22 @@ pub struct Closure { pub body: Spanned, } +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct Class { + pub name: Ident, + pub cpp: String, + pub public: bool +} + +impl Class { + pub fn name_hash(&self) -> u64 { + // XXX: Use a better hasher than the default? + let mut hasher = DefaultHasher::new(); + self.hash(&mut hasher); + hasher.finish() + } +} + pub enum Macro { Closure(Closure), Lit(Spanned), @@ -78,8 +94,8 @@ pub enum Macro { pub mod parsing { use syn::parse::{ident, string, tt, ty}; - use syn::{Spanned, Ty, DUMMY_SPAN}; - use super::{Capture, Closure, ClosureSig, Macro}; + use syn::{Spanned, Ty, DUMMY_SPAN, Ident}; + use super::{Capture, Closure, ClosureSig, Macro, Class}; macro_rules! mac_body { ($i: expr, $submac:ident!( $($args:tt)* )) => { @@ -95,10 +111,12 @@ pub mod parsing { }; } + named!(ident_or_self -> Ident, alt!( ident | keyword!("self") => { |_| "self".into() } )); + named!(name_as_string -> Capture, do_parse!( is_mut: option!(keyword!("mut")) >> - id: ident >> + id: ident_or_self >> keyword!("as") >> cty: string >> (Capture { @@ -168,4 +186,19 @@ pub mod parsing { map!(tuple!( punct!("@"), keyword!("TYPE"), cpp_closure ), (|(_, _, x)| x)))); + + named!(pub cpp_class -> Class, + do_parse!( + is_pub: option!(keyword!("pub")) >> + keyword!("struct") >> + name: ident >> + punct!(",") >> + cpp_type: string >> + (Class { + name: name, + cpp: cpp_type.value, + public: is_pub.is_some(), + }))); + + named!(pub class_macro -> Class , mac_body!(cpp_class)); } diff --git a/cpp_macros/src/lib.rs b/cpp_macros/src/lib.rs index a63dfcc..11014ef 100644 --- a/cpp_macros/src/lib.rs +++ b/cpp_macros/src/lib.rs @@ -34,6 +34,12 @@ use std::fs::File; use aho_corasick::{AcAutomaton, Automaton}; use byteorder::{LittleEndian, ReadBytesExt}; +struct MetaData { + size: usize, + align: usize, + flags: u64 +} + lazy_static! { static ref OUT_DIR: PathBuf = PathBuf::from(env::var("OUT_DIR").expect(r#" @@ -42,7 +48,7 @@ lazy_static! { The OUT_DIR environment variable was not set. NOTE: rustc must be run by Cargo."#)); - static ref METADATA: HashMap> = { + static ref METADATA: HashMap> = { let file = open_lib_file().expect(r#" -- rust-cpp fatal error -- @@ -58,7 +64,7 @@ I/O error while reading metadata from target library file."#) } /// NOTE: This panics when it can produce a better error message -fn read_metadata(file: File) -> io::Result>> { +fn read_metadata(file: File) -> io::Result>> { let mut file = BufReader::new(file); let end = { const AUTO_KEYWORD: &'static [&'static [u8]] = &[&cpp_common::STRUCT_METADATA_MAGIC]; @@ -99,11 +105,12 @@ Version mismatch between cpp_macros and cpp_build for same crate."# let hash = file.read_u64::()?; let size = file.read_u64::()? as usize; let align = file.read_u64::()? as usize; + let flags = file.read_u64::()? as u64; size_data .entry(hash) .or_insert(Vec::new()) - .push((size, align)); + .push(MetaData{size, align, flags}); } Ok(size_data) } @@ -195,7 +202,7 @@ NOTE: They cannot be generated by macro expansion."#, // Generate the assertion to check that the size and align of the types // match before calling. - let (size, align) = size_data[i + 1]; + let (size, align) = (size_data[i + 1].size, size_data[i + 1].align); let sizeof_msg = format!( "size_of for argument `{}` does not match between c++ and \ rust", @@ -231,7 +238,9 @@ NOTE: They cannot be generated by macro expansion."#, quote!(*const) }; - extern_params.push(quote!(#written_name : #ptr u8)); + let arg_name : Ident = format!("arg_{}", written_name).into(); + + extern_params.push(quote!(#arg_name : #ptr u8)); tt_args.push(quote!(#mb_mut #mac_name : ident as #mac_cty : tt)); @@ -243,7 +252,7 @@ NOTE: They cannot be generated by macro expansion."#, let extern_name = closure.extern_name(); let ret_ty = &closure.ret; - let (ret_size, ret_align) = size_data[0]; + let (ret_size, ret_align) = (size_data[0].size, size_data[0].align); let is_void = closure.cpp == "void"; let decl = if is_void { @@ -295,3 +304,99 @@ NOTE: They cannot be generated by macro expansion."#, result.to_string().parse().unwrap() } + + +#[proc_macro_derive(__cpp_internal_class)] +pub fn expand_wrap_class(input: TokenStream) -> TokenStream { + let source = input.to_string(); + + #[cfg_attr(rustfmt, rustfmt_skip)] + const SUFFIX: &'static [&'static str] = &[ + ")", ",", "0", ")", ".", "1", "]", ",", "}" + ]; + + let s = source.find("stringify!(").expect("expected 'strignify!' token in class content") + 11; + let mut tokens : &str = &source[s..].trim(); + + for token in SUFFIX.iter().rev() { + assert!( + tokens.ends_with(token), + "expected suffix token {}, got {}", + token, + tokens + ); + tokens = &tokens[..tokens.len() - token.len()].trim(); + } + + let class = parsing::cpp_class(synom::ParseState::new(tokens)) + .expect("cpp_class! macro"); + + let hash = class.name_hash(); + + let size_data = METADATA.get(&hash).expect( + r#" +-- rust-cpp fatal error -- + +This cpp_class! macro is not found in the library's rust-cpp metadata. +NOTE: Only cpp_class! macros found directly in the program source will be parsed - +NOTE: They cannot be generated by macro expansion."#, + ); + + let (size, align) = (size_data[0].size, size_data[0].align); + + let base_type = match align { + 1 => { quote!(u8) } + 2 => { quote!(u16) } + 4 => { quote!(u32) } + 8 => { quote!(u64) } + _ => { panic!("unsupported alignement") } + }; + + let destructor_name : Ident = format!("__cpp_destructor_{}", hash).into(); + let copyctr_name : Ident = format!("__cpp_copy_{}", hash).into(); + let class_name = class.name; + + let mut result = quote! { + impl ::cpp::CppTrait for #class_name { + type BaseType = #base_type; + const ARRAY_SIZE: usize = #size / #align; + const CPP_TYPE: &'static str = stringify!(#class_name); + } + }; + if (size_data[0].flags & 1) == 0 { + result = quote!{ #result + impl Drop for #class_name { + fn drop(&mut self) { + unsafe { + extern "C" { fn #destructor_name(_: *mut #class_name); } + #destructor_name(&mut *self); + } + } + } + }; + }; + if (size_data[0].flags & 2) == 0 { + result = quote!{ #result + impl Clone for #class_name { + fn clone(&self) -> Self { + unsafe { + extern "C" { fn #copyctr_name(src: *const #class_name, dst: *mut #class_name); } + let mut ret : Self = std::mem::uninitialized(); + #copyctr_name(& *self, &mut ret); + ret + } + } + } + }; + } else { + result = quote!{ #result + impl Copy for #class_name { } + impl Clone for #class_name { + fn clone(&self) -> Self { *self } + } + }; + }; + + result.to_string().parse().unwrap() +} + diff --git a/test/src/header.h b/test/src/header.h index 874f1e6..4bc280b 100644 --- a/test/src/header.h +++ b/test/src/header.h @@ -1,12 +1,38 @@ #ifndef header_h__ #define header_h__ +#include +#include + +// This counter is incremented by destructors and constructors +// and must be 0 at the end of the program +inline int &counter() { + static int counter = 0; + struct CheckCounter { + ~CheckCounter() { + assert(counter == 0); + } + }; + static CheckCounter checker; + return counter; +} + +// class with destructor and copy constructor class A { public: int a; int b; - A(int a, int b) : a(a), b(b) {} - ~A() {} + A(int a, int b) : a(a), b(b) { counter()++; } + A(const A &cpy) : a(cpy.a), b(cpy.b) { counter()++; } + ~A() { counter()--; } + void setValues(int _a, int _b) { a = _a; b = _b; } + int multiply() const { return a * b; } +}; + +// Simple struct without a destructor or copy constructor +struct B { + int a; + int b; }; #endif // defined(header_h__) diff --git a/test/src/lib.rs b/test/src/lib.rs index f2d8c6d..18fa652 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -16,13 +16,38 @@ cpp!{{ #define _USE_MATH_DEFINES #include #include "src/header.h" + #include + #include }} -#[repr(C)] -struct A { - _opaque: [i32; 2], +cpp_class!(struct A, "A"); +impl A { + fn new(a : i32, b: i32) -> Self { + unsafe { + return cpp!([a as "int", b as "int"] -> A as "A" { + return A(a, b); + }); + } + } + + fn set_values(&mut self, a : i32, b: i32) { + unsafe { + return cpp!([self as "A*", a as "int", b as "int"] { + self->setValues(a, b); + }); + } + } + + fn multiply(&self) -> i32 { + unsafe { + return cpp!([self as "const A*"] -> i32 as "int" { + return self->multiply(); + }); + } + } } + #[test] fn captures() { let x: i32 = 10; @@ -86,14 +111,68 @@ fn destructor() { return A(5, 10); }); + let a1 = a.clone(); + let first = cpp!([a as "A"] -> i32 as "int32_t" { return a.a; }); assert_eq!(first, 5); + + let second = cpp!([a1 as "A"] -> i32 as "int32_t" { + return a1.b; + }); + + assert_eq!(second, 10); } } +#[test] +fn member_function() { + let mut a = A::new(2,3); + assert_eq!(a.multiply(), 2*3); + + a.set_values(5,6); + assert_eq!(a.multiply(), 5*6); +} + +cpp_class!(struct B, "B"); +impl B { + fn new(a : i32, b: i32) -> Self { + unsafe { + return cpp!([a as "int", b as "int"] -> B as "B" { + B ret = { a, b }; + return ret; + }); + } + } + fn a(&mut self) -> &mut i32 { + unsafe { + return cpp!([self as "B*"] -> &mut i32 as "int*" { + return &self->a; + }); + } + } + fn b(&mut self) -> &mut i32 { + unsafe { + return cpp!([self as "B*"] -> &mut i32 as "int*" { + return &self->b; + }); + } + } +} + + +#[test] +fn simple_class() { + let mut b = B::new(12,34); + assert_eq!(*b.a(), 12); + assert_eq!(*b.b(), 34); + *b.a() = 45; + let mut b2 = b; + assert_eq!(*b2.a(), 45); +} + #[test] fn test_nomod() { assert_eq!(nomod::inner::nomod_inner(), 10); From cef628e0953dceb4fa9307054ea2d202194004b3 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 15 Mar 2018 14:24:50 +0100 Subject: [PATCH 2/8] cpp_class! automatically implement Default and support move only types --- cpp/src/lib.rs | 6 ++-- cpp_build/src/lib.rs | 65 +++++++++++++++++++++++++++++++++++++++---- cpp_common/src/lib.rs | 8 ++++++ cpp_macros/src/lib.rs | 52 ++++++++++++++++++++++++---------- test/src/header.h | 12 ++++++++ test/src/lib.rs | 27 ++++++++++++++++++ 6 files changed, 148 insertions(+), 22 deletions(-) diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index 14cfb2e..cc38a1d 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -136,8 +136,10 @@ pub trait CppTrait { /// ``` /// /// This will create a rust struct MyClass, which has the same size and -/// alignement as the the C++ class "MyClass". It will also call the destructor -/// of MyClass on drop, and its copy constructor on clone. +/// alignement as the the C++ class "MyClass". It will also implement the Drop trait +/// calling the destructor, the Clone trait calling the copy constructor, if the +/// class is copyable (or Copy if it is trivialy copyable), and Default if the class +/// is default constructible /// /// Warning: This only work if the C++ class can be moved in memory (using /// memcpy). This disallow most classes from the standard library. diff --git a/cpp_build/src/lib.rs b/cpp_build/src/lib.rs index bc3af9d..c104411 100644 --- a/cpp_build/src/lib.rs +++ b/cpp_build/src/lib.rs @@ -24,7 +24,7 @@ use std::io::prelude::*; use syn::visit::Visitor; use syn::{Mac, Span, Spanned, DUMMY_SPAN}; use cpp_common::{parsing, Capture, Closure, ClosureSig, Macro, Class, LIB_NAME, STRUCT_METADATA_MAGIC, - VERSION}; + VERSION, flags}; use cpp_synmap::SourceMap; fn warnln_impl(a: String) { @@ -45,6 +45,50 @@ const INTERNAL_CPP_STRUCTS: &'static str = r#" #include "stdint.h" // For {u}intN_t #include // For placement new +#if __cplusplus <= 199711L && !defined(_MSC_VER) +namespace rustcpp { +template +struct is_default_constructible { + template + static double test(int(*)[sizeof(new X)]); + template + static char test(...); + enum { value = sizeof(test(0)) == sizeof(double) }; +}; +template +struct is_copy_constructible { + static T &declval(); + template + static double test(int(*)[sizeof(new X(declval()))]); + template + static char test(...); + enum { value = sizeof(test(0)) == sizeof(double) }; +}; +template struct enable_if {}; +template struct enable_if { typedef T type; }; +} +#else +#include +namespace rustcpp { +using std::is_default_constructible; +using std::is_copy_constructible; +using std::enable_if; +} +#endif + +namespace rustcpp { +template +typename enable_if::value>::type copy_helper(const void *src, void *dest) +{ new (dest) T (*static_cast(src)); } +template +typename enable_if::value>::type copy_helper(const void *, void *) { } +template +typename enable_if::value>::type default_helper(void *dest) +{ new (dest) T(); } +template +typename enable_if::value>::type default_helper(void *) { } +} + "#; lazy_static! { @@ -190,7 +234,10 @@ void __cpp_destructor_{hash}(void *ptr) {{ static_cast< {cpp_name} *>(ptr)->~T(); }} void __cpp_copy_{hash}(const void *src, void *dest) {{ - new (dest) {cpp_name} (*static_cast<{cpp_name} const*>(src)); + rustcpp::copy_helper<{cpp_name}>(src, dest); +}} +void __cpp_default_{hash}(void *dest) {{ + rustcpp::default_helper<{cpp_name}>(dest); }} }} "#, @@ -222,9 +269,12 @@ struct AlignOf {{ template struct Flags {{ static const uintptr_t value = + (is_copy_constructible::value << {flag_is_copy_constructible}) | + (is_default_constructible::value << {flag_is_default_constructible}) | #if __cplusplus > 199711L - (std::is_trivially_destructible::value << 0) | - (std::is_trivially_copyable::value << 1) | + (std::is_trivially_destructible::value << {flag_is_trivially_destructible}) | + (std::is_trivially_copyable::value << {flag_is_trivially_copyable}) | + (std::is_trivially_default_constructible::value << {flag_is_trivially_default_constructible}) | #endif 0; }}; @@ -256,7 +306,12 @@ MetaData metadata = {{ data = sizealign.join(", "), length = sizealign.len(), magic = magic.join(", "), - version = VERSION + version = VERSION, + flag_is_copy_constructible = flags::IS_COPY_CONSTRUCTIBLE, + flag_is_default_constructible = flags::IS_DEFAULT_CONSTRUCTIBLE, + flag_is_trivially_destructible = flags::IS_TRIVIALLY_DESTRUCTIBLE, + flag_is_trivially_copyable = flags::IS_TRIVIALLY_COPYABLE, + flag_is_trivially_default_constructible = flags::IS_TRIVIALLY_DEFAULT_CONSTRUCTIBLE, ).unwrap(); result_path diff --git a/cpp_common/src/lib.rs b/cpp_common/src/lib.rs index e820125..4f4e365 100644 --- a/cpp_common/src/lib.rs +++ b/cpp_common/src/lib.rs @@ -17,6 +17,14 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub const LIB_NAME: &'static str = "librust_cpp_generated.a"; pub const MSVC_LIB_NAME: &'static str = "rust_cpp_generated.lib"; +pub mod flags { + pub const IS_COPY_CONSTRUCTIBLE : u32 = 0; + pub const IS_DEFAULT_CONSTRUCTIBLE : u32 = 1; + pub const IS_TRIVIALLY_DESTRUCTIBLE : u32 = 2; + pub const IS_TRIVIALLY_COPYABLE : u32 = 3; + pub const IS_TRIVIALLY_DEFAULT_CONSTRUCTIBLE : u32 = 4; +} + /// This constant is expected to be a unique string within the compiled binary /// which preceeds a definition of the metadata. It begins with /// rustcpp~metadata, which is printable to make it easier to locate when diff --git a/cpp_macros/src/lib.rs b/cpp_macros/src/lib.rs index 11014ef..59d868f 100644 --- a/cpp_macros/src/lib.rs +++ b/cpp_macros/src/lib.rs @@ -26,7 +26,7 @@ use std::env; use std::path::PathBuf; use std::collections::HashMap; use proc_macro::TokenStream; -use cpp_common::{parsing, LIB_NAME, MSVC_LIB_NAME, VERSION}; +use cpp_common::{parsing, LIB_NAME, MSVC_LIB_NAME, VERSION, flags}; use syn::Ident; use std::io::{self, BufReader, Read, Seek, SeekFrom}; @@ -39,6 +39,11 @@ struct MetaData { align: usize, flags: u64 } +impl MetaData { + fn has_flag(&self, f : u32) -> bool { + self.flags & (1 << f) != 0 + } +} lazy_static! { static ref OUT_DIR: PathBuf = @@ -354,6 +359,7 @@ NOTE: They cannot be generated by macro expansion."#, let destructor_name : Ident = format!("__cpp_destructor_{}", hash).into(); let copyctr_name : Ident = format!("__cpp_copy_{}", hash).into(); + let defaultctr_name : Ident = format!("__cpp_default_{}", hash).into(); let class_name = class.name; let mut result = quote! { @@ -363,7 +369,7 @@ NOTE: They cannot be generated by macro expansion."#, const CPP_TYPE: &'static str = stringify!(#class_name); } }; - if (size_data[0].flags & 1) == 0 { + if !size_data[0].has_flag(flags::IS_TRIVIALLY_DESTRUCTIBLE) { result = quote!{ #result impl Drop for #class_name { fn drop(&mut self) { @@ -375,27 +381,43 @@ NOTE: They cannot be generated by macro expansion."#, } }; }; - if (size_data[0].flags & 2) == 0 { + if size_data[0].has_flag(flags::IS_COPY_CONSTRUCTIBLE) { + if !size_data[0].has_flag(flags::IS_TRIVIALLY_COPYABLE) { + result = quote!{ #result + impl Clone for #class_name { + fn clone(&self) -> Self { + unsafe { + extern "C" { fn #copyctr_name(src: *const #class_name, dst: *mut #class_name); } + let mut ret : Self = std::mem::uninitialized(); + #copyctr_name(& *self, &mut ret); + ret + } + } + } + }; + } else { + result = quote!{ #result + impl Copy for #class_name { } + impl Clone for #class_name { + fn clone(&self) -> Self { *self } + } + }; + }; + } + if size_data[0].has_flag(flags::IS_DEFAULT_CONSTRUCTIBLE) { result = quote!{ #result - impl Clone for #class_name { - fn clone(&self) -> Self { + impl Default for #class_name { + fn default() -> Self { unsafe { - extern "C" { fn #copyctr_name(src: *const #class_name, dst: *mut #class_name); } + extern "C" { fn #defaultctr_name(dst: *mut #class_name); } let mut ret : Self = std::mem::uninitialized(); - #copyctr_name(& *self, &mut ret); + #defaultctr_name(&mut ret); ret } } } }; - } else { - result = quote!{ #result - impl Copy for #class_name { } - impl Clone for #class_name { - fn clone(&self) -> Self { *self } - } - }; - }; + } result.to_string().parse().unwrap() } diff --git a/test/src/header.h b/test/src/header.h index 4bc280b..1ebd16c 100644 --- a/test/src/header.h +++ b/test/src/header.h @@ -35,4 +35,16 @@ struct B { int b; }; +struct MoveOnly { + MoveOnly(int a = 8, int b = 9) : data(a,b) { } +#if __cplusplus > 199711L + MoveOnly(const MoveOnly &) = delete ; + MoveOnly& operator=(const MoveOnly &) = delete ; + MoveOnly(MoveOnly &&other) : data(other.data) { } + MoveOnly& operator=(MoveOnly &&other) { data = other.data; return *this; } +#endif + A data; +}; + + #endif // defined(header_h__) diff --git a/test/src/lib.rs b/test/src/lib.rs index 18fa652..f7d8cf6 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -171,6 +171,33 @@ fn simple_class() { *b.a() = 45; let mut b2 = b; assert_eq!(*b2.a(), 45); + + let mut b3 = B::default(); + assert_eq!(*b3.a(), 0); + assert_eq!(*b3.b(), 0); +} + +#[test] +fn move_only() { + cpp_class!(struct MoveOnly, "MoveOnly"); + impl MoveOnly { + fn data(&self) -> &A { + unsafe { + return cpp!([self as "MoveOnly*"] -> &A as "A*" { + return &self->data; + }); + } + } + } + let mo1 = MoveOnly::default(); + assert_eq!(mo1.data().multiply(), 8*9); + let mut mo2 = mo1; + let mo3 = unsafe { cpp!([mut mo2 as "MoveOnly"] -> MoveOnly as "MoveOnly" { + mo2.data.a = 7; + return MoveOnly(3,2); + })}; + assert_eq!(mo2.data().multiply(), 7*9); + assert_eq!(mo3.data().multiply(), 3*2); } #[test] From 26712ab1eb88b7352011f7b6cc1ad99c26c94c52 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Wed, 28 Feb 2018 14:53:05 +0100 Subject: [PATCH 3/8] Use atomic for the counter test --- test/src/header.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/src/header.h b/test/src/header.h index 1ebd16c..594a57e 100644 --- a/test/src/header.h +++ b/test/src/header.h @@ -4,10 +4,18 @@ #include #include +#if __cplusplus > 199711L +#include +typedef std::atomic counter_t; +#define COUNTER_STATIC static +#else +typedef int counter_t; +#endif + // This counter is incremented by destructors and constructors // and must be 0 at the end of the program -inline int &counter() { - static int counter = 0; +inline counter_t &counter() { + static counter_t counter; struct CheckCounter { ~CheckCounter() { assert(counter == 0); From 68468e432a43b89a167a9d27a645550ca0b2abf3 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 17 May 2018 09:51:44 +0200 Subject: [PATCH 4/8] Change the syntax of cpp_class: replace the ',' with 'as' --- cpp/src/lib.rs | 10 +++++----- cpp_common/src/lib.rs | 2 +- test/src/lib.rs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index cc38a1d..43102ed 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -122,7 +122,7 @@ pub trait CppTrait { /// trait appropriately. /// /// ```ignore -/// cpp_class!(pub struct MyClass, "MyClass"); +/// cpp_class!(pub struct MyClass as "MyClass"); /// impl MyClass { /// fn new() -> Self { /// unsafe { cpp!([] -> MyClass as "MyClass" { return MyClass(); }) } @@ -146,18 +146,18 @@ pub trait CppTrait { /// #[macro_export] macro_rules! cpp_class { - (struct $name:ident, $type:expr) => { + (struct $name:ident as $type:expr) => { #[derive(__cpp_internal_class)] #[repr(C)] struct $name { - _opaque : [<$name as $crate::CppTrait>::BaseType ; <$name as $crate::CppTrait>::ARRAY_SIZE + (stringify!(struct $name, $type), 0).1] + _opaque : [<$name as $crate::CppTrait>::BaseType ; <$name as $crate::CppTrait>::ARRAY_SIZE + (stringify!(struct $name as $type), 0).1] } }; - (pub struct $name:ident, $type:expr) => { + (pub struct $name:ident as $type:expr) => { #[derive(__cpp_internal_class)] #[repr(C)] pub struct $name { - _opaque : [<$name as $crate::CppTrait>::BaseType ; <$name as $crate::CppTrait>::ARRAY_SIZE + (stringify!(pub struct $name, $type), 0).1] + _opaque : [<$name as $crate::CppTrait>::BaseType ; <$name as $crate::CppTrait>::ARRAY_SIZE + (stringify!(pub struct $name as $type), 0).1] } }; } diff --git a/cpp_common/src/lib.rs b/cpp_common/src/lib.rs index 4f4e365..0a951b2 100644 --- a/cpp_common/src/lib.rs +++ b/cpp_common/src/lib.rs @@ -200,7 +200,7 @@ pub mod parsing { is_pub: option!(keyword!("pub")) >> keyword!("struct") >> name: ident >> - punct!(",") >> + keyword!("as") >> cpp_type: string >> (Class { name: name, diff --git a/test/src/lib.rs b/test/src/lib.rs index f7d8cf6..a37ecaf 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -20,7 +20,7 @@ cpp!{{ #include }} -cpp_class!(struct A, "A"); +cpp_class!(struct A as "A"); impl A { fn new(a : i32, b: i32) -> Self { unsafe { @@ -136,7 +136,7 @@ fn member_function() { assert_eq!(a.multiply(), 5*6); } -cpp_class!(struct B, "B"); +cpp_class!(struct B as "B"); impl B { fn new(a : i32, b: i32) -> Self { unsafe { @@ -179,7 +179,7 @@ fn simple_class() { #[test] fn move_only() { - cpp_class!(struct MoveOnly, "MoveOnly"); + cpp_class!(struct MoveOnly as "MoveOnly"); impl MoveOnly { fn data(&self) -> &A { unsafe { From efc5a19c1eb868b0b72511125e5b301f57f22655 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 17 May 2018 10:12:57 +0200 Subject: [PATCH 5/8] small changes to the documentation --- cpp/src/lib.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index 43102ed..549006f 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -121,6 +121,12 @@ pub trait CppTrait { /// have destructor or copy constructor, and instantiate the Drop and Clone /// trait appropriately. /// +/// Warning: This only work if the C++ class that are relocatable, i.e., that +/// can be moved in memory using memmove. +/// This disallows most classes from the standard library. +/// This restriction exists because rust is allowed to move your types around. +/// Most C++ types that do not contain self-references or +/// /// ```ignore /// cpp_class!(pub struct MyClass as "MyClass"); /// impl MyClass { @@ -136,14 +142,11 @@ pub trait CppTrait { /// ``` /// /// This will create a rust struct MyClass, which has the same size and -/// alignement as the the C++ class "MyClass". It will also implement the Drop trait +/// alignment as the the C++ class "MyClass". It will also implement the Drop trait /// calling the destructor, the Clone trait calling the copy constructor, if the -/// class is copyable (or Copy if it is trivialy copyable), and Default if the class +/// class is copyable (or Copy if it is trivially copyable), and Default if the class /// is default constructible /// -/// Warning: This only work if the C++ class can be moved in memory (using -/// memcpy). This disallow most classes from the standard library. -/// #[macro_export] macro_rules! cpp_class { (struct $name:ident as $type:expr) => { From c5a61bacd20e0df351fd7e8106689ec7571b5361 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 17 May 2018 10:55:15 +0200 Subject: [PATCH 6/8] Fix issues raised in the pull request https://github.com/mystor/rust-cpp/pull/28 --- cpp_build/src/lib.rs | 9 ++++++--- cpp_common/src/lib.rs | 2 +- cpp_macros/src/lib.rs | 11 +++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cpp_build/src/lib.rs b/cpp_build/src/lib.rs index c104411..24fb288 100644 --- a/cpp_build/src/lib.rs +++ b/cpp_build/src/lib.rs @@ -44,6 +44,7 @@ const INTERNAL_CPP_STRUCTS: &'static str = r#" #include "stdint.h" // For {u}intN_t #include // For placement new +#include // For abort #if __cplusplus <= 199711L && !defined(_MSC_VER) namespace rustcpp { @@ -81,12 +82,14 @@ template typename enable_if::value>::type copy_helper(const void *src, void *dest) { new (dest) T (*static_cast(src)); } template -typename enable_if::value>::type copy_helper(const void *, void *) { } +typename enable_if::value>::type copy_helper(const void *, void *) +{ std::abort(); } template typename enable_if::value>::type default_helper(void *dest) { new (dest) T(); } template -typename enable_if::value>::type default_helper(void *) { } +typename enable_if::value>::type default_helper(void *) +{ std::abort(); } } "#; @@ -231,7 +234,7 @@ void {name}({params}{comma} void* __result) {{ extern "C" {{ void __cpp_destructor_{hash}(void *ptr) {{ typedef {cpp_name} T; - static_cast< {cpp_name} *>(ptr)->~T(); + static_cast(ptr)->~T(); }} void __cpp_copy_{hash}(const void *src, void *dest) {{ rustcpp::copy_helper<{cpp_name}>(src, dest); diff --git a/cpp_common/src/lib.rs b/cpp_common/src/lib.rs index 0a951b2..91171f5 100644 --- a/cpp_common/src/lib.rs +++ b/cpp_common/src/lib.rs @@ -83,7 +83,7 @@ pub struct Closure { pub struct Class { pub name: Ident, pub cpp: String, - pub public: bool + pub public: bool, } impl Class { diff --git a/cpp_macros/src/lib.rs b/cpp_macros/src/lib.rs index 59d868f..ba84d71 100644 --- a/cpp_macros/src/lib.rs +++ b/cpp_macros/src/lib.rs @@ -105,19 +105,19 @@ Version mismatch between cpp_macros and cpp_build for same crate."# ); let length = file.read_u64::()?; - let mut size_data = HashMap::new(); + let mut metadata = HashMap::new(); for _ in 0..length { let hash = file.read_u64::()?; let size = file.read_u64::()? as usize; let align = file.read_u64::()? as usize; let flags = file.read_u64::()? as u64; - size_data + metadata .entry(hash) .or_insert(Vec::new()) .push(MetaData{size, align, flags}); } - Ok(size_data) + Ok(metadata) } /// Try to open a file handle to the lib file. This is used to scan it for @@ -207,7 +207,7 @@ NOTE: They cannot be generated by macro expansion."#, // Generate the assertion to check that the size and align of the types // match before calling. - let (size, align) = (size_data[i + 1].size, size_data[i + 1].align); + let MetaData { size, align, .. } = size_data[i + 1]; let sizeof_msg = format!( "size_of for argument `{}` does not match between c++ and \ rust", @@ -310,7 +310,6 @@ NOTE: They cannot be generated by macro expansion."#, result.to_string().parse().unwrap() } - #[proc_macro_derive(__cpp_internal_class)] pub fn expand_wrap_class(input: TokenStream) -> TokenStream { let source = input.to_string(); @@ -354,7 +353,7 @@ NOTE: They cannot be generated by macro expansion."#, 2 => { quote!(u16) } 4 => { quote!(u32) } 8 => { quote!(u64) } - _ => { panic!("unsupported alignement") } + _ => { panic!("unsupported alignment") } }; let destructor_name : Ident = format!("__cpp_destructor_{}", hash).into(); From 9ac8cea73eca59f3b8f1bb522f2b71a8851f8464 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 25 May 2018 11:35:05 +0200 Subject: [PATCH 7/8] Use #line to report the error at the cpp_class location For example if there is a typo in the C++ class name, or if the class cannot be destructed or is only forward declared. --- cpp_build/src/lib.rs | 48 +++++++++++++++++++++---------------------- cpp_common/src/lib.rs | 9 +++++--- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/cpp_build/src/lib.rs b/cpp_build/src/lib.rs index 24fb288..77d538f 100644 --- a/cpp_build/src/lib.rs +++ b/cpp_build/src/lib.rs @@ -92,6 +92,12 @@ typename enable_if::value>::type default_helper(voi { std::abort(); } } +#define RUST_CPP_CLASS_HELPER(HASH, ...) \ + extern "C" { \ + void __cpp_destructor_##HASH(void *ptr) { typedef __VA_ARGS__ T; static_cast(ptr)->~T(); } \ + void __cpp_copy_##HASH(const void *src, void *dest) { rustcpp::copy_helper<__VA_ARGS__>(src, dest); } \ + void __cpp_default_##HASH(void *dest) { rustcpp::default_helper<__VA_ARGS__>(dest); } \ + } "#; lazy_static! { @@ -227,25 +233,12 @@ void {name}({params}{comma} void* __result) {{ rustcpp::Flags<{type}>::value }}", hash=hash, type=class.cpp)); - // Generate constructor + // Generate helper function. + // (this is done in a macro, which right after a #line directing pointing to the location of + // the cpp_class! macro in order to give right line information in the possible errors) write!( - output, - r#" -extern "C" {{ -void __cpp_destructor_{hash}(void *ptr) {{ - typedef {cpp_name} T; - static_cast(ptr)->~T(); -}} -void __cpp_copy_{hash}(const void *src, void *dest) {{ - rustcpp::copy_helper<{cpp_name}>(src, dest); -}} -void __cpp_default_{hash}(void *dest) {{ - rustcpp::default_helper<{cpp_name}>(dest); -}} -}} -"#, - hash = hash, - cpp_name = class.cpp + output, "{line}RUST_CPP_CLASS_HELPER({hash}, {cpp_name})\n", + line = class.line, hash = hash, cpp_name = class.cpp ).unwrap(); } @@ -601,17 +594,21 @@ struct Handle<'a> { sm: &'a SourceMap, } +fn line_directive(span: syn::Span, sm: &SourceMap) -> String { + let loc = sm.locinfo(span).unwrap(); + let mut line = format!("#line {} {:?}\n", loc.line, loc.path); + for _ in 0..loc.col { + line.push(' '); + } + return line; +} + fn extract_with_span(spanned: &mut Spanned, src: &str, offset: usize, sm: &SourceMap) { if spanned.span != DUMMY_SPAN { let src_slice = &src[spanned.span.lo..spanned.span.hi]; spanned.span.lo += offset; spanned.span.hi += offset; - - let loc = sm.locinfo(spanned.span).unwrap(); - spanned.node = format!("#line {} {:?}\n", loc.line, loc.path); - for _ in 0..loc.col { - spanned.node.push(' '); - } + spanned.node = line_directive(spanned.span, sm); spanned.node.push_str(src_slice); } } @@ -651,7 +648,8 @@ impl<'a> Visitor for Handle<'a> { }; let src = self.sm.source_text(span).unwrap(); let input = synom::ParseState::new(&src); - let class = parsing::class_macro(input).expect("cpp_class! macro"); + let mut class = parsing::class_macro(input).expect("cpp_class! macro"); + class.line = line_directive(span, self.sm); self.classes.push(class); } } diff --git a/cpp_common/src/lib.rs b/cpp_common/src/lib.rs index 91171f5..5c8b245 100644 --- a/cpp_common/src/lib.rs +++ b/cpp_common/src/lib.rs @@ -79,18 +79,20 @@ pub struct Closure { pub body: Spanned, } -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Debug)] pub struct Class { pub name: Ident, pub cpp: String, pub public: bool, + pub line: String, // the #line directive } impl Class { pub fn name_hash(&self) -> u64 { - // XXX: Use a better hasher than the default? let mut hasher = DefaultHasher::new(); - self.hash(&mut hasher); + self.name.hash(&mut hasher); + self.cpp.hash(&mut hasher); + self.public.hash(&mut hasher); hasher.finish() } } @@ -206,6 +208,7 @@ pub mod parsing { name: name, cpp: cpp_type.value, public: is_pub.is_some(), + line: String::default(), }))); named!(pub class_macro -> Class , mac_body!(cpp_class)); From 44791f4b16c71ff4c8a96b9e56b5bdd89b73fb3f Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 24 May 2018 17:57:51 +0200 Subject: [PATCH 8/8] Use C++11 unconditionally Note that some type traits are still not implemented in old GCC --- cpp_build/src/lib.rs | 45 +++++++------------------------------------- test/src/header.h | 2 +- 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/cpp_build/src/lib.rs b/cpp_build/src/lib.rs index 77d538f..67be96d 100644 --- a/cpp_build/src/lib.rs +++ b/cpp_build/src/lib.rs @@ -45,50 +45,20 @@ const INTERNAL_CPP_STRUCTS: &'static str = r#" #include "stdint.h" // For {u}intN_t #include // For placement new #include // For abort - -#if __cplusplus <= 199711L && !defined(_MSC_VER) -namespace rustcpp { -template -struct is_default_constructible { - template - static double test(int(*)[sizeof(new X)]); - template - static char test(...); - enum { value = sizeof(test(0)) == sizeof(double) }; -}; -template -struct is_copy_constructible { - static T &declval(); - template - static double test(int(*)[sizeof(new X(declval()))]); - template - static char test(...); - enum { value = sizeof(test(0)) == sizeof(double) }; -}; -template struct enable_if {}; -template struct enable_if { typedef T type; }; -} -#else #include -namespace rustcpp { -using std::is_default_constructible; -using std::is_copy_constructible; -using std::enable_if; -} -#endif namespace rustcpp { template -typename enable_if::value>::type copy_helper(const void *src, void *dest) +typename std::enable_if::value>::type copy_helper(const void *src, void *dest) { new (dest) T (*static_cast(src)); } template -typename enable_if::value>::type copy_helper(const void *, void *) +typename std::enable_if::value>::type copy_helper(const void *, void *) { std::abort(); } template -typename enable_if::value>::type default_helper(void *dest) +typename std::enable_if::value>::type default_helper(void *dest) { new (dest) T(); } template -typename enable_if::value>::type default_helper(void *) +typename std::enable_if::value>::type default_helper(void *) { std::abort(); } } @@ -265,9 +235,9 @@ struct AlignOf {{ template struct Flags {{ static const uintptr_t value = - (is_copy_constructible::value << {flag_is_copy_constructible}) | - (is_default_constructible::value << {flag_is_default_constructible}) | -#if __cplusplus > 199711L + (std::is_copy_constructible::value << {flag_is_copy_constructible}) | + (std::is_default_constructible::value << {flag_is_default_constructible}) | +#if !defined(__GNUC__) || (__GNUC__ + 0 >= 5) || defined(__clang__) (std::is_trivially_destructible::value << {flag_is_trivially_destructible}) | (std::is_trivially_copyable::value << {flag_is_trivially_copyable}) | (std::is_trivially_default_constructible::value << {flag_is_trivially_default_constructible}) | @@ -275,7 +245,6 @@ struct Flags {{ 0; }}; - struct SizeAlign {{ uint64_t hash; uint64_t size; diff --git a/test/src/header.h b/test/src/header.h index 594a57e..3f30346 100644 --- a/test/src/header.h +++ b/test/src/header.h @@ -45,7 +45,7 @@ struct B { struct MoveOnly { MoveOnly(int a = 8, int b = 9) : data(a,b) { } -#if __cplusplus > 199711L +#if !defined (_MSC_VER) || (_MSC_VER + 0 >= 1900) MoveOnly(const MoveOnly &) = delete ; MoveOnly& operator=(const MoveOnly &) = delete ; MoveOnly(MoveOnly &&other) : data(other.data) { }