Skip to content
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
9 changes: 9 additions & 0 deletions c2rust-ast-exporter/src/AstExporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2569,6 +2569,15 @@ class TranslateASTVisitor final
// 4. Encode the type's full bit width (even if a
// bitfield)
cbor_encode_uint(array, bitWidth);

// 5. Encode manually specified alignment
// (e.g. `__attribute__((aligned(N)))` on the field)
auto align = D->getMaxAlignment();
if (align == 0) {
cbor_encode_null(array);
} else {
cbor_encode_uint(array, align / 8);
}
});

// This might be the only occurrence of this type in the translation unit
Expand Down
3 changes: 3 additions & 0 deletions c2rust-transpile/src/c_ast/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2457,12 +2457,15 @@ impl ConversionContext {
from_value(node.extras[2].clone()).expect("Did not find field bit offset");
let platform_type_bitwidth =
from_value(node.extras[3].clone()).expect("Did not find field bitwidth");
let manual_alignment =
expect_opt_u64(&node.extras[4]).expect("Expected field alignment");
let field = CDeclKind::Field {
name,
typ,
bitfield_width,
platform_bit_offset,
platform_type_bitwidth,
manual_alignment,
};
self.add_decl(new_id, located(node, field));
self.processed_nodes.insert(new_id, FIELD_DECL);
Expand Down
3 changes: 3 additions & 0 deletions c2rust-transpile/src/c_ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1708,6 +1708,9 @@ pub enum CDeclKind {
bitfield_width: Option<u64>,
platform_bit_offset: u64,
platform_type_bitwidth: u64,
/// Manually specified alignment in bytes, e.g. from
/// `__attribute__((aligned(N)))` on the field itself.
manual_alignment: Option<u64>,
},

MacroObject {
Expand Down
25 changes: 25 additions & 0 deletions c2rust-transpile/src/translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3225,6 +3225,31 @@ impl<'c> Translation<'c> {
ConvertVector(..) => Err(TranslationError::generic("convert vector not supported")),

UnaryType(result_type_id, kind, opt_expr, arg_ty) => {
// `__alignof__`/`_Alignof` applied directly to a struct
// field (e.g. `__alignof__(s->f)`) should reflect that
// field's own manual `__attribute__((aligned(N)))`, if any,
// rather than its type's natural alignment.
if matches!(kind, CUnTypeOp::AlignOf | CUnTypeOp::PreferredAlignOf) {
if let Some(field_expr) = opt_expr {
if let CExprKind::Member(_, _, decl_id, _, _) =
self.ast_context.index_unwrap_parens(field_expr).kind
{
if let CDeclKind::Field {
manual_alignment: Some(alignment),
..
} = self.ast_context[decl_id].kind
{
let ty = self.convert_type(result_type_id.ctype)?;
let val = mk().cast_expr(
mk().lit_expr(mk().int_unsuffixed_lit(alignment)),
ty,
);
return Ok(WithStmts::new_val(val));
}
}
}
}

let result = match kind {
CUnTypeOp::SizeOf => match opt_expr {
None => self.compute_size_of_type(
Expand Down
60 changes: 57 additions & 3 deletions c2rust-transpile/src/translator/structs_unions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ impl<'a> Translation<'a> {
_ => {}
}

// A field with its own `__attribute__((aligned(N)))` forces clang to
// raise the *whole record's* alignment to (at least) N, since every
// instance of the struct must be aligned enough for that field.
// Rust's automatic `repr(C)` layout only derives struct alignment
// from each field's natural type alignment, so make the requirement
// explicit; combined with `repr(C)`, `align(N)` sets a *minimum*
// alignment without disturbing fields that already need more.
// (The field's own byte offset is corrected separately, via explicit
// padding inserted in `get_field_types`.)
let field_manual_alignment = fields
.iter()
.filter_map(|field_id| match self.ast_context.index(*field_id).kind {
CDeclKind::Field {
manual_alignment, ..
} => manual_alignment,
_ => None,
})
.max();
if let Some(alignment) = field_manual_alignment {
reprs.push(mk().meta_list("align", vec![alignment]));
}

if let Some(alignment) = manual_alignment {
// This is the most complicated case: we have `align(N)` which
// might be mixed with or included into a `packed` structure,
Expand All @@ -117,6 +139,7 @@ impl<'a> Translation<'a> {
// instead, we should only split when needed, but that
// would significantly complicate the implementation
assert!(self.ast_context.has_inner_struct_decl(decl_id));
let outer_alignment = alignment.max(field_manual_alignment.unwrap_or(0));
let inner_name = self.resolve_decl_inner_name(decl_id);
let inner_ty = mk().path_ty(vec![inner_name.clone()]);
let inner_struct = mk()
Expand All @@ -136,7 +159,7 @@ impl<'a> Translation<'a> {
"repr",
vec![
mk().meta_path("C"),
mk().meta_list("align", vec![alignment]),
mk().meta_list("align", vec![outer_alignment]),
// TODO: copy others from `reprs` above
],
)
Expand Down Expand Up @@ -322,6 +345,15 @@ impl<'a> Translation<'a> {

field_entries.push(field);
}
FieldType::AlignPadding { bytes } => {
let field_name = next_padding_field();
let ty = mk().array_ty(
mk().ident_ty("u8"),
mk().lit_expr(mk().int_unsuffixed_lit(bytes)),
);

field_entries.push(mk().pub_().struct_field(field_name, ty));
}
FieldType::ComputedPadding { ident } => {
let field_name = next_padding_field();
let ty = mk().array_ty(mk().ident_ty("u8"), mk().ident_expr(ident));
Expand Down Expand Up @@ -417,7 +449,7 @@ impl<'a> Translation<'a> {

fields.push(WithStmts::new_val(field));
}
FieldType::Padding { bytes } => {
FieldType::Padding { bytes } | FieldType::AlignPadding { bytes } => {
let field_name = next_padding_field();
let array_expr = mk().repeat_expr(
mk().lit_expr(mk().int_unsuffixed_lit(0)),
Expand Down Expand Up @@ -646,7 +678,7 @@ impl<'a> Translation<'a> {

fields.push(WithStmts::new_val(field));
}
FieldType::Padding { bytes } => {
FieldType::Padding { bytes } | FieldType::AlignPadding { bytes } => {
let field_name = next_padding_field();
let array_expr = mk().repeat_expr(
mk().lit_expr(mk().int_unsuffixed_lit(0)),
Expand Down Expand Up @@ -812,6 +844,7 @@ impl<'a> Translation<'a> {
bitfield_width,
platform_bit_offset,
platform_type_bitwidth,
manual_alignment,
..
} = self.ast_context.index(*field_id).kind
{
Expand Down Expand Up @@ -883,6 +916,20 @@ impl<'a> Translation<'a> {
})
}

if manual_alignment.is_some() && (platform_bit_offset / 8) > next_byte_pos {
// A per-field `__attribute__((aligned(N)))` forced
// clang to place this field further along than
// Rust's automatic `repr(C)` layout would (which
// only knows about the field's *natural*
// alignment). Insert explicit padding so the
// field lands at the same byte offset as in C;
// `convert_struct` separately pushes `align(M)`
// onto the struct's `repr` so that offset is
// actually reachable at a valid address.
let bytes = (platform_bit_offset / 8) - next_byte_pos;
reorganized_fields.push(FieldType::AlignPadding { bytes });
}

let field = mk().pub_().struct_field(field_name.clone(), ty);

reorganized_fields.push(FieldType::Regular {
Expand Down Expand Up @@ -1108,6 +1155,13 @@ enum FieldType {
Padding {
bytes: u64,
},
/// Like [`FieldType::Padding`], but not tagged `#[bitfield(padding)]`:
/// used to reach the byte offset a manually `__attribute__((aligned(N)))`
/// field needs, in a struct that may have no real bitfields (and thus no
/// `#[derive(BitfieldStruct)]` to make that attribute meaningful).
AlignPadding {
bytes: u64,
},
ComputedPadding {
ident: String,
},
Expand Down
22 changes: 22 additions & 0 deletions tests/unit/structs/src/structs.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,28 @@ size_t alignment_of_aligned8_struct(void) {
return alignof(Aligned8Struct);
}

typedef struct {
char before;
int __attribute__((aligned(16))) f;
char after;
} FieldAlignStruct;

size_t alignment_of_field_align_struct_field(void) {
return __alignof__(((FieldAlignStruct *)0)->f);
}

size_t offset_of_field_align_struct_field(void) {
return offsetof(FieldAlignStruct, f);
}

int read_field_align_struct_field(const FieldAlignStruct *s) {
return s->f;
}

void write_field_align_struct_field(FieldAlignStruct *s, int v) {
s->f = v;
}

#define DEFINE_TEST_STRUCT(x) struct x { char c; short s; long long ll; }
DEFINE_TEST_STRUCT(S1);

Expand Down
43 changes: 41 additions & 2 deletions tests/unit/structs/src/test_structs.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::structs::{rust_alignment_entry, rust_entry, Aligned8Struct};
use crate::structs::{rust_alignment_entry, rust_entry, Aligned8Struct, FieldAlignStruct};
use libc::size_t;
use std::ffi::{c_int, c_uint};
use std::mem::align_of;
use std::mem::{align_of, size_of};

#[link(name = "test")]
extern "C" {
fn entry(_: c_uint, _: *mut c_int);
fn alignment_of_aligned8_struct() -> size_t;
fn alignment_entry(_: c_uint, _: *mut c_int);
fn alignment_of_field_align_struct_field() -> size_t;
fn offset_of_field_align_struct_field() -> size_t;
fn read_field_align_struct_field(_: *const FieldAlignStruct) -> c_int;
fn write_field_align_struct_field(_: *mut FieldAlignStruct, _: c_int);
}

const BUFFER_SIZE: usize = 9;
Expand Down Expand Up @@ -35,6 +39,41 @@ pub fn test_alignment() {
assert_eq!(align_of::<Aligned8Struct>(), c_alignment);
}

#[test]
pub fn test_field_alignment() {
let c_alignment = unsafe { alignment_of_field_align_struct_field() };
let c_offset = unsafe { offset_of_field_align_struct_field() };

// `f`'s manual alignment is only reachable in Rust by forcing the whole
// struct's alignment up to it (Rust has no way to raise a single
// field's alignment without also inflating its size), so the field's
// required alignment shows up as the struct's alignment instead.
assert_eq!(align_of::<FieldAlignStruct>(), c_alignment);
assert_eq!(offset_of!(FieldAlignStruct, f), c_offset as usize);
assert_eq!(size_of::<FieldAlignStruct>() % c_alignment, 0);

let mut s = FieldAlignStruct {
before: 1,
c2rust_padding: [0; 15],
f: 0,
after: 2,
};

// The field's actual runtime address must be aligned to `c_alignment`,
// regardless of where `s` itself happens to live.
let field_addr = std::ptr::addr_of!(s.f) as usize;
assert_eq!(field_addr % c_alignment as usize, 0);

unsafe {
write_field_align_struct_field(&mut s, 42);
}
assert_eq!(s.f, 42);

s.f = 99;
let read_back = unsafe { read_field_align_struct_field(&s) };
assert_eq!(read_back, 99);
}

#[test]
pub fn test_alignments() {
let mut buffer = [0; ALIGNMENT_BUFFER_SIZE];
Expand Down
Loading