Skip to content

ctest: improve translation backend #4617

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 1 commit into from
Aug 6, 2025

Conversation

mbyx
Copy link
Contributor

@mbyx mbyx commented Aug 5, 2025

Description

Improves the Translator type by using the new cdecl module, as well as restructuring TranslateHelper to be more minimal and have less duplication.
Also modifies the cdecl module to allow the correct ABI of a function to be used.

Sources

Checklist

  • Relevant tests in libc-test/semver have been updated
  • No placeholder or unstable values like *LAST or *MAX are
    included (see #3131)
  • Tested locally (cd libc-test && cargo test --target mytarget);
    especially relevant for platforms that may not be checked in CI

@rustbot rustbot added ctest Issues relating to the ctest crate S-waiting-on-review labels Aug 5, 2025
@tgross35
Copy link
Contributor

tgross35 commented Aug 5, 2025

Ah unfortunately this will have some small conflicts with #4616, which adds handling for volatile and restrict.

Comment on lines 165 to 196
// Important change here:
// Basically, `syn` always gives us the `constness` of the inner type of a pointer.
// However `cdecl::ptr` wants the `constness` of the pointer. So we just modify
// the way it is built so that `cdecl::ptr` takes the `constness` of the inner type.
pub(crate) fn ptr_with_inner(inner: CTy, constness: Constness) -> CTy {
let mut ty = Box::new(inner);
ty.modify_constness(constness);
CTy::Ptr {
ty,
constness: Constness::Mut,
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Put this function in the translator module and just inline modify_constness, since "inherited constness" is a concept in Rust but not C

Comment on lines 115 to 130
match ty.as_ref() {
CTy::Fn {
abi: Some(abi_str), ..
} => {
let modifier = match constness {
Const => format!("({abi_str} *const "),
Mut => format!("({abi_str} *"),
};
s.insert_str(0, &modifier);
s.push(')');
}
_ => match constness {
Const => s.insert_str(0, "*const "),
Mut => s.insert(0, '*'),
},
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't seem like the best way to add it in because of the inconsistency between functions and function pointers, and the extra quoting. Also it seems like MSVC wants this attribute in a different place from the others https://gcc.godbolt.org/z/4Y84dWrv1.

I think this is worth revisiting on its own later. Would it be possible to ignore any tests with a different ABI for now with a todo!()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be fine for now, the system abi expands to nothing on everything other than a specific windows target, and windows already skips all fn ptr checks, so it might not even affect it.

@mbyx mbyx force-pushed the ctest-improve-translator branch 3 times, most recently from b837b5a to a36802f Compare August 5, 2025 07:48
@mbyx mbyx requested a review from tgross35 August 5, 2025 07:48
}

/// Translate a Rust type into a c typedef declaration.
fn cdecl(s: &str) -> Result<String, TranslationError> {
let ty: syn::Type = syn::parse_str(s).unwrap();
fn cdecl(s: &str, name: &str) -> Result<String, TranslationError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe rename this to something like r2cdecl, since the name cdecl is a bit overloaded now

Comment on lines 40 to 43
/// Translate a Rust type to C.
fn ty(s: &str) -> Result<String, TranslationError> {
let translator = Translator {};
let ty: syn::Type = syn::parse_str(s).unwrap();
translator.translate_type(&ty)
cdecl(s, "")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably don't need this if it just calls the below function

Ok(format!("{modifier} {base_type}*").trim().to_string())
let type_name = translate_primitive_type(&last_segment.ident.to_string());
Ok(ptr_with_inner(
cdecl::named(&type_name, cdecl::Constness::Mut),
Copy link
Contributor

Choose a reason for hiding this comment

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

cdecl::Constness is used everywhere, just import cdecl::Constness::{Const, Mut}

Comment on lines 104 to 108
fn translate_mut(&self, mutability: Option<syn::Token![mut]>) -> cdecl::Constness {
mutability
.map(|_| cdecl::Constness::Mut)
.unwrap_or(cdecl::Constness::Const)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This actually never needed &self. Could you move it to a free function?

Comment on lines 337 to 341
// Important change here:
// Basically, `syn` always gives us the `constness` of the inner type of a pointer.
// However `cdecl::ptr` wants the `constness` of the pointer. So we just modify
// the way it is built so that `cdecl::ptr` takes the `constness` of the inner type.
pub(crate) fn ptr_with_inner(inner: cdecl::CTy, constness: cdecl::Constness) -> cdecl::CTy {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you turn this into a doc comment?

Also, every time this is called, it is preceded by a call to self.translate_mut(reference.mutability). Instead of taking constness, could it maybe take the Option<syn::Token![mut]>? Makes it more clear that we're applying Rust const/mut rules to C (it should say that too)

Ok(format!(
"{}[{}]",
fn translate_array(&self, array: &syn::TypeArray) -> Result<cdecl::CTy, TranslationError> {
// FIXME(ctest): Maybe `array_arg` should be here?
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably not here, we don't know whether or not the array is in a function. I think this should be handled in translate_bare_fn's arg setup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried that and it works perfectly for adding the volatile keyword to the innermost type of the parameter, and in theory it should also work the same for array arg, but a simple replacement of cdecl::array to cdecl::ptr doesn't work, it needs to handle it recursively.

Comment on lines 83 to 79
ty("*const [u128; 2 + 3]").unwrap(),
"unsigned __int128 (*const) [2 + 3]".to_string()
"unsigned __int128 (*)[2 + 3]".to_string()
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this change expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, arrays in cdecl don't have a const qualifier.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, so a non-const pointer is actually expected here? I guess that makes sense, since the data it points to is const but the pointer itself may not be (like below).

For reference, the syntax x int[const 2 + 3] is what would be needed to make an array itself const. But this is pretty limited: it's only supported in specific places (I think function definitions but not local vars or statics) and it's not supported by MSVC, so I didn't bother adding it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is expected for this PR, but for the extern fn tests, I did have to modify translate_ptr to handle things differently for pointers to arrays.

Comment on lines 94 to 97
assert_eq!(ty("&u8").unwrap(), "const uint8_t *".to_string());
assert_eq!(ty("&&u8").unwrap(), "const uint8_t *const *".to_string());
assert_eq!(ty("*mut &u8").unwrap(), "const uint8_t **".to_string());
assert_eq!(ty("& &mut u8").unwrap(), "uint8_t *const *".to_string());
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these changes expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, cdecl has better formatting than the previous implementation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I asked the wrong thing. Meant to ask if these should have an extra const, because e.g. let x: &u8 is a const pointer to a const u8 (const uint8_t *const). I guess we don't know at this point whether or not we would want the variable to be mutable, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ctest-test only tests references in extern statics, and from what I can see, it chooses to use const uint8_t * for &u8. If we did change it, it would have to be after ctest is completely removed, because changing the translation leads to changing the source (t1.h), and ctest would start diverging in output from ctest-next.

@mbyx mbyx force-pushed the ctest-improve-translator branch from a36802f to 46da1b8 Compare August 5, 2025 09:20
@mbyx mbyx requested a review from tgross35 August 5, 2025 09:20
@mbyx mbyx mentioned this pull request Aug 6, 2025
3 tasks
@tgross35 tgross35 added this pull request to the merge queue Aug 6, 2025
Copy link
Contributor

@tgross35 tgross35 left a comment

Choose a reason for hiding this comment

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

I am just merging this now because it cleans a lot up as-is. However, there are some changes needed that I have in the review comments. Could you cover this in a followup?

)
.map_err(|_| {
TranslationError::new(
crate::translator::TranslationErrorKind::InvalidReturn,
Copy link
Contributor

Choose a reason for hiding this comment

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

Import TranslationErrorKind so the whole path isn't needed

@@ -528,6 +531,14 @@ impl<'a> TranslateHelper<'a> {
MapInput::Type(_) => panic!("MapInput::Type is not allowed!"),
};

let ty = cdecl::cdecl(&ty, "".to_string()).map_err(|_| {
TranslationError::new(
crate::translator::TranslationErrorKind::InvalidReturn,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

let translated = translator.translate_type(&ty)?;
cdecl::cdecl(&translated, name.to_string()).map_err(|_| {
TranslationError::new(
crate::translator::TranslationErrorKind::InvalidReturn,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

Comment on lines +78 to +83
r2cdecl("*const [u128; 2 + 3]", "").unwrap(),
"unsigned __int128 (*)[2 + 3]".to_string()
);
assert_eq!(
r2cdecl("*const *mut [u8; 5]", "").unwrap(),
"uint8_t (*const *)[5]".to_string()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you might be getting at this on Zulip, but this doesn't seem right to me. On Rust it's a single indirection, but in C this is double indirection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I fix this in the (not yet pushed) extern function test PR.

Comment on lines +74 to +75
r2cdecl("*const *mut i32", "").unwrap(),
"int32_t *const *".to_string()
Copy link
Contributor

Choose a reason for hiding this comment

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

May as well clean this up while you are here: the righthand side of these tests shouldn't need .to_string(), since PartialEq works between String and &str (e.g. assert_eq!(hi.to_string(), "hi")).

You could also put everything into a function so the unwrap() isn't needed everywhere:

#[track_caller]
fn assert_r2cdecl(rust: &str, expected: &str) {
    assert_eq!(r2cdecl(rust, "").unwrap(), expected)
}

(The C versions may be a bit easier to read if they always include the name e.g. the above function could always set it to "foo")

Comment on lines 117 to 122
fn test_translation_type_array() {
assert_eq!(
ty("[&u8; 2 + 2]").unwrap(),
"const uint8_t*[2 + 2]".to_string()
r2cdecl("[&u8; 2 + 2]", "").unwrap(),
"const uint8_t *[2 + 2]".to_string()
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm noting on Zulip, but how an array gets translated is actually context-dependent. For statics and struct/union fields they are inline, like Rust arrays, so this translation would be incorrect. For function arguments and for anything where they aren't the top-level item, they turn into a pointer.

So I think the entrypoint needs to be updated to take an decay_to_ptr argument that is false for statics and fields, but true otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you please explain a bit more about binding_constness and decay_to_ptr?

Also, I believe that most of these are actually only ever used to specify the return type of a function with a typedef, so whatever works within that context is what we would need.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please explain a bit more about binding_constness and decay_to_ptr?

What I was thinking is that translate_type needs some context/config, which would be those two (maybe wrapped in a struct so others can be added easily).

For binding_constness (or maybe var_constness is a better name): the constness of a binding / arg isn't really related to its type in Rust. E.g. in both fn foo(mut a: i32) { ... } and fn foo(a: i32) { ... }, the type is only i32, there is no const/mut tied to it. You don't get that until you assign that to something in Rust.

In C though, constness of a binding/arg is tied to its type: "i32" could translate to either int or const int, those are (effectively) two distinct types in C. I'm just thinking that binding_constness: Constness would control this so you could make either as needed. If it's Const, just mark the outermost CTy item Const if it's a pointer or named ty.

For array_decays_to_ptr: in C, a T [N] in function signatures needs to map to a *const [T; N] in Rust, because it "decays" to a pointer when passed. Same thing for if it's behind indirection (another pointer or array). However, for statics and fields, that same T [N] is actually inlined, so it would be [T; N] without the pointer.

array_decays_to_ptr would just control that, basically whether or not we error on plain [T; N] rather than being an unconditional error.

Also, I believe that most of these are actually only ever used to specify the return type of a function with a typedef, so whatever works within that context is what we would need.

That's fair, we might not need the array_decays_to_ptr config. I think we do want something like binding_constness though.

Merged via the queue into rust-lang:main with commit 7f3d696 Aug 6, 2025
48 of 52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ctest Issues relating to the ctest crate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants