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:
1 parent
f36284a155
commit
5b8e551dc0
8 files changed
+233
-7
No files matched your search
Generated
+1
@@ -16439,6 +16439,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"serde_path_to_error",
|
||||
"settings_json",
|
||||
"settings_macros",
|
||||
"strum 0.28.0",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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 §ions {
|
||||
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:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user