Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1512,7 +1512,7 @@ fn codegen_regular_intrinsic_call<'tcx>(
}

// FIXME implement variadics in cranelift
sym::va_arg | sym::va_end => {
sym::va_arg => {
fx.tcx.dcx().span_fatal(
source_info.span,
"Defining variadic functions is not yet supported by Cranelift",
Expand Down
4 changes: 0 additions & 4 deletions compiler/rustc_codegen_gcc/src/intrinsic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,10 +705,6 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc
unimplemented!();
}

fn va_end(&mut self, _va_list: RValue<'gcc>) {
// FIXME(antoyo): implement.
}

fn retag_reg(&mut self, _ptr: Self::Value, _info: &RetagInfo<Self::Value>) -> Self::Value {
unimplemented!()
}
Expand Down
4 changes: 0 additions & 4 deletions compiler/rustc_codegen_llvm/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,10 +1070,6 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
self.call_intrinsic("llvm.va_start", &[self.val_ty(va_list)], &[va_list]);
}

fn va_end(&mut self, va_list: &'ll Value) {
self.call_intrinsic("llvm.va_end", &[self.val_ty(va_list)], &[va_list]);
}

fn retag_reg(&mut self, ptr: Self::Value, info: &RetagInfo<Self::Value>) -> Self::Value {
codegen_retag_inner(self, "__rust_retag_reg", ptr, info)
}
Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_codegen_ssa/src/mir/block.rs

@RalfJung RalfJung Jun 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What was the reason again for us not calling va_copy / va_end when cloning / dropping a VaList?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well another constraint on the LLVM va_end is that it must be called in the same function as the corresponding va_start or va_copy, but also va_list can be moved. So va_end does not actually correspond to rust Drop.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sounds like ideally we'd get LLVM to make some more guarantees about va_end, not having quite as much UB there.

Let's see what Nikita says.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Probably best to file a LangRef PR with the necessary guarantees. Adding something like this is probably sufficiently open ended (in the event that a future target is very weird)?

Calls to va_end can be omitted if they are a no-op for the given target. va_end is a no-op for all currently supported targets.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Opened a PR here llvm/llvm-project#203087

Original file line number Diff line number Diff line change
Expand Up @@ -525,13 +525,17 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}

fn codegen_return_terminator(&mut self, bx: &mut Bx) {
// Call `va_end` if this is the definition of a C-variadic function.
// Explicitly end the lifetime of the VaList if this function is c-variadic. We explicitly
// start the lifetime when desugaring `...`. Ending the lifetime meaningfully improves
// codegen.
if self.fn_abi.c_variadic {
// The `VaList` "spoofed" argument is just after all the real arguments.
let va_list_arg_idx = self.fn_abi.args.len();
match self.locals[mir::Local::arg(va_list_arg_idx)] {
LocalRef::Place(va_list) => {
bx.va_end(va_list.val.llval);
// NOTE: we don't actually call LLVM's va_end here. We know it's a no-op for
// all current targets and hence don't bother
// (as permitted by https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic).

// Explicitly end the lifetime of the `va_list`, improves LLVM codegen.
bx.lifetime_end(va_list.val.llval, va_list.layout.size);
Expand Down
3 changes: 0 additions & 3 deletions compiler/rustc_codegen_ssa/src/traits/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ pub trait IntrinsicCallBuilderMethods<'tcx>: BackendTypes {
/// Trait method used to inject `va_start` on the "spoofed" `VaList` in
/// Rust defined C-variadic functions.
fn va_start(&mut self, val: Self::Value);
/// Trait method used to inject `va_end` on the "spoofed" `VaList` before
/// Rust defined C-variadic functions return.
fn va_end(&mut self, val: Self::Value);
/// Trait method used to retag a pointer stored within a place.
fn retag_mem(&mut self, place: Self::Value, info: &RetagInfo<Self::Value>);
/// Trait method used to retag a pointer that has been loaded into a register.
Expand Down
5 changes: 4 additions & 1 deletion tests/codegen-llvm/c-variadic-lifetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ unsafe extern "C" fn variadic(a: f64, mut args: ...) -> f64 {

a + b + c

// CHECK: call void @llvm.va_end.p0(ptr nonnull %args)
// We no longer call the LLVM va_end.
// CHECK-NOT: call void @llvm.va_end

// But we do still explicitly end the lifetime of the VaList.
// CHECK: call void @llvm.lifetime.end.p0({{(i64 [0-9]+, )?}}ptr nonnull %args)
}
5 changes: 1 addition & 4 deletions tests/codegen-llvm/c-variadic-va-end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ unsafe extern "C" {
pub unsafe extern "C" fn f(mut args: ...) {
// CHECK: call void @llvm.va_start
unsafe { g(&raw mut args as *mut u8) }
// We expect one call to the LLVM va_end from our desugaring of `...`. The `Drop` implementation
// should only call the rust va_end intrinsic, which is a no-op.
//
// CHECK: call void @llvm.va_end
// We no longer call the LLVM va_end.
// CHECK-NOT: call void @llvm.va_end
}
3 changes: 2 additions & 1 deletion tests/codegen-llvm/cffi/c-variadic-opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ extern "C" {
pub unsafe extern "C" fn c_variadic_no_use(fmt: *const i8, mut ap: ...) -> i32 {
// CHECK: call void @llvm.va_start
vprintf(fmt, ap)
// CHECK: call void @llvm.va_end
// We no longer call the LLVM va_end.
// CHECK-NOT: call void @llvm.va_end
}
3 changes: 2 additions & 1 deletion tests/codegen-llvm/cffi/c-variadic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ pub unsafe extern "C" fn c_variadic(n: i32, mut ap: ...) -> i32 {
sum += ap.next_arg::<i32>();
}
sum
// CHECK: call void @llvm.va_end
// We no longer call the LLVM va_end.
// CHECK-NOT: call void @llvm.va_end
}

// Ensure that we generate the correct `call` signature when calling a Rust
Expand Down
Loading