Skip to content

Remove flag scope data from BuildOptions - #31007

Draft
fmeum wants to merge 3 commits into
bazelbuild:masterfrom
fmeum:flag-scope-details
Draft

Remove flag scope data from BuildOptions#31007
fmeum wants to merge 3 commits into
bazelbuild:masterfrom
fmeum:flag-scope-details

Conversation

@fmeum

@fmeum fmeum commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Description

Preparatory changes are split into #31030 (exec transition cache equality) and #31031 (record conversions).

The scope of a Starlark flag (its scope and on_leave_scope attributes and, for project-scoped flags, the project directories from its PROJECT.scl) is a property of the flag's definition, not of a configuration. This PR stops storing it in BuildOptions. Scope data is resolved through Skyframe from the flag definitions, once per configuration, and read off the BuildConfigurationValue by its only two consumers, the exec transition and project scoping. BuildOptions and every key derived from it now contain only flag values.

Concretely:

  • StarlarkBuildSettingsDetailsValue, the node that already materializes per-flag-definition metadata for transitions, now also carries each build setting's scope type, its on_leave_scope value and, for project-scoped settings, its Scope including the project definition. BuildOptionsScopeFunction and BuildOptionsScopeValue are deleted.
  • BuildConfigurationFunction requests that value for the configuration's own Starlark flags (plus the host_ flag aliases that the custom exec:--host_foo scope needs even when neither flag is set) and stores it in a single field on BuildConfigurationValue. The field participates in equals so that a changed flag definition is not change-pruned away.
  • The Starlark exec transition reads the details off the configuration through the exec transition provider. Cache equality compares the details reference by identity, and hashing uses its identity hash without traversing the metadata maps. Configurations with different scope details therefore do not share a cached exec transition.
  • Project scoping in BuildConfigurationKeyProducer receives the source configuration's details through the dependency producers and only asks Skyframe when a transition introduces a flag the source configuration does not set. That fallback uses the same key as BuildConfigurationFunction, so the node is shared.
  • The baseline configuration's options are marked as such by a boolean field on BuildConfigurationKeyValue.Key instead of having placeholder scopes written into them, which is what kept scoping the baseline from depending on itself.

This supersedes #28574, which had the same goal but kept a separate per-flag-set Skyframe function and threaded two values through the configuration. The internal benchmark of that PR still showed a CPU regression after several rounds of fixes. The costs identified there do not exist in this version, see below.

Motivation

Fixes #29319.

Carrying scope data in BuildOptions means that the same flag values can come with different states of scope fill-in, depending on whether they arrived through the options parser, a platform mapping, a transition or the scope function. Since the scope maps are part of BuildOptions equality and its checksum, otherwise identical options become unequal, which produces duplicate configurations and makes every operation on BuildOptions care about metadata it should not know about.

Why this should not regress performance

Accounting of every hot path against master:

  • Per configured target. Master calls loadStarlarkExecTransition once per target and once per Skyframe restart of it. This PR passes one extra reference into that call. There is no lookup and no hashing. Remove flag scope data from BuildOptions #28574 computed the hash of the details record, seven maps deep, in the provider's constructor on every one of those calls, which is the most likely source of its residual CPU cost.
  • Per dependency edge with a transition. Master iterates the transitioned options' Starlark flags and checks each one's scope type in the options' scope map. This PR checks whether the source configuration's details cover the transitioned flags, which is an identity check followed by containsAll over cached label hashes, and iterates the flags only if the details contain a project-scoped flag at all. Master performed a Skyframe lookup keyed by the entire BuildOptions on every edge where a flag was project-scoped, custom-exec-scoped or introduced by a transition. This PR only looks up when a transition introduces a flag the source configuration does not set, and that key is a small interned label set.
  • Per dependency edge with a Starlark transition, which includes every exec edge. Unchanged: one details lookup, as on master. The host flag aliases now come from a cache on CoreOptions instead of being recomputed from the alias map on every edge.
  • Per configuration. One additional Skyframe lookup of an interned key. Configurations number in the hundreds to low thousands, not millions.
  • Per rule target. With --trim_test_configuration every rule goes through the rule transition path. For rules whose transition is the identity, this PR requests the pre-rule-transition configuration one step earlier than master did, which Skyframe deduplicates within the evaluation. For rules whose transition changes the configuration, it is one additional dep edge. The request is skipped entirely when the configuration sets no Starlark flags.
  • Memory. BuildOptions loses two maps and their entries. The BuildOptionsScopeValue.Keys, each of which embedded a full BuildOptions, and their values disappear. The details value of a configuration is one instance shared by all targets in it.
  • The separate scope function is gone. The internal analysis of Remove flag scope data from BuildOptions #28574 found it loading packages sequentially, causing one Skyframe restart per package, and round-tripping labels through strings on every evaluation. The details function batches the package loads it already performs and reads targets directly from Package.

Two behavior notes, both shared with #28574: a flag scoped exec:--host_foo no longer causes --host_foo's default value to be injected as an explicit value into the target configuration, since the exec transition now takes the value from the flag definition instead. And every configuration now depends on the packages defining its Starlark flags, so editing such a package dirties the configurations that set the flag, with change pruning limiting the fallout to configurations whose scope data actually changed. flags_scoping_integration_tests gained a case that edits a PROJECT.scl between builds to exercise this.

Tested with the unit tests under analysis, skyframe, runtime, buildtool, packages and rules/config that cover transitions, configurations, aspects, feature flags, producers and serialization, a new StarlarkBuildSettingsDetailsFunctionTest, and the flags_scoping_integration_tests and starlark_configurations_test shell suites.

Build API Changes

No

Checklist

  • I have added tests for the new use cases (if any).
  • I have updated the documentation (if applicable). Not applicable, there is no user-visible change.

Release Notes

RELNOTES: None

The scope of a Starlark flag (its `scope` and `on_leave_scope` attributes and, for project-scoped flags, the project directories from its PROJECT.scl) is a property of the flag's definition, not of the configuration. Carrying it in `BuildOptions` meant that the same flag values could come with different states of scope fill-in, which made otherwise identical `BuildOptions` unequal, produced duplicate configurations and made every operation on `BuildOptions` care about metadata it should not know about (bazelbuild#29319).

Scope data is now resolved through Skyframe from the flag definitions and never stored in `BuildOptions`, so `BuildOptions` and every key derived from it contain only flag values.

There is exactly one node that materializes per-flag-definition metadata for transitions already, `StarlarkBuildSettingsDetailsValue`, and it now also carries each build setting's scope type, its on_leave_scope value and, for project-scoped settings, its `Scope` including the project definition. `BuildOptionsScopeFunction` and `BuildOptionsScopeValue` are gone.

`BuildConfigurationFunction` resolves that value once per configuration for the configuration's own Starlark flags and stores it on `BuildConfigurationValue`. Both consumers read it off the configuration: the exec transition gets it through the exec transition provider, and project scoping in `BuildConfigurationKeyProducer` gets the source configuration's value through the dependency producers and only asks Skyframe when a transition introduces flags the source configuration did not set. No per-target or per-edge Skyframe lookup is added, the exec transition provider neither hashes nor compares the details, and `BuildConfigurationValue.equals` includes them so that a changed flag definition is not change-pruned away.

The baseline configuration's options are marked as such on `BuildConfigurationKeyValue.Key` instead of having all their scopes overwritten with a placeholder, which is what previously kept scoping the baseline from depending on itself.

Fixes bazelbuild#29319

Claude-Session: https://claude.ai/code/session_01AGXYj2nBsaAk4s4inx5JoR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Indistinguishable configurations with different hash and output path

1 participant