-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Consider folkertdev's c_variadic
proposal
#141524
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
Comments
To explore these ideas, I've implemented my proposal in ea75827. This is prototype quality at the moment, but I think the examples/tests look much better now that the only user-visible type is I think my current proposal is more T-libs than T-lang, but For T-lang specifically:
For T-libs when the time comes
|
The current proposal does not include
While having Footnotes |
I found something more interesting while reviewing @folkertdev's PR #141538 which is that we currently decide the ABI we handle variable arguments for based on the target. However, if we want our So we need at least one more decision here from T-lang, though more of a confirmation:
unsafe extern "abi" {
fn uses_varargs(...);
} Note that doing such will bring us afoul of this: |
Note that modern C can pass |
@beetrees Well, yes, but |
I don't think the
I agree that Rust shouldn't be automatically promoting arguments for people. |
Fascinating. May have to rethink how this should be approached a bit. |
@rustbot labels -I-lang-nominated +I-lang-radar We discussed this in the lang call today. People generally liked the idea. Probably it'll need an RFC before stabilization, but we're open to a lang experiment here if it would help. @joshtriplett volunteered to champion it and help guide it along. |
Currently, this proposal doesn't give any way to write the C type I think a potential solution would be to have the new This would mean that this: unsafe extern "C" {
fn takes_va_list(args: VaList<'_>);
fn takes_va_list_ref(args: *mut VaList<'_>);
}
unsafe extern "C" fn takes_varargs(mut args: ...) {
// function body
unsafe { takes_va_list_ref(&mut args); }
unsafe { takes_va_list(args); }
} would roughly desugar into this: unsafe extern "C" fn takes_varargs() {
let mut tag = MaybeUninit::<VaList<'_>>::uninit();
unsafe { va_start(tag.as_mut_ptr()); }
let mut args = tag.assume_init();
// function body
// Passing a pointer to a `VaList<'_>` works as expected.
unsafe { takes_va_list_reg(&mut args); }
// On targets where the tag is a pointer, the `va_list` would be passed directly, matching the
// C ABI.
unsafe { takes_va_list(args); }
// On target where the tag is a single-element array of a struct, the `va_list` would be passed
// indirectly, also matching the C ABI. This would happen using the ABI handling in
// `rustc_target`.
unsafe { takes_va_list(&mut args); }
} and the API would look something like this: struct VaList<'a> {
// ...
}
impl Clone for VaList<'_> {
fn clone(&self) -> Self {
// ...
}
}
impl VaList<'_> {
fn arg<T: VaArgSafe>(&mut self) -> T {
// ...
}
} (I wasn't sure whether to post this here or on the main tracking issue. Please let me know if I've posted it in the wrong place.) |
What do you mean by that exactly? I don't see how that is true when I also don't see why having/passing |
This isn't true of the original proposal. In the original proposal, For example, on x86_64 Linux, typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
void *overflow_arg_area;
void *reg_s;
} va_list[1]; This is equivalent to void foo(va_list va);
void bar(...) {
va_list va;
va_start(va);
foo(va);
va_end(va);
} is roughly desugared to this: void foo(va_list* va);
void bar(...) {
va_list va;
va_start(va);
foo(&va);
va_end(va);
} In C it is legal to do this: #include <stdarg.h>
int get_next(va_list* va);
int add2(...) {
va_list va;
va_start(va);
int a = get_next(&va);
a += get_next(&va);
va_end(va);
return a;
} (To quote footnote 295 from the C23 standard (standard disclaimer that I only have access to a recent draft): "A pointer to a With the original proposal there is no way to convert this code to the equivalent Rust code (for example, using C2Rust). The solution i suggested would make the above C code equivalent to: unsafe extern "C" {
fn get_next(va: *mut VaList<'_>) -> c_int;
}
unsafe extern "C" fn add2(mut va: ...) -> c_int {
unsafe {
let mut a = get_next(&mut va);
a += get_next(&mut va);
a
}
} |
I recognize that the single-element array construction is a bit weird, in ways that I admittedly don't fully understand. For instance, https://godbolt.org/z/n8c4aq5hM #include <stdarg.h>
extern int foo(va_list va) {
return va_arg(va, int);
}
extern int bar(va_list *va) {
return va_arg(*va, int);
} Looking at that example, However, looking at this code #include <stdarg.h>
int get_next(va_list* va);
int add2(...) {
va_list va;
va_start(va);
int a = get_next(&va);
a += get_next(&va);
va_end(va);
return a;
}
I mean, you can generate a valid translation: extern "C" {
fn get_next(va: *mut VaList);
}
fn add2(va: ...) -> i32 {
let mut a = unsafe { get_next(&va) };
a + unsafe { get_next(&va) }
} Which produces roughly equivalent LLVM IR and assembly: https://godbolt.org/z/veon5zqja
Right, so the relation between
That seems perfectly workable to me if it is documented clearly. I don't think we should spend language design budget on accommodating C quirks around when arrays are actually pointers here. |
(As I've just realised I never linked it before, here's a link to the C23 standard draft I've been referencing.)
A brief summary, using a typedef char array_ty[20];
struct struct_def { array_ty field; };
union union_def { array_ty field; };
array_ty global_var;
void function(array_ty arg) {
array_ty local_var;
} The only time that For
This is incorrect. In the IR in the Compiler Explorer you linked, the Rust example passes a
With the original proposal, these are only true when
The only time that arrays are pointers in a way that is relevant to Rust is when they are direct arguments to functions. The only alternative to the solution I suggested that's occurred to me is to go back to the design of having two |
right, I also noticed that later. So, thank you for arguing your point. To make the issue explicit: to make the C example work in rust like it does in C you'd have to lie about the method signature, and use use std::ffi::{va_copy, VaList};
extern "C" {
fn get_next(va: VaList) -> i32;
}
#[no_mangle]
unsafe extern "C" fn add2(mut va: ...) -> i32 {
let a = unsafe { get_next(va_copy!(va)) };
a + unsafe { get_next(va) }
} Some thoughts on the alternative proposal. It introduces a type that is always passed by memory (and hence uses One potential complication is how an attribute interacts with having multiple vararg ABIs in the same program. It's unclear whether we'll ever actually implement that, but one of the ideas to do it is to add a defaulted generic to I think this attribute semantically means that the The The next step is probably to get a vibe check from the ABI specialists on whether this idea is feasible and desirable. |
Do you mean |
I've created a draft PR of a possible implementation at #141980. |
I guess actually more the former |
At the backend level, there isn't really any difference between an |
Well because Anyway, @bjorn3 are there any issues with that approach that you can see? |
Move semantics mean that the drop will occur in the callee, not the caller (I've just realised I forgot to mention this in the summary of the differences between extern "C" fn foo(x: VaList<'_>) {
// `x` is dropped here
}
unsafe extern "C" fn bar(x: ...) {
foo(x);
} Regardless, this doesn't matter as the To clarify, my comment in #141524 (comment) was about what happens at the backend level: from a Rust semantics perspective all arguments are passed by value regardless of |
I guess it would work, but didn't think about it too deeply. |
The API as implemented in Bee's PR is still unsound, as it is still possible to move or leak a |
To be clear, the problem you're describing is that I wanted to follow up on that. I suspect that it's probably OK these days to annotate the |
That as well, but it's more generally about the fact that you can move a fn example(list: ...) {
let moved = list;
} The C standard says that this is implementation-defined behaviour, which means it could be undefined. |
Over in #44930, @folkertdev proposed:
Thoughts?
Tracking:
cc @rust-lang/lang
The text was updated successfully, but these errors were encountered: