Skip to content

explicit_tail_calls misorders sret argument, breaking ABI for become calls #148239

@InvalidPathException

Description

@InvalidPathException

I am not sure if this is an ICE (the compilation is successful but running the code causes invalid memory access) I tried this code:

#![feature(explicit_tail_calls)]

#[derive(Default)]
struct Instance {
    bytes: Vec<u8>,
}

#[derive(Default)]
struct Stack;
#[derive(Default)]
struct Control;
#[derive(Default)]
struct Bases;

#[derive(Clone, Copy)]
struct Error([u8; 24]);

type Handler = fn(
    &Instance,
    usize,
    &mut Stack,
    &mut Control,
    &mut Bases,
    &mut Bases,
) -> Result<(), Error>;

#[inline(never)]
fn op_dummy(
    _instance: &Instance,
    _pc: usize,
    _stack: &mut Stack,
    _control: &mut Control,
    _func_bases: &mut Bases,
    _ctrl_bases: &mut Bases,
) -> Result<(), Error> {
    Err(Error([1; 24]))
}

static HANDLERS: [Handler; 1] = [op_dummy];

#[inline(never)]
fn dispatch(
    instance: &Instance,
    pc: usize,
    stack: &mut Stack,
    control: &mut Control,
    func_bases: &mut Bases,
    ctrl_bases: &mut Bases,
) -> Result<(), Error> {
    let opcode = 0u8;
    let next_pc = pc + opcode as usize;
    become HANDLERS[opcode as usize](instance, next_pc, stack, control, func_bases, ctrl_bases)
}

fn main() {
    let instance = Instance { bytes: vec![0] };
    let mut stack = Stack::default();
    let mut control = Control::default();
    let mut func_bases = Bases::default();
    let mut ctrl_bases = Bases::default();
    let result = dispatch(&instance, 0, &mut stack, &mut control, &mut func_bases, &mut ctrl_bases);
    eprintln!("dispatch returned: {:?}", result.is_ok());
}

I expected to see this happen: at the bare minimum, it should not crash.

Instead, this happened (other memory errors can happen, but this minimal reproduction has this):

dispatch returned: true
minimal_tail_bin(44100,0x204b46140) malloc: *** error for object 0x101010101010101: pointer being freed was not allocated
minimal_tail_bin(44100,0x204b46140) malloc: *** set a breakpoint in malloc_error_break to debug
[1]    44100 abort      ./tmp/minimal_tail_bin

Meta

rustc +nightly --version --verbose:

rustc 1.93.0-nightly (278a90913 2025-10-28)
binary: rustc
commit-hash: 278a90913daf7707791020a592d722dd7971f196
commit-date: 2025-10-28
host: aarch64-apple-darwin
release: 1.93.0-nightly
LLVM version: 21.1.3
Backtrace (no panic so using lldb)

➜  wagmi git:(tail) ✗ lldb ./tmp/minimal_tail_bin
(lldb) target create "./tmp/minimal_tail_bin"
Current executable set to '/Users/mark/Desktop/github/wagmi/tmp/minimal_tail_bin' (arm64).
(lldb) run
(lldb) bt
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = signal SIGABRT
  * frame #0: 0x0000000196bb2388 libsystem_kernel.dylib`__pthread_kill + 8
    frame #1: 0x0000000196beb848 libsystem_pthread.dylib`pthread_kill + 296
    frame #2: 0x0000000196af49e4 libsystem_c.dylib`abort + 124
    frame #3: 0x00000001969f8174 libsystem_malloc.dylib`malloc_vreport + 892
    frame #4: 0x00000001969fbc90 libsystem_malloc.dylib`malloc_report + 64
    frame #5: 0x0000000196a0021c libsystem_malloc.dylib`___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED + 32
    frame #6: 0x0000000100000c5c minimal_tail_bin`core::ptr::drop_in_place$LT$alloc..raw_vec..RawVec$LT$u8$GT$$GT$::h4bd847816a4750fa + 12
    frame #7: 0x0000000100000c38 minimal_tail_bin`core::ptr::drop_in_place$LT$alloc..vec..Vec$LT$u8$GT$$GT$::h5f9070da464d00f0 + 60
    frame #8: 0x0000000100000bf4 minimal_tail_bin`core::ptr::drop_in_place$LT$minimal_tail..Instance$GT$::he8bdfbeda8fdd895 + 12
    frame #9: 0x00000001000009a8 minimal_tail_bin`minimal_tail::main::h50e25767feace6ee + 288
    frame #10: 0x0000000100000b90 minimal_tail_bin`core::ops::function::FnOnce::call_once::h5557aeb42d126e1f + 16
    frame #11: 0x0000000100000ae4 minimal_tail_bin`std::sys::backtrace::__rust_begin_short_backtrace::h48b7c738fea099aa + 12
    frame #12: 0x0000000100000ac8 minimal_tail_bin`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hbcf70399e5365f1a + 16
    frame #13: 0x0000000100008d4c minimal_tail_bin`core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::h53a415d597411a63 at function.rs:287:21 [opt] [inlined]
    frame #14: 0x0000000100008d44 minimal_tail_bin`std::panicking::catch_unwind::do_call::hdf550c281fdcb7e4 at panicking.rs:590:40 [opt] [inlined]
    frame #15: 0x0000000100008d40 minimal_tail_bin`std::panicking::catch_unwind::h1357aa3b9f9f23d0 at panicking.rs:553:19 [opt] [inlined]
    frame #16: 0x0000000100008d40 minimal_tail_bin`std::panic::catch_unwind::h4f55fe568389ccc1 at panic.rs:359:14 [opt] [inlined]
    frame #17: 0x0000000100008d40 minimal_tail_bin`std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::hf4ba1b7953ccb8d3 at rt.rs:175:24 [opt] [inlined]
    frame #18: 0x0000000100008970 minimal_tail_bin`std::panicking::catch_unwind::do_call::h83012b9119ac28e0 at panicking.rs:590:40 [opt] [inlined]
    frame #19: 0x0000000100008970 minimal_tail_bin`std::panicking::catch_unwind::h857551789dca2cb6 at panicking.rs:553:19 [opt] [inlined]
    frame #20: 0x0000000100008970 minimal_tail_bin`std::panic::catch_unwind::hd425c1766906aced at panic.rs:359:14 [opt] [inlined]
    frame #21: 0x0000000100008970 minimal_tail_bin`std::rt::lang_start_internal::h5aea9edca65f5711 at rt.rs:171:5 [opt]
    frame #22: 0x0000000100000aac minimal_tail_bin`std::rt::lang_start::h372fdb6c50762ee6 + 60
    frame #23: 0x00000001000010ac minimal_tail_bin`main + 36
    frame #24: 0x000000019684ab98 dyld`start + 6076

More info: if you change become to return it behaves normally, IR:

  define internal void @_ZN19minimal_tail_return8dispatch17h5f0bc40cbbb7273fE(
      ptr sret([25 x i8]) align 1 %_0,
      ptr align 8 %instance,
      i64 %pc,
      ptr align 1 %stack,
      ptr align 1 %control,
      ptr align 1 %func_bases,
      ptr align 1 %ctrl_bases
  ) unnamed_addr #1 {
  start:
    %_10.0 = add i64 %pc, 0
    %_10.1 = icmp ult i64 %_10.0, %pc
    br i1 %_10.1, label %panic, label %bb1

  bb1:
    %_11 = load ptr, ptr @_ZN19minimal_tail_return8HANDLERS17h2eb1f46685216284E,
  align 8
    call void %_11(ptr sret([25 x i8]) align 1 %_0,
                   ptr align 8 %instance,
                   i64 %_10.0,
                   ptr align 1 %stack,
                   ptr align 1 %control,
                   ptr align 1 %func_bases,
                   ptr align 1 %ctrl_bases)
    ret void

But when you do become it is now an ABI mismatch (line 116-129):

; minimal_tail::dispatch
; Function Attrs: noinline uwtable
define internal void @_ZN12minimal_tail8dispatch17h0f2db7a170a13413E(ptr sret([25 x i8]) align 1 %_0, ptr align 8 %instance, i64 %pc, ptr align 1 %stack, ptr align 1 %control, ptr align 1 %func_bases, ptr align 1 %ctrl_bases) unnamed_addr #1 {
start:
  %_10.0 = add i64 %pc, 0
  %_10.1 = icmp ult i64 %_10.0, %pc
  br i1 %_10.1, label %panic, label %bb1

bb1:                                              ; preds = %start
  %_11 = load ptr, ptr @_ZN12minimal_tail8HANDLERS17h844a04c0bd8adf43E, align 8
  %0 = bitcast i64 %_10.0 to ptr
  %1 = bitcast ptr %stack to i64
  musttail call void %_11(ptr sret([25 x i8]) align 1 %instance, ptr align 8 %0, i64 %1, ptr align 1 %control, ptr align 1 %func_bases, ptr align 1 %ctrl_bases)
  ret void

minimal_tail.ll.txt

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.F-explicit_tail_calls`#![feature(explicit_tail_calls)]`I-ICEIssue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️I-miscompileIssue: Correct Rust code lowers to incorrect machine codeT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.requires-incomplete-featuresThis issue requires the use of incomplete features.requires-nightlyThis issue requires a nightly compiler in some way.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions