Change:
While testing variadic function support with I discovered that Rust requires such functions to use either "C" or "cdecl" ABIs but requires the "system" ABI on non-x86 Windows. My undertstanding from discussing with is that this is not an issue with itself but with Rust's insistence that such functions aren't allowed to use the "system" ABI. It's just that is very strict about calling convention so that's where the issue surfaced. On x64 I should be able to compile this but Rust says no: This example works if I change the ABI to "cdecl" but that's not meant to be a valid ABI on x64 and so when I try to compile this with it fails: The practical result is that it is impossible to link this function with on non-x86.
cc which originally variadics support to some other calling conventions (though not it seems) behind the feature gate .
A workaround is to use "C" on non-x86 as I implemented here:
Thoughts on permitting variadic parameters with the calling convention for non-x86 Windows? If gets merged then we'll be pushing folks to use so that they get the correct calling convention for Windows APIs on all architectures, but that breaks compatibility with variadic parameters.
This work-around is . Would be great if there was a way that it could be avoided. :)
There's a comment saying this can't work: I don't understand anything about these ABIs so I can't say how accurate this is. Also I see nothing here special-casing x86, so why does the issue specifically mention non-x86?
Happy to update as soon as there's a way to avoid it.
Can we make equal to when applied to a variadic function? It's supposed to be whatever ABI is correct for the system api and is the correct ABI for a variadic win32 function. According to the acts as when applied to a variadic function.
Went ahead and opened for this.

Discussion:
rustc_fluent_macro::fluent_messages! { "../messages.ftl" } fn require_c_abi_if_c_variadic(tcx: TyCtxt<'_>, decl: &hir::FnDecl<'_>, abi: Abi, span: Span) { <del> const CONVENTIONS_UNSTABLE: &str = "`C`, `cdecl`, `aapcs`, `win64`, `sysv64` or `efiapi`"; </del> <ins> const CONVENTIONS_UNSTABLE: &str = "`C`, `cdecl`, `system`, `aapcs`, `win64`, `sysv64` or `efiapi`"; </ins> const CONVENTIONS_STABLE: &str = "`C` or `cdecl`"; const UNSTABLE_EXPLAIN: &str = "using calling conventions other than `C` or `cdecl` for varargs functions is unstable";

) -> DllImport { let span = self.tcx.def_span(item); <ins> // this logic is similar to `Target::adjust_abi` (in rustc_target/src/spec/mod.rs) but errors on unsupported inputs </ins> let calling_convention = if self.tcx.sess.target.arch == "x86" { match abi { Abi::C { .. } | Abi::Cdecl { .. } => DllCallingConvention::C, <del> Abi::Stdcall { .. } | Abi::System { .. } => { DllCallingConvention::Stdcall(self.i686_arg_list_size(item)) </del> <ins> Abi::Stdcall { .. } => DllCallingConvention::Stdcall(self.i686_arg_list_size(item)), // On Windows, `extern "system"` behaves like msvc's `__stdcall`. // `__stdcall` only applies on x86 and on non-variadic functions: // https://learn.microsoft.com/en-us/cpp/cpp/stdcall?view=msvc-170 Abi::System { .. } => { let c_variadic = self.tcx.type_of(item).instantiate_identity().fn_sig(self.tcx).c_variadic(); if c_variadic { DllCallingConvention::C } else { DllCallingConvention::Stdcall(self.i686_arg_list_size(item)) } </ins> } Abi::Fastcall { .. } => { DllCallingConvention::Fastcall(self.i686_arg_list_size(item))

"sparc" => sparc::compute_abi_info(cx, self), "sparc64" => sparc64::compute_abi_info(cx, self), "nvptx64" => { <del> if cx.target_spec().adjust_abi(abi) == spec::abi::Abi::PtxKernel { </del> <ins> if cx.target_spec().adjust_abi(abi, self.c_variadic) == spec::abi::Abi::PtxKernel { </ins> nvptx64::compute_ptx_kernel_abi_info(cx, self) } else { nvptx64::compute_abi_info(self)

"hexagon" => hexagon::compute_abi_info(self), "riscv32" | "riscv64" => riscv::compute_abi_info(cx, self), "wasm32" | "wasm64" => { <del> if cx.target_spec().adjust_abi(abi) == spec::abi::Abi::Wasm { </del> <ins> if cx.target_spec().adjust_abi(abi, self.c_variadic) == spec::abi::Abi::Wasm { </ins> wasm::compute_wasm_abi_info(self) } else { wasm::compute_c_abi_info(cx, self)

// * C and Cdecl obviously support varargs. // * C can be based on Aapcs, SysV64 or Win64, so they must support varargs. // * EfiApi is based on Win64 or C, so it also supports it. <ins> // * System falls back to C for functions with varargs. </ins> // // * Stdcall does not, because it would be impossible for the callee to clean //   up the arguments. (callee doesn't know how many arguments are there) // * Same for Fastcall, Vectorcall and Thiscall. <del> // * System can become Stdcall, so is also a no-no. </del> // * Other calling conventions are related to hardware or the compiler itself. match self { Self::C { .. } | Self::Cdecl { .. } <ins> | Self::System { .. } </ins> | Self::Aapcs { .. } | Self::Win64 { .. } | Self::SysV64 { .. }

impl Target { /// Given a function ABI, turn it into the correct ABI for this target. <del> pub fn adjust_abi(&self, abi: Abi) -> Abi { </del> <ins> pub fn adjust_abi(&self, abi: Abi, c_variadic: bool) -> Abi { </ins> match abi { Abi::C { .. } => self.default_adjusted_cabi.unwrap_or(abi), <del> Abi::System { unwind } if self.is_like_windows && self.arch == "x86" => { </del> <ins> // On Windows, `extern "system"` behaves like msvc's `__stdcall`. // `__stdcall` only applies on x86 and on non-variadic functions: // https://learn.microsoft.com/en-us/cpp/cpp/stdcall?view=msvc-170 Abi::System { unwind } if self.is_like_windows && self.arch == "x86" && !c_variadic => { </ins> Abi::Stdcall { unwind } } Abi::System { unwind } => Abi::C { unwind },

} #[inline] <del> fn conv_from_spec_abi(tcx: TyCtxt<'_>, abi: SpecAbi) -> Conv { </del> <ins> fn conv_from_spec_abi(tcx: TyCtxt<'_>, abi: SpecAbi, c_variadic: bool) -> Conv { </ins> use rustc_target::spec::abi::Abi::*; <del> match tcx.sess.target.adjust_abi(abi) { </del> <ins> match tcx.sess.target.adjust_abi(abi, c_variadic) { </ins> RustIntrinsic | PlatformIntrinsic | Rust | RustCall => Conv::Rust, // This is intentionally not using `Conv::Cold`, as that has to preserve

) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { let sig = cx.tcx.normalize_erasing_late_bound_regions(cx.param_env, sig); <del> let conv = conv_from_spec_abi(cx.tcx(), sig.abi); </del> <ins> let conv = conv_from_spec_abi(cx.tcx(), sig.abi, sig.c_variadic); </ins> let mut inputs = sig.inputs(); let extra_args = if sig.abi == RustCall {

fn baz(f: extern "stdcall" fn(usize, ...)) { //~^ ERROR: C-variadic function must have a compatible calling convention, <del> // like C, cdecl, aapcs, win64, sysv64 or efiapi </del> <ins> // like C, cdecl, system, aapcs, win64, sysv64 or efiapi </ins> f(22, 44); } <ins> fn system(f: extern "system" fn(usize, ...)) { f(22, 44); } </ins> fn aapcs(f: extern "aapcs" fn(usize, ...)) { f(22, 44); }

<del> error[E0045]: C-variadic function must have a compatible calling convention, like `C`, `cdecl`, `aapcs`, `win64`, `sysv64` or `efiapi` </del> <ins> error[E0045]: C-variadic function must have a compatible calling convention, like `C`, `cdecl`, `system`, `aapcs`, `win64`, `sysv64` or `efiapi` </ins> --> $DIR/variadic-ffi-2.rs:4:11 | LL | fn baz(f: extern "stdcall" fn(usize, ...)) {