ctest: improve translation backend#4617
Conversation
|
Ah unfortunately this will have some small conflicts with #4616, which adds handling for volatile and restrict. |
ctest-next/src/cdecl.rs
Outdated
| // 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, | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Put this function in the translator module and just inline modify_constness, since "inherited constness" is a concept in Rust but not C
ctest-next/src/cdecl.rs
Outdated
| 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, '*'), | ||
| }, | ||
| } |
There was a problem hiding this comment.
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!()?
There was a problem hiding this comment.
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.
b837b5a to
a36802f
Compare
ctest-next/src/tests.rs
Outdated
| /// 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> { |
There was a problem hiding this comment.
Maybe rename this to something like r2cdecl, since the name cdecl is a bit overloaded now
ctest-next/src/tests.rs
Outdated
| /// 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, "") | ||
| } |
There was a problem hiding this comment.
Probably don't need this if it just calls the below function
ctest-next/src/translator.rs
Outdated
| 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), |
There was a problem hiding this comment.
cdecl::Constness is used everywhere, just import cdecl::Constness::{Const, Mut}
ctest-next/src/translator.rs
Outdated
| fn translate_mut(&self, mutability: Option<syn::Token![mut]>) -> cdecl::Constness { | ||
| mutability | ||
| .map(|_| cdecl::Constness::Mut) | ||
| .unwrap_or(cdecl::Constness::Const) | ||
| } |
There was a problem hiding this comment.
This actually never needed &self. Could you move it to a free function?
ctest-next/src/translator.rs
Outdated
| // 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 { |
There was a problem hiding this comment.
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)
ctest-next/src/translator.rs
Outdated
| Ok(format!( | ||
| "{}[{}]", | ||
| fn translate_array(&self, array: &syn::TypeArray) -> Result<cdecl::CTy, TranslationError> { | ||
| // FIXME(ctest): Maybe `array_arg` should be here? |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| ty("*const [u128; 2 + 3]").unwrap(), | ||
| "unsigned __int128 (*const) [2 + 3]".to_string() | ||
| "unsigned __int128 (*)[2 + 3]".to_string() |
There was a problem hiding this comment.
Yes, arrays in cdecl don't have a const qualifier.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
ctest-next/src/tests.rs
Outdated
| 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()); |
There was a problem hiding this comment.
Are these changes expected?
There was a problem hiding this comment.
Yes, cdecl has better formatting than the previous implementation.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
a36802f to
46da1b8
Compare
tgross35
left a comment
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Import TranslationErrorKind so the whole path isn't needed
|
|
||
| let ty = cdecl::cdecl(&ty, "".to_string()).map_err(|_| { | ||
| TranslationError::new( | ||
| crate::translator::TranslationErrorKind::InvalidReturn, |
| let translated = translator.translate_type(&ty)?; | ||
| cdecl::cdecl(&translated, name.to_string()).map_err(|_| { | ||
| TranslationError::new( | ||
| crate::translator::TranslationErrorKind::InvalidReturn, |
| 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() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Yeah, I fix this in the (not yet pushed) extern function test PR.
| r2cdecl("*const *mut i32", "").unwrap(), | ||
| "int32_t *const *".to_string() |
There was a problem hiding this comment.
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")
| 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() | ||
| ); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Could you please explain a bit more about
binding_constnessanddecay_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.
Description
Improves the
Translatortype by using the newcdeclmodule, as well as restructuringTranslateHelperto be more minimal and have less duplication.Also modifies the
cdeclmodule to allow the correct ABI of a function to be used.Sources
Checklist
libc-test/semverhave been updated*LASTor*MAXareincluded (see #3131)
cd libc-test && cargo test --target mytarget);especially relevant for platforms that may not be checked in CI