Skip to content

Commit 6a868c4

Browse files
committed
feat: extend runtime memory admission to compile edges
1 parent 4d98dff commit 6a868c4

5 files changed

Lines changed: 183 additions & 27 deletions

File tree

‎docs/04-mcpp-toml.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,19 @@ with two behaviours has to be.
937937

938938
`bmi_schedule` decides when importers are unblocked.
939939

940+
`memory_threshold_percent` is a machine setting in `~/.mcpp/config.toml`, not a
941+
project-manifest field. It defaults to `80` and limits the compiler pool using
942+
the available memory observed at build start. `MCPP_MEMORY_THRESHOLD_PERCENT`
943+
overrides it for one invocation. Ordinary C/C++ edges and both detached GCC and
944+
two-phase Clang module edges use the same admission check; unsupported memory
945+
metrics leave the configured job/compiler cap in force.
946+
947+
```toml
948+
# ~/.mcpp/config.toml
949+
[build]
950+
memory_threshold_percent = 80
951+
```
952+
940953
| value | |
941954
|---|---|
942955
| `"auto"` | **the default, and it currently means OFF** |

‎docs/zh/04-mcpp-toml.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,18 @@ bmi_schedule = "off" # auto (default, = off) | on | off
878878

879879
`bmi_schedule` 决定 importer 何时被解除阻塞。
880880

881+
`memory_threshold_percent` 是 `~/.mcpp/config.toml` 中的本机设置,不是
882+
工程 manifest 字段。默认值为 `80`,它依据构建开始时观测到的可用内存限制
883+
编译器池;单次调用可以用 `MCPP_MEMORY_THRESHOLD_PERCENT` 覆盖。普通
884+
C/C++ 编译边、GCC 分离式模块边和 Clang 两阶段模块边使用同一条准入检查;
885+
不受支持的内存指标不会取消已经配置的 jobs/编译器上限。
886+
887+
```toml
888+
# ~/.mcpp/config.toml
889+
[build]
890+
memory_threshold_percent = 80
891+
```
892+
881893
| 取值 | |
882894
|---|---|
883895
| `"auto"` | **默认值,目前意味着关闭** |

‎src/build/ninja_backend.cppm‎

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,6 +1839,21 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements,
18391839
append(std::format(" rspfile_content ={}{}\n",
18401840
msvcDeps ? " " + std::string(kUtf8ByteOrderMark) : "", payload));
18411841
};
1842+
// Keep ordinary compile edges inside the same admission controller as the
1843+
// detached GCC path. The compiler command remains argv-based; only the
1844+
// existing shell-free depfile filter is moved into the wrapper.
1845+
const bool memoryAdmission = plan.memoryCompilerCap > 0
1846+
&& plan.memoryBaselineBytes != 0;
1847+
auto admission_prefix = [&]() {
1848+
return std::format("$mcpp compile-admit --sem .mcpp-sched --cap {}"
1849+
" --mem-baseline {} --mem-threshold {}",
1850+
plan.memoryCompilerCap, plan.memoryBaselineBytes,
1851+
plan.memoryThresholdPercent);
1852+
};
1853+
auto admission_dep = [&]() {
1854+
return needsGnuModuleFilter
1855+
? std::string(" --dep-raw $out.d.raw --dep-out $out.d") : std::string{};
1856+
};
18421857

18431858
// Tell the driver, every time, that this TU is a module interface.
18441859
//
@@ -1957,26 +1972,39 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements,
19571972
append("rule cxx_precompile\n");
19581973
if constexpr (mcpp::platform::is_windows) {
19591974
const std::string payload = " $local_includes";
1960-
append(std::format(
1961-
" command = {}$cxx{} $cxxflags $unit_cxxflags{}{} {}$in {}$out\n",
1962-
win_depfile_filter, rsp_ref(payload), traits.bmiOnlyFlags,
1963-
module_src_flags, mmd_flag, dial.outputObjPrefix));
1975+
if (memoryAdmission)
1976+
append(std::format(
1977+
" command = {} -- $cxx{} $cxxflags $unit_cxxflags{}{} {}$in {}$out\n",
1978+
admission_prefix(), rsp_ref(payload), traits.bmiOnlyFlags,
1979+
module_src_flags, mmd_flag, dial.outputObjPrefix));
1980+
else
1981+
append(std::format(
1982+
" command = {}$cxx{} $cxxflags $unit_cxxflags{}{} {}$in {}$out\n",
1983+
win_depfile_filter, rsp_ref(payload), traits.bmiOnlyFlags,
1984+
module_src_flags, mmd_flag, dial.outputObjPrefix));
19641985
append_rspfile(payload);
19651986
append_cxx_deps();
19661987
} else {
19671988
// Same bak / bmi-equal / restore dance as cxx_module, and for the
19681989
// same reason: ninja's `restat` compares the output's MTIME, and a
19691990
// compiler that rewrites a byte-identical BMI still moves it. What
19701991
// suppresses the cascade is putting the old file back.
1971-
append(std::format(
1972-
" command = "
1973-
"if [ -f \"$out\" ]; then cp -p \"$out\" \"$out.bak\"; fi && "
1974-
"$cxx $local_includes $cxxflags $unit_cxxflags{}{} {}$in {}$out && "
1975-
"if [ -f \"$out.bak\" ] && $mcpp bmi-equal \"$out\" \"$out.bak\"; then "
1976-
"mv \"$out.bak\" \"$out\"; "
1977-
"else rm -f \"$out.bak\"; fi\n",
1978-
traits.bmiOnlyFlags, module_src_flags, mmd_flag,
1979-
dial.outputObjPrefix));
1992+
if (memoryAdmission)
1993+
append(std::format(" command = {}{} -- $cxx $local_includes"
1994+
" $cxxflags $unit_cxxflags{}{} {}$in {}$out\n",
1995+
admission_prefix(), admission_dep(),
1996+
traits.bmiOnlyFlags, module_src_flags, mmd_flag,
1997+
dial.outputObjPrefix));
1998+
else
1999+
append(std::format(
2000+
" command = "
2001+
"if [ -f \"$out\" ]; then cp -p \"$out\" \"$out.bak\"; fi && "
2002+
"$cxx $local_includes $cxxflags $unit_cxxflags{}{} {}$in {}$out && "
2003+
"if [ -f \"$out.bak\" ] && $mcpp bmi-equal \"$out\" \"$out.bak\"; then "
2004+
"mv \"$out.bak\" \"$out\"; "
2005+
"else rm -f \"$out.bak\"; fi\n",
2006+
traits.bmiOnlyFlags, module_src_flags, mmd_flag,
2007+
dial.outputObjPrefix));
19802008
append_cxx_deps();
19812009
}
19822010
append(" description = BMI $out\n");
@@ -1989,15 +2017,26 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements,
19892017
append("rule cxx_module_object\n");
19902018
if constexpr (mcpp::platform::is_windows) {
19912019
const std::string payload = " $local_includes";
1992-
append(std::format(" command = {}$cxx{} $cxxflags $unit_cxxflags{} {}{}\n",
1993-
win_depfile_filter, rsp_ref(payload), module_src_flags,
1994-
mmd_flag, compile_tail));
2020+
if (memoryAdmission)
2021+
append(std::format(" command = {}{} -- $cxx{} $cxxflags $unit_cxxflags{} {}{}\n",
2022+
admission_prefix(), admission_dep(), rsp_ref(payload),
2023+
module_src_flags, mmd_flag, compile_tail));
2024+
else
2025+
append(std::format(" command = {}$cxx{} $cxxflags $unit_cxxflags{} {}{}\n",
2026+
win_depfile_filter, rsp_ref(payload), module_src_flags,
2027+
mmd_flag, compile_tail));
19952028
append_rspfile(payload);
19962029
append_cxx_deps();
19972030
} else {
1998-
append(std::format(
1999-
" command = $cxx $local_includes $cxxflags $unit_cxxflags{} {}{}{}\n",
2000-
module_src_flags, mmd_flag, compile_tail, mmd_filter));
2031+
if (memoryAdmission)
2032+
append(std::format(" command = {}{} -- $cxx $local_includes"
2033+
" $cxxflags $unit_cxxflags{} {}{}\n",
2034+
admission_prefix(), admission_dep(),
2035+
module_src_flags, mmd_flag, compile_tail));
2036+
else
2037+
append(std::format(
2038+
" command = $cxx $local_includes $cxxflags $unit_cxxflags{} {}{}{}\n",
2039+
module_src_flags, mmd_flag, compile_tail, mmd_filter));
20012040
append_cxx_deps();
20022041
}
20032042
append(" description = OBJ $out\n");
@@ -2007,14 +2046,24 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements,
20072046
append("rule cxx_object\n");
20082047
if constexpr (mcpp::platform::is_windows) {
20092048
const std::string payload = " $local_includes";
2010-
append(std::format(" command = {}$cxx{} $cxxflags $unit_cxxflags {}{}\n",
2011-
win_depfile_filter, rsp_ref(payload), mmd_flag,
2012-
compile_tail));
2049+
if (memoryAdmission)
2050+
append(std::format(" command = {}{} -- $cxx{} $cxxflags $unit_cxxflags {}{}\n",
2051+
admission_prefix(), admission_dep(), rsp_ref(payload),
2052+
mmd_flag, compile_tail));
2053+
else
2054+
append(std::format(" command = {}$cxx{} $cxxflags $unit_cxxflags {}{}\n",
2055+
win_depfile_filter, rsp_ref(payload), mmd_flag, compile_tail));
20132056
append_rspfile(payload);
20142057
} else {
2015-
append(std::format(
2016-
" command = $cxx $local_includes $cxxflags $unit_cxxflags {}{}{}\n",
2017-
mmd_flag, compile_tail, mmd_filter));
2058+
if (memoryAdmission)
2059+
append(std::format(" command = {}{} -- $cxx $local_includes"
2060+
" $cxxflags $unit_cxxflags {}{}\n",
2061+
admission_prefix(), admission_dep(),
2062+
mmd_flag, compile_tail));
2063+
else
2064+
append(std::format(
2065+
" command = $cxx $local_includes $cxxflags $unit_cxxflags {}{}{}\n",
2066+
mmd_flag, compile_tail, mmd_filter));
20182067
}
20192068
append(" description = OBJ $out\n");
20202069
append_cxx_deps();
@@ -2025,8 +2074,13 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements,
20252074
if (need_c_rule) {
20262075
append("rule c_object\n");
20272076
const std::string payload = " $local_includes";
2028-
append(std::format(" command = $cc{} $cflags $unit_cflags {}{}\n",
2029-
rsp_ref(payload), c_mmd_flag, compile_tail));
2077+
if (memoryAdmission)
2078+
append(std::format(" command = {} -- $cc{} $cflags $unit_cflags {}{}\n",
2079+
admission_prefix(), rsp_ref(payload), c_mmd_flag,
2080+
compile_tail));
2081+
else
2082+
append(std::format(" command = $cc{} $cflags $unit_cflags {}{}\n",
2083+
rsp_ref(payload), c_mmd_flag, compile_tail));
20302084
append_rspfile(payload);
20312085
append(" description = CC $out\n");
20322086
append_cxx_deps();

‎src/build/schedule/detach_codegen.cppm‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export module mcpp.build.schedule.detach_codegen;
8383
import std;
8484
import mcpp.platform.process;
8585
import mcpp.platform.capacity;
86+
import mcpp.build.depfile;
8687
import mcpp.build.stage;
8788

8889
export namespace mcpp::build::schedule::detach {
@@ -151,6 +152,17 @@ int supervise(const std::filesystem::path& slot,
151152
// must not pass on.
152153
int await_unit(const std::filesystem::path& slot, const std::filesystem::path& object);
153154

155+
// Run an ordinary compiler edge while holding the same admission token used
156+
// by detached compilation. The command is argv-based so compiler arguments do
157+
// not acquire a second quoting language at the admission boundary.
158+
int run_admitted(const std::vector<std::string>& command,
159+
const std::filesystem::path& semaphore,
160+
int maxCompilers,
161+
unsigned long long memoryBaselineBytes,
162+
int memoryThresholdPercent,
163+
const std::filesystem::path& depFrom = {},
164+
const std::filesystem::path& depTo = {});
165+
154166
} // namespace mcpp::build::schedule::detach
155167

156168
// ---------------------------------------------------------------------------
@@ -690,4 +702,35 @@ int await_unit(const std::filesystem::path& slot, const std::filesystem::path& o
690702
return 0;
691703
}
692704

705+
int run_admitted(const std::vector<std::string>& command,
706+
const std::filesystem::path& semaphore,
707+
int maxCompilers,
708+
unsigned long long memoryBaselineBytes,
709+
int memoryThresholdPercent,
710+
const std::filesystem::path& depFrom,
711+
const std::filesystem::path& depTo) {
712+
if (command.empty()) return 2;
713+
const auto token = acquire_token(semaphore, maxCompilers,
714+
memoryBaselineBytes, memoryThresholdPercent);
715+
bool timedOut = false;
716+
const int rc = mcpp::platform::process::run_exec_deadline(
717+
command, {}, std::chrono::milliseconds(0), &timedOut);
718+
if (!token.empty()) {
719+
std::error_code ec;
720+
std::filesystem::remove(token, ec);
721+
}
722+
if (rc != 0 || depFrom.empty() || depTo.empty()) return rc;
723+
724+
std::ifstream in(depFrom, std::ios::binary);
725+
if (!in) return 1;
726+
std::string raw{std::istreambuf_iterator<char>{in}, {}};
727+
std::ofstream out(depTo, std::ios::binary | std::ios::trunc);
728+
if (!out) return 1;
729+
out << mcpp::build::depfile::first_record(raw);
730+
if (!out) return 1;
731+
std::error_code ec;
732+
std::filesystem::remove(depFrom, ec);
733+
return 0;
734+
}
735+
693736
} // namespace mcpp::build::schedule::detach

‎src/cli.cppm‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import mcpp.platform; // is_windows — the PATH separator of `__
3535
import mcpp.platform.env; // --offline → MCPP_OFFLINE
3636
import mcpp.platform.process; // __action-stamp runs the checked command
3737
import mcpp.platform.fs; // __action-stamp writes its stamps
38+
import mcpp.build.schedule.detach_codegen;
3839
import mcpp.build.progress; // __action reports its start
3940
import mcpp.platform.runtime_search; // linker-wrapper path-injection opt-out
4041
import mcpp.ui;
@@ -1136,6 +1137,39 @@ int run(int argc, char** argv) {
11361137
return 0;
11371138
}
11381139

1140+
if (std::string_view(argv[1]) == "compile-admit") {
1141+
std::string sem, capText, baselineText, thresholdText, depRaw, depOut;
1142+
int i = 2;
1143+
for (; i < argc && std::string_view(argv[i]) != "--"; ++i) {
1144+
const std::string_view a = argv[i];
1145+
if (i + 1 >= argc) return 2;
1146+
if (a == "--sem") sem = argv[++i];
1147+
else if (a == "--cap") capText = argv[++i];
1148+
else if (a == "--mem-baseline") baselineText = argv[++i];
1149+
else if (a == "--mem-threshold") thresholdText = argv[++i];
1150+
else if (a == "--dep-raw") depRaw = argv[++i];
1151+
else if (a == "--dep-out") depOut = argv[++i];
1152+
else {
1153+
std::println(stderr, "error: compile-admit: unknown option '{}'", a);
1154+
return 2;
1155+
}
1156+
}
1157+
if (i + 1 >= argc || sem.empty()) {
1158+
std::println(stderr, "error: compile-admit requires --sem and -- <compiler argv...>");
1159+
return 2;
1160+
}
1161+
int cap = 0, threshold = 80;
1162+
unsigned long long baseline = 0;
1163+
std::from_chars(capText.data(), capText.data() + capText.size(), cap);
1164+
std::from_chars(baselineText.data(), baselineText.data() + baselineText.size(), baseline);
1165+
std::from_chars(thresholdText.data(), thresholdText.data() + thresholdText.size(), threshold);
1166+
std::vector<std::string> command;
1167+
for (++i; i < argc; ++i) command.emplace_back(argv[i]);
1168+
return mcpp::build::schedule::detach::run_admitted(
1169+
command, std::filesystem::path{sem}, cap, baseline, threshold,
1170+
std::filesystem::path{depRaw}, std::filesystem::path{depOut});
1171+
}
1172+
11391173
if (std::string_view(argv[1]) == "__action-stamp"
11401174
|| std::string_view(argv[1]) == "__action") {
11411175
const bool named = std::string_view(argv[1]) == "__action";

0 commit comments

Comments
 (0)