From ab5f2457fd2972e219a9390b0a02e6b3945bc181 Mon Sep 17 00:00:00 2001 From: Per Larsen Date: Mon, 7 Sep 2026 20:33:30 -0700 Subject: [PATCH 1/5] ast-exporter: register comment-only files before emitting the file table Translation units without declarations have no AST nodes to register their main file. Exporting comments then introduces a file ID absent from the file table, causing include-path lookup to panic. Register the main file before serializing the table so comments always refer to an exported file. --- c2rust-ast-exporter/src/AstExporter.cpp | 6 +++++- c2rust-transpile/tests/snapshots.rs | 5 +++++ c2rust-transpile/tests/snapshots/comments_only.c | 7 +++++++ ...ts__transpile@comments_only.c.2021.clang15.snap | 13 +++++++++++++ ...ts__transpile@comments_only.c.2024.clang15.snap | 14 ++++++++++++++ 5 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 c2rust-transpile/tests/snapshots/comments_only.c create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@comments_only.c.2021.clang15.snap create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@comments_only.c.2024.clang15.snap diff --git a/c2rust-ast-exporter/src/AstExporter.cpp b/c2rust-ast-exporter/src/AstExporter.cpp index 2cae4d28f6..b1718002c5 100644 --- a/c2rust-ast-exporter/src/AstExporter.cpp +++ b/c2rust-ast-exporter/src/AstExporter.cpp @@ -3084,6 +3084,11 @@ class TranslateConsumer : public clang::ASTConsumer { cbor_encoder_close_container(&outer, &array); // 3. Encode all of the visited file names + // A file containing only comments has no AST nodes to register its + // file ID. Register it before emitting the table, since encoding + // comments below must not introduce file IDs missing from it. + const SourceManager& sourceMgr = Context.getSourceManager(); + visitor.getExporterFileId(sourceMgr.getMainFileID(), false); auto files = visitor.getFiles(); cbor_encoder_create_array(&outer, &array, files.size()); for (auto const &file : files) { @@ -3107,7 +3112,6 @@ class TranslateConsumer : public clang::ASTConsumer { // // Getting all comments requires -fparse-all-comments (see // augment_argv())! - const SourceManager& sourceMgr = Context.getSourceManager(); #if CLANG_VERSION_MAJOR < 10 auto comments = Context.getRawCommentList().getComments(); cbor_encoder_create_array(&outer, &array, comments.size()); diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index 8fde688bd5..dfdfdf098b 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -312,6 +312,11 @@ fn test_bool() { transpile("bool.c").run(); } +#[test] +fn test_comments_only() { + transpile("comments_only.c").run(); +} + #[test] fn test_compound_literals() { transpile("compound_literals.c").run(); diff --git a/c2rust-transpile/tests/snapshots/comments_only.c b/c2rust-transpile/tests/snapshots/comments_only.c new file mode 100644 index 0000000000..34fe9108ea --- /dev/null +++ b/c2rust-transpile/tests/snapshots/comments_only.c @@ -0,0 +1,7 @@ +/* A translation unit may contain comments without any declarations. */ + +#ifdef DISABLED_FEATURE +int feature(void) { return 42; } +#endif + +/* Keep multiple comments to exercise source location sorting. */ diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@comments_only.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@comments_only.c.2021.clang15.snap new file mode 100644 index 0000000000..252d890824 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@comments_only.c.2021.clang15.snap @@ -0,0 +1,13 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/comments_only.2021.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@comments_only.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@comments_only.c.2024.clang15.snap new file mode 100644 index 0000000000..d9aa4f6658 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@comments_only.c.2024.clang15.snap @@ -0,0 +1,14 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/comments_only.2024.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unsafe_op_in_unsafe_fn, + unused_assignments, + unused_mut +)] From 228dcbb62fef5143efefd30ca0092472f539ce42 Mon Sep 17 00:00:00 2001 From: Per Larsen Date: Mon, 7 Sep 2026 20:33:30 -0700 Subject: [PATCH 2/5] transpile: cast integer addresses to opaque pointers through c_void pointers Exposed-provenance constructors require Sized pointees, but forward declarations become unsized extern types. Construct a c_void pointer first, then cast to the opaque pointer type while preserving its constness. This allows integer sentinel values and runtime addresses to initialize opaque pointers in both supported Rust editions. --- c2rust-transpile/src/translator/pointers.rs | 21 +++++++- c2rust-transpile/tests/snapshots.rs | 5 ++ .../tests/snapshots/opaque_pointer_casts.c | 11 +++++ ...e@opaque_pointer_casts.c.2021.clang15.snap | 45 +++++++++++++++++ ...e@opaque_pointer_casts.c.2024.clang15.snap | 49 +++++++++++++++++++ 5 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 c2rust-transpile/tests/snapshots/opaque_pointer_casts.c create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@opaque_pointer_casts.c.2021.clang15.snap create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@opaque_pointer_casts.c.2024.clang15.snap diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 9647413fcd..65172c32ed 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -593,14 +593,31 @@ impl<'c> Translation<'c> { Mutability::Mutable => "with_exposed_provenance_mut", }, }; - let pointee_type_rs = self.convert_pointee_type(pointee_type_id.ctype)?; + // Extern types are unsized, but the exposed-provenance constructors + // require Sized pointees. Construct a c_void pointer first and cast + // it to the opaque pointer type, which is still a thin pointer. + let is_opaque = self + .ast_context + .is_forward_declared_type(pointee_type_id.ctype); + let pointee_type_rs = if is_opaque { + mk().abs_path_ty(vec!["core", "ffi", "c_void"]) + } else { + self.convert_pointee_type(pointee_type_id.ctype)? + }; let type_args = mk().angle_bracketed_args(vec![pointee_type_rs]); let fn_expr = mk().abs_path_expr(vec![ mk().path_segment("core"), mk().path_segment("ptr"), mk().path_segment_with_args(fn_name, type_args), ]); - let val = val.map(|val| mk().call_expr(fn_expr, vec![val])); + let val = val.map(|val| { + let ptr = mk().call_expr(fn_expr, vec![val]); + if is_opaque { + mk().cast_expr(ptr, target_ty) + } else { + ptr + } + }); Ok(val) } diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index dfdfdf098b..fb47993bf5 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -415,6 +415,11 @@ fn test_main_fn() { transpile("main_fn.c").run(); } +#[test] +fn test_opaque_pointer_casts() { + transpile("opaque_pointer_casts.c").run(); +} + #[test] fn test_out_of_range_lit() { transpile("out_of_range_lit.c").run(); diff --git a/c2rust-transpile/tests/snapshots/opaque_pointer_casts.c b/c2rust-transpile/tests/snapshots/opaque_pointer_casts.c new file mode 100644 index 0000000000..fc0c19bec2 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/opaque_pointer_casts.c @@ -0,0 +1,11 @@ +struct Opaque; +union OpaqueUnion; +typedef struct Opaque Opaque; + +Opaque *sentinel(void) { return (Opaque *)8; } +const Opaque *const_sentinel(void) { return (const Opaque *)8; } +union OpaqueUnion *union_sentinel(void) { return (union OpaqueUnion *)8; } +Opaque *from_address(unsigned long address) { return (Opaque *)address; } + +static const Opaque *static_sentinel = (const Opaque *)8; +const Opaque *get_static_sentinel(void) { return static_sentinel; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@opaque_pointer_casts.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@opaque_pointer_casts.c.2021.clang15.snap new file mode 100644 index 0000000000..7c3b5597b0 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@opaque_pointer_casts.c.2021.clang15.snap @@ -0,0 +1,45 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/opaque_pointer_casts.2021.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] +#![feature(extern_types, strict_provenance)] +extern "C" { + pub type Opaque; + pub type OpaqueUnion; +} +#[no_mangle] +pub unsafe extern "C" fn sentinel() -> *mut Opaque { + return ::core::ptr::from_exposed_addr_mut::<::core::ffi::c_void>( + 8 as ::core::ffi::c_int as usize, + ) as *mut Opaque; +} +#[no_mangle] +pub unsafe extern "C" fn const_sentinel() -> *const Opaque { + return ::core::ptr::from_exposed_addr::<::core::ffi::c_void>(8 as ::core::ffi::c_int as usize) + as *const Opaque; +} +#[no_mangle] +pub unsafe extern "C" fn union_sentinel() -> *mut OpaqueUnion { + return ::core::ptr::from_exposed_addr_mut::<::core::ffi::c_void>( + 8 as ::core::ffi::c_int as usize, + ) as *mut OpaqueUnion; +} +#[no_mangle] +pub unsafe extern "C" fn from_address(mut address: ::core::ffi::c_ulong) -> *mut Opaque { + return ::core::ptr::from_exposed_addr_mut::<::core::ffi::c_void>(address as usize) + as *mut Opaque; +} +static mut static_sentinel: *const Opaque = 8 as ::core::ffi::c_int as *const Opaque; +#[no_mangle] +pub unsafe extern "C" fn get_static_sentinel() -> *const Opaque { + return static_sentinel; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@opaque_pointer_casts.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@opaque_pointer_casts.c.2024.clang15.snap new file mode 100644 index 0000000000..38ae77ca60 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@opaque_pointer_casts.c.2024.clang15.snap @@ -0,0 +1,49 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/opaque_pointer_casts.2024.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unsafe_op_in_unsafe_fn, + unused_assignments, + unused_mut +)] +#![feature(extern_types)] +unsafe extern "C" { + pub type Opaque; + pub type OpaqueUnion; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sentinel() -> *mut Opaque { + return ::core::ptr::with_exposed_provenance_mut::<::core::ffi::c_void>( + 8 as ::core::ffi::c_int as usize, + ) as *mut Opaque; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn const_sentinel() -> *const Opaque { + return ::core::ptr::with_exposed_provenance::<::core::ffi::c_void>( + 8 as ::core::ffi::c_int as usize, + ) as *const Opaque; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn union_sentinel() -> *mut OpaqueUnion { + return ::core::ptr::with_exposed_provenance_mut::<::core::ffi::c_void>( + 8 as ::core::ffi::c_int as usize, + ) as *mut OpaqueUnion; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn from_address(mut address: ::core::ffi::c_ulong) -> *mut Opaque { + return ::core::ptr::with_exposed_provenance_mut::<::core::ffi::c_void>(address as usize) + as *mut Opaque; +} +static mut static_sentinel: *const Opaque = ::core::ptr::with_exposed_provenance::< + ::core::ffi::c_void, +>(8 as ::core::ffi::c_int as usize) as *const Opaque; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn get_static_sentinel() -> *const Opaque { + return static_sentinel; +} From 13b87b4d8bf23cdf6d03ec64348f75b2640a1945 Mon Sep 17 00:00:00 2001 From: Per Larsen Date: Mon, 7 Sep 2026 20:33:30 -0700 Subject: [PATCH 3/5] transpile: specify the destination type for function pointers read by va_arg An enclosing cast can prevent Rust from inferring the destination of the transmute used for nullable function pointers. Pass the converted C type explicitly so va_arg results can be assigned through function pointer typedefs and struct fields. --- c2rust-transpile/src/translator/variadic.rs | 16 ++++++++++++---- tests/unit/pointers/src/function_pointers.c | 18 ++++++++++++++++++ tests/unit/pointers/src/test_pointers.rs | 12 ++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/c2rust-transpile/src/translator/variadic.rs b/c2rust-transpile/src/translator/variadic.rs index a4824b2348..bacdf9f362 100644 --- a/c2rust-transpile/src/translator/variadic.rs +++ b/c2rust-transpile/src/translator/variadic.rs @@ -184,7 +184,7 @@ impl<'c> Translation<'c> { enum VaArgCastKind { Cast(Box), Enum(CDeclId), - Transmute, + Transmute(Box), } let mut arg_ty: Option> = None; @@ -217,7 +217,7 @@ impl<'c> Translation<'c> { is_variadic, )?; - cast_kind = Some(VaArgCastKind::Transmute); + cast_kind = Some(VaArgCastKind::Transmute(self.convert_type(ty.ctype)?)); arg_ty = Some(mk().set_mutbl(p.mutability()).ptr_ty(fn_ty)); } else if self.ast_context.is_forward_declared_type(p.ctype) { cast_kind = Some(VaArgCastKind::Cast(self.convert_type(ty.ctype).unwrap())); @@ -250,8 +250,16 @@ impl<'c> Translation<'c> { VaArgCastKind::Enum(enum_id) => { self.enum_constructor_expr(enum_id, val, false) } - VaArgCastKind::Transmute => { - transmute_expr(mk().infer_ty(), mk().infer_ty(), val) + VaArgCastKind::Transmute(ty) => { + // An enclosing cast or field access may prevent + // inference of the transmute's destination type. + // For `callbacks.fn = va_arg(arg, char_to_int_fp)`, emit + // `transmute::<_, char_to_int_fp>(arg.arg::<*mut Fn>()) + // as Option`, where Fn abbreviates the underlying + // `unsafe extern "C" fn(c_char) -> c_int` signature. + // With `transmute::<_, _>(...)`, inference fails: + // the `as` cast does not determine its input type. + transmute_expr(mk().infer_ty(), ty, val) } }; } diff --git a/tests/unit/pointers/src/function_pointers.c b/tests/unit/pointers/src/function_pointers.c index aaac4639ad..f6da3e924b 100644 --- a/tests/unit/pointers/src/function_pointers.c +++ b/tests/unit/pointers/src/function_pointers.c @@ -26,6 +26,24 @@ int varargs_fp(const int c, ...) { return fp((char)c); } +// The typedef forces a cast on the va_arg result when assigning to a field +// spelled with the underlying function pointer type (as in SQLite's config). +int varargs_fp_field(const int c, ...) { + struct { int (*fn)(char); } callbacks; + va_list arg; + va_start(arg, c); + callbacks.fn = va_arg(arg, char_to_int_fp); + va_end(arg); + return callbacks.fn ? callbacks.fn((char)c) : -1; +} + +int varargs_fp_field_test(void) { + char_to_int_fp null_fp = 0; + return varargs_fp_field('a', intval) == 'a' && + varargs_fp_field('b', negintval) == -'b' && + varargs_fp_field('c', null_fp) == -1; +} + #endif void entry3(const unsigned sz, int buffer[const]) { diff --git a/tests/unit/pointers/src/test_pointers.rs b/tests/unit/pointers/src/test_pointers.rs index beb74e85c6..30cde7a0fe 100644 --- a/tests/unit/pointers/src/test_pointers.rs +++ b/tests/unit/pointers/src/test_pointers.rs @@ -15,6 +15,18 @@ extern "C" { fn entry2(_: c_uint, _: *mut c_int); fn entry3(_: c_uint, _: *mut c_int); + + #[cfg(not(target_arch = "aarch64"))] + fn varargs_fp_field_test() -> c_int; +} + +#[test] +#[cfg(not(target_arch = "aarch64"))] +pub fn test_varargs_fp_field() { + unsafe { + assert_eq!(varargs_fp_field_test(), 1); + assert_eq!(crate::function_pointers::rust_varargs_fp_field_test(), 1); + } } const BUFFER_SIZE: usize = 5; From 68e7e794472dd3af5cff12d0b41252b91a71c934 Mon Sep 17 00:00:00 2001 From: Per Larsen Date: Mon, 7 Sep 2026 20:33:30 -0700 Subject: [PATCH 4/5] transpile: lower atomic function pointer operations through raw pointers Rust atomic intrinsics reject Option, the representation of nullable C function pointers. Convert atomic operands to raw pointers and convert results back to their function pointer type. Share the conversion between Clang atomic expressions and legacy __sync builtins. Preserve null callbacks, exchange and compare-exchange results, and the expected value written back after a failed comparison. Lock release stores a null raw pointer instead of an integer zero. --- c2rust-transpile/src/translator/atomics.rs | 97 +++++++++++++++++-- c2rust-transpile/src/translator/builtins.rs | 11 ++- .../pointers/src/atomic_function_pointers.c | 41 ++++++++ .../pointers/src/sync_function_pointers.c | 30 ++++++ tests/unit/pointers/src/test_pointers.rs | 26 ++++- 5 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 tests/unit/pointers/src/atomic_function_pointers.c create mode 100644 tests/unit/pointers/src/sync_function_pointers.c diff --git a/c2rust-transpile/src/translator/atomics.rs b/c2rust-transpile/src/translator/atomics.rs index d5184b17aa..f544ea6469 100644 --- a/c2rust-transpile/src/translator/atomics.rs +++ b/c2rust-transpile/src/translator/atomics.rs @@ -34,7 +34,67 @@ pub(crate) fn order_ty_name(order: Ordering) -> &'static str { } } +// Rust represents nullable C function pointers as Option, which atomic +// intrinsics reject. Both AtomicExpr and legacy builtin calls must operate on +// the raw pointer representation, restoring the function pointer on results. +pub(crate) struct AtomicValue { + function_pointer: Option>, +} + +impl AtomicValue { + fn raw_pointer_type() -> Box { + mk().mutbl().ptr_ty(mk().tuple_ty(vec![])) + } + + pub(crate) fn storage(&self, ptr: Box) -> Box { + if self.function_pointer.is_some() { + mk().cast_expr(ptr, mk().mutbl().ptr_ty(Self::raw_pointer_type())) + } else { + ptr + } + } + + pub(crate) fn lower(&self, val: Box) -> Box { + match &self.function_pointer { + Some(ty) => transmute_expr(ty.clone(), Self::raw_pointer_type(), val), + None => val, + } + } + + pub(crate) fn restore(&self, val: Box) -> Box { + match &self.function_pointer { + Some(ty) => transmute_expr(Self::raw_pointer_type(), ty.clone(), val), + None => val, + } + } + + pub(crate) fn zero(&self) -> Box { + if self.function_pointer.is_some() { + mk().call_expr(mk().abs_path_expr(vec!["core", "ptr", "null_mut"]), vec![]) + } else { + mk().lit_expr(mk().int_lit(0, "")) + } + } +} + impl<'c> Translation<'c> { + pub(crate) fn atomic_value(&self, ptr_id: CExprId) -> TranslationResult { + let ptr_ty = self.ast_context[ptr_id].kind.get_qual_type().unwrap(); + let mut value_ty = self + .ast_context + .get_pointee_qual_type(ptr_ty.ctype) + .unwrap(); + if let CTypeKind::Atomic(inner) = self.ast_context.resolve_type(value_ty.ctype).kind { + value_ty = inner; + } + let function_pointer = if self.ast_context.is_function_pointer(value_ty.ctype) { + Some(self.convert_type(value_ty.ctype)?) + } else { + None + }; + Ok(AtomicValue { function_pointer }) + } + fn atomic_intrinsic_expr_edition_2021( &self, base_name: &str, @@ -155,6 +215,13 @@ impl<'c> Translation<'c> { .transpose()?; let weak = weak_id.and_then(|x| self.convert_constant_bool(x)); + let value = self.atomic_value(ptr_id)?; + let ptr = if name != "__c11_atomic_init" { + ptr.map(|ptr| value.storage(ptr)) + } else { + ptr + }; + fn static_order(order: Option) -> T { order.unwrap_or_else(|| { // We have to select which intrinsic to use at runtime @@ -167,7 +234,7 @@ impl<'c> Translation<'c> { let order = static_order(order); let atomic_load = self.atomic_intrinsic_expr("load", &[order]); - let call = mk().call_expr(atomic_load, vec![ptr]); + let call = value.restore(mk().call_expr(atomic_load, vec![ptr])); if name == "__atomic_load" { let ret = val1.expect("__atomic_load should have a ret argument"); Ok(ret.and_then(|ret| { @@ -200,7 +267,7 @@ impl<'c> Translation<'c> { } else { val }; - let call = mk().call_expr(atomic_store, vec![ptr, val]); + let call = mk().call_expr(atomic_store, vec![ptr, value.lower(val)]); self.convert_side_effects_expr( ctx, WithStmts::new_val(call), @@ -233,7 +300,7 @@ impl<'c> Translation<'c> { } else { val }; - let call = mk().call_expr(fn_path, vec![ptr, val]); + let call = value.restore(mk().call_expr(fn_path, vec![ptr, value.lower(val)])); if name == "__atomic_exchange" { // LLVM stores the ret pointer in the order_fail slot Ok(order_fail_id @@ -315,8 +382,10 @@ impl<'c> Translation<'c> { let atomic_cxchg = self.atomic_intrinsic_cxchg_expr(weak, order, order_fail); - let call = - mk().call_expr(atomic_cxchg, vec![ptr, expected.clone(), desired]); + let call = mk().call_expr( + atomic_cxchg, + vec![ptr, value.lower(expected.clone()), value.lower(desired)], + ); let res_name = self .renamer .borrow_mut() @@ -328,7 +397,7 @@ impl<'c> Translation<'c> { ))); let assignment = mk().semi_stmt(mk().assign_expr( expected, - mk().anon_field_expr(mk().ident_expr(&res_name), 0), + value.restore(mk().anon_field_expr(mk().ident_expr(&res_name), 0)), )); let return_value = mk().anon_field_expr(mk().ident_expr(&res_name), 1); Ok(self.convert_side_effects_expr( @@ -366,6 +435,7 @@ impl<'c> Translation<'c> { weak: bool, order_succ: Ordering, order_fail: Ordering, + ptr_id: CExprId, dst: Box, old_val: Box, src_val: Box, @@ -373,9 +443,22 @@ impl<'c> Translation<'c> { ) -> TranslationResult>> { // Emit `atomic_cxchg(a0, a1, a2).idx` let atomic_cxchg = self.atomic_intrinsic_cxchg_expr(weak, order_succ, order_fail); - let call = mk().call_expr(atomic_cxchg, vec![dst, old_val, src_val]); + let value = self.atomic_value(ptr_id)?; + let call = mk().call_expr( + atomic_cxchg, + vec![ + value.storage(dst), + value.lower(old_val), + value.lower(src_val), + ], + ); let field_idx = if returns_val { 0 } else { 1 }; let call_expr = mk().anon_field_expr(call, field_idx); + let call_expr = if returns_val { + value.restore(call_expr) + } else { + call_expr + }; Ok(self.convert_side_effects_expr( ctx, WithStmts::new_val(call_expr), diff --git a/c2rust-transpile/src/translator/builtins.rs b/c2rust-transpile/src/translator/builtins.rs index 765b4be1c3..e8a5ddae38 100644 --- a/c2rust-transpile/src/translator/builtins.rs +++ b/c2rust-transpile/src/translator/builtins.rs @@ -508,6 +508,7 @@ impl<'c> Translation<'c> { false, SeqCst, SeqCst, + args[0], arg0, arg1, arg2, @@ -570,10 +571,13 @@ impl<'c> Translation<'c> { | "__sync_lock_test_and_set_16" => { // Emit `atomic_xchg_acquire(arg0, arg1)` let atomic_func = self.atomic_intrinsic_expr("xchg", &[Acquire]); + let value = self.atomic_value(args[0])?; let arg0 = self.convert_expr(ctx.used(), args[0], None)?; let arg1 = self.convert_expr(ctx.used(), args[1], None)?; Ok(arg0.zip(arg1).and_then(|(arg0, arg1)| { - let call_expr = mk().call_expr(atomic_func, vec![arg0, arg1]); + let call_expr = value.restore( + mk().call_expr(atomic_func, vec![value.storage(arg0), value.lower(arg1)]), + ); self.convert_side_effects_expr( ctx, WithStmts::new_val(call_expr), @@ -589,10 +593,11 @@ impl<'c> Translation<'c> { | "__sync_lock_release_16" => { // Emit `atomic_store_release(arg0, 0)` let atomic_func = self.atomic_intrinsic_expr("store", &[Release]); + let value = self.atomic_value(args[0])?; let arg0 = self.convert_expr(ctx.used(), args[0], None)?; Ok(arg0.and_then(|arg0| { - let zero = mk().lit_expr(mk().int_lit(0, "")); - let call_expr = mk().call_expr(atomic_func, vec![arg0, zero]); + let call_expr = + mk().call_expr(atomic_func, vec![value.storage(arg0), value.zero()]); self.convert_side_effects_expr( ctx, WithStmts::new_val(call_expr), diff --git a/tests/unit/pointers/src/atomic_function_pointers.c b/tests/unit/pointers/src/atomic_function_pointers.c new file mode 100644 index 0000000000..dfd4fbc3f8 --- /dev/null +++ b/tests/unit/pointers/src/atomic_function_pointers.c @@ -0,0 +1,41 @@ +typedef int (*callback)(int); + +static int increment(int x) { return x + 1; } +static int decrement(int x) { return x - 1; } + +int atomic_function_pointers(void) { + callback ptr = 0; + callback expected = 0; + callback desired = increment; + callback result = 0; + + __atomic_store_n(&ptr, increment, __ATOMIC_RELEASE); + result = __atomic_load_n(&ptr, __ATOMIC_ACQUIRE); + if (!result || result(10) != 11) return 1; + result = __atomic_exchange_n(&ptr, decrement, __ATOMIC_ACQ_REL); + if (result != increment) return 2; + if (__atomic_compare_exchange_n(&ptr, &expected, increment, 0, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) return 3; + if (expected != decrement) return 4; + if (!__atomic_compare_exchange_n(&ptr, &expected, (callback)0, 0, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) return 5; + if (__atomic_load_n(&ptr, __ATOMIC_RELAXED)) return 6; + + __atomic_store(&ptr, &desired, __ATOMIC_RELEASE); + __atomic_load(&ptr, &result, __ATOMIC_ACQUIRE); + if (result != increment) return 7; + desired = decrement; + __atomic_exchange(&ptr, &desired, &result, __ATOMIC_ACQ_REL); + if (result != increment) return 8; + expected = increment; + desired = 0; + if (__atomic_compare_exchange(&ptr, &expected, &desired, 0, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) return 9; + if (expected != decrement) return 10; + if (!__atomic_compare_exchange(&ptr, &expected, &desired, 0, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) return 11; + __atomic_load(&ptr, &result, __ATOMIC_RELAXED); + if (result) return 12; + + return 0; +} diff --git a/tests/unit/pointers/src/sync_function_pointers.c b/tests/unit/pointers/src/sync_function_pointers.c new file mode 100644 index 0000000000..ff8ec57f45 --- /dev/null +++ b/tests/unit/pointers/src/sync_function_pointers.c @@ -0,0 +1,30 @@ +typedef int (*callback)(int); + +static int increment(int x) { return x + 1; } +static int decrement(int x) { return x - 1; } + +int sync_function_pointers(void) { + callback ptr = 0; + callback result = __sync_lock_test_and_set(&ptr, increment); + if (result || !ptr || ptr(10) != 11) return 1; + result = __sync_lock_test_and_set(&ptr, decrement); + if (result != increment || ptr != decrement) return 2; + + result = __sync_val_compare_and_swap(&ptr, increment, (callback)0); + if (result != decrement || ptr != decrement) return 3; + result = __sync_val_compare_and_swap(&ptr, decrement, (callback)0); + if (result != decrement || ptr) return 4; + result = __sync_val_compare_and_swap(&ptr, (callback)0, increment); + if (result || ptr != increment) return 5; + + if (__sync_bool_compare_and_swap(&ptr, decrement, (callback)0)) return 6; + if (ptr != increment) return 7; + if (!__sync_bool_compare_and_swap(&ptr, increment, (callback)0)) return 8; + if (ptr) return 9; + if (!__sync_bool_compare_and_swap(&ptr, (callback)0, decrement)) return 10; + if (!ptr || ptr(10) != 9) return 11; + + __sync_lock_release(&ptr); + if (ptr) return 12; + return 0; +} diff --git a/tests/unit/pointers/src/test_pointers.rs b/tests/unit/pointers/src/test_pointers.rs index 30cde7a0fe..2f05520e4f 100644 --- a/tests/unit/pointers/src/test_pointers.rs +++ b/tests/unit/pointers/src/test_pointers.rs @@ -1,4 +1,4 @@ -//! feature_c_variadic, feature_raw_ref_op, feature_strict_provenance +//! feature_c_variadic, feature_raw_ref_op, feature_strict_provenance, feature_core_intrinsics use crate::function_pointers::rust_entry3; use crate::pointer_arith::rust_entry2; @@ -10,6 +10,8 @@ use std::ffi::{c_int, c_uint}; #[link(name = "test")] extern "C" { + fn atomic_function_pointers() -> c_int; + fn sync_function_pointers() -> c_int; fn entry(_: c_uint, _: *mut c_int); fn entry2(_: c_uint, _: *mut c_int); @@ -20,6 +22,28 @@ extern "C" { fn varargs_fp_field_test() -> c_int; } +#[test] +pub fn test_sync_function_pointers() { + unsafe { + assert_eq!(sync_function_pointers(), 0); + assert_eq!( + crate::sync_function_pointers::rust_sync_function_pointers(), + 0 + ); + } +} + +#[test] +pub fn test_atomic_function_pointers() { + unsafe { + assert_eq!(atomic_function_pointers(), 0); + assert_eq!( + crate::atomic_function_pointers::rust_atomic_function_pointers(), + 0 + ); + } +} + #[test] #[cfg(not(target_arch = "aarch64"))] pub fn test_varargs_fp_field() { From b72bfb84db7c6782da27f86e7fc874b6215e013f Mon Sep 17 00:00:00 2001 From: Per Larsen Date: Mon, 7 Sep 2026 20:33:30 -0700 Subject: [PATCH 5/5] transpile: initialize atomic values through their underlying type Default initialization does not handle CTypeKind::Atomic, so local _Atomic declarations fail to translate even when they have an explicit initializer. Delegate to the underlying value type to produce the initializer for the atomic storage. --- c2rust-transpile/src/translator/mod.rs | 2 ++ tests/unit/pointers/src/atomic_function_pointers.c | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 4308e61bf5..5a0a6450db 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -4468,6 +4468,8 @@ impl<'c> Translation<'c> { mk().lit_expr(mk().float_unsuffixed_lit("0.")), )), } + } else if let &CTypeKind::Atomic(inner) = resolved_ty { + self.implicit_default_expr(ctx, inner.ctype) } else if let &CTypeKind::Pointer(_) = resolved_ty { self.null_ptr(resolved_ty_id).map(WithStmts::new_val) } else if let &CTypeKind::ConstantArray(elt, sz) = resolved_ty { diff --git a/tests/unit/pointers/src/atomic_function_pointers.c b/tests/unit/pointers/src/atomic_function_pointers.c index dfd4fbc3f8..7d2d6a5617 100644 --- a/tests/unit/pointers/src/atomic_function_pointers.c +++ b/tests/unit/pointers/src/atomic_function_pointers.c @@ -37,5 +37,19 @@ int atomic_function_pointers(void) { __atomic_load(&ptr, &result, __ATOMIC_RELAXED); if (result) return 12; + _Atomic(callback) c11_ptr = (callback)0; + __c11_atomic_init(&c11_ptr, (callback)0); + __c11_atomic_store(&c11_ptr, increment, __ATOMIC_RELEASE); + result = __c11_atomic_load(&c11_ptr, __ATOMIC_ACQUIRE); + if (!result || result(10) != 11) return 13; + result = __c11_atomic_exchange(&c11_ptr, decrement, __ATOMIC_ACQ_REL); + if (result != increment) return 14; + expected = increment; + if (__c11_atomic_compare_exchange_strong(&c11_ptr, &expected, (callback)0, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) return 15; + if (expected != decrement) return 16; + if (!__c11_atomic_compare_exchange_strong(&c11_ptr, &expected, (callback)0, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) return 17; + if (__c11_atomic_load(&c11_ptr, __ATOMIC_RELAXED)) return 18; return 0; }