Preserve CS memory relations and reduce SV-COMP state#835
Conversation
Init-function bodies may use memory that is not rooted at any global (a stack array, a call result). Cross-graph translation cannot apply to such values because they are not shared global memory; aborting on them was a regression against the baseline, which anchored them as ordinary regions. idxTranslated now reports no translation for values without an underlying global, letting the caller fall back to an ordinary (unknown) region in the target context. DSAWrapper's translated getNode/getOffset follow the same policy: global-rooted values whose field-sensitive translation fails degrade to the whole target node (matching idxTranslated's conservative fallback) and non-global-rooted values report no node. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Call-site mapping propagates one call-graph level per pass when callers precede callees in module order, so a valid pointer-passing chain deeper than the fixed 100-pass cap aborted translation while the analysis was converging normally. Allow one pass per function plus slack; genuine divergence still aborts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The preliminary interface pass and the shared-map fallback for callee interface regions lacking a caller counterpart were gated on the SV-COMP private-map policy, but procedure-interface threading applies in every context-sensitive mode. A threaded region with no mapping at some call site would still reach the missing-mapping translation error in ordinary runs; apply the guard whenever the analysis is context-sensitive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Under '-x svcomp' the C assert maps to a bodiless __VERIFIER_assert, so svcomp_private_maps.c's 'verified' expectation could never fail and only its BPL check had teeth. Add --local-private-memory-maps to the smack CLI (the SVCOMP language mode still enables it automatically) and switch the test to the flag with the regular frontend, making its assertion a real verification obligation alongside the local-map BPL check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Document the SV-COMP private-map policy (which skips the blanket module-level promotion of entry regions), the preliminary interface guard, the dead static-initializer map elimination, the scaled convergence bound, and the non-aborting fallbacks for untranslatable cells; the previous text described the pre-policy declaration rules as unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Audit report (543c2cc..76d7ad5 + sea-dsa 78db357→23cd94a)Multi-agent review across six dimensions (region relations, DSAWrapper translation, private-map policy, dead-map elimination, sea-dsa bump, plumbing/tests/docs) with adversarial verification: 35 independent verdicts, each backed by line-level evidence or a live reproduction on A/B builds (this branch vs. What holds up
Fixed in
|
shaobo-he
left a comment
There was a problem hiding this comment.
Optimization suggestions from the audit — none blocking. Prioritization context: in the round-1 A/B sweep, floppy2/dp83640/cpia2 still time out at 600 s under the real SV-COMP Corral configuration on both builds, so at driver scale the bottleneck is tracked-state size under /trackAllVars, not translation time. The two suggestions that shrink the tracked-global set (write-only-region elimination, one-function shared classes) are therefore the ones most likely to move SV-COMP results; the call-graph-ordering one is the clean translation-time win.
| // Deep pointer-passing call chains propagate one level per pass under | ||
| // adverse module order, so the bound must scale with the number of | ||
| // functions; a fixed cap aborts on valid deep call chains. | ||
| const unsigned maxIters = |
There was a problem hiding this comment.
Suggestion (translation time): process call sites in bottom-up call-graph order instead of module order. Information currently propagates one call-graph level per pass — that is why a 150-deep chain needs ~149 passes and this bound has to scale with module size. Walking call sites callee-first (llvm::scc_iterator over CallGraph, the same order sea-dsa's BU pass uses) converges in one or two passes regardless of depth, cuts Phase-3 time by the chain-depth factor on driver benchmarks, and turns maxIters back into a true divergence detector.
| return countMemoryMapNames(os.str()); | ||
| } | ||
|
|
||
| void eliminateDeadStaticInitMaps(Program &program, SmackRep &rep) { |
There was a problem hiding this comment.
Suggestion (SV-COMP state): generalize dead-map elimination from static-init stores to write-only region classes, computed semantically. After the Phase-4 closure, any class absent from every function's readRegions is write-only memory (status registers, log buffers — common in the ldv drivers): its map declaration and stores can be dropped for reach-safety properties (must stay off for memory safety). Under /trackAllVars every eliminated map is a tracked global removed, so this compounds directly with this PR's headline mechanism — and it would subsume this textual eliminator, retiring the whole-program string scan and its implicit naming invariants.
| // SV-COMP runs Corral with /trackAllVars, which removes the abstraction | ||
| // advantage of globals. Keep genuinely private classes local in that mode | ||
| // so they do not enlarge Corral's tracked state. | ||
| if (localPrivateMaps && !classNeedsShared.count(root) && |
There was a problem hiding this comment.
Suggestion (SV-COMP state): classNeedsShared classes whose members all belong to one non-entry function (and hold no global) currently bind to a module-level $M.S map, but the representational requirement is only one backing map — it could be a single procedure-local map under -local-private-memory-maps. Merging the members into one $M.L index removes another tracked global per such class; the relation-preservation argument is identical to the shared-map case.
| sm.insert(from, to); | ||
| } | ||
| return sm; | ||
| bool DSAWrapper::translateGlobalCell(const Value *v, seadsa::Graph &src, |
There was a problem hiding this comment.
Suggestion (robustness at scale): memoize the mapper per (global, src graph, dst graph). A 4,000-global probe showed the fresh-mapper cost is invisible at that scale, so this is insurance rather than a measured win — but static initializers of large arrays re-seed the same global's mapper once per field store, and driver-scale graphs are both bigger and probed more often. A small cache keyed on the underlying global keeps the exact-identity semantics that motivated dropping the old all-globals mapper.
| // CodifyStaticInits generates pointer-based stores ($store) for them | ||
| // in __SMACK_static_init, which requires map-typed $M variables. | ||
| !DSA->isStaticInitd(node); | ||
| !DSA->isMemOpd(v) && !DSA->isStaticInitd(node); |
There was a problem hiding this comment.
Suggestion (encoding): the !isStaticInitd exclusion may be liftable now. The comment it came with predates SmackRep::store's direct-assignment handling for singleton regions; if __SMACK_static_init stores route through the same emission, statically initialized scalar globals (drivers have many) become plain Boogie variables instead of maps — the cheapest possible encoding under /trackAllVars. One experiment decides it: lift the guard, confirm static init emits $M.k := v rather than $store, and run two-sided static-init probes.
| // every inlined instance with map parameters and copies; emitting | ||
| // shared memory as globals keeps the encoding within the abstraction. | ||
| // Only function-private regions remain procedure-local. | ||
| if (DSA->isContextSensitive()) { |
There was a problem hiding this comment.
Note (minor): this preliminary pass runs the whole-module access closure twice per CS run. The second run starts from saturated sets so it converges quickly, but if profiling ever shows it, snapshotting the access sets here and propagating only the unification-induced delta in Phase 4 is straightforward.
Summary
This completes the context-sensitive memory-region pipeline on top of
csand addresses the state explosion observed in SV-COMP device-driver benchmarks.remove_dead()'s graph invariantThe private-map policy is enabled only for
-x svcompwith context-sensitive DSA. Global-backed, external, unknown, and cross-function regions remain module-level. Context-insensitive DSA and ordinarycsinvocations retain the existing map policy.Memory-safety properties, concurrency, and contracts are outside this PR's scope.
Performance
Exact SV-COMP translations with the default Corral configuration:
ldv-linux-3.0/model0001.i/it913xldv-linux-3.0/model0001.i/i2cfloppy2DD unknown investigation
The first full DD run exposed two additional SMACK-side defects:
mapping[i].insert(idx(...))had unspecified operand evaluation order.idx()may merge regions and rebuild the mapping, invalidating the set selected bymapping[i]. This caused segmentation faults and allocator-corruption failures. Sequencing the lookup fixes the issue; 12 affected DD cases now verify, and the remaining long-running repro reaches Corral instead of crashing during translation.ET1310_PhyAccessMiBithas an output region mapped at calls that pass stack pointers but no caller region at calls that pass null. A single threaded Boogie procedure signature cannot represent that relation. The affected equivalence class now falls back to one conservative shared map. The old build fails during translation; the corrected build verifies the benchmark and emits no map argument at those calls.The partial run was later killed by host OOM while extra targeted checks were running alongside its 11 workers. A clean run from commit
97fd8034has been restarted with the same stock-Corral configuration.Validation
c/basic --all-configs --threads 1, 528/528 passedet131xdifferential: old build fails with a missing call-site mapping; corrected build verifiesi2c-diolan-u2c) verifies in 2.2 s after the sequencing fixc/basic528/528 configurations passed-DNDEBUG, with sanity checks enabledgit diff --check, C/C++ formatting checks, and Python bytecode compilation passedDependency
The submodule update is proposed upstream in seahorn/sea-dsa#179 at
23cd94a. The exact commit is fetchable through the existing upstream submodule URL, so.gitmodulesremains unchanged.