Reduce LLVM IR bloat in closure funnels (#63426)

Follow-up to https://github.com/zed-industries/zed/pull/63232

Cuts closure monomorphization in settings and editor: settings file
updates now share one boxed write-pipeline instantiation instead of
inlining the `SettingsContent` clone, both `serde_json::to_value` calls,
and the JSON diff at every call site; `Editor::register_action` and the
settings enum dropdown get the same treatment. Behavior and public
signatures are unchanged; thin generic shims remain.

Symbol table of the release binary:
Before: `update_settings_file_inner` 229 instantiations, inlined
`serde_json` `SerializeMap` machinery 6547 copies
After: 3 instantiations, 673 copies (-89.7%); `render_dropdown` is no
longer instantiated at all

With sccache disabled, `cargo clean` and `cargo build --release -p zed`:
Before: zed binary 420677880 bytes
After: zed binary 396999464 bytes (-5.6%, -23.7MB)

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov authored and GitHub committed 2026-08-29 22:51:20 +00:00
1 parent fa839e548d
commit 6dc2206180
5 files changed
+149 -71

No files matched your search

+14 -3
View File
@@ -10809,15 +10809,26 @@ impl Editor {
pub fn register_action<A: Action>(
&mut self,
listener: impl Fn(&A, &mut Window, &mut App) + 'static,
) -> Subscription {
self.register_action_erased(
TypeId::of::<A>(),
Arc::new(move |action, window, cx| {
listener(action.downcast_ref().unwrap(), window, cx)
}),
)
}
fn register_action_erased(
&mut self,
action_type: TypeId,
listener: Arc<dyn Fn(&dyn Any, &mut Window, &mut App)>,
) -> Subscription {
let id = self.next_editor_action_id.post_inc();
let listener = Arc::new(listener);
self.editor_actions.borrow_mut().insert(
id,
Box::new(move |_, window, _| {
let listener = listener.clone();
window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
let action = action.downcast_ref().unwrap();
window.on_action(action_type, move |action, phase, window, cx| {
if phase == DispatchPhase::Bubble {
listener(action, window, cx)
}
+17 -2
View File
@@ -10678,10 +10678,25 @@ pub fn register_action<T: Action>(
editor: &Entity<Editor>,
window: &mut Window,
listener: impl Fn(&mut Editor, &T, &mut Window, &mut Context<Editor>) + 'static,
) {
register_action_erased(
editor,
window,
TypeId::of::<T>(),
Box::new(move |editor, action, window, cx| {
listener(editor, action.downcast_ref().unwrap(), window, cx)
}),
)
}
fn register_action_erased(
editor: &Entity<Editor>,
window: &mut Window,
action_type: TypeId,
listener: Box<dyn Fn(&mut Editor, &dyn std::any::Any, &mut Window, &mut Context<Editor>)>,
) {
let editor = editor.clone();
window.on_action(TypeId::of::<T>(), move |action, phase, window, cx| {
let action = action.downcast_ref().unwrap();
window.on_action(action_type, move |action, phase, window, cx| {
if phase == DispatchPhase::Bubble {
editor.update(cx, |editor, cx| {
listener(editor, action, window, cx);
+43 -12
View File
@@ -559,7 +559,7 @@ impl SettingsStore {
fn update_settings_file_inner(
&self,
fs: Arc<dyn Fs>,
update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
update: Box<dyn Send + FnOnce(String, AsyncApp) -> Result<String>>,
) -> oneshot::Receiver<Result<()>> {
let (tx, rx) = oneshot::channel::<Result<()>>();
self.setting_file_updates_tx
@@ -626,11 +626,17 @@ impl SettingsStore {
fs: Arc<dyn Fs>,
update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
) -> oneshot::Receiver<Result<()>> {
self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
cx.read_global(|store: &SettingsStore, cx| {
store.new_text_for_update(old_text, |content| update(content, cx))
})
})
let mut update = Some(update);
self.update_settings_file_inner(
fs,
Box::new(move |old_text: String, cx: AsyncApp| {
cx.read_global(|store: &SettingsStore, cx| {
store.new_text_for_update_inner(old_text, &mut |content| {
(update.take().expect("called once"))(content, cx)
})
})
}),
)
}
pub fn import_vscode_settings(
@@ -638,11 +644,14 @@ impl SettingsStore {
fs: Arc<dyn Fs>,
vscode_settings: VsCodeSettings,
) -> oneshot::Receiver<Result<()>> {
self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
cx.read_global(|store: &SettingsStore, _cx| {
store.get_vscode_edits(old_text, &vscode_settings)
})
})
self.update_settings_file_inner(
fs,
Box::new(move |old_text: String, cx: AsyncApp| {
cx.read_global(|store: &SettingsStore, _cx| {
store.get_vscode_edits(old_text, &vscode_settings)
})
}),
)
}
pub fn get_all_files(&self) -> Vec<SettingsFile> {
@@ -833,7 +842,18 @@ impl SettingsStore {
old_text: String,
update: impl FnOnce(&mut SettingsContent),
) -> Result<String> {
let edits = self.edits_for_update(&old_text, update)?;
let mut update = Some(update);
self.new_text_for_update_inner(old_text, &mut |content| {
(update.take().expect("called once"))(content)
})
}
fn new_text_for_update_inner(
&self,
old_text: String,
update: &mut dyn FnMut(&mut SettingsContent),
) -> Result<String> {
let edits = self.edits_for_update_inner(&old_text, update)?;
let mut new_text = old_text;
for (range, replacement) in edits.into_iter() {
new_text.replace_range(range, &replacement);
@@ -853,6 +873,17 @@ impl SettingsStore {
&self,
text: &str,
update: impl FnOnce(&mut SettingsContent),
) -> Result<Vec<(Range<usize>, String)>> {
let mut update = Some(update);
self.edits_for_update_inner(text, &mut |content| {
(update.take().expect("called once"))(content)
})
}
fn edits_for_update_inner(
&self,
text: &str,
update: &mut dyn FnMut(&mut SettingsContent),
) -> Result<Vec<(Range<usize>, String)>> {
let old_content = if text.trim().is_empty() {
UserSettingsContent::default()
+29 -27
View File
@@ -8,37 +8,47 @@ use ui::{
};
#[derive(IntoElement)]
pub struct EnumVariantDropdown<T>
where
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
{
pub struct EnumVariantDropdown {
id: ElementId,
current_value: T,
variants: &'static [T],
selected_index: usize,
labels: &'static [&'static str],
should_do_title_case: bool,
tab_index: Option<isize>,
disabled: bool,
aria_label: Option<SharedString>,
aria_description: Option<SharedString>,
on_change: Rc<dyn Fn(T, &mut ui::Window, &mut App) + 'static>,
on_change: Rc<dyn Fn(usize, &mut ui::Window, &mut App) + 'static>,
}
impl<T> EnumVariantDropdown<T>
where
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
{
pub fn new(
impl EnumVariantDropdown {
pub fn new<T>(
id: impl Into<ElementId>,
current_value: T,
variants: &'static [T],
labels: &'static [&'static str],
on_change: impl Fn(T, &mut ui::Window, &mut App) + 'static,
) -> Self
where
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
{
let selected_index = variants
.iter()
.position(|v| *v == current_value)
.unwrap_or(0);
Self::new_indexed(id, selected_index, labels, move |index, window, cx| {
on_change(variants[index], window, cx)
})
}
pub fn new_indexed(
id: impl Into<ElementId>,
selected_index: usize,
labels: &'static [&'static str],
on_change: impl Fn(usize, &mut ui::Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
current_value,
variants,
selected_index,
labels,
should_do_title_case: true,
tab_index: None,
@@ -79,33 +89,25 @@ where
}
}
impl<T> RenderOnce for EnumVariantDropdown<T>
where
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
{
impl RenderOnce for EnumVariantDropdown {
fn render(self, window: &mut ui::Window, cx: &mut ui::App) -> impl gpui::IntoElement {
let current_value_label = self.labels[self
.variants
.iter()
.position(|v| *v == self.current_value)
.unwrap()];
let current_value_label = self.labels[self.selected_index];
let context_menu = window.use_keyed_state(current_value_label, cx, |window, cx| {
ContextMenu::new(window, cx, move |mut menu, _, _| {
for (&value, &label) in std::iter::zip(self.variants, self.labels) {
for (index, &label) in self.labels.iter().enumerate() {
let on_change = self.on_change.clone();
let current_value = self.current_value;
menu = menu.toggleable_entry(
if self.should_do_title_case {
label.to_title_case()
} else {
label.to_string()
},
value == current_value,
index == self.selected_index,
IconPosition::End,
None,
move |window, cx| {
on_change(value, window, cx);
on_change(index, window, cx);
},
);
}
+46 -27
View File
@@ -354,33 +354,52 @@ impl SettingFieldRenderer {
) -> Stateful<Div>
+ 'static,
) -> &mut Self {
let key = TypeId::of::<T>();
let renderer = Box::new(
move |settings_window: &SettingsWindow,
item: &SettingItem,
settings_file: SettingsUiFile,
metadata: Option<&SettingsFieldMetadata>,
sub_field: bool,
window: &mut Window,
cx: &mut Context<SettingsWindow>| {
let field = *item
.field
.as_ref()
.as_any()
.downcast_ref::<SettingField<T>>()
.unwrap();
renderer(
settings_window,
item,
field,
settings_file,
metadata,
sub_field,
window,
cx,
)
},
);
self.add_renderer_erased(
TypeId::of::<T>(),
Box::new(
move |settings_window: &SettingsWindow,
item: &SettingItem,
settings_file: SettingsUiFile,
metadata: Option<&SettingsFieldMetadata>,
sub_field: bool,
window: &mut Window,
cx: &mut Context<SettingsWindow>| {
let field = *item
.field
.as_ref()
.as_any()
.downcast_ref::<SettingField<T>>()
.unwrap();
renderer(
settings_window,
item,
field,
settings_file,
metadata,
sub_field,
window,
cx,
)
},
),
)
}
fn add_renderer_erased(
&mut self,
key: TypeId,
renderer: Box<
dyn Fn(
&SettingsWindow,
&SettingItem,
SettingsUiFile,
Option<&SettingsFieldMetadata>,
bool,
&mut Window,
&mut Context<SettingsWindow>,
) -> Stateful<Div>,
>,
) -> &mut Self {
self.renderers.borrow_mut().insert(key, renderer);
self
}