-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathmodel.cppm
More file actions
350 lines (319 loc) · 17.1 KB
/
Copy pathmodel.cppm
File metadata and controls
350 lines (319 loc) · 17.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
// mcpp.toolchain.model - stable toolchain data model.
export module mcpp.toolchain.model;
import std;
import mcpp.toolchain.triple;
export namespace mcpp::toolchain {
enum class CompilerId { Unknown, GCC, Clang, MSVC };
// WHERE A COMPILER CAME FROM. Two values, and there will only ever be two.
//
// `Managed` an xlings payload the manifest named. The answer is in the
// manifest; the machine only decides whether it has been
// downloaded yet.
// `SystemMsvc` probed on this machine. The answer depends on what happens to
// be installed here.
//
// THE SECOND ONE IS NOT A GENERAL CAPABILITY, and reading it as one is a
// mistake this comment exists to prevent. mcpp is built on xlings, a
// user-space OS, and the whole design is to drive host dependencies to a
// minimum — there is no `gcc@system`, deliberately. `msvc@system` is a
// concession to ONE platform: Visual Studio is very often already installed
// and cannot always be redistributed, so refusing to use it would cost more
// than it buys. (The bare, family-less `system` spec is a different thing
// again: a deliberate escape hatch to the PATH compiler.)
//
// Lives in the data model rather than in msvc.cppm so the toolchain-spec
// side (registry) and the located-compiler side (msvc) name the SAME axis.
// They used to answer it independently, in 26 scattered branches, which is
// how "is this managed" and "is this a system MSVC" came to be asked with
// different predicates in the same build.
enum class Origin { Managed, SystemMsvc };
inline std::string_view origin_name(Origin o) {
return o == Origin::SystemMsvc ? "system" : "managed";
}
// Fine-grained sysroot paths derived from xpkgs payloads.
// When populated, flags are assembled from these paths instead of --sysroot.
// One environment variable a toolchain needs at tool-invocation time.
struct EnvVar {
std::string key;
std::string value;
};
// The runtime this toolchain builds AGAINST, in xlings's spelling
// ("glibc@2.39"). Resolved before payload probing from the root-selected
// RuntimeBinding snapshot. Empty means no libc payload applies, and payload
// resolution then declines rather than guessing.
struct PayloadPaths {
std::filesystem::path glibcInclude; // glibc headers (features.h, bits/)
std::filesystem::path glibcLib; // glibc runtime (libc.so, crt*.o, ld-linux)
std::filesystem::path linuxInclude; // linux kernel headers (linux/, asm/)
};
struct Toolchain {
CompilerId compiler = CompilerId::Unknown;
std::string version; // "15.1.0"
std::filesystem::path binaryPath;
std::string driverIdent; // normalized --version output
std::string targetTriple; // "x86_64-linux-gnu"
// The runtime this toolchain builds AGAINST, in xlings's own spelling
// ("glibc@2.39"). Resolved BEFORE payload probing from the root-selected
// RuntimeBinding snapshot.
//
// Empty is a refusal, not a default: payload resolution declines rather
// than picking a libc by directory order. That guess is what let the
// compile side and the artifact's interpreter name different glibc
// versions, with nothing in the resulting binary looking wrong until it
// loaded a library built against the other one.
std::string runtimeBinding;
// Hash of the complete immutable RuntimeBinding snapshot (selection,
// provider, runtime and environment), not merely its libc label. Two
// named SubOS environments may both say glibc@2.39 and still require
// different loader/driver contracts, so the build cache must separate
// them. Empty keeps compatibility for low-level detector unit tests.
std::string runtimeContractHash;
std::string stdlibId; // "libstdc++"
std::string stdlibVersion;
std::filesystem::path stdModuleSource; // bits/std.cc / std.cppm
std::filesystem::path stdCompatSource; // bits/std_compat.cc / std.compat.cppm
std::filesystem::path sysroot; // -print-sysroot output (or empty)
std::optional<PayloadPaths> payloadPaths; // fine-grained sysroot from xpkgs
std::vector<std::filesystem::path> compilerRuntimeDirs; // LD_LIBRARY_PATH for private tools
std::vector<std::filesystem::path> linkRuntimeDirs; // -L/-rpath dirs for produced binaries
// Environment the toolchain's tools need when invoked (set on the ninja
// process, inherited by compiler/linker children). Empty for GCC/Clang
// (their LD_LIBRARY_PATH need goes through compilerRuntimeDirs); the
// MSVC backend fills INCLUDE/LIB/PATH here (design §5.1).
// (Own struct, not std::pair — GCC 16 modules choke on a std::pair
// member added to this exported class: "failed to load pendings".)
std::vector<EnvVar> envOverrides;
bool hasImportStd = false;
// Lowest -std= level this toolchain can build the std module at. 0 = the
// provider did not say, callers fall back to the plain hasImportStd
// question. Filled next to hasImportStd by each provider, because "which
// std module does this compiler ship" is provider-local knowledge — a
// central table would derive the same decision in a second place.
// GCC/libc++ answer 20; MSVC answers 20 from cl 19.38 (VS 2022 17.8,
// microsoft/STL#3977) and 23 below that.
int importStdMinLevel = 0;
// The Windows SDK this toolchain compiles and links against
// ("10.0.26100.0"), once resolved. Empty everywhere else — and empty on
// Windows too when no SDK was found, which detection tolerates so that
// toolchain SELECTION still works on an SDK-less box.
//
// Recorded rather than re-derived because two different questions read it
// and they must not be able to disagree: the compile environment
// (INCLUDE/LIB) and the runtime identity (`ucrt@<version>`, which enters
// the runtime contract hash). Deriving the second from a second search is
// how the SDK came to have no identity in the first place.
std::string windowsSdkVersion;
// Something about HOW this toolchain was resolved that the user has to be
// told, but which is not a failure. Non-empty ⇒ the caller MUST surface it.
//
// Today's only producer is the Windows SDK axis: a managed toolset binds
// the SDK that came with it, so a `WindowsSdkDir` in the environment is
// ignored — and an override that is ignored SILENTLY is indistinguishable
// from one that did not exist. That is the failure shape this whole round
// kept finding: "it did not happen" and "it succeeded" producing identical
// output.
std::string resolutionNote;
std::string label() const {
return std::format("{} {} ({})", compiler_name(), version, targetTriple);
}
std::string_view compiler_name() const {
switch (compiler) {
case CompilerId::GCC: return "gcc";
case CompilerId::Clang: return "clang";
case CompilerId::MSVC: return "msvc";
default: return "unknown";
}
}
};
struct DetectError { std::string message; };
bool is_gcc(const Toolchain& tc);
bool is_clang(const Toolchain& tc);
bool is_musl_target(const Toolchain& tc);
bool is_msvc_target(const Toolchain& tc);
bool is_mingw_target(const Toolchain& tc);
// Can the artifact we are building be fully statically linked (`-static`)?
//
// This is a property of the TARGET, not of the machine doing the build —
// a Windows host cross-compiling to x86_64-linux-musl still produces a fully
// static ELF. `mcpp::platform::supports_full_static` answers a DIFFERENT
// question ("can THIS machine's own binaries be static"), and using it here
// silently dropped `-static` from every Windows→Linux cross build.
//
// `hostCapability` is threaded in explicitly rather than read from
// mcpp::platform so the decision is testable on any host: passing false
// models a Windows/macOS host. It is only consulted for the host target
// (empty triple), where target *is* host. Callers pass
// mcpp::platform::supports_full_static — keeping that dependency at the call
// site leaves this module free of any platform import.
bool target_supports_full_static(std::string_view targetTriple, bool hostCapability);
struct BmiTraits {
std::string_view bmiDir; // "gcm.cache" | "pcm.cache" | "ifc.cache"
std::string_view bmiExt; // ".gcm" | ".pcm" | ".ifc"
std::string_view manifestPrefix; // "gcm" | "pcm" | "ifc"
bool needsExplicitModuleOutput = false;
bool needsPrebuiltModulePath = false;
bool scanNeedsFModules = true;
// Module-flag spellings (leading space included; empty = not emitted).
std::string_view compileModulesFlag; // " -fmodules" (GCC) | ""
std::string_view stdBmiUsePrefix; // "" | " -fmodule-file=std=" | " /reference std="
std::string_view stdCompatBmiUsePrefix; // "" | " -fmodule-file=std.compat=" | " /reference std.compat="
std::string_view moduleOutputPrefix; // "" | " -fmodule-output=" | " /ifcOutput "
std::string_view bmiSearchPrefix; // "" | " -fprebuilt-module-path=" | " /ifcSearchDir "
// How this compiler is TOLD that a translation unit is a module interface.
// Emitted UNCONDITIONALLY on every module compile — mcpp never asks
// "does this driver recognize this extension?".
//
// WHY UNCONDITIONALLY. Every driver has a private, version-dependent
// suffix->language table, and the three disagree in both directions:
// measured 2026-08-11, Clang 22.1.8 does not recognize `.ixx` at all
// (it hands the file to the LINKER, warns, and exits 0 with no BMI —
// a silent no-op), while cl.exe does not recognize `.cppm`. Maintaining
// "who knows which suffix" would be a table that expires with every
// compiler release AND whose errors are silent.
//
// Saying it every time costs nothing: the flag is IDEMPOTENT on a suffix
// the driver already knows. Measured on the same day — Clang's `.cppm`
// BMI is byte-identical with and without `-x c++-module` (18896 bytes
// both); GCC's `.gcm` is unchanged too (its output is not byte-
// reproducible run to run, so the comparison is same-size plus a diff
// offset indistinguishable from the run-to-run noise).
//
// NOT interchangeable between families: `-x c++-module` makes GCC exit
// with "language c++-module not recognized", and `-x c++` makes Clang
// emit a 174-byte stub instead of a module BMI.
//
// Positional on GNU, so the emitter must place it before `-c $in`.
std::string_view moduleInterfaceLangFlag; // " -x c++" | " -x c++-module" | " /interface /TP"
// Non-empty ⇔ the driver can emit the BMI *and stop*, producing the SAME
// BMI an ordinary compile of that TU would have produced. Both halves
// matter, and the second one is the trap.
//
// MEASURED (clang 22.1.8, src/build/prepare.cppm):
// -fmodule-output= … -c 7.35s BMI 9,102,984 B (reduced)
// --precompile 1.81s BMI 18,402,920 B (FULL)
// --precompile
// -Xclang -emit-reduced-
// module-interface 1.67s BMI 9,102,968 B (reduced)
//
// `--precompile` alone is fast but emits a *full* BMI, because its output
// is meant to be fed back in for codegen. Publishing those to importers is
// not a drop-in substitution: BMIs grow ~16x on small modules, and on
// mcpp's own graph clang 22.1.8 then miscompiles a downstream TU outright —
// error: call to implicitly-deleted default constructor of
// 'formatter<basic_string<char>, wchar_t>'
// on a narrow format string, from inside `std`. The same TU compiles
// against reduced BMIs. So the reduced form is not an optimisation here,
// it is the contract: this flag must reproduce it byte for byte.
//
// GCC leaves this empty even though `-fmodule-only` exists: MEASURED, it
// does not skip the back end (~99% of a full compile). GCC's split is a
// different mechanism — see Strategy::DetachCodegen.
std::string_view bmiOnlyFlags;
};
BmiTraits bmi_traits(const Toolchain& tc);
} // namespace mcpp::toolchain
namespace mcpp::toolchain {
bool is_gcc(const Toolchain& tc) {
return tc.compiler == CompilerId::GCC;
}
bool is_clang(const Toolchain& tc) {
return tc.compiler == CompilerId::Clang;
}
// Target-shape predicates read the parsed canonical Triple (triple.cppm is
// the single triple parser), with the old substring heuristics kept only as
// a fallback for triples outside the language. This makes them spelling-
// independent: "x86_64-w64-mingw32" and canonical "x86_64-windows-gnu" give
// the same answer.
bool is_musl_target(const Toolchain& tc) {
if (auto t = triple::parse(tc.targetTriple)) return t->is_musl();
return tc.targetTriple.find("-musl") != std::string::npos;
}
bool is_msvc_target(const Toolchain& tc) {
if (auto t = triple::parse(tc.targetTriple)) return t->is_msvc_env();
return tc.targetTriple.find("msvc") != std::string::npos;
}
bool is_mingw_target(const Toolchain& tc) {
if (auto t = triple::parse(tc.targetTriple)) return t->is_windows_gnu();
// "x86_64-w64-mingw32" (mingw-w64) / legacy "*-pc-mingw32".
return tc.targetTriple.find("mingw32") != std::string::npos;
}
bool target_supports_full_static(std::string_view targetTriple, bool hostCapability) {
// Empty triple means "build for this machine" — target IS host, so the
// host answer is the correct one. This is the only case where the host
// capability legitimately decides.
if (targetTriple.empty()) return hostCapability;
auto t = triple::parse(targetTriple);
// Outside the triple language we have nothing to reason from. Fall back
// to the host answer rather than guessing from a substring — a wrong
// `true` here would emit `-static` at a target that cannot honour it.
if (!t) return hostCapability;
// PE targets get their `-static` from the C++ runtime distribution
// contract (dist::Format::Pe in flags.cppm), never from here. Returning
// false is what keeps the two mechanisms from both emitting the flag.
if (t->is_pe()) return false;
// macOS cannot fully static-link: libSystem must stay dynamic.
if (t->os == "macos") return false;
// Linux ELF — glibc or musl, native or cross. This is the line that was
// previously gated on the HOST being Linux.
return t->os == "linux";
}
BmiTraits bmi_traits(const Toolchain& tc) {
if (tc.compiler == CompilerId::MSVC) {
// Native cl.exe builds are gated off until the .ifc pipeline lands;
// these traits exist so nothing silently reuses the GCC defaults.
return {
.bmiDir = "ifc.cache",
.bmiExt = ".ifc",
.manifestPrefix = "ifc",
.needsExplicitModuleOutput = true,
.needsPrebuiltModulePath = true,
.scanNeedsFModules = false,
.compileModulesFlag = "",
.stdBmiUsePrefix = " /reference std=",
.stdCompatBmiUsePrefix = " /reference std.compat=",
.moduleOutputPrefix = " /ifcOutput ",
.bmiSearchPrefix = " /ifcSearchDir ",
// Pre-existing behaviour, unchanged: cl has always been told
// explicitly, because mcpp's interfaces are `.cppm` and cl does
// not know that suffix. The other two families now match it.
.moduleInterfaceLangFlag = " /interface /TP",
};
}
if (is_clang(tc)) {
return {
.bmiDir = "pcm.cache",
.bmiExt = ".pcm",
.manifestPrefix = "pcm",
.needsExplicitModuleOutput = true,
.needsPrebuiltModulePath = true,
.scanNeedsFModules = false,
.compileModulesFlag = "",
.stdBmiUsePrefix = " -fmodule-file=std=",
.stdCompatBmiUsePrefix = " -fmodule-file=std.compat=",
.moduleOutputPrefix = " -fmodule-output=",
.bmiSearchPrefix = " -fprebuilt-module-path=",
.moduleInterfaceLangFlag = " -x c++-module",
.bmiOnlyFlags = " --precompile -Xclang -emit-reduced-module-interface",
};
}
return {
.bmiDir = "gcm.cache",
.bmiExt = ".gcm",
.manifestPrefix = "gcm",
.needsExplicitModuleOutput = false,
.needsPrebuiltModulePath = false,
.scanNeedsFModules = true,
// GCC: -fmodules on every TU; BMIs implicit in cwd/gcm.cache, no
// std=/search flags needed.
.compileModulesFlag = " -fmodules",
.stdBmiUsePrefix = "",
.stdCompatBmiUsePrefix = "",
.moduleOutputPrefix = "",
.bmiSearchPrefix = "",
// GCC decides interface-ness from the content (`export module`), so
// it only needs to be told the LANGUAGE. `-x c++-module` is not a
// value GCC accepts.
.moduleInterfaceLangFlag = " -x c++",
};
}
} // namespace mcpp::toolchain