The Signature That Changed Everything: Wiring PceCache Through the Extraction Pipeline

"I need to update extract_and_cache_pce_from_c1 and the other extraction functions to take &PceCache."

At first glance, message <msg id=2118> appears to be a mundane edit — a simple signature change in a Rust codebase, one of dozens that occur in any refactoring session. The assistant states its intent, issues an edit to /tmp/czk/extern/cuzk/cuzk-core/src/pipeline.rs, and the tool reports success. Yet this single message represents a critical inflection point in a much larger architectural transformation: the replacement of a fragile, static memory management system with a unified, budget-aware memory manager for the cuzk GPU proving engine.

To understand why this message matters, we must zoom out to the full context of the session. The cuzk daemon is a CUDA-based ZK proof generator for Filecoin, responsible for producing the computationally intensive proofs that underpin the Filecoin storage verification protocol. Prior to this session, cuzk's memory management was a patchwork of dead configuration fields, advisory-only budgets, and static global caches that accumulated memory without any eviction mechanism. A comprehensive specification document (cuzk-memory-manager.md) had already been written, detailing a new architecture built around a single unified byte budget, auto-detected from system RAM, with LRU eviction for SRS and PCE caches under memory pressure. The current session was the implementation of that specification.

The Architecture of the Old World

Before this message, the PCE (Pre-Compiled Constraint Evaluator) cache lived in four static OnceLock<PreCompiledCircuit<Fr>> globals defined in pipeline.rs. These globals were write-once, never-cleared caches that accumulated PCE data for four proof types: PoRep (Proof of Replication), SnapDeals, WinningPoSt, and WindowPoSt. Each PCE consumed approximately 26 GiB of heap memory. Once loaded, they stayed resident for the lifetime of the process. The extraction functions — extract_and_cache_pce_from_c1, extract_and_cache_pce_from_winning_post, extract_and_cache_pce_from_window_post, and extract_and_cache_pce_from_snap — each wrote directly into their corresponding static global. There was no way to evict a PCE, no way to track which PCEs were in use, and no integration with any memory budget system.

This design had a critical flaw: if a daemon served multiple proof types, the PCEs would accumulate without bound. PoRep alone consumed ~26 GiB for its PCE; add SnapDeals (+~26 GiB), WindowPoSt (+~26 GiB), and WinningPoSt (+~26 GiB), and the baseline heap cost for PCEs alone could reach over 100 GiB. Combined with SRS (Structured Reference String) data that could exceed 200 GiB across all proof types, the static accumulation model was a recipe for OOM crashes on machines with limited RAM.

The New World: PceCache

The specification called for replacing these four static globals with a single PceCache struct — a managed cache that could:

Why This Message Exists

Message <msg id=2118> sits at the boundary between introducing the PceCache struct and wiring it into every consumer. The assistant had already:

  1. Created the memory.rs module with MemoryBudget, MemoryReservation, and estimation constants ([msg 2093])
  2. Updated config.rs with the new unified budget fields ([msg 2097][msg 2103])
  3. Rewritten srs_manager.rs to be budget-aware with eviction support ([msg 2105])
  4. Introduced PceCache in pipeline.rs and removed the static OnceLock globals ([msg 2108])
  5. Updated load_pce_from_disk to work with PceCache ([msg 2109])
  6. Removed preload_pce_from_disk ([msg 2113])
  7. Updated the core extract_and_cache_pce function ([msg 2115]) But the four specialized extraction functions — one per proof type — still used the old pattern. They were written to populate the static globals directly. Until they were updated to accept &PceCache, the codebase would be in an inconsistent state: the cache existed, but the functions that populated it couldn't access it. The assistant's reasoning, visible in the message's preamble, shows a clear understanding of this dependency chain: "I need to update extract_and_cache_pce_from_c1 and the other extraction functions to take &PceCache." The word "and" is telling — the assistant recognizes that this is not a single function change but a coordinated update across all four extraction functions. They share the same pattern, and they all need the same treatment.

The Edit Itself

The message issues a single edit to pipeline.rs. The edit content is not shown in the message (the tool call result appears in the next message, <msg id=2119>, which confirms success), but we can infer what changed. The signature of extract_and_cache_pce_from_c1 previously looked something like:

pub fn extract_and_cache_pce_from_c1(
    vanilla_proof_json: &str,
    param_cache: Option<&std::path::Path>,
) -> Result<()>

The updated signature would add a pce_cache: &amp;PceCache parameter:

pub fn extract_and_cache_pce_from_c1(
    pce_cache: &PceCache,
    vanilla_proof_json: &str,
    param_cache: Option<&std::path::Path>,
) -> Result<()>

This is a textbook refactoring pattern: add a parameter to carry the new dependency, then update the function body to use it instead of the old global. The body would replace references to PCE_C1 (the old static OnceLock) with calls to pce_cache.get(...) and pce_cache.insert(...).

Assumptions and Decisions

The assistant makes several implicit assumptions in this message:

Assumption 1: All four extraction functions share the same pattern. The assistant states it needs to update extract_and_cache_pce_from_c1 "and the other extraction functions," implying that the change is mechanical and uniform. This is a reasonable assumption — the functions were written as parallel implementations for different proof types, each following the same structure of "build circuit → run RecordingCS → store in static OnceLock."

Assumption 2: The PceCache API is already sufficient. At this point, PceCache has been defined but the assistant assumes its interface (insert, get, evict, etc.) is complete enough for the extraction functions to use. Any missing methods would need to be added retroactively.

Assumption 3: The extraction functions are the only remaining consumers of the old statics. The assistant had already updated load_pce_from_disk, extract_and_cache_pce, and synthesize_auto. The assumption is that updating the four extraction functions will complete the migration away from the static globals.

Assumption 4: The call sites of these extraction functions will be updated separately. The extraction functions are called from the bench tool (cuzk-bench/src/main.rs) and potentially from the daemon's background extraction threads. By changing the signature, the assistant creates compilation errors at those call sites, which must be fixed in subsequent edits. The assistant does not address those call sites in this message, implying a plan to handle them later or in parallel.

Input Knowledge Required

To understand this message, a reader needs knowledge of:

  1. The cuzk architecture: That cuzk is a GPU proving daemon for Filecoin, that it uses PCEs (Pre-Compiled Constraint Evaluators) to accelerate proof generation, and that PCEs are derived from circuit structure via RecordingCS.
  2. The old static cache design: That four OnceLock&lt;PreCompiledCircuit&lt;Fr&gt;&gt; globals existed in pipeline.rs, one per proof type, and that the extraction functions wrote into them directly.
  3. The PceCache design: That a new PceCache struct was introduced to replace these statics, supporting multiple entries, LRU tracking, and budget integration.
  4. The memory manager specification: That a unified MemoryBudget system was being implemented to track all memory consumers (SRS, PCE, working memory) under a single byte budget.
  5. Rust's ownership and borrowing model: Why the parameter is &amp;PceCache (a shared reference) rather than &amp;mut PceCache or ownership — the cache needs to be accessible from multiple threads (synthesis, extraction, GPU workers) simultaneously.
  6. The broader refactoring context: That this is Step 5 of a multi-step implementation plan, following changes to memory.rs, config.rs, lib.rs, and srs_manager.rs.

Output Knowledge Created

This message creates:

  1. A modified extract_and_cache_pce_from_c1 function that accepts &amp;PceCache instead of writing to a static global. This function can now be used in contexts where the cache is managed by the memory budget system.
  2. A template for the other three extraction functions. The assistant explicitly states it will apply the same pattern to extract_and_cache_pce_from_winning_post, extract_and_cache_pce_from_window_post, and extract_and_cache_pce_from_snap. The edit to extract_and_cache_pce_from_c1 serves as the prototype.
  3. A compilation error at every call site that calls the old signature. This is intentional — it forces the developer (or the assistant in subsequent messages) to update those call sites to pass the PceCache reference.
  4. Forward progress on the memory manager implementation. With this change, the PCE cache is no longer a set of unmanaged static globals but a managed resource that can participate in eviction decisions and budget tracking.

The Thinking Process

The assistant's thinking, visible in the message text, reveals a methodical approach to the refactoring. The phrase "I need to update extract_and_cache_pce_from_c1 and the other extraction functions to take &amp;PceCache" shows:

What Could Go Wrong

Several potential issues lurk beneath this seemingly straightforward edit:

Issue 1: The PceCache API might not support all operations needed by the extraction functions. The old code wrote directly to OnceLock, which has a specific initialization pattern (OnceLock::get_or_init). If PceCache doesn't provide an equivalent atomic "get or insert" operation, the extraction logic might need restructuring.

Issue 2: Thread safety. The old OnceLock statics were thread-safe by construction — OnceLock guarantees at-most-once initialization. PceCache uses internal locking (the implementation in the spec uses Arc&lt;Mutex&lt;...&gt;&gt;), but if the extraction functions hold the lock across long-running operations (like building a circuit), they could cause contention with other threads trying to read the cache.

Issue 3: The param_cache parameter interaction. The extraction functions accept an optional param_cache path for disk persistence. The PceCache design might need to integrate with this persistence layer — when a PCE is evicted, should its disk copy be deleted? When a PCE is loaded from disk, should it bypass the budget? These questions are not addressed in this message.

Issue 4: Call site updates. The bench tool (cuzk-bench/src/main.rs) calls extract_and_cache_pce_from_c1 with the old signature. Until that call site is updated, the codebase won't compile. The assistant handles this in subsequent messages ([msg 2124] onward), but at the moment of message [msg 2118], the codebase is in a temporarily broken state.

Conclusion

Message &lt;msg id=2118&gt; is a small but essential step in a larger architectural transformation. It represents the moment when the new PceCache struct begins to displace the old static global pattern, wiring the memory-aware cache into the functions that populate it. The message reveals the assistant's methodical, pattern-based approach to refactoring: identify the shared pattern, update one instance as a template, then systematically apply the same change to all remaining instances. While the edit itself is simple — adding a parameter to a function signature — its implications ripple through the entire extraction pipeline, the bench tool, and the daemon's background extraction threads. It is a reminder that in software architecture, the most consequential changes often begin with the smallest edits.