Skip to content

Commit f974321

Browse files
committed
Add a macro to derive ByteRepr automatically
Shrinks the translated code Part of #216
1 parent ccae460 commit f974321

206 files changed

Lines changed: 1794 additions & 5278 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎cpp2rust/converter/converter.cpp‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ void Converter::EmitGlobalInits(Model model, std::string &out) {
9595
void Converter::EmitOpaqueRecords(std::string &out) {
9696
record_decls_.ForEachUndefined([&](const std::string &name) {
9797
out += "#[derive(Clone, Copy, Default, ByteRepr, VaArg, FnPtrArg)]";
98+
out += "#[byte_size(1)]";
9899
out += "pub struct ";
99100
out += name;
100101
out += ";\n";
@@ -812,6 +813,7 @@ void Converter::EmitRustStructOrUnion(clang::RecordDecl *decl) {
812813
StrCat(attr, ',');
813814
}
814815
StrCat(")]");
816+
EmitByteSizeAttr(decl);
815817

816818
// Fields
817819
auto access = clang::dyn_cast<clang::CXXRecordDecl>(decl)
@@ -837,7 +839,6 @@ void Converter::EmitRustStructOrUnion(clang::RecordDecl *decl) {
837839
}
838840
AddCloneTrait(decl);
839841
AddDefaultTrait(decl);
840-
AddByteReprTrait(decl);
841842
}
842843

843844
void Converter::ConvertLateInstantiatedMethods(clang::CXXRecordDecl *decl) {
@@ -925,7 +926,6 @@ void Converter::EmitRustUnion(clang::RecordDecl *decl) {
925926
}
926927

927928
AddDefaultTrait(decl);
928-
AddByteReprTrait(decl);
929929
}
930930

931931
bool Converter::VisitCXXRecordDecl(clang::CXXRecordDecl *decl) {
@@ -4675,7 +4675,7 @@ void Converter::ConvertFieldInit(const clang::FieldDecl *field,
46754675
}
46764676
}
46774677

4678-
void Converter::AddByteReprTrait(const clang::RecordDecl *decl) {}
4678+
void Converter::EmitByteSizeAttr(const clang::RecordDecl *decl) {}
46794679

46804680
void Converter::ConvertUnsignedArithBinaryOperator(clang::BinaryOperator *op,
46814681
clang::Expr *expr) {

‎cpp2rust/converter/converter.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
673673

674674
void EmitDefaultStructLiteral(const clang::RecordDecl *decl);
675675

676-
virtual void AddByteReprTrait(const clang::RecordDecl *decl);
676+
virtual void EmitByteSizeAttr(const clang::RecordDecl *decl);
677677

678678
virtual void
679679
ConvertUnsignedArithBinaryOperator(clang::BinaryOperator *binary_operator,

‎cpp2rust/converter/converter_lib.cpp‎

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -272,33 +272,6 @@ bool IsMut(clang::QualType qual_type) {
272272
qual_type->getPointeeType().isConstQualified());
273273
}
274274

275-
bool TypeImplementsByteRepr(clang::QualType qt) {
276-
if (qt->isIntegerType() || qt->isFloatingType() || qt->isEnumeralType()) {
277-
return true;
278-
}
279-
if (qt->isPointerType()) {
280-
return true;
281-
}
282-
if (const auto *arr = qt->getAsArrayTypeUnsafe()) {
283-
return TypeImplementsByteRepr(arr->getElementType());
284-
}
285-
if (const auto *rd = qt->getAsRecordDecl()) {
286-
if (rd->isUnion()) {
287-
return true;
288-
}
289-
if (!qt.isTriviallyCopyableType(rd->getASTContext())) {
290-
return false;
291-
}
292-
for (const auto *field : rd->fields()) {
293-
if (!TypeImplementsByteRepr(field->getType())) {
294-
return false;
295-
}
296-
}
297-
return true;
298-
}
299-
return false;
300-
}
301-
302275
bool RustSizeDivergesFromC(clang::QualType qt) {
303276
qt = qt.getCanonicalType();
304277
// Records have Rc<RefCell<>> fields that diverge from the C size
@@ -484,10 +457,6 @@ bool HasDefaultedCopyConstructor(const clang::RecordDecl *decl) {
484457
return !cxx->defaultedCopyConstructorIsDeleted();
485458
}
486459

487-
bool RecordDerivesByteRepr(const clang::RecordDecl *decl) {
488-
return !decl->isUnion() && decl->field_empty();
489-
}
490-
491460
bool RecordHasOnlyReferenceFields(const clang::RecordDecl *decl) {
492461
for (auto *field : decl->fields()) {
493462
if (!field->getType()->isReferenceType()) {

‎cpp2rust/converter/converter_lib.h‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ bool IsUnsignedArithOp(const clang::BinaryOperator *expr);
6767

6868
bool IsMut(clang::QualType qual_type);
6969

70-
bool TypeImplementsByteRepr(clang::QualType qt);
71-
7270
bool RustSizeDivergesFromC(clang::QualType qt);
7371

7472
bool IsMutatingCall(const clang::CallExpr *expr);
@@ -109,8 +107,6 @@ bool HasDefaultedCopyConstructor(const clang::RecordDecl *decl);
109107

110108
bool RecordHasOnlyReferenceFields(const clang::RecordDecl *decl);
111109

112-
bool RecordDerivesByteRepr(const clang::RecordDecl *decl);
113-
114110
bool HasDefaultedCopyAssignment(const clang::RecordDecl *decl);
115111

116112
bool IsRValueConvertingConstructor(const clang::CXXConstructorDecl *ctor);

‎cpp2rust/converter/models/converter_refcount.cpp‎

Lines changed: 19 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,11 @@ void ConverterRefCount::EmitRustUnion(clang::RecordDecl *decl) {
626626
ctx_, ctx_.getCanonicalTagType(decl),
627627
std::vector<std::string>(attrs.begin(), attrs.end()));
628628

629-
StrCat(std::format("pub struct {} {{ __bytes: Value<Box<[u8]>> }}", name));
629+
auto size = ctx_.getTypeSizeInChars(ctx_.getCanonicalTagType(decl));
630+
StrCat(std::format("#[derive(ByteRepr)] #[byte_size({0})] pub struct {1} {{ "
631+
"#[offset(0)] #[byte_size({0})] __bytes: Value<Box<[u8]>> "
632+
"}}",
633+
size.getQuantity(), name));
630634

631635
StrCat("impl", name);
632636
{
@@ -647,84 +651,12 @@ void ConverterRefCount::EmitRustUnion(clang::RecordDecl *decl) {
647651

648652
AddCloneTrait(decl);
649653
AddDefaultTrait(decl);
650-
AddByteReprTrait(decl);
651654
}
652655

653-
void ConverterRefCount::AddByteReprTrait(const clang::RecordDecl *decl) {
654-
if (RecordDerivesByteRepr(decl)) {
655-
return;
656-
}
657-
658-
auto struct_name = GetRecordName(decl);
659-
660-
if (!TypeImplementsByteRepr(ctx_.getCanonicalTagType(decl))) {
661-
// The size locates the fields of the elements of arrays of structs.
662-
StrCat(std::format("impl ByteRepr for {}", struct_name));
663-
PushBrace brace(*this);
664-
if (!decl->isUnion()) {
665-
StrCat(std::format("fn byte_size() -> usize {{ {} }}",
666-
ctx_.getTypeSize(ctx_.getCanonicalTagType(decl)) / 8));
667-
}
668-
return;
669-
}
670-
671-
StrCat("impl ByteRepr for ", struct_name);
672-
PushBrace impl_brace(*this);
673-
674-
if (decl->isUnion()) {
675-
StrCat(std::format("fn byte_size() -> usize {{ {} }}",
676-
ctx_.getTypeSize(ctx_.getCanonicalTagType(decl)) / 8));
677-
StrCat("fn to_bytes(&self, buf: &mut [u8]) { "
678-
"buf.copy_from_slice(&self.__bytes.borrow()); }");
679-
StrCat(std::format("fn from_bytes(buf: &[u8]) -> Self {{ {} {{ __bytes: "
680-
"Rc::new(RefCell::new(Box::from(buf))) }} }}",
681-
struct_name));
682-
return;
683-
}
684-
685-
const auto &layout = ctx_.getASTRecordLayout(decl);
686-
687-
StrCat(std::format("fn byte_size() -> usize {{ {} }}",
688-
ctx_.getTypeSize(ctx_.getCanonicalTagType(decl)) / 8));
689-
690-
StrCat("fn to_bytes(&self, buf: &mut [u8])");
691-
{
692-
PushBrace fn_brace(*this);
693-
unsigned idx = 0;
694-
for (auto *field : decl->fields()) {
695-
auto byte_off = layout.getFieldOffset(idx) / 8;
696-
auto byte_size = ctx_.getTypeSize(field->getType()) / 8;
697-
auto value = "self." + GetNamedDeclAsString(field);
698-
if (IsValueField(ctx_, field)) {
699-
value = std::format("(*{}.borrow())", value);
700-
}
701-
StrCat(std::format("{}.to_bytes(&mut buf[{}..{}]);", value, byte_off,
702-
byte_off + byte_size));
703-
++idx;
704-
}
705-
}
706-
707-
StrCat("fn from_bytes(buf: &[u8]) -> Self");
708-
{
709-
PushBrace fn_brace(*this);
710-
StrCat("Self");
711-
PushBrace lit_brace(*this);
712-
unsigned idx = 0;
713-
for (auto *field : decl->fields()) {
714-
auto byte_off = layout.getFieldOffset(idx) / 8;
715-
auto byte_size = ctx_.getTypeSize(field->getType()) / 8;
716-
PushConversionKind push(*this, ConversionKind::FullRefCount);
717-
std::string storage_ty = ToString(field->getType());
718-
Unwrap(storage_ty, "Value<", ">");
719-
auto value = std::format("<{}>::from_bytes(&buf[{}..{}])", storage_ty,
720-
byte_off, byte_off + byte_size);
721-
if (IsValueField(ctx_, field)) {
722-
value = std::format("Rc::new(RefCell::new({}))", value);
723-
}
724-
StrCat(std::format("{}: {},", GetNamedDeclAsString(field), value));
725-
++idx;
726-
}
727-
}
656+
void ConverterRefCount::EmitByteSizeAttr(const clang::RecordDecl *decl) {
657+
StrCat(std::format(
658+
"#[byte_size({})]",
659+
ctx_.getTypeSizeInChars(ctx_.getCanonicalTagType(decl)).getQuantity()));
728660
}
729661

730662
std::string
@@ -745,6 +677,14 @@ bool ConverterRefCount::VisitFieldDecl(clang::FieldDecl *decl) {
745677
const auto &layout = ctx_.getASTRecordLayout(decl->getParent());
746678
StrCat(std::format("#[offset({})]",
747679
layout.getFieldOffset(decl->getFieldIndex()) / 8));
680+
// Only arithmetic types have the same size in Rust as in C.
681+
auto type = decl->getType().getCanonicalType();
682+
if (!type->isIntegerType() &&
683+
!type->isSpecificBuiltinType(clang::BuiltinType::Float) &&
684+
!type->isSpecificBuiltinType(clang::BuiltinType::Double)) {
685+
StrCat(std::format("#[byte_size({})]",
686+
ctx_.getTypeSizeInChars(decl->getType()).getQuantity()));
687+
}
748688
PushConversionKind push(*this, IsValueField(ctx_, decl)
749689
? ConversionKind::FullRefCount
750690
: ConversionKind::Pointee);
@@ -2356,10 +2296,8 @@ ConverterRefCount::GetStructAttributes(const clang::RecordDecl *decl) {
23562296

23572297
// Gives access to the fields through pointers to them.
23582298
attrs.emplace_back("Record");
2359-
2360-
if (RecordDerivesByteRepr(decl)) {
2361-
attrs.emplace_back("ByteRepr");
2362-
}
2299+
// Gives access to the bytes of the struct, at the offsets of its fields.
2300+
attrs.emplace_back("ByteRepr");
23632301

23642302
if (RecordImplementsClone(decl)) {
23652303
attrs.emplace_back("VaArg");

‎cpp2rust/converter/models/converter_refcount.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class ConverterRefCount final : public Converter {
5353

5454
bool RecordDerivesClone(const clang::RecordDecl *decl);
5555

56-
void AddByteReprTrait(const clang::RecordDecl *decl) override;
56+
void EmitByteSizeAttr(const clang::RecordDecl *decl) override;
5757

5858
bool
5959
VisitUnaryExprOrTypeTraitExpr(clang::UnaryExprOrTypeTraitExpr *expr) override;

‎libcc2rs-macros/src/byte_repr.rs‎

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,67 @@
22
// Distributed under the MIT license that can be found in the LICENSE file.
33

44
use proc_macro::TokenStream;
5-
use syn::{Data, DeriveInput, Fields, parse_macro_input};
5+
use quote::quote;
6+
use syn::{Attribute, Data, DeriveInput, Expr, Fields, parse_macro_input};
7+
8+
fn byte_size(attrs: &[Attribute]) -> Option<Expr> {
9+
attrs
10+
.iter()
11+
.find(|attr| attr.path().is_ident("byte_size"))
12+
.map(|attr| attr.parse_args().unwrap())
13+
}
614

715
pub fn expand(input: TokenStream) -> TokenStream {
816
let input = parse_macro_input!(input as DeriveInput);
917
let name = &input.ident;
10-
let empty = match &input.data {
11-
Data::Struct(s) => match &s.fields {
12-
Fields::Unit => true,
13-
Fields::Named(f) => f.named.is_empty(),
14-
Fields::Unnamed(f) => f.unnamed.is_empty(),
15-
},
16-
_ => false,
18+
let Data::Struct(data) = &input.data else {
19+
panic!("derive(ByteRepr) is only supported on structs, `{name}` is not one");
1720
};
18-
if !empty {
19-
panic!("derive(ByteRepr) is only supported on empty structs, `{name}` is not empty");
20-
}
21+
let fields = match &data.fields {
22+
Fields::Named(fields) => fields.named.iter().collect(),
23+
Fields::Unit => Vec::new(),
24+
Fields::Unnamed(_) => {
25+
panic!("derive(ByteRepr) is not supported on tuple structs, `{name}` is one")
26+
}
27+
};
28+
29+
let size = byte_size(&input.attrs)
30+
.unwrap_or_else(|| panic!("struct `{name}` has no #[byte_size(...)]"));
31+
32+
// The bytes of each field are at its offset, and span the byte size of
33+
// its type, unless given by its byte_size attribute.
34+
let ranges: Vec<_> = fields
35+
.iter()
36+
.map(|field| {
37+
let ty = &field.ty;
38+
let offset = crate::record::offset(name, field);
39+
let size = byte_size(&field.attrs)
40+
.map(|size| quote! { #size })
41+
.unwrap_or_else(|| quote! { <#ty as ::libcc2rs::ByteRepr>::byte_size() });
42+
quote! { (#offset)..(#offset) + #size }
43+
})
44+
.collect();
45+
let idents: Vec<_> = fields.iter().map(|field| &field.ident).collect();
46+
let types: Vec<_> = fields.iter().map(|field| &field.ty).collect();
47+
2148
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
22-
quote::quote! {
49+
quote! {
2350
impl #impl_generics ::libcc2rs::ByteRepr for #name #ty_generics #where_clause {
2451
#[inline]
2552
fn byte_size() -> usize {
26-
1
53+
#size
54+
}
55+
56+
#[allow(unused_variables)]
57+
fn to_bytes(&self, buf: &mut [u8]) {
58+
#(::libcc2rs::ByteRepr::to_bytes(&self.#idents, &mut buf[#ranges]);)*
59+
}
60+
61+
#[allow(unused_variables)]
62+
fn from_bytes(buf: &[u8]) -> Self {
63+
Self {
64+
#(#idents: <#types as ::libcc2rs::ByteRepr>::from_bytes(&buf[#ranges]),)*
65+
}
2766
}
2867
}
2968
}

‎libcc2rs-macros/src/lib.rs‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,22 @@ pub fn goto(_input: TokenStream) -> TokenStream {
8585
}
8686

8787
// #[derive(ByteRepr)]
88-
// pub struct S;
88+
// #[byte_size(16)]
89+
// pub struct S {
90+
// #[offset(0)]
91+
// pub x: i32,
92+
// #[offset(4)]
93+
// #[byte_size(12)]
94+
// pub a: Value<Box<[i32]>>,
95+
// ...
96+
// }
8997
//
90-
// Adds ByteRepr implementation for S. Currently only empty structs are handled. Non-empty structs
91-
// panic.
98+
// Implements libcc2rs::ByteRepr for S, laying out each field at the byte
99+
// offset given by its offset attribute (see derive(Record)). The byte_size
100+
// attribute gives the byte size of S in C, and of the fields whose size in C
101+
// differs from the byte_size() of their type (e.g., arrays and pointers).
92102

93-
#[proc_macro_derive(ByteRepr)]
103+
#[proc_macro_derive(ByteRepr, attributes(offset, byte_size))]
94104
pub fn derive_byte_repr(input: TokenStream) -> TokenStream {
95105
byte_repr::expand(input)
96106
}

‎libcc2rs-macros/src/record.rs‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@
33

44
use proc_macro::TokenStream;
55
use quote::{format_ident, quote};
6-
use syn::{Data, DeriveInput, Expr, Fields, parse_macro_input};
6+
use syn::{Data, DeriveInput, Expr, Field, Fields, Ident, parse_macro_input};
7+
8+
// The byte offset of `field` of struct `name`, given by its offset attribute.
9+
pub fn offset(name: &Ident, field: &Field) -> Expr {
10+
let ident = field.ident.as_ref().unwrap();
11+
field
12+
.attrs
13+
.iter()
14+
.find(|attr| attr.path().is_ident("offset"))
15+
.unwrap_or_else(|| panic!("field `{name}::{ident}` has no #[offset(...)]"))
16+
.parse_args()
17+
.unwrap()
18+
}
719

820
pub fn expand(input: TokenStream) -> TokenStream {
921
let input = parse_macro_input!(input as DeriveInput);
@@ -16,17 +28,7 @@ pub fn expand(input: TokenStream) -> TokenStream {
1628
Fields::Named(fields) => fields
1729
.named
1830
.iter()
19-
.map(|field| {
20-
let ident = field.ident.clone().unwrap();
21-
let offset: Expr = field
22-
.attrs
23-
.iter()
24-
.find(|attr| attr.path().is_ident("offset"))
25-
.unwrap_or_else(|| panic!("field `{name}::{ident}` has no #[offset(...)]"))
26-
.parse_args()
27-
.unwrap();
28-
(ident, offset)
29-
})
31+
.map(|field| (field.ident.clone().unwrap(), offset(name, field)))
3032
.collect(),
3133
_ => Vec::new(),
3234
};

0 commit comments

Comments
 (0)