makepad/libs/relax/compiler/tests/scrutinee_autoderef_field.rs
Admin b5706d8e9b makepad run: sysv64/win64 ABIs, clean quits, boundary gate, Android and Windows fixes
Explicit extern "sysv64" and "win64" (and their -unwind forms) follow
rustc's x86_64 conventions, with rustc's E0570 on other targets. A quit is
clean at any time (signal, window close, remote quit, during boot); the boot
window moves, resizes and closes while the app boots, and a boot probe
reports stalls. `makepad check --boundary` audits every provided crate the
loader binds: JIT code reaches makepad-hal, script, shader and Linux
capture state only through #[relax_api] ops. Domains keep their package
names and threads; constants are copied per domain through their pointer
words; a read binding owns its snapshot on the reading thread. Android:
zone arenas reserve 8 GB, inspect shows thread stacks, opening an app from
the phone WM no longer hangs. Windows: the source watcher ends at quit and
the boot window's shaders compile for D3D11. Tests that build or drop a JIT
unit run alone in a child process. The load screen follows the WM's
dark/light live, has a close button and drags from anywhere.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 07:24:22 +02:00

103 lines
4.3 KiB
Rust

// A field reached through an autoderef of a reference (`get().desc.ty`, `src(0)?.desc.ty`)
// is a place in the pointee: the scrutinee borrows it, nothing is owned by the statement
// (rustc as_place.rs: the base `get()` becomes a temporary of type &T, the autoderef a Deref
// projection, the field a place behind it; dropping that temporary drops only the reference).
// Relax took `get().desc` for a value expression (rvalue_rooted followed the field chain to
// the call without looking at the autoderef), copied `desc` into a temporary and dropped the
// copy at the statement's end: the pointee's `desc` (its name String) was freed while the
// tensor still owned it. Found as the phone WM's SIGSEGV in the domain heap on 'llm-turns'
// (the on-device Translator: makepad_ai_llm vulkan_exec select(), `match src(0)?.desc.ty`).
use std::cell::RefCell;
thread_local! { static LOG: RefCell<Vec<String>> = RefCell::new(Vec::new()); }
fn log(s: String) { LOG.with(|l| l.borrow_mut().push(s)); }
fn take() -> Vec<String> { LOG.with(|l| std::mem::take(&mut *l.borrow_mut())) }
fn v(s: &[&str]) -> Vec<String> { s.iter().map(|x| x.to_string()).collect() }
struct D(&'static str);
impl Drop for D { fn drop(&mut self) { log(format!("drop {}", self.0)); } }
#[derive(Clone, Copy, Debug, PartialEq)]
enum Ty { F32, I32 }
struct Desc { name: Option<String>, ty: Ty, d: D }
struct Tensor { desc: Desc, ne: [i64; 2] }
fn mk(name: &'static str, ty: Ty) -> Tensor {
Tensor { desc: Desc { name: Some(name.to_string()), ty, d: D(name) }, ne: [4, 1] }
}
fn get(ts: &[Tensor], i: usize) -> &Tensor { &ts[i] }
fn getr(ts: &[Tensor], i: usize) -> Result<&Tensor, String> { ts.get(i).ok_or_else(|| "missing".to_string()) }
struct Pair(D, u32);
fn pair_of(t: &(Pair, D)) -> &(Pair, D) { t }
#[test]
fn match_on_a_field_behind_a_returned_reference_drops_nothing() {
take(); // a runner may run the tests on one thread
let ts = vec![mk("a", Ty::F32), mk("b", Ty::I32)];
let n = match get(&ts, 0).desc.ty { Ty::F32 => 1, _ => 2 };
assert_eq!(n, 1);
let n = match get(&ts, 1).desc.ty { Ty::F32 => 1, ty => ty as u32 + 10 };
assert_eq!(n, 11);
assert_eq!(take(), Vec::<String>::new());
assert_eq!(get(&ts, 0).desc.name.as_deref(), Some("a"));
}
#[test]
fn match_on_a_field_behind_try_drops_nothing() {
take(); // a runner may run the tests on one thread
fn sel(ts: &[Tensor]) -> Result<u32, String> {
let src = |i: usize| -> Result<&Tensor, String> { getr(ts, i) };
let a = match src(0)?.desc.ty { Ty::F32 => 1, ty => ty as u32 };
let b = match getr(ts, 1)?.desc.ty { Ty::F32 => 1, _ => 2 };
Ok(a * 10 + b)
}
let ts = vec![mk("a", Ty::F32), mk("b", Ty::I32)];
for _ in 0..3 {
assert_eq!(sel(&ts), Ok(12));
}
assert_eq!(take(), Vec::<String>::new());
assert_eq!(ts[0].desc.name.as_deref(), Some("a"));
assert_eq!(ts[1].desc.name.as_deref(), Some("b"));
}
#[test]
fn if_let_and_nested_fields_behind_a_reference_drop_nothing() {
take(); // a runner may run the tests on one thread
let ts = vec![mk("a", Ty::F32)];
let x = if let Ty::F32 = get(&ts, 0).desc.ty { 1 } else { 2 };
assert_eq!(x, 1);
let y = match ts.get(0).unwrap().desc.ty { Ty::F32 => 3, _ => 4 };
assert_eq!(y, 3);
let t = (Pair(D("p"), 7), D("q"));
let z = match pair_of(&t).0 .1 { 7 => 5, _ => 6 };
assert_eq!(z, 5);
assert_eq!(take(), Vec::<String>::new());
drop(t);
assert_eq!(take(), v(&["drop p", "drop q"]));
}
#[test]
fn let_pattern_on_a_field_behind_a_reference_drops_nothing() {
take(); // a runner may run the tests on one thread
let ts = vec![mk("a", Ty::I32)];
{
let Desc { ty, .. } = get(&ts, 0).desc;
assert_eq!(ty, Ty::I32);
let Desc { ty: Ty::I32, ref name, .. } = get(&ts, 0).desc else { panic!() };
assert_eq!(name.as_deref(), Some("a"));
}
assert_eq!(take(), Vec::<String>::new());
drop(ts);
assert_eq!(take(), v(&["drop a"]));
}
#[test]
fn a_field_of_a_value_still_drops_with_its_temporary() {
take(); // a runner may run the tests on one thread
// the rvalue case stays: `mk().desc.ty` is a place in one temporary of the statement
let n = match mk("t", Ty::F32).desc.ty { Ty::F32 => 1, _ => 2 };
assert_eq!(n, 1);
assert_eq!(take(), v(&["drop t"]));
}