Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/8439-size-opt-default-on.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make `-Os` the default native optimization level. Measured on the quiet bench mini under the sweep's bench lock, `-Os` costs no runtime speed on the benchmark corpus — every delta across `churn`, `cycles`, `fib40`, `interp`, `iso_miss` and `tree_wide` was within 0.8% and none disjoint from the `-O3` arm — while #8418 measured a further 346.7 MiB off a 4,743-module generated bundle. `PERRY_LL_SIZE_OPT=0` (or `off`/`false`/`no`) restores `-O3`.
28 changes: 21 additions & 7 deletions crates/perry-codegen/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,24 @@ fn cpu_tuning_arg_for(
}
}

/// Size optimization is **on by default**. Measured on the quiet bench mini
/// under the sweep's bench lock, `-Os` costs no measurable runtime speed: across
/// `churn`, `cycles`, `fib40`, `interp`, `iso_miss` and `tree_wide` every delta
/// was within 0.8% and none was disjoint from the `-O3` arm's samples. What it
/// buys on a dense generated bundle is large — #8418 measured a further
/// 346.7 MiB off a 4,743-module executable beyond the structural wins.
///
/// So the trade is compile time (about +21% on that bundle) against shipped
/// binary size, with runtime speed unaffected. Smaller artifacts for every user
/// are worth a developer-side build cost, so unset means enabled.
///
/// `PERRY_LL_SIZE_OPT=0` (or `off`/`false`/`no`) restores `-O3` for bisection or
/// for a build that would rather have the compile time back.
fn size_optimization_requested(value: Option<&str>) -> bool {
value
.map(str::trim)
.map(str::to_ascii_lowercase)
.is_some_and(|value| matches!(value.as_str(), "1" | "true" | "on" | "yes"))
match value.map(str::trim).map(str::to_ascii_lowercase) {
None => true,
Some(value) => !matches!(value.as_str(), "0" | "false" | "off" | "no"),
}
}

fn build_clang_compile_plan(
Expand All @@ -330,9 +343,10 @@ fn build_clang_compile_plan(
cpu_tuning_arg_for(requested_cpu.as_deref(), target_triple, &effective_target);
let stderr_remarks_path = PathBuf::from(format!("{}.clang-stderr", obj_path.display()));

// Perry defaults to speed-optimized native output. Generated-bundle users
// can explicitly trade runtime speed for artifact size with
// PERRY_LL_SIZE_OPT; there is no module-size-driven policy change.
// Perry defaults to SIZE-optimized native output: `-Os` measured no runtime
// cost on the benchmark corpus (see `size_optimization_requested`), and it
// materially shrinks dense generated bundles. `PERRY_LL_SIZE_OPT=0` restores
// `-O3`. There is no module-size-driven policy change.
let size_opt = env::var("PERRY_LL_SIZE_OPT").ok();
let opt_flag = if size_optimization_requested(size_opt.as_deref()) {
"-Os"
Expand Down
27 changes: 18 additions & 9 deletions crates/perry-codegen/src/linker_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,9 @@ fn compile_plan_records_effective_target_and_native_tuning() {
false,
);
assert!(plan.clang_args.contains(&"-fno-math-errno".to_string()));
// Native compilation defaults to speed-optimized -O3.
assert!(plan.clang_args.contains(&"-O3".to_string()));
// Native compilation defaults to size-optimized -Os (see
// `size_optimization_requested`); `PERRY_LL_SIZE_OPT=0` restores -O3.
assert!(plan.clang_args.contains(&"-Os".to_string()));
assert!(plan.clang_args.contains(&"-target".to_string()));
assert!(plan.analysis_clang_args.contains(&"-target".to_string()));
// Apple aarch64 pins `apple-m1` rather than `native`: the decision to emit
Expand All @@ -185,11 +186,15 @@ fn compile_plan_records_effective_target_and_native_tuning() {
}

#[test]
fn compile_plan_defaults_to_o3() {
fn compile_plan_defaults_to_os() {
// Module size is deliberately absent from the compile plan: Perry's
// runtime optimization contract does not change for large generated IR.
// Scalability is handled by codegen-unit partitioning and structured
// outlining before LLVM sees the function bodies.
//
// The default optimization level is `-Os`: measured on the quiet bench mini,
// `-Os` costs no runtime speed on the benchmark corpus while materially
// shrinking dense generated bundles. `PERRY_LL_SIZE_OPT=0` restores `-O3`.
let plan = build_clang_compile_plan(
PathBuf::from("clang"),
PathBuf::from("/tmp/input.ll"),
Expand All @@ -198,20 +203,24 @@ fn compile_plan_defaults_to_o3() {
false,
false,
);
assert!(plan.clang_args.contains(&"-O3".to_string()));
assert!(!plan.clang_args.contains(&"-Os".to_string()));
assert!(plan.clang_args.contains(&"-Os".to_string()));
assert!(!plan.clang_args.contains(&"-O3".to_string()));
assert!(!plan.clang_args.contains(&"-O0".to_string()));
}

#[test]
fn size_optimization_flag_is_explicit_and_truthy() {
for enabled in ["1", "true", "TRUE", " on ", "yes"] {
fn size_optimization_is_on_unless_explicitly_disabled() {
// Unset means enabled — the default flipped once `-Os` was measured to cost
// no runtime speed on the benchmark corpus.
assert!(size_optimization_requested(None));
for enabled in ["1", "true", "TRUE", " on ", "yes", "", "anything-else"] {
assert!(size_optimization_requested(Some(enabled)), "{enabled}");
}
for disabled in ["", "0", "false", "off", "no", "anything-else"] {
// Only an explicit negative restores `-O3`, for bisection or to buy back
// compile time.
for disabled in ["0", "false", "off", "no", "OFF", " 0 "] {
assert!(!size_optimization_requested(Some(disabled)), "{disabled}");
}
assert!(!size_optimization_requested(None));
}

#[test]
Expand Down
Loading