makepad/libs/relax/compiler/tests/async_boxed_recursion.rs
Admin a94d41a3b4 makepad run / Relax: recursive async fns lower against their real frame layout
A recursive coroutine is lowered again once its frame is known; the second
lowering returned the cached first one, made with the provisional 8-byte
layout, so a boxed recursive future (24 bytes) was written into an 8-byte
slot. The relowering now bypasses that cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:14:07 +02:00

19 lines
1.1 KiB
Rust

// A recursive boxed async fn: the future built for `Box::pin(boxed(n - 1))` is of the type being
// lowered. Its slot must have the final size (24 bytes: state, flags and saved locals), not the
// layout so far: the callee wrote 24 bytes into an 8-byte slot (coro_frame lowers the body again;
// the first lowering is not what the build takes). Found at the baseline tier with the quick
// register allocator (MAKEPAD_RUN_O1=always), where the frame order exposed the overwrite.
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
struct Noop; impl Wake for Noop { fn wake(self: Arc<Self>) {} }
fn block_on<F: Future>(f: F) -> F::Output {
let mut f = Box::pin(f); let w = Waker::from(Arc::new(Noop)); let mut cx = Context::from_waker(&w);
loop { if let Poll::Ready(v) = f.as_mut().poll(&mut cx) { return v; } }
}
async fn boxed(n: u32) -> u32 { if n == 0 { 0 } else { n + Box::pin(boxed(n - 1)).await } }
#[test]
fn run() { assert_eq!(std::mem::size_of_val(&boxed(3)), 24);
assert_eq!(block_on(boxed(2)), 3);
assert_eq!(block_on(boxed(12)), 78); }