Skip to content

Commit be02dc5

Browse files
committed
perf(P2): BMI copy_if_different — prevent cascade rebuilds
GCC always updates the .gcm (BMI) file's timestamp even when the module interface hasn't changed. This causes all downstream modules to be recompiled unnecessarily. Fix: the cxx_module rule now backs up the BMI before compilation, and if the new BMI is byte-identical to the backup, restores the old file (preserving its timestamp). Combined with restat = 1 in the per-file dyndep entries, ninja skips downstream modules when only the implementation changed. This means modifying a function body without changing the module interface no longer triggers a cascade rebuild of all importers.
1 parent c734bcd commit be02dc5

1 file changed

Lines changed: 27 additions & 2 deletions

File tree

src/build/ninja_backend.cppm

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,27 @@ std::string emit_ninja_string(const BuildPlan& plan) {
193193
append(" description = DYNDEP $out\n");
194194
append(" restat = 1\n\n");
195195

196+
// P2: cxx_module preserves BMI timestamps when interface is unchanged.
197+
// GCC always updates the .gcm timestamp even if content is identical.
198+
// We backup the BMI before compilation, compile, then restore the old
199+
// file if content is byte-identical. Combined with restat = 1 in the
200+
// dyndep file, this prevents cascading rebuilds when only the module
201+
// implementation changed (not the interface).
202+
//
203+
// $bmi_out is set per build edge to the BMI path (gcm.cache/<module>.gcm).
204+
// If $bmi_out is empty (no module provided), we just compile normally.
196205
append("rule cxx_module\n");
197-
append(" command = $cxx $cxxflags -c $in -o $out\n");
206+
append(" command = "
207+
"if [ -n \"$bmi_out\" ] && [ -f \"$bmi_out\" ]; then "
208+
"cp -p \"$bmi_out\" \"$bmi_out.bak\"; "
209+
"fi && "
210+
"$cxx $cxxflags -c $in -o $out && "
211+
"if [ -n \"$bmi_out\" ] && [ -f \"$bmi_out.bak\" ] && "
212+
"cmp -s \"$bmi_out\" \"$bmi_out.bak\"; then "
213+
"mv \"$bmi_out.bak\" \"$bmi_out\"; "
214+
"else "
215+
"rm -f \"$bmi_out.bak\"; "
216+
"fi\n");
198217
append(" description = MOD $out\n");
199218
if (dyndep)
200219
append(" restat = 1\n");
@@ -307,6 +326,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
307326

308327
// ── Phase 3: compile edges with per-file dyndep. ────────────────
309328
// Each compile edge references its OWN .dd file instead of a global one.
329+
// P2: module compile edges get a $bmi_out variable for BMI preservation.
310330
for (auto& cu : plan.compileUnits) {
311331
std::string rule = pick_rule(cu.source);
312332

@@ -320,7 +340,12 @@ std::string emit_ninja_string(const BuildPlan& plan) {
320340
auto it = ddi_to_dd.find(ddi);
321341
if (it != ddi_to_dd.end()) {
322342
out_line += " | " + it->second;
323-
out_line += "\n dyndep = " + it->second + "\n";
343+
out_line += "\n dyndep = " + it->second;
344+
// P2: set bmi_out for the copy_if_different logic in cxx_module.
345+
if (cu.providesModule) {
346+
out_line += "\n bmi_out = " + bmi_path(*cu.providesModule);
347+
}
348+
out_line += "\n";
324349
} else {
325350
out_line += "\n";
326351
}

0 commit comments

Comments
 (0)