Skip to content

What happens to the validiy requirements of the return value on a tail call? #491

Open
@RalfJung

Description

@RalfJung

This came up here. @WaffleLapkin provided an example:

#![feature(explicit_tail_calls)]
use std::num::NonZeroU8;
use std::mem::transmute;

fn a() -> u8 { 0 }

fn b() -> NonZeroU8 { unsafe {
    become transmute::<fn() -> u8, fn() -> NonZeroU8>(a)();
} }

fn c() {
    let _x: u8 = unsafe { transmute::<fn() -> NonZeroU8, fn() -> u8>(b)() };
}

As a normal call, there would be UB when b returns, since the return place at that moment contains something that is invalid for b's return type (despite being valid for c's return type).

However, with tail calls, b is long gone by the time a returns. a returns directly to c, and both caller and callee see a return type of u8. So arguably this program should be allowed? More fundamentally, not allowing this program means that Miri / the spec would have to keep around the return type of all the stack frames that were "popped early" due to tail calls, and ensure that the eventual return type is valid according to all of them -- which seems to go against the very idea of a tail call.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions