Reduce LLVM IR bloat (#63232)

Cuts serde monomorphization in settings_content: both parse entry points
now share one deserializer instantiation, and the flatten-heavy settings
structs deserialize through a small manual routing macro instead of
serde's Content buffering.
Parsed values seem to be unchanged; JSON schema and serialization
derives are untouched.

`cargo llvm-lines -p settings_content`:
Before (a90d3772f8): 2716889 lines, 92801 copies
After: 1203744 lines (-55.7%), 47122 copies (-49.2%)

With sccache disabled, `cargo clean` and `cargo build -p zed`:
Before: 132.8s wall, 1440.6s CPU, zed binary 1241791992 bytes
After: 128.8s wall (-3.0%), 1400.0s CPU (-2.8%), zed binary 1204044312
bytes (-3.0%, -37.7MB)

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov authored and GitHub committed 2026-08-26 14:33:14 +00:00
1 parent f36284a155
commit 5b8e551dc0
8 files changed
+233 -7

No files matched your search

Generated
+1
View File
@@ -16439,6 +16439,7 @@ dependencies = [
"serde",
"serde_json",
"serde_json_lenient",
"serde_path_to_error",
"settings_json",
"settings_macros",
"strum 0.28.0",
+1
View File
@@ -25,6 +25,7 @@ schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
serde_path_to_error.workspace = true
settings_json.workspace = true
settings_macros.workspace = true
strum.workspace = true
@@ -17,14 +17,14 @@ where
});
let mut deserializer = serde_json_lenient::Deserializer::from_str(json);
let value = T::deserialize(&mut deserializer);
let value = serde_path_to_error::deserialize::<_, T>(&mut deserializer);
let value = match value {
Ok(value) => value,
Err(error) => {
return (
None,
ParseStatus::Failed {
error: error.to_string(),
error: error.into_inner().to_string(),
},
);
}
@@ -64,6 +64,88 @@ where
pub trait FallibleOption: Default {}
impl<T> FallibleOption for Option<T> {}
macro_rules! flattened_deserialize {
($type_name:ty {
sections: { $($section:ident),* $(,)? },
options: { $($option_field:ident),* $(,)? },
defaults: { $($default_field:ident),* $(,)? } $(,)?
}) => {
impl $type_name {
#[doc(hidden)]
pub const NAMED_DESERIALIZE_KEYS: &'static [&'static str] = &[
$(stringify!($option_field),)*
$(stringify!($default_field),)*
];
}
impl<'de> serde::Deserialize<'de> for $type_name {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
let mut object =
serde_json::Map::<String, serde_json::Value>::deserialize(deserializer)?;
(|| -> Result<Self, serde_json::Error> {
$(
let $option_field =
$crate::fallible_options::take_option_field(
&mut object,
stringify!($option_field),
)?;
)*
$(
let $default_field =
$crate::fallible_options::take_default_field(
&mut object,
stringify!($default_field),
)?;
)*
let rest = serde_json::Value::Object(object);
Ok(Self {
$($section: $crate::fallible_options::section(&rest)?,)*
$($option_field,)*
$($default_field,)*
})
})()
.map_err(serde::de::Error::custom)
}
}
};
}
pub(crate) use flattened_deserialize;
pub(crate) fn take_option_field<T>(
object: &mut serde_json::Map<String, serde_json::Value>,
key: &str,
) -> Result<T, serde_json::Error>
where
T: serde::de::DeserializeOwned + FallibleOption,
{
match object.remove(key) {
None => Ok(T::default()),
Some(value) => deserialize(&value),
}
}
pub(crate) fn take_default_field<T>(
object: &mut serde_json::Map<String, serde_json::Value>,
key: &str,
) -> Result<T, serde_json::Error>
where
T: serde::de::DeserializeOwned + Default,
{
match object.remove(key) {
None => Ok(T::default()),
Some(value) => T::deserialize(&value),
}
}
pub(crate) fn section<T>(rest: &serde_json::Value) -> Result<T, serde_json::Error>
where
T: serde::de::DeserializeOwned,
{
T::deserialize(rest)
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
+7 -1
View File
@@ -34,7 +34,7 @@ pub struct ModifiersContent {
}
#[with_fallible_options]
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Clone, Default, PartialEq, Serialize, JsonSchema)]
pub struct AllLanguageSettingsContent {
/// The edit prediction settings.
pub edit_predictions: Option<EditPredictionSettingsContent>,
@@ -49,6 +49,12 @@ pub struct AllLanguageSettingsContent {
pub file_types: Option<FileTypeMap>,
}
crate::fallible_options::flattened_deserialize!(AllLanguageSettingsContent {
sections: { defaults },
options: { edit_predictions, file_types },
defaults: { languages },
});
impl merge_from::MergeFrom for AllLanguageSettingsContent {
fn merge_from(&mut self, other: &Self) {
self.file_types.merge_from(&other.file_types);
+9 -1
View File
@@ -40,7 +40,7 @@ impl RootUserSettings for ProjectSettingsContent {
}
#[with_fallible_options]
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[derive(Debug, PartialEq, Clone, Default, Serialize, JsonSchema, MergeFrom)]
pub struct ProjectSettingsContent {
#[serde(flatten)]
pub all_languages: AllLanguageSettingsContent,
@@ -87,6 +87,14 @@ pub struct ProjectSettingsContent {
pub disable_ai: Option<SaturatingBool>,
}
crate::fallible_options::flattened_deserialize!(ProjectSettingsContent {
sections: { all_languages, worktree },
options: {
terminal, context_server_timeout, load_direnv, git_hosting_providers, disable_ai,
},
defaults: { lsp, dap, context_servers },
});
/// When to scan content of linked directories.
#[derive(
Copy,
@@ -170,7 +170,7 @@ pub enum ReduceMotionMode {
}
#[with_fallible_options]
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[derive(Debug, PartialEq, Default, Clone, Serialize, JsonSchema, MergeFrom)]
pub struct SettingsContent {
#[serde(flatten)]
pub project: ProjectSettingsContent,
@@ -394,6 +394,26 @@ impl SettingsContent {
}
}
fallible_options::flattened_deserialize!(SettingsContent {
sections: { project, theme, extension, workspace, editor, remote },
options: {
file_finder, git_panel, tabs, tab_bar, status_bar, preview_tabs, agent, agent_servers,
audio, auto_update, base_keymap, collaboration_panel, debugger, diagnostics, git,
global_lsp_settings, image_viewer, markdown_preview, repl, helix_mode, hide_mouse,
journal, log, line_indicator_format, language_models, outline_panel, project_panel,
node, proxy, reduce_motion, server_url, credentials_url, session, telemetry, terminal,
title_bar, vim_mode, calls, which_key, vim, modeline_lines, feature_flags,
instrumentation,
},
defaults: {},
});
fallible_options::flattened_deserialize!(UserSettingsContent {
sections: { content, release_channel_overrides, platform_overrides },
options: {},
defaults: { profiles },
});
// These impls are there to optimize builds by avoiding monomorphization downstream. Yes, they're repetitive, but using default impls
// break the optimization, for whatever reason.
pub trait RootUserSettings: Sized + DeserializeOwned {
@@ -469,7 +489,7 @@ pub struct SettingsProfile {
}
#[with_fallible_options]
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[derive(Debug, Default, PartialEq, Clone, Serialize, JsonSchema, MergeFrom)]
pub struct UserSettingsContent {
#[serde(flatten)]
pub content: Box<SettingsContent>,
+13 -1
View File
@@ -66,7 +66,7 @@ pub struct ProjectTerminalSettingsContent {
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[derive(Clone, Debug, PartialEq, Default, Serialize, JsonSchema, MergeFrom)]
pub struct TerminalSettingsContent {
#[serde(flatten)]
pub project: ProjectTerminalSettingsContent,
@@ -195,6 +195,18 @@ pub struct TerminalSettingsContent {
pub bell: Option<TerminalBell>,
}
crate::fallible_options::flattened_deserialize!(TerminalSettingsContent {
sections: { project },
options: {
font_size, font_family, font_fallbacks, line_height, font_features, font_weight,
cursor_shape, blinking, alternate_scroll, option_as_meta, copy_on_select,
keep_selection_on_copy, open_links_in_mouse_mode, button, dock, starts_open, flexible,
default_width, default_height, max_scroll_history_lines, scroll_multiplier, toolbar,
scrollbar, minimum_contrast, show_count_badge, bell,
},
defaults: {},
});
/// Shell configuration to open the terminal with.
#[derive(
Clone,
@@ -0,0 +1,96 @@
use std::collections::BTreeMap;
const KNOWN_SHADOWED_KEYS: &[(&str, &str, &str)] = &[("SettingsContent", "terminal", "project")];
#[test]
fn no_key_collisions_between_named_fields_and_flattened_sections() {
check(
"SettingsContent",
settings_content::SettingsContent::NAMED_DESERIALIZE_KEYS,
vec![
section::<settings_content::ProjectSettingsContent>("project"),
section::<settings_content::ThemeSettingsContent>("theme"),
section::<settings_content::ExtensionSettingsContent>("extension"),
section::<settings_content::WorkspaceSettingsContent>("workspace"),
section::<settings_content::EditorSettingsContent>("editor"),
section::<settings_content::RemoteSettingsContent>("remote"),
],
);
check(
"UserSettingsContent",
settings_content::UserSettingsContent::NAMED_DESERIALIZE_KEYS,
vec![
section::<settings_content::SettingsContent>("content"),
section::<settings_content::ReleaseChannelOverrides>("release_channel_overrides"),
section::<settings_content::PlatformOverrides>("platform_overrides"),
],
);
check(
"ProjectSettingsContent",
settings_content::ProjectSettingsContent::NAMED_DESERIALIZE_KEYS,
vec![
section::<settings_content::AllLanguageSettingsContent>("all_languages"),
section::<settings_content::WorktreeSettingsContent>("worktree"),
],
);
check(
"AllLanguageSettingsContent",
settings_content::AllLanguageSettingsContent::NAMED_DESERIALIZE_KEYS,
vec![section::<settings_content::LanguageSettingsContent>(
"defaults",
)],
);
check(
"TerminalSettingsContent",
settings_content::TerminalSettingsContent::NAMED_DESERIALIZE_KEYS,
vec![section::<settings_content::ProjectTerminalSettingsContent>(
"project",
)],
);
}
fn property_names<T: schemars::JsonSchema>() -> Vec<String> {
let schema = schemars::schema_for!(T);
let value = serde_json::to_value(&schema).unwrap();
value
.get("properties")
.and_then(|properties| properties.as_object())
.map(|object| object.keys().cloned().collect())
.unwrap_or_default()
}
fn section<T: schemars::JsonSchema>(name: &'static str) -> (&'static str, Vec<String>) {
(name, property_names::<T>())
}
fn check(container: &str, named_keys: &[&str], sections: Vec<(&'static str, Vec<String>)>) {
let mut owners = BTreeMap::<&str, Vec<&str>>::new();
for (section_name, keys) in &sections {
for key in keys {
owners.entry(key).or_default().push(section_name);
}
}
for named_key in named_keys {
let Some(shadowed_sections) = owners.get(named_key) else {
continue;
};
let known = shadowed_sections.iter().all(|section_name| {
KNOWN_SHADOWED_KEYS.contains(&(container, named_key, section_name))
});
assert!(
known,
"{container}: named field `{named_key}` shadows the same key in flattened \
section(s) {shadowed_sections:?}: the named field wins and the section never \
sees the key; if intended, add it to KNOWN_SHADOWED_KEYS"
);
}
for (key, sections_with_key) in owners {
assert_eq!(
sections_with_key.len(),
1,
"{container}: key `{key}` is provided by multiple flattened sections: \
{sections_with_key:?}"
);
}
}