Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dag/gunbc/census_closure_frontier.dag
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module gunbc.census_closure_frontier

import gunbc.runner.runner_canary_evidence_frontier { runner_canary_executed_evidence_frontier }
import std.roster_frontier { FrontierRow, concat_frontier_row_groups }
import gunbc.resolver_cost_frontier { resolver_cost_frontier_rows }
import gunbc.auth.privileged_effect_census { human_intervention_frequency_frontier_rows }
import extdeps.cloud.hetzner { hetzner_money_rate_frontier_rows }
import extdeps.dhcp.v4 { mac_address_anemic_brand_frontier_rows }
Expand Down Expand Up @@ -82,6 +83,7 @@ import gunbc.instruments.github_app_acquire {

fn census_closure_frontier_row_groups() -> List<List<FrontierRow>> {
[
resolver_cost_frontier_rows,
review_sheet_whole_estate_consumer_frontier_rows,
review_sheet_formula_observation_consumer_frontier_rows,
review_sheet_header_init_consumer_frontier_rows,
Expand Down
21 changes: 21 additions & 0 deletions dag/gunbc/resolver_cost_frontier.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module gunbc.resolver_cost_frontier

import std.decl_ref { decl_ref }
import std.dissolution { unbound_dissolution }
import std.roster_frontier { FrontierRow, frontier_row_decl, frontier_row_path }

// THE RESOLVER-COST LANE'S DECLARED FRONTIERS: work the v1 seed resolver still does eagerly or
// retains, each named with the capability whose arrival retires it. Rostered through
// gunbc.census_closure_frontier so the dissolution census reads them.
data resolver_cost_frontier_rows: List<FrontierRow> = [
frontier_row_decl(
ref: decl_ref(module_path: "v1.compiler.infer", decl_name: "census_bare_fill_with_resolved_fn_sigs"),
reason: "bare-fill signature upgrade is eager over every bare name of the tree, while a closure's typecheck reads only the names it looks up (gunbc#12716)",
dissolution: unbound_dissolution(description: "demand identity for 'this bare lookup, in this closure' exists as a carrier (docs/plans/demand-engine-program.md M1), so a global_bare key can be upgraded on demand without a lazy cell or a new refusal arm in frozen v1"),
),
frontier_row_path(
path: "src/v1/stage0/src/cli_run/pool_acquire.rs",
reason: "HeadsReading.module is retained for the process: every MultiEntryIndex whose pool contains the file projects it, and a run builds its indexes on demand by phase routing, so no release point is known when the first census projects it (gunbc#12768)",
dissolution: unbound_dissolution(description: "the set of MultiEntryIndex constructions that will demand each pool file is derivable before the first projection, so the heads module node is released after the last demanding index projects it rather than re-parsed"),
),
]
14 changes: 7 additions & 7 deletions src/v1/stage0/src/cli_run/census_heads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,18 +236,18 @@ mod heads_reading_item_boundary_tests {
/// `heads_projection_tests` (fixture) and `entry_resolve::heads_projection_live_differential`
/// (live pool).
pub(crate) fn project_heads_reading(
local: &crate::v1_compiler_parse::ParseWithTableResult,
local: &pool_acquire::HeadsReading,
incoming: &Rc<InternTable>,
) -> Result<(crate::v1_compiler_parse::ParseResult, Rc<InternTable>), String> {
let base = incoming.authored_token_ordinals.allocator.next_id;
let mut table = incoming.clone();
let mut relabel: Vec<i64> = Vec::with_capacity(local.intern_table.strings.len());
for s in local.intern_table.strings.iter() {
let mut relabel: Vec<i64> = Vec::with_capacity(local.local_strings.len());
for s in local.local_strings.iter() {
let r = crate::v1_std_core::intern(table.clone(), s.clone());
relabel.push(r.id);
table = r.table.clone();
}
let local_next = local.intern_table.authored_token_ordinals.allocator.next_id;
let local_next = local.local_next;
let table = crate::v1_std_core::intern_table_with_authored_token_ordinals(
table,
crate::std_occurrence_identity::authored_token_ordinal_space_from_allocator(
Expand All @@ -256,14 +256,14 @@ pub(crate) fn project_heads_reading(
},
),
);
let module = match &local.result.module {
let module = match &local.module {
Some(m) => Some(project_node(m, base, &relabel)?),
None => None,
};
Ok((
crate::v1_compiler_parse::ParseResult {
module,
error: local.result.error.clone(),
error: local.error.clone(),
},
table,
))
Expand Down Expand Up @@ -524,7 +524,7 @@ mod heads_projection_tests {
first.intern_table.clone(),
);
let local = pool_acquire::heads_reading_for(&b.0, &b.1);
assert_ne!(threaded.result.module, local.result.module);
assert_ne!(threaded.result.module, local.module);
let (projected, _) = project_heads_reading(&local, &first.intern_table).unwrap();
assert_eq!(threaded.result.module, projected.module);
}
Expand Down
37 changes: 37 additions & 0 deletions src/v1/stage0/src/cli_run/entry_resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3626,3 +3626,40 @@ mod tree_census_from_raw_differential {
assert!(compared >= 2, "both live roots compared ({compared})");
}
}

/// ONE HEADS PARSE PER (SPELLING, BYTES), asserted: over the live `[dag, src/v2]` pool, building
/// the module path index (`parse_module_binding`) and the pool census (`pool_parse`) -- the two
/// consumers of `pool_acquire::heads_reading_for` -- parses each acquisition key's heads exactly
/// once, and every pool file was read.
#[cfg(test)]
mod heads_parse_count {
use super::*;
#[test]
#[ignore = "live-corpus: prepares or builds over the live tree (minutes per test); the receipts lane runs these with --ignored, the required unit run does not"]
fn each_pool_file_is_heads_parsed_once_on_the_live_pool() {
let root = process_workspace_root();
let roots: Vec<String> = ["dag", "src/v2"]
.iter()
.map(|r| root.join(r).to_string_lossy().into_owned())
.collect();
let _ = build_module_path_index(&pool_roots_for_module_graph_closure(&roots));
let index = process_shared_index(&roots);
let _ = super::super::pool_parse(&index).expect("pool parse");
let (keys, max, over): (usize, usize, Vec<String>) = super::pool_acquire::HEADS_PARSES
.with(|p| {
let p = p.borrow();
(
p.len(),
p.values().copied().max().unwrap_or(0),
p.iter()
.filter(|(_, n)| **n > 1)
.map(|((f, _, _), n)| format!("{f} x{n}"))
.take(20)
.collect(),
)
});
eprintln!("HEADS keys={keys} max_parses_per_key={max}");
assert!(keys > 5000, "the live pool was read ({keys} keys)");
assert!(over.is_empty(), "heads parsed more than once: {over:?}");
}
}
60 changes: 56 additions & 4 deletions src/v1/stage0/src/cli_run/pool_acquire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

use crate::v1_compiler_parse::ParseWithTableResult;
use crate::v1_compiler_tokenize::V1LexArtifact;
use crate::v1_std_core::{build_newline_index, NewlineIndex, Token};
use im::Vector as RtVec;
Expand All @@ -101,7 +100,7 @@ struct Acquired {
newline_index: Rc<NewlineIndex>,
/// The heads reading of these bytes, parsed in a FILE-LOCAL space (empty intern table,
/// occurrence ordinals from zero), filled on first demand. See `heads_reading_for`.
heads: RefCell<Option<Rc<ParseWithTableResult>>>,
heads: RefCell<Option<Rc<HeadsReading>>>,
}

thread_local! {
Expand Down Expand Up @@ -149,6 +148,40 @@ pub fn newline_index_for(file: &str, content: &str) -> Rc<NewlineIndex> {
acquire(file, content).newline_index.clone()
}

/// WHAT THE HEADS READING RETAINS: exactly the fields its consumers read, and nothing else. Its
/// consumers are `module_path_index::parse_module_binding` (the module name and span, and the
/// refusal) and the pool census's `census_heads::project_heads_reading` (the module node, the
/// refusal, and the file-local intern strings and occurrence-allocator end it relabels into the
/// pool's space). The parse's occurrence transport, its full intern index and its allocator are
/// not read by either, so they are not retained: holding the whole parse result for every pool
/// file for the life of the process was the +16% peak-RSS regression the #12656 bisect measured.
pub struct HeadsReading {
/// RETAINED FOR THE PROCESS, and that is the obligated lifetime rather than a leak. Every
/// `MultiEntryIndex` whose pool contains this file projects this node (`pool_parse`), and a
/// run builds its indexes on demand, one per module-name set its phases ask for (measured on
/// #12765's floor: the shared index, a v1 attribution index and the `namespace_baseline`
/// [dag] closure, besides the duplicate that PR removed). When the first census projects the
/// node, nothing at this layer can know whether a later phase will build another index whose
/// pool contains the file: that set is decided by the run's phase routing, and consulting it
/// from the acquisition layer would invert the layers. Releasing after the first projection
/// would force a re-parse for the next index, which is ruled out. The frontier and its
/// dissolution are rostered in `gunbc.resolver_cost_frontier`.
pub module: Option<Rc<crate::v1_std_core::Node>>,
pub error: Option<Rc<crate::v1_std_core::ErrorNode>>,
/// The file-local intern strings, in local-id order (local id `k` is `local_strings[k]`).
pub local_strings: Rc<RtVec<String>>,
/// The file-local occurrence allocator's next id after the parse.
pub local_next: i64,
}

#[cfg(test)]
thread_local! {
/// Heads parses per (spelling, bytes) acquisition key: the control that the heads reading is
/// parsed at most once per key.
pub(crate) static HEADS_PARSES: RefCell<HashMap<(String, usize, u64), usize>> =
RefCell::new(HashMap::new());
}

/// THE ONE HEADS READING of a file, computed once per (spelling, bytes).
///
/// It is parsed in a FILE-LOCAL space: an empty intern table and occurrence ordinals from zero.
Expand All @@ -157,18 +190,37 @@ pub fn newline_index_for(file: &str, content: &str) -> Rc<NewlineIndex> {
/// name, its span and the refusal, none of which carry an id. The pool census reads it through
/// `census_heads::project_heads_reading`, which maps it into the pool's threaded intern table
/// and occurrence space. That projection is total, so the census needs no second parse.
pub fn heads_reading_for(file: &str, content: &str) -> Rc<ParseWithTableResult> {
pub fn heads_reading_for(file: &str, content: &str) -> Rc<HeadsReading> {
let acquired = acquire(file, content);
if let Some(hit) = acquired.heads.borrow().clone() {
return hit;
}
#[cfg(test)]
{
let (len, hash) = content_fingerprint(content);
HEADS_PARSES.with(|p| {
*p.borrow_mut()
.entry((file.to_string(), len, hash))
.or_default() += 1
});
}
let mut indices = im::HashMap::new();
indices.insert(file.to_string(), acquired.newline_index.clone());
let reading = crate::v1_compiler_parse::parse_heads_with_table(
let parsed = crate::v1_compiler_parse::parse_heads_with_table(
acquired.artifact.tokens.clone(),
Rc::new(indices),
crate::v1_std_core::empty_intern_table(),
);
let reading = Rc::new(HeadsReading {
module: parsed.result.module.clone(),
error: parsed.result.error.clone(),
local_strings: parsed.intern_table.strings.clone(),
local_next: parsed
.intern_table
.authored_token_ordinals
.allocator
.next_id,
});
*acquired.heads.borrow_mut() = Some(reading.clone());
reading
}
4 changes: 1 addition & 3 deletions src/v1/stage0/src/module_path_index/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,7 @@ pub fn parse_module_binding(
//
// The reading is the file-local one `pool_acquire::heads_reading_for` holds, which the pool
// census projects rather than re-parses: one heads reading per file per process.
let result = crate::cli_run::pool_acquire::heads_reading_for(&key, content)
.result
.clone();
let result = crate::cli_run::pool_acquire::heads_reading_for(&key, content);
if let Some(err) = result.error.as_ref() {
if module_declaration_line_present(content) {
return Err(ModuleBindingRefusal {
Expand Down
Loading