-
Notifications
You must be signed in to change notification settings - Fork 64
Problems with Loops #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: pdg
Are you sure you want to change the base?
Conversation
|
What’s going wrong (most likely) No control-dependence edges for loops Broken SSA across back-edges (missing φ nodes) Address-taken / reference values treated as “no-def” Calls elided by missing side-effect summary you filter “pure” calls (but push is not pure), or you never connect calls to the memory object for v (vector buffer / length / capacity). Minimal failing test (close to your description) Expected PDG essentials Nodes: alloc v, init Vec::new, loop header (cond), i φ, addr_of i, load *user_data_ref, call v.push, loop latch/increment. Data deps: i φ → addr_of i → load → call push(arg) v (memory obj) ↔ call push (ModRef). Control deps: loop header branch → {all loop body instructions & latch}. Fix plan (surgical) Compute post-dominators and control dependences (Ferrante–Ottenstein–Warren): For a branch ,S , any node and not post-dominated by both successors is control dependent on Add PDG control edges from the loop header’s conditional branch to: every instruction in the loop body, the latch/increment block, and to the back-edge target (usually the header) where appropriate. Implementation sketch: for (auto* br : function.branches()) { B) SSA across back-edges (value φ) Build SSA before PDG or on-the-fly with renaming: Insert φ for loop-carried defs (i, accumulators). Connect incoming edges from preheader and latch to the φ; then φ → uses. Ensure address-producing instructions (like &i) are modeled as real value defs. Key rule: any value used in the loop body that changes per iteration needs either a φ or a MemorySSA chain. C) Memory modeling (MemorySSA / ModRef) Introduce MemorySSA nodes (MemoryDef/MemoryUse) for: stores/loads of locals with their memory objects, calls; attach a Mod/Ref summary per call. For v.push(..), mark: Mod of v’s buffer/len/capacity object, Ref of the argument memory (if pointer/alias-able), Control a conservative path first; refine later via alias analysis. Minimal conservative solution: auto* callNode = pdg.addCall(inst); D) Keep call nodes (don’t dead-code-eliminate) If you have a PDG “pruner”, never drop calls with Mod/Ref ≠ None. When in doubt, keep all intraprocedural calls unless proven pure. E) Ensure loop blocks are in CFG region walk Some builders start from entry and stop at post-dominated regions—ensure back-edges don’t break your traversal. Always traverse full CFG and keep a visited set that allows revisits for back-edges during structural discovery but prevents infinite loops. Quick pseudo-patch (illustrative C++-like steps) // 2) SSA / φ // 3) Create PDG nodes // 4) Control deps // 5) Keep side-effectful calls Corner cases to include &mut vs & references → different memory effects. Inlined push vs method call → normalize to a call node or summary node. Loops with break/continue → control deps from those branches too. Sanity checks (add to your PR test) Presence assertions: PDG has node for the push call. PDG has a node for addr_of i (or equivalent MIR op for &i). There is a control edge from loop header branch → push node. Path checks: There is a data path: i φ → addr_of i → load → push(arg). There is a memory path: v_memobj ↔ push (Mod/Def/Ref). Example (pseudo) test API: assert!(pdg.contains_node("call Vec::push")); Parallelizing the pass (safe + simple) PDG construction has global dependencies (dom/postdom, MemorySSA), but you can parallelize safely: Parallel per function / SCC Within a function: stage parallelism Stage A (sequential): compute DT, PDT, MemorySSA. Stage B (parallel): create instruction nodes & local def–use: Partition basic blocks into groups (e.g., by reverse postorder strata). Each worker scans its blocks, creates nodes, records local edges into a thread-local buffer. Stage C (sequential merge): merge edges; add control deps; finalize call summaries. Skeleton: ThreadPool pool; Data structures Use thread-local vectors for edges, then a single merge. Use concurrent hash map only for instruction→node mapping; or pre-assign node IDs in a first pass to avoid contention. Why this will fix your specific symptoms Loop body “not connected” → adding control dependences from the loop header branch and φ-propagation stitches it back. user_data_ref missing → model address-of and reference as value defs; attach to MemorySSA object of i if needed. push missing → keep side-effectful calls + ModRef edges to v’s memory object ensure the call remains and is visible in PDG queries. If you can share the tiny MIR/IR of your failing case, I can map exact instruction IDs → PDG nodes and tailor the control-dependence set for your CFG shape (esp. preheader, header, body, latch). |
I found there to be issues with loops where the body of the loop is not connected to either the state before or after.
This PR provides a simple test case that illustrates the problem.
The
user_data_refvariable isn't even found in the PDG at all and I believe neither is the call topush(the latter I only verified in the virtually identical Paralegal test case where I could dump both the PDG and the MIR to check which location corresponded to the call).