layout: Stop using Rayon in single-threaded mode (#37832)

In layout, some parts of the code were still using parallel iterators
from Rayon even when single-thread layout was activated. This change
modifies those parts to use non-parallel iterators when
`LayoutContext::use_rayon` is not active.

Testing: It's very hard to make an automated test for this, but I've
manually
verified this by building with tracing and observing that layout runs
only on
a single thread now when loading https://servo.org.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-07-02 14:44:43 +02:00 committed by GitHub
parent da364f7a61
commit 647122d0f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 197 additions and 144 deletions

View file

@ -44,6 +44,90 @@ enum ModernContainerJob<'dom> {
TextRuns(Vec<ModernContainerTextRun<'dom>>), TextRuns(Vec<ModernContainerTextRun<'dom>>),
} }
impl<'dom> ModernContainerJob<'dom> {
fn finish(
self,
builder: &ModernContainerBuilder,
anonymous_info: &LazyLock<NodeAndStyleInfo<'dom>, impl FnOnce() -> NodeAndStyleInfo<'dom>>,
) -> Option<ModernItem<'dom>> {
match self {
ModernContainerJob::TextRuns(runs) => {
let mut inline_formatting_context_builder =
InlineFormattingContextBuilder::new(builder.info);
for flex_text_run in runs.into_iter() {
inline_formatting_context_builder
.push_text(flex_text_run.text, &flex_text_run.info);
}
let inline_formatting_context = inline_formatting_context_builder.finish(
builder.context,
true, /* has_first_formatted_line */
false, /* is_single_line_text_box */
builder.info.style.to_bidi_level(),
)?;
let block_formatting_context = BlockFormattingContext::from_block_container(
BlockContainer::InlineFormattingContext(inline_formatting_context),
);
let info: &NodeAndStyleInfo = anonymous_info;
let formatting_context = IndependentFormattingContext {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
contents: IndependentFormattingContextContents::NonReplaced(
IndependentNonReplacedContents::Flow(block_formatting_context),
),
};
Some(ModernItem {
kind: ModernItemKind::InFlow,
order: 0,
box_slot: None,
formatting_context,
})
},
ModernContainerJob::ElementOrPseudoElement {
info,
display,
contents,
box_slot,
} => {
let is_abspos = info.style.get_box().position.is_absolutely_positioned();
// Text decorations are not propagated to any out-of-flow descendants. In addition,
// absolutes don't affect the size of ancestors so it is fine to allow descendent
// tables to resolve percentage columns.
let propagated_data = match is_abspos {
false => builder.propagated_data,
true => PropagatedBoxTreeData::default(),
};
let formatting_context = IndependentFormattingContext::construct(
builder.context,
&info,
display.display_inside(),
contents,
propagated_data,
);
if is_abspos {
Some(ModernItem {
kind: ModernItemKind::OutOfFlow,
order: 0,
box_slot: Some(box_slot),
formatting_context,
})
} else {
Some(ModernItem {
kind: ModernItemKind::InFlow,
order: info.style.clone_order(),
box_slot: Some(box_slot),
formatting_context,
})
}
},
}
}
}
struct ModernContainerTextRun<'dom> { struct ModernContainerTextRun<'dom> {
info: NodeAndStyleInfo<'dom>, info: NodeAndStyleInfo<'dom>,
text: Cow<'dom, str>, text: Cow<'dom, str>,
@ -137,84 +221,17 @@ impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> {
.pseudo(self.context, PseudoElement::ServoAnonymousBox) .pseudo(self.context, PseudoElement::ServoAnonymousBox)
.expect("Should always be able to construct info for anonymous boxes.") .expect("Should always be able to construct info for anonymous boxes.")
}); });
let mut children: Vec<ModernItem> = std::mem::take(&mut self.jobs)
.into_par_iter()
.filter_map(|job| match job {
ModernContainerJob::TextRuns(runs) => {
let mut inline_formatting_context_builder =
InlineFormattingContextBuilder::new(self.info);
for flex_text_run in runs.into_iter() {
inline_formatting_context_builder
.push_text(flex_text_run.text, &flex_text_run.info);
}
let inline_formatting_context = inline_formatting_context_builder.finish( let jobs = std::mem::take(&mut self.jobs);
self.context, let mut children: Vec<_> = if self.context.use_rayon {
true, /* has_first_formatted_line */ jobs.into_par_iter()
false, /* is_single_line_text_box */ .filter_map(|job| job.finish(&self, &anonymous_info))
self.info.style.to_bidi_level(), .collect()
)?; } else {
jobs.into_iter()
let block_formatting_context = BlockFormattingContext::from_block_container( .filter_map(|job| job.finish(&self, &anonymous_info))
BlockContainer::InlineFormattingContext(inline_formatting_context), .collect()
); };
let info: &NodeAndStyleInfo = &anonymous_info;
let formatting_context = IndependentFormattingContext {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
contents: IndependentFormattingContextContents::NonReplaced(
IndependentNonReplacedContents::Flow(block_formatting_context),
),
};
Some(ModernItem {
kind: ModernItemKind::InFlow,
order: 0,
box_slot: None,
formatting_context,
})
},
ModernContainerJob::ElementOrPseudoElement {
info,
display,
contents,
box_slot,
} => {
let is_abspos = info.style.get_box().position.is_absolutely_positioned();
// Text decorations are not propagated to any out-of-flow descendants. In addition,
// absolutes don't affect the size of ancestors so it is fine to allow descendent
// tables to resolve percentage columns.
let propagated_data = match is_abspos {
false => self.propagated_data,
true => PropagatedBoxTreeData::default(),
};
let formatting_context = IndependentFormattingContext::construct(
self.context,
&info,
display.display_inside(),
contents,
propagated_data,
);
if is_abspos {
Some(ModernItem {
kind: ModernItemKind::OutOfFlow,
order: 0,
box_slot: Some(box_slot),
formatting_context,
})
} else {
Some(ModernItem {
kind: ModernItemKind::InFlow,
order: info.style.clone_order(),
box_slot: Some(box_slot),
formatting_context,
})
}
},
})
.collect::<Vec<_>>();
// https://drafts.csswg.org/css-flexbox/#order-modified-document-order // https://drafts.csswg.org/css-flexbox/#order-modified-document-order
children.sort_by_key(|child| child.order); children.sort_by_key(|child| child.order);

View file

@ -1221,14 +1221,25 @@ impl InitialFlexLineLayout<'_> {
); );
// https://drafts.csswg.org/css-flexbox/#algo-cross-item // https://drafts.csswg.org/css-flexbox/#algo-cross-item
let layout_results = items let layout_results: Vec<_> = if flex_context.layout_context.use_rayon {
.par_iter() items
.zip(&item_used_main_sizes) .par_iter()
.map(|(item, used_main_size)| { .zip(&item_used_main_sizes)
item.layout(*used_main_size, flex_context, None, None) .map(|(item, used_main_size)| {
.unwrap() item.layout(*used_main_size, flex_context, None, None)
}) .unwrap()
.collect::<Vec<_>>(); })
.collect()
} else {
items
.iter()
.zip(&item_used_main_sizes)
.map(|(item, used_main_size)| {
item.layout(*used_main_size, flex_context, None, None)
.unwrap()
})
.collect()
};
let items: Vec<_> = izip!( let items: Vec<_> = izip!(
items.into_iter(), items.into_iter(),

View file

@ -1069,71 +1069,96 @@ impl<'a> TableLayout<'a> {
layout_context: &LayoutContext, layout_context: &LayoutContext,
containing_block_for_table: &ContainingBlock, containing_block_for_table: &ContainingBlock,
) { ) {
self.cells_laid_out = self let layout_table_slot = |coordinate: TableSlotCoordinates, slot: &TableSlot| {
.table let TableSlot::Cell(cell) = slot else {
.slots return None;
.par_iter() };
.enumerate()
.map(|(row_index, row_slots)| {
row_slots
.par_iter()
.enumerate()
.map(|(column_index, slot)| {
let TableSlot::Cell(cell) = slot else {
return None;
};
let cell = cell.borrow(); let cell = cell.borrow();
let area = LogicalSides { let area = LogicalSides {
inline_start: column_index, inline_start: coordinate.x,
inline_end: column_index + cell.colspan, inline_end: coordinate.x + cell.colspan,
block_start: row_index, block_start: coordinate.y,
block_end: row_index + cell.rowspan, block_end: coordinate.y + cell.rowspan,
}; };
let layout_style = cell.layout_style(); let layout_style = cell.layout_style();
let border = self let border = self
.get_collapsed_border_widths_for_area(area) .get_collapsed_border_widths_for_area(area)
.unwrap_or_else(|| { .unwrap_or_else(|| {
layout_style layout_style.border_width(containing_block_for_table.style.writing_mode)
.border_width(containing_block_for_table.style.writing_mode) });
}); let padding: LogicalSides<Au> = layout_style
let padding: LogicalSides<Au> = layout_style .padding(containing_block_for_table.style.writing_mode)
.padding(containing_block_for_table.style.writing_mode) .percentages_relative_to(self.basis_for_cell_padding_percentage);
.percentages_relative_to(self.basis_for_cell_padding_percentage); let inline_border_padding_sum = border.inline_sum() + padding.inline_sum();
let inline_border_padding_sum = border.inline_sum() + padding.inline_sum();
let mut total_cell_width: Au = (column_index..column_index + cell.colspan) let mut total_cell_width: Au = (coordinate.x..coordinate.x + cell.colspan)
.map(|column_index| self.distributed_column_widths[column_index]) .map(|column_index| self.distributed_column_widths[column_index])
.sum::<Au>() - .sum::<Au>() -
inline_border_padding_sum; inline_border_padding_sum;
total_cell_width = total_cell_width.max(Au::zero()); total_cell_width = total_cell_width.max(Au::zero());
let containing_block_for_children = ContainingBlock { let containing_block_for_children = ContainingBlock {
size: ContainingBlockSize { size: ContainingBlockSize {
inline: total_cell_width, inline: total_cell_width,
block: SizeConstraint::default(), block: SizeConstraint::default(),
}, },
style: &cell.base.style, style: &cell.base.style,
}; };
let mut positioning_context = PositioningContext::default(); let mut positioning_context = PositioningContext::default();
let layout = cell.contents.layout( let layout = cell.contents.layout(
layout_context, layout_context,
&mut positioning_context, &mut positioning_context,
&containing_block_for_children, &containing_block_for_children,
false, /* depends_on_block_constraints */ false, /* depends_on_block_constraints */
); );
Some(CellLayout { Some(CellLayout {
layout, layout,
padding, padding,
border, border,
positioning_context, positioning_context,
})
})
.collect()
}) })
.collect(); };
self.cells_laid_out = if layout_context.use_rayon {
self.table
.slots
.par_iter()
.enumerate()
.map(|(row_index, row_slots)| {
row_slots
.par_iter()
.enumerate()
.map(|(column_index, slot)| {
layout_table_slot(
TableSlotCoordinates::new(column_index, row_index),
slot,
)
})
.collect()
})
.collect()
} else {
self.table
.slots
.iter()
.enumerate()
.map(|(row_index, row_slots)| {
row_slots
.iter()
.enumerate()
.map(|(column_index, slot)| {
layout_table_slot(
TableSlotCoordinates::new(column_index, row_index),
slot,
)
})
.collect()
})
.collect()
};
// Now go through all cells laid out and update the cell measure based on the size // Now go through all cells laid out and update the cell measure based on the size
// determined during layout. // determined during layout.