You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Harden the rc-elision measurement with tests, fix a loop soundness bug
Add tests pinning both directions of the movability classification: a last-use
call-argument move, a value used again (not a move), an aliasing move, a
field-store escape, and a borrowed element load. Writing the loop test caught a
real unsoundness: the linear last-use marked a loop-carried variable's in-loop
use as a move, when it is live across the back-edge. Fix it with a loop-aware
rule, extracting loop_ranges from loop_depths: a use inside a loop is a move only
if the variable is also defined inside that same loop (a per-iteration temporary),
never carried in from outside. Conservative by construction, so it under-counts
rather than ever over-counting.
Record the follow-ups in PROFILING.md: the liveness is a first cut (full dataflow
for nested loops, branches, and cross-block moves is the non-trivial next step),
more elision categories to find (return moves, orphan-temp direct frees,
retain/release cancellation, struct-field moves), the counts being static rather
than runtime-weighted, and that the transform reuses the dormant moved flag.
assert_eq!(r.call_arg_movable,1,"only the last use is a move");
1868
+
}
1869
+
1870
+
#[test]
1871
+
fnrc_loop_carried_is_not_movable(){
1872
+
// `x` is read every iteration, so it is live across the back-edge: its last linear use inside
1873
+
// the loop is NOT a move. A linear last-use analysis would wrongly mark it movable.
1874
+
let r = rc_report_for(
1875
+
&format!("{RC_SINK}def loopy() -> Int:\n let x = \"ab\"\n let total = 0\n let i = 0\n while i < 2:\n total = total + sink(x)\n i = i + 1\n return total\n"),
1876
+
"loopy",
1877
+
);
1878
+
assert_eq!(r.call_arg_total,1,"one reference argument, in the loop");
1879
+
assert_eq!(
1880
+
r.call_arg_movable,0,
1881
+
"a loop-carried variable is live across iterations, so it must not be counted movable"
1882
+
);
1883
+
}
1884
+
1885
+
#[test]
1886
+
fnrc_aliasing_last_use_is_movable(){
1887
+
// `let b = a` with `a` dead afterward is a move of the reference into `b`. `a` is a parameter
1888
+
// so it is not a folded constant (a literal aliasing would fold away and emit no retain).
1889
+
let r = rc_report_for(
1890
+
"def f(a: String) -> Int:\n let b = a\n return len(b)\n",
1891
+
"f",
1892
+
);
1893
+
assert_eq!(r.alias_total,1,"one aliasing copy");
1894
+
assert_eq!(r.alias_movable,1,"the source is dead after, so it is a move");
1895
+
}
1896
+
1897
+
#[test]
1898
+
fnrc_field_store_is_kept_not_movable(){
1899
+
// Storing a reference into a struct field makes the struct share it, an escape that stays
0 commit comments