Skip to content

Introduce new macro to wrap a C++ class: cpp_class! #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,59 @@ 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.
///
/// 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 {
/// 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
/// 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 trivially copyable), and Default if the class
/// is default constructible
///
#[macro_export]
macro_rules! cpp_class {
(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 as $type), 0).1]
}
};
(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 as $type), 0).1]
}
};
}

107 changes: 95 additions & 12 deletions cpp_build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ 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,
VERSION};
use cpp_common::{parsing, Capture, Closure, ClosureSig, Macro, Class, LIB_NAME, STRUCT_METADATA_MAGIC,
VERSION, flags};
use cpp_synmap::SourceMap;

fn warnln_impl(a: String) {
Expand All @@ -44,7 +44,30 @@ const INTERNAL_CPP_STRUCTS: &'static str = r#"

#include "stdint.h" // For {u}intN_t
#include <new> // For placement new
#include <cstdlib> // For abort
#include <type_traits>

namespace rustcpp {
template<typename T>
typename std::enable_if<std::is_copy_constructible<T>::value>::type copy_helper(const void *src, void *dest)
{ new (dest) T (*static_cast<T const*>(src)); }
template<typename T>
typename std::enable_if<!std::is_copy_constructible<T>::value>::type copy_helper(const void *, void *)
{ std::abort(); }
template<typename T>
typename std::enable_if<std::is_default_constructible<T>::value>::type default_helper(void *dest)
{ new (dest) T(); }
template<typename T>
typename std::enable_if<!std::is_default_constructible<T>::value>::type default_helper(void *)
{ std::abort(); }
}

#define RUST_CPP_CLASS_HELPER(HASH, ...) \
extern "C" { \
void __cpp_destructor_##HASH(void *ptr) { typedef __VA_ARGS__ T; static_cast<T*>(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! {
Expand Down Expand Up @@ -89,21 +112,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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this could be made cleaner by unifying all of this logic into a single ::Flags-like invocation. We could just have a list of rustcpp::Meta<{type}, {hash}ull>::value?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you mean.
You mean that rustcpp::Meta<{type}, {hash}ull>::value would be a SizeAlign object?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that was my thought.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs C++11's constexpr, so it depends on the other change that enables C++11

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that MSVC 2013 which is still tested in the CI doesn't support constexpr enough to get this to work.

}}", 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));
}

Expand Down Expand Up @@ -167,6 +192,26 @@ 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 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, "{line}RUST_CPP_CLASS_HELPER({hash}, {cpp_name})\n",
line = class.line, hash = hash, cpp_name = class.cpp
).unwrap();
}

let mut magic = vec![];
for mag in STRUCT_METADATA_MAGIC.iter() {
magic.push(format!("{}", mag));
Expand All @@ -187,10 +232,24 @@ struct AlignOf {{
static const uintptr_t value = sizeof(Inner) - sizeof(T);
}};

template<typename T>
struct Flags {{
static const uintptr_t value =
(std::is_copy_constructible<T>::value << {flag_is_copy_constructible}) |
(std::is_default_constructible<T>::value << {flag_is_default_constructible}) |
#if !defined(__GNUC__) || (__GNUC__ + 0 >= 5) || defined(__clang__)
(std::is_trivially_destructible<T>::value << {flag_is_trivially_destructible}) |
(std::is_trivially_copyable<T>::value << {flag_is_trivially_copyable}) |
(std::is_trivially_default_constructible<T>::value << {flag_is_trivially_default_constructible}) |
#endif
0;
}};

struct SizeAlign {{
uint64_t hash;
uint64_t size;
uint64_t align;
uint64_t flags;
}};

struct MetaData {{
Expand All @@ -212,7 +271,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
Expand Down Expand Up @@ -472,6 +536,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,
};
Expand All @@ -493,21 +558,26 @@ pub fn build<P: AsRef<Path>>(path: P) {

struct Handle<'a> {
closures: Vec<Closure>,
classes: Vec<Class>,
snippets: String,
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<String>, 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);
}
}
Expand Down Expand Up @@ -538,5 +608,18 @@ 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 mut class = parsing::class_macro(input).expect("cpp_class! macro");
class.line = line_directive(span, self.sm);
self.classes.push(class);
}
}
}
52 changes: 48 additions & 4 deletions cpp_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be cleaner to use bitflags here, but I'm not super picky. This feels fine to me.

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
Expand All @@ -28,7 +36,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,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chef's kiss nice :-P

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,
Expand Down Expand Up @@ -71,15 +79,33 @@ pub struct Closure {
pub body: Spanned<String>,
}

#[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 {
let mut hasher = DefaultHasher::new();
self.name.hash(&mut hasher);
self.cpp.hash(&mut hasher);
self.public.hash(&mut hasher);
hasher.finish()
}
}

pub enum Macro {
Closure(Closure),
Lit(Spanned<String>),
}

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)* )) => {
Expand All @@ -95,10 +121,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 {
Expand Down Expand Up @@ -168,4 +196,20 @@ 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 >>
keyword!("as") >>
cpp_type: string >>
(Class {
name: name,
cpp: cpp_type.value,
public: is_pub.is_some(),
line: String::default(),
})));

named!(pub class_macro -> Class , mac_body!(cpp_class));
}
Loading