Repository navigation
hy-emit: fix O8 slot-loan sibling-branch handoff and shadowing binder last-use (#546, #547) - #559
Conversation
|
Reviewed this against the #547 binder class — the sibling-branch half (#546) is the part that is easy to miss, and the flush-before-top-level- One shape is still open. Verified on unmodified Repro — struct item { x int }
fn worker(m map[string]item, done chan bool) {
m["w"] = item{x: 1}
done <- true
}
fn main() {
m := map[string]item{"a": item{x: 0}}
done := make(chan bool, 1)
n := &m["a"]
n.x = 1
task worker(move m, done)
g := fn() (n int) {
n = 7
return
}
<-done
fmt.println(g())
}Emitted on go worker(m, done)
g := func() (n int) {
n = 7
return
}
m["a"] = nThree lines close it (the pattern has to bind let binds = params.iter().any(|p| p.name == target)
|| results.iter().any(|r| r.name.as_deref() == Some(target));PR #551 carries this hunk plus two corpus programs (61/62) that gate the emit order through build+run, so a future refactor of that arm cannot regress silently. No preference on which PR lands — flagging it only so #547 does not close with one shape still open, and so a reviewer does not have to re-derive the shape list. |
a294303 to
ba1f0c8
Compare
ba1f0c8 to
8759e4e
Compare
|
Thanks @Leimiu-G — great catch! We have rebased this branch cleanly onto All |
|
Re-verified independently on The I also took it past the unit suites to emitted-Go behaviour, since the whole point of O8 is the store-back and a passing suite cannot show a missing write: // sibling branch: n is rebound in both arms, loan name shadows the result
fn pick(c *counter, flag bool) int {
n := 0
if flag { n = c.hits; n = n + 10 } else { n = c.hits; n = n + 20 }
return n
}and the plain branch handoff ( One heads-up for merge order, not a request for changes. This branch touches Nothing else from my side; this is the stronger take of the #546/#547 family. |
…e-rendered store statement, not re-derive a chained assignment The #559 merge landed emit-side handoff consumers that formatted "{place} = {name}" from loan-frame entries whose second element is main's full store statement ("m[\"a\"] = n" from emit_local's new_loans) - the synthetic-else / match-none / select-block / pre-flush paths emitted invalid chained Go (m["a"] = n = n; go build: expected '==', found '=') for accepted programs, violating GUARANTEES row 13. All 5 sites now push the stored statement directly. Repro: if-without-else + slot loan + in-branch slot read (p559t1), match none-arm move (p559t2), block rebind (p603a, also fixes the stale store-back slot corruption - now prints 10). Credit: Khanhtnq #559 (fixes #546 #547). Battery: cargo 1505/0, corpus 109/109, differential FULL 36+73, hy-emit 221/0.
|
Landed: merged via API (46c9df9) + maintainer integration repair 37df41f - thank you @Khanhtnq! Probe evidence on main before landing:
Integration repair (37df41f): the PR's handoff consumers re-derived Battery on repaired main: cargo 1523/0, corpus 109/109, differential FULL 36+73. fixes #546 fixes #547. |
|
@limitlessprojects Thanks! Here are my payout details (whichever method is easiest for you):
|
Fixes #546
Fixes #547
Summary
This PR resolves two P0 soundness bugs in O8 map/slice slot-loan store-back emission (
compiler/crates/hy-emit/src/stmt.rs):Sibling-branch / nested control-flow store-back ordering ([soundness] P0: O8 slot-loan store-back emitted after a sibling-branch
move— accepted program races on the moved map #546):emit_block_innerdecided whether to flush a slot loan after statementisolely by checking whether the loan bindervhad no remaining mentions instmts[i + 1..](or before a top-levelStmt::Task/Stmt::Send).stmts[i]was a branching or compound statement (if,match,select,block,managed,for) whose one branch last-usedvand whose sibling branch movedm(task worker(move m)/ch <- move m), mutatedm(delete(m, k),clear(m),m[k] = ...), or readmafter the loan's last use in the same branch, the outeremit_block_innerkept the loan on the outer scope stack and emittedm[k] = vafter the entire compound statement?racing with concurrent tasks or overwritingdelete/clear/subsequent reads.branch_inherithandoff (inherit_loans_into_branch/stmt_needs_branch_loan_handoff/stmt_crosses_collection_boundary/block_reads_ident_after_last_mention_of) acrossStmt::If(includingelse ifchains and syntheticelsebranches whenelse_is omitted),Stmt::Match(synthesizing_/Nonefallthrough arms when needed),Stmt::Select(pre-flushing beforeselectif aSendarm moves the collection),Stmt::Block,Stmt::Managed, andStmt::For.branch_inheritacrossExpr::FnLitemission (compiler/crates/hy-emit/src/expr.rs) so closure literals inside condition/header expressions cannot consume pending branch handoffs.Shadowing loop / select / if-init binders defeating non-lexical last-use flush ([soundness] P0: shadowing loop binder defeats the O8 last-use flush — store-back deferred past
task worker(move m), data race #547):stmt_mentions_identandblock_mentions_identwalked intoForKind::Range,ForKind::Iterate,SelectArmKind::Recv, andStmt::Ifbodies without checking whether a header binder (for n in 0..3,for k, n in xs,select { n := <-ch: ... },if n := ...; cond) shadowed the outer loan bindern.n,stmt_mentions_ident(stmt, "n")returnedtrue, trickingemit_block_innerinto deferring the store-back of the outer loan pasttask worker(move m)into the inner loop's completion.stmt_mentions_identandblock_mentions_identshadowing-aware forForKind::Range(var),ForKind::Iterate(vars),SelectArmKind::Recv(define: truevars), andStmt::If(initlocal declarations), while still inspecting the iterable/channel/init expressions evaluated in the outer scope.block_mentions_identso a slot-loan rebind inside a nested block stops scanning once shadowed.Verification
compiler/crates/hy-emit/tests/loan_branch_move.rscovering#546(if/elsesibling move,ifwithoutelsesynthetic store-back, post-loan read inside a branch with and without a sibling move,delete/clearin sibling branches, andmatch/select/nested block handoffs).compiler/crates/hy-emit/src/tests.rscovering#547(ForKind::Range,ForKind::Iterate,SelectArmKind::Recv, andStmt::Ifinitshadowing).cargo testpasses across all workspace crates (hy-emit164/164 tests,hy-check60/60 corpus unchanged).python tools/check_test_registration.pyandpython tools/check_conflict_markers.pypass cleanly.