Skip to content

Replace per-run record maps with a slot-indexed arena table - #11107

Merged
kalenikaliaksandr merged 2 commits into
LadybirdBrowser:masterfrom
kalenikaliaksandr:run-records-slot-table
Aug 12, 2026
Merged

Replace per-run record maps with a slot-indexed arena table#11107
kalenikaliaksandr merged 2 commits into
LadybirdBrowser:masterfrom
kalenikaliaksandr:run-records-slot-table

Conversation

@kalenikaliaksandr

Copy link
Copy Markdown
Member

When used-values records moved from the pass-global PagedStore into
run-scoped ownership, lookups regressed from an indexed page-table load
to a per-run HashMap: a RefCell borrow, a SipHash of the slot index, a
bucket probe, and an Rc clone, adding up to roughly a fifth of
WebContent's layout profile on table- and flex-heavy documents. Restore
indexed lookups without giving up run scoping by storing the records in
one slot-indexed side table in the layout node arena, sized with the
slot space and stamped with a per-run nonce: a read becomes an indexed
load and a compare. Runs strictly nest on the call stack, so a run that
registers a slot another live run owns displaces that entry into its
undo list and restores it on drop, which keeps the ownership semantics
(and the unowned-read panics) exactly as before.

Record reads drop from ~57ns to ~9ns and registrations halve, which
translates to 23-31% faster layout passes on documents dominated by
large tables, deep block nesting, or many flex/grid items.

subgrid_item_contributions_to_track_sizing already binds the subgrid's
live record before building its scratch measurement run, but re-fetched
it from the parent run's records afterwards. Use the captured handle for
the two remaining reads so nothing consults the parent table while the
scratch run shadows the subgrid's slot.
When used-values records moved from the pass-global PagedStore into
run-scoped ownership, lookups regressed from an indexed page-table load
to a per-run HashMap: a RefCell borrow, a SipHash of the slot index, a
bucket probe, and an Rc clone, adding up to roughly a fifth of
WebContent's layout profile on table- and flex-heavy documents. Restore
indexed lookups without giving up run scoping by storing the records in
one slot-indexed side table in the layout node arena, sized with the
slot space and stamped with a per-run nonce: a read becomes an indexed
load and a compare. Runs strictly nest on the call stack, so a run that
registers a slot another live run owns displaces that entry into its
undo list and restores it on drop, which keeps the ownership semantics
(and the unowned-read panics) exactly as before.

Record reads drop from ~57ns to ~9ns and registrations halve, which
translates to 23-31% faster layout passes on documents dominated by
large tables, deep block nesting, or many flex/grid items.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

RunRecords now stores records in LayoutNodeArena slots identified by nonces. The arena initializes and clears a run-record slot for each layout node. Registration replaces existing records and records displaced values for LIFO restoration when a run ends. All formatting, grid, and sizing context constructors now pass the relevant arena. Subgrid intrinsic sizing reuses captured used values for its definite inline-size check.

Possibly related PRs

Suggested reviewers: gmta

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description directly matches the changes to move run records into an arena side table while preserving nested-run ownership and restoration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs`:
- Around line 855-870: Replace the debug-only lifecycle checks with
release-enforced assertions: in restore_run_record, use assert_eq! to reject
non-LIFO restoration before overwriting the slot, and at the run-record slot
freeing logic around lines 350-358, use assert! or assert_eq! to reject clearing
a slot that still contains a live record. Apply both changes in
Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs at the specified ranges.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5af11a93-e19c-4067-add3-a9c95ab0ce3a

📥 Commits

Reviewing files that changed from the base of the PR and between d3f5e38 and 1fd6241.

📒 Files selected for processing (5)
  • Libraries/LibWeb/Rust/src/layout/formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs
  • Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs
  • Libraries/LibWeb/Rust/src/layout/run_records.rs
  • Libraries/LibWeb/Rust/src/layout/sizing_context.rs

Comment on lines +855 to +870
pub(crate) fn restore_run_record(&self, slot_index: u32, run_nonce: u64, previous: Option<(u64, Rc<UsedValues>)>) {
let mut records = self.run_used_records.borrow_mut();
let slot = records
.get_mut(slot_index as usize)
.expect("restored layout run record slot must exist");
debug_assert_eq!(
slot.nonce, run_nonce,
"layout run records were not restored in LIFO order"
);
*slot = match previous {
Some((nonce, record)) => RunRecordSlot {
nonce,
record: Some(record),
},
None => RunRecordSlot::default(),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep run-record lifecycle checks enabled in release builds.

debug_assert! and debug_assert_eq! are removed in release builds. A non-LIFO restore can overwrite another live run's record. Freeing a slot with a live record can clear that record before the run drops. Use assert! and assert_eq! at both sites.

  • Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs#L855-L870: reject a non-LIFO restoration before writing the slot.
  • Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs#L350-L358: reject freeing a slot that still has a live run record before clearing it.
📍 Affects 1 file
  • Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs#L855-L870 (this comment)
  • Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs#L350-L358
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs` around lines 855 -
870, Replace the debug-only lifecycle checks with release-enforced assertions:
in restore_run_record, use assert_eq! to reject non-LIFO restoration before
overwriting the slot, and at the run-record slot freeing logic around lines
350-358, use assert! or assert_eq! to reject clearing a slot that still contains
a live record. Apply both changes in
Libraries/LibWeb/Rust/src/layout/layout_node_arena.rs at the specified ranges.

@kalenikaliaksandr
kalenikaliaksandr enabled auto-merge (rebase) August 12, 2026 11:00
@kalenikaliaksandr
kalenikaliaksandr merged commit 0f594af into LadybirdBrowser:master Aug 12, 2026
14 of 15 checks passed
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.

1 participant