BP-BUILD, configuring and building the compiler¶
Status: partial
Applies to: GCC 16.2.0 (tag releases/gcc-16.2.0)
Target-dependent: yes
Generated sections: 2
Last verified: 2026-09-05 against releases/gcc-16.2.0
This document specifies what happens between a source tree and a working cc1. It covers the single stage build, meaning configure followed by make, and it covers the part of that which is specific to GCC rather than generic to autoconf.
1. Purpose and scope¶
The build turns a source tree, a target triple and a set of configure options into an installed compiler. Three things about it are unlike building an ordinary C++ program, and everything else in this document follows from them.
The first is that a large part of the compiler does not exist in the source tree. It is written during the build by programs that are themselves compiled during the build, out of the target's machine description. There is no insn-recog.cc to read until you have built one.
The second is that the build has to keep three compilers straight at once. The build compiler is the one running on the machine doing the work, the host compiler is the one being built, and the target is what the compiler being built will emit code for. The generator programs are compiled with the build compiler because they run now, and the compiler proper is compiled with the build compiler as well but linked for the host. When all three are the same triple this distinction is invisible, which is why every mistake in it surfaces only in a cross build.
The third is that the target is chosen at configure time and cannot be changed afterwards. config.gcc selects the header files, the machine description, and the object files that will be compiled, and those choices are baked into generated headers before the first compilation.
What this document does not cover: the three stage bootstrap and its stage comparison, which is BP-BOOTSTRAP; running the test suite, which is BP-TESTSUITE; the plugin ABI that the --enable-plugin build produces, which is BP-PLUGIN; and the runtime libraries under libgcc/, libstdc++-v3/ and the rest, which are separate autoconf projects that the top level drives and which no blueprint covers yet.
The unit of this document is the gcc/ subdirectory. The top level exists to configure and order a dozen sibling projects, and it appears here only where it changes what happens inside gcc/.
2. Data structures¶
The build's data are its generated artifacts and its configuration variables. Three tables below are read out of the pinned tree rather than typed, because all three are facts GCC keeps in machine readable form and none of the three is written down as a table anywhere in GCC's own documentation.
2.1 The generator programs¶
Generated by bpc build from the pinned GCC tree. Do not edit inside the markers, edit the generator.
The build compiles 29 generator programs before it compiles any of the compiler. They are built with the build compiler rather than the host one, because they run on the machine doing the building even when the compiler being built runs somewhere else. 17 link the RTL reader, 6 link error reporting, 3 link nothing extra, 3 link the MD reader.
| Program | Links | Writes |
|---|---|---|
genattr |
the RTL reader | insn-attr.h |
genattr-common |
the RTL reader | insn-attr-common.h |
genattrtab |
the RTL reader | insn-attrtab.cc, insn-dfatab.cc, insn-latencytab.cc |
genautomata |
the RTL reader | insn-automata.cc |
gencfn-macros |
error reporting | case-cfn-macros.h, cfn-operators.pd |
gencheck |
nothing extra | tree-check.h |
genchecksum |
nothing extra | nothing, or nothing by that route |
gencodes |
the RTL reader | insn-codes.h |
genconditions |
the RTL reader | build/gencondmd.cc |
gencondmd |
error reporting | insn-conditions.md |
genconfig |
the RTL reader | insn-config.h |
genconstants |
the MD reader | insn-constants.h |
genemit |
the RTL reader | insn-emit-N.cc |
genenums |
the MD reader | insn-enums.cc |
genextract |
the RTL reader | insn-extract.cc |
genflags |
the RTL reader | insn-flags.h |
gengenrtl |
error reporting | genrtl.h |
gengtype |
error reporting | gtype.state |
genhooks |
error reporting | target-hooks-def.h, c-family/c-target-hooks-def.h, common/common-target-hooks-def.h, d/d-target-hooks-def.h, rust/rust-target-hooks-def.h, jit/jit-target-hooks-def.h, tm.texi |
genmatch |
nothing extra | gimple-match-N.cc, gimple-match-auto.h, generic-match-N.cc, generic-match-auto.h |
genmddeps |
the MD reader | mddeps.mk |
genmddump |
the RTL reader | nothing, or nothing by that route |
genmodes |
error reporting | insn-modes.cc, insn-modes.h, insn-modes-inline.h, min-insn-modes.cc |
genopinit |
the RTL reader | insn-opinit.h, insn-opinit.cc |
genoutput |
the RTL reader | insn-output.cc |
genpeep |
the RTL reader | insn-peep.cc |
genpreds |
the RTL reader | insn-preds.cc, tm-preds.h, tm-constrs.h |
genrecog |
the RTL reader | insn-recog-N.cc |
gentarget-def |
the RTL reader | insn-target-def.h |
The Writes column is read from the move-if-change call in each rule, which is also why a rebuild after an unrelated edit is fast: the generator runs every time and its output replaces the file on disk only when the contents differ. A program with nothing in that column writes through some other mechanism, gengtype writing one file per input and genchecksum writing to standard output among them.
2.2 The front ends¶
Generated by bpc build from the pinned GCC tree. Do not edit inside the markers, edit the generator.
There are 14 front end declarations in the tree, and 4 of them say they should be built when --enable-languages is not given. A declaration is a shell fragment the top level configure sources, not a makefile and not a list, which is why no single file in GCC holds this table.
| Directory | --enable-languages name |
Compiler | Built by default | Also needs |
|---|---|---|---|---|
gcc/ada/ |
ada |
gnat1 |
no | c c++, when not cross building |
gcc/algol68/ |
algol68 |
a681 |
no | nothing |
gcc/c/ |
c |
cc1 |
yes | nothing |
gcc/cobol/ |
cobol |
cobol1 |
no | nothing |
gcc/cp/ |
c++ |
cc1plus |
yes | nothing |
gcc/d/ |
d |
d21 |
no | nothing |
gcc/fortran/ |
fortran |
f951 |
yes | nothing |
gcc/go/ |
go |
go1 |
no | nothing |
gcc/jit/ |
jit |
none | no | nothing |
gcc/lto/ |
lto |
lto1 |
no | nothing |
gcc/m2/ |
m2 |
cc1gm2 |
no | c++ |
gcc/objc/ |
objc |
cc1obj |
yes | c |
gcc/objcp/ |
obj-c++ |
cc1objplus |
no | objc c++ |
gcc/rust/ |
rust |
crab1 |
no | nothing |
The Built by default column is what the fragment asks for and not what you get. The top level decides and overrides this in both directions, lto being the one that says no and is built anyway, because --enable-lto is on unless you turn it off. ada, d, lto if $enable_lto set boot_language, which puts them in stage one and so makes them available to build the stages above.
2.3 The checking levels¶
Generated by bpc build from the pinned GCC tree. Do not edit inside the markers, edit the generator.
--enable-checking takes a comma separated list of 14 categories and 4 whole settings. The loop that reads it starts for check in release $ac_checking_flags, so release is applied first every single time, and --enable-checking=rtl therefore means release checking plus RTL checking rather than RTL checking alone. Only no, none, yes and all clear what release set, because they are the four words whose case arm assigns every variable rather than one.
| Category | Sets | Defines | GCC calls it |
|---|---|---|---|
assert |
ac_assert_checking |
ENABLE_ASSERT_CHECKING |
cheap |
df |
ac_df_checking |
ENABLE_DF_CHECKING |
not said |
extra |
ac_extra_checking |
ENABLE_EXTRA_CHECKING |
not said |
fold |
ac_fold_checking |
ENABLE_FOLD_CHECKING |
quite expensive |
gc |
ac_gc_checking |
ENABLE_GC_CHECKING |
quite expensive |
gcac |
ac_gc_always_collect |
ENABLE_GC_ALWAYS_COLLECT |
extremely expensive |
gimple |
ac_gimple_checking |
ENABLE_GIMPLE_CHECKING |
moderately expensive |
misc |
ac_checking |
CHECKING_P |
cheap |
rtl |
ac_rtl_checking |
ENABLE_RTL_CHECKING |
quite expensive |
rtlflag |
ac_rtlflag_checking |
ENABLE_RTL_FLAG_CHECKING |
cheap |
runtime |
ac_runtime_checking |
ENABLE_RUNTIME_CHECKING |
cheap |
tree |
ac_tree_checking |
ENABLE_TREE_CHECKING |
moderately expensive |
types |
ac_types_checking |
ENABLE_TYPES_CHECKING |
cheap |
valgrind |
ac_valgrind_checking |
ENABLE_VALGRIND_CHECKING |
extremely expensive |
The whole settings, and which categories each one leaves turned on. This is the table to read before choosing what to build.
| Category | no or none |
release |
yes |
all |
|---|---|---|---|---|
assert |
no | yes | yes | yes |
df |
no | no | no | yes |
extra |
no | no | no | yes |
fold |
no | no | no | yes |
gc |
no | no | yes | yes |
gcac |
no | no | no | yes |
gimple |
no | no | yes | yes |
misc |
no | no | yes | yes |
rtl |
no | no | no | yes |
rtlflag |
no | no | yes | yes |
runtime |
no | yes | yes | yes |
tree |
no | no | yes | yes |
types |
no | no | yes | yes |
valgrind |
no | no | no | no |
The default is not in this table because it is not a word. A tree whose DEV-PHASE file says experimental defaults to yes,extra and any other tree defaults to release, so the same configure line gives different checking on a release tarball and on a git checkout of the development branch.
2.4 The records the rest of this document uses¶
record Triple
cpu symbol first field of the canonical name
vendor symbol
os symbol
canonical symbol what config.sub returned
record TargetConfig
cpu_type symbol which gcc/config/ directory
tm_file sequence of symbol headers concatenated into tm.h
tm_p_file sequence of symbol headers concatenated into tm_p.h
md_file symbol the machine description
out_file symbol the target's C++ support file
extra_objs sequence of symbol extra objects linked into cc1
tmake_file sequence of symbol makefile fragments
extra_options sequence of symbol target .opt files
supported boolean false for the obsolete list
record BuildTree
srcdir symbol where the sources are
objdir symbol where the build happens
host_subdir symbol "." out of tree, "host-<triple>" in tree
record Artifact
path symbol
written_by symbol or nothing the generator, or nothing if a real source
stamp symbol or nothing the s- file that records it is current
TargetConfig is not a C structure. It is the set of shell variables config.gcc leaves behind, which configure then substitutes into the makefile. Writing it as a record here is the honest way to say what it is: one value with a dozen fields, produced by a shell script, consumed by a makefile, and never held in memory by any program.
3. Algorithms¶
3.1 Resolving the target¶
function configure_target (t: Triple, options: Map) -> TargetConfig or error(message)
complexity: O(1), a shell case statement over about 190 arms
c = TargetConfig with every field empty
c.cpu_type = first_field(t.canonical)
for each pattern in the obsolete list
if matches(t.canonical, pattern) and not options["enable-obsolete"]
return error("Configuration " + t.canonical + " not supported")
apply_cpu_arm(c, t)
apply_os_arm(c, t)
apply_target_arm(c, t)
if c.tm_file == []
return error("Configuration " + t.canonical + " not supported")
c.md_file = c.md_file or (c.cpu_type + "/" + c.cpu_type + ".md")
c.out_file = c.out_file or (c.cpu_type + "/" + c.cpu_type + ".cc")
return c
The three arms are three separate case statements over the same triple, and a target's configuration is the union of what all three set. This is why reading config.gcc for one target means reading it in three places, and why grepping for a triple finds the answer only about half the time. The obsolete list and the message both live at gcc/config.gcc:306@releases/gcc-16.2.0 and gcc/config.gcc:350@releases/gcc-16.2.0, and the variables the script is expected to leave behind are documented in the comment at the top of the file.
The defaulting on the last two lines is the reason a new port that follows the naming convention needs no entry for its machine description at all.
3.2 Building the headers the compiler compiles against¶
function make_tm_h (c: TargetConfig) -> Artifact
complexity: O(k) in the number of headers named by c.tm_file
lines = []
for each define in c.tm_defines
append(lines, "#define " + define)
for each header in c.tm_file
append(lines, "#include \"" + header + "\"")
append(lines, "#include \"defaults.h\"")
write_if_changed("tm.h", lines)
return Artifact("tm.h", "mkconfig.sh", "cs-tm.h")
mkconfig.sh is what writes it, driven from gcc/Makefile.in:2157@releases/gcc-16.2.0, and tm.h itself is a rule with an empty recipe at gcc/Makefile.in:2136@releases/gcc-16.2.0. tm.h is not written by a program that understands the target. It is a list of #include lines assembled by a shell script, and the target's behaviour comes from the headers it names and the order it names them in. defaults.h goes last so that a macro no header defined gets a default, which is why removing a definition from a target header does not produce an undefined macro error but silently selects the generic behaviour. The script holds it back deliberately, at gcc/mkconfig.sh:66@releases/gcc-16.2.0, because the insn- headers it emits immediately above have to be seen first.
The same function produces config.h, bconfig.h, tconfig.h, tm_p.h, tm_d.h, tm_rust.h and tm_jit.h, from different variable lists. bconfig.h is the one the generator programs include, and it describes the build machine rather than the target, which is the mechanism that lets a generator run here and produce code for elsewhere.
3.3 The stamp file idiom¶
Every generated file in GCC is produced by this pattern, and understanding it explains most of the build's observable behaviour.
function regenerate (a: Artifact, generator: symbol, inputs: sequence of Artifact) -> boolean
complexity: O(size of the output)
if newer(a.stamp, inputs)
return false
run(generator, inputs, into: "tmp-" + a.path)
changed = not identical("tmp-" + a.path, a.path)
if changed
move("tmp-" + a.path, a.path)
else
delete("tmp-" + a.path)
touch(a.stamp)
return changed
Make cannot express "this rule may not change its output". The stamp file is how GCC expresses it anyway. The rule's target is s-recog, not insn-recog.cc, and insn-recog.cc depends on s-recog with an empty recipe. So make always considers the generator out of date when the machine description changed, always runs it, and updates the timestamp on insn-recog.cc only when the bytes differ.
The rule at gcc/Makefile.in:2803@releases/gcc-16.2.0 is the pattern in its shortest form: seven generators, one recipe, a move-if-change and a stamp.
The consequence is the one every reader notices first. Editing a comment in a .md file causes the generators to run, which takes about a minute, and then causes nothing at all to recompile, which takes none.
3.4 The order of a single stage build¶
function build (tree: BuildTree, c: TargetConfig) -> sequence of Artifact
complexity: O(n) in the number of translation units, dominated by the compile
configure(tree, c)
for each g in generator_programs
compile_with_build_compiler(g)
for each a in generated_headers
regenerate(a, its generator, [c.md_file] + machine_description_includes)
for each a in generated_sources
regenerate(a, its generator, [c.md_file] + machine_description_includes)
regenerate(gtype_state, "gengtype", gtfiles)
for each a in gtype_outputs
regenerate(a, "gengtype", [gtype_state])
for each a in match_outputs
regenerate(a, "genmatch", [match_pd, cfn_operators_pd])
archive("libbackend.a", every object in OBJS)
for each language in enabled_languages
link(language.compiler, language.objects + backend_objects)
link("xgcc", driver_objects)
return artifacts
Three things about this order are worth stating because they are what a parallel build has to respect.
gengtype runs twice, writing a state file and then reading it back to write the per file output. GCC does this so that a bug in reading the state file is caught during the build that wrote it rather than during someone else's.
genmatch reads cfn-operators.pd, which gencfn-macros writes, so those two generators are ordered with respect to each other while the rest are not.
libbackend.a at gcc/Makefile.in:2334@releases/gcc-16.2.0 is where the middle end and back end end up, and BACKEND at gcc/Makefile.in:1972@releases/gcc-16.2.0 is that archive plus the pieces that cannot live in one. Every front end links the same BACKEND, which is what gcc/c/Make-lang.in:86@releases/gcc-16.2.0 shows for cc1.
The driver is built as xgcc in the build directory and installed as gcc. The rename is not cosmetic. A build directory containing a program called gcc would be found by anything with the build directory on its path, including the build itself, and would then be used as the build compiler.
4. Invariants¶
I1. Every file named in generated_files exists and is current before any object in ALL_HOST_OBJS is compiled.
Established by: $(ALL_HOST_OBJS) : | $(generated_files) at gcc/Makefile.in:4934@releases/gcc-16.2.0, which is an order only prerequisite, so the files must exist and their timestamps do not force a recompile. Checked by: nothing, at build time. A build that violates this fails to compile with a missing header, which is a clear error but is not a check. May be broken by: nobody. The list itself is at gcc/Makefile.in:3210@releases/gcc-16.2.0.
I2. A generated file's contents are a function of the machine description, the configuration and the generator, and of nothing else. Established by: the generators, which read no environment. Checked by: nothing directly. The bootstrap's stage two against stage three comparison checks a strictly stronger property and is the only thing that would catch a violation. May be broken by: nobody. A generator that embedded a timestamp or a path would break the bootstrap comparison, which is why none of them do.
I3. The stamp file s-x is newer than every input of x if and only if x is current.
Established by: regenerate in 3.3. Checked by: nothing. May be broken by: touching a source file with a timestamp in the past, which make cannot detect and which produces a stale generated file that looks current.
I4. tm.h includes defaults.h last, and every target macro is either defined by an earlier header or defaulted by it.
Established by: mkconfig.sh. Checked by: #pragma GCC poison at gcc/system.h:954@releases/gcc-16.2.0, which names 125 target macros that were replaced by target hooks, so using one of them is a compile error rather than a silent default. May be broken by: nobody. The poison list is the mechanism that made it possible to retire a target macro at all, and it is the reason a fifteen year old port does not compile against a modern tree.
I5. A generator program never sees the target's insn- headers or the target's gmp.h dependency.
Established by: -DGENERATOR_FILE on the build/%.o rule, and by tm.h guarding its own includes of insn-flags.h and insn-modes.h on !GENERATOR_FILE at gcc/mkconfig.sh:97@releases/gcc-16.2.0. Checked by: nothing. A generator that includes tm.h compiles, and gets a tm.h with most of its contents switched off. system.h uses the same guard for gmp.h at gcc/system.h:706@releases/gcc-16.2.0, for the same reason and with the same absence of a check. May be broken by: anybody, silently. This is the invariant in this document with the widest gap between how load bearing it is and how little enforces it.
I6. Exactly one directory under gcc/config/ supplies the target's machine description, chosen by cpu_type.
Established by: config.gcc. Checked by: nothing. May be broken by: a tm_file naming headers from two CPU directories, which several targets legitimately do for shared OS support and which no rule distinguishes from a mistake.
I7. The installed compiler's -print-prog-name for cc1 resolves inside the install prefix.
Established by: the driver's search path, built from --prefix at configure time. Checked by: nothing in the build. May be broken by: an install that moves the tree after the fact, since the prefix is compiled in.
I8. Every language listed in --enable-languages has a config-lang.in and every config-lang.in sets language.
Established by: the top level configure, which errors on an unknown language. Checked by: the top level, and by bpc's reader for section 2.2, which raises rather than skipping a fragment with no language.
5. Observable behaviour¶
make in the gcc/ directory with no arguments builds all, which autoconf has substituted to all.internal for a native build and all.cross for a cross one, at gcc/Makefile.in:29@releases/gcc-16.2.0. Both build native, doc and selftest. all.internal at gcc/Makefile.in:2278@releases/gcc-16.2.0 then builds start.encap and rest.encap, which produce xgcc and the per language "rest" targets. all.cross produces gcc-cross instead and asks each language for lang.all.cross rather than lang.rest.encap, which is where a language drops whatever needs a working host compiler.
The build prints the generator runs as bare command lines, so the transition from "building the build tools" to "building the compiler" is visible in the log as the point where build/gen... lines stop and g++ -c lines start.
A no change rebuild after a successful build runs every generator and compiles nothing. Timed on the rel configuration in this project's matrix it is under a minute against a build of twenty two.
--enable-checking=all roughly doubles the wall clock of the compiler it produces, and produces a compiler that is several times the size on disk. The chk and rel rows of containers/matrix.toml are 47 minutes against 22, which is measured on this project's runners, and 4.6 GB against 1.2, which is estimated.
make install-strip at gcc/Makefile.in:4116@releases/gcc-16.2.0 runs install with INSTALL_PROGRAM overridden to strip, so the installed binaries have no symbol table. It is the default in this project's matrix and the two configurations built with -g override it, because a compiler built with debug info and then stripped debugs no better than one built without any, and nothing about the build says so: it succeeds, the image is smaller than it should be, and the first sign of trouble is a debugger reporting no symbol table hours later. Most of the difference between the rel image at 1.2 GB and the dbg image at 7.0 GB is elsewhere, in -O0 -g3 and in dbg keeping the source tree the debug info points at.
A corpus entry recording any of this is not yet written. The observations above come from the build matrix rather than from the golden corpus, and this section is partial for that reason.
6. Edge cases and error paths¶
Building in the source directory is allowed. This is worth stating flatly because the folklore says otherwise and this project's own container comment said otherwise until this document was written. When srcdir is . and a gcc directory is present, the top level sets host_subdir to host-<triple> and builds there instead of in the top level, so an in tree build works and puts its objects somewhere else than you expect.
Building out of tree from a tree that has been built in is refused. The check is at configure.ac:222@releases/gcc-16.2.0. The error is building out of tree but <srcdir> contains host-<triple>, and it exists because the two layouts would otherwise interleave. This is the check people remember as "GCC will not build in its source directory", and it is the opposite of that.
An unsupported target fails at configure time with a specific message. The obsolete list is a separate case before the real ones, and a target on it configures only with --enable-obsolete. A target that is not on the list and not in any arm falls through to the same message, so the two failures look identical and are not: one is a target that was removed, and one is a target that never existed.
A target with no explicit md_file gets a defaulted one. If neither the file nor the directory exists, the failure is not at configure time but at the first generator run, as an open error from a program whose name means nothing to the reader.
--enable-checking with an unknown category is an error, and with a misspelled one is not always. The loop is at gcc/configure.ac:661@releases/gcc-16.2.0. --enable-checking=trees errors out with unknown check category trees. --enable-checking=no,tree does not error and gives you tree checking with nothing else, because the loop applies words left to right and the last word wins per variable.
--enable-checking never turns off what release turned on unless you name a whole setting. --enable-checking=fold gives release plus fold. This surprises people benchmarking a single check in isolation.
A stale s- file is invisible. Deleting a generated file without deleting its stamp leaves make convinced it is current, and the build fails with a missing file that it will not regenerate. make clean removes both, and rm insn-recog.cc on its own does not.
Two generators write files whose names do not match their own. genconditions writes build/gencondmd.cc, the source of another generator, and gencondmd writes insn-conditions.md, a machine description fragment. A reader tracing a file back to its producer by name gets both of these wrong.
genmatch and genemit and genrecog split their output into numbered files. The count is set at configure time and is not in the source, so insn-recog-3.cc exists in one build and not another. Anything that lists source files by name breaks on this.
A parallel build with an insufficient -j limit and a large --enable-languages runs out of memory rather than slowing down. Linking cc1plus at -O0 -g3 takes several gigabytes on its own, and the dbg configuration in this project's matrix exists partly to make that cost visible.
--enable-plugin changes what is built and not only what is installed. It adds gengtype to the native target at gcc/Makefile.in:2294@releases/gcc-16.2.0, because a plugin that wants GTY roots needs the tool that produces them. A build configured without it produces a compiler that accepts -fplugin= and a tree with no installed gengtype.
A cross compiler's smoke test cannot link. There is no target libc, so the only honest check that the compiler works is compile and disassemble. This is a property of cross builds rather than of GCC, and it is why containers/matrix.toml gives the cross configuration its own smoke command.
7. Interactions¶
The build reads the machine description, so BP-RTL and BP-FINAL describe the content of what the generators consume. It produces insn-recog.cc and insn-emit-N.cc, which BP-EXPAND describes the use of.
config.gcc sets extra_objs, which is how a target adds passes to the compiler, so the pass list in BP-PIPELINE is target dependent through this file and not only through the pass manager.
gengtype reads GTFILES and writes the mark and walk routines for every GTY marked type, which is the mechanism behind every GTY(()) marker in BP-GIMPLE section 2 and BP-RTL section 2. A type that is not in a file listed in GTFILES is not marked and is collected while live.
--enable-plugin is the configure option that makes BP-PLUGIN apply at all. It also determines whether the plugin headers are installed, which is what gxplug compiles against, and it adds gengtype to the native target at gcc/Makefile.in:2294@releases/gcc-16.2.0.
--enable-bootstrap hands control of the whole build to the top level rather than to gcc/, which is BP-BOOTSTRAP.
Globals, named honestly because section 3 passed them as arguments: srcdir, objdir, host_subdir, target_noncanonical and the whole of TargetConfig are shell variables in configure's environment and then substituted makefile variables. md_file is a makefile variable read by every generator rule. There is no program here holding state, which is why the failure mode of this subsystem is a stale file rather than a corrupted structure.
8. Conformance¶
There is no test suite for the build, and that is the honest summary of this section. What exists is indirect.
The bootstrap's stage comparison is the strongest available check on I2, because a generator whose output varied would produce differing objects across stages. BP-BOOTSTRAP section 8 states the comparison.
gcc/testsuite/gcc.dg/plugin/ requires --enable-plugin to have worked and so exercises the interaction in section 7. It does not exercise the build.
This project's build matrix is the closest thing to a conformance suite for this document. Six configurations, two architectures, each with a smoke test that compiles and runs a program, recorded by digest in containers/images.lock.json. A configuration that stops building is caught within a week by the scheduled run. That checks that the configurations build, not that they build correctly.
Restating section 4 as assertions an implementation could run:
I1 is checkable by compiling one object with an empty build directory and observing the failure.
I3 is checkable by touching a machine description backwards in time and observing that the build does not notice.
I5 is checkable by adding #include "tm.h" to a generator source and observing the poisoned macro error.
I8 is checkable by --enable-languages=nonesuch.
I2, I4, I6 and I7 have no direct check, which is stated in section 4 and repeated here because a reader who skips to section 8 for a checklist should not come away thinking the list is complete.
9. Port notes¶
The header says this document is target dependent, and the dependence is total: config.gcc is a six thousand line shell script whose entire purpose is target dependence. What differs is not a detail of the build but the set of files the build compiles.
| What | Where it is decided | Range across targets |
|---|---|---|
Which gcc/config/ directory |
cpu_type in config.gcc |
one directory, occasionally shared by several triples |
Which headers are in tm.h |
tm_file |
between two and a dozen |
| The machine description | md_file, defaulted from cpu_type |
one .md, which includes others |
| Extra passes compiled in | extra_objs |
none for most, several for x86 and aarch64 |
Whether collect2 is used |
use_collect2 |
yes on targets without proper constructor support |
| Multilib variants | tmake_file fragments |
none, or dozens |
What is forced and what is not.
Generating source from a machine description is forced only in the sense that the alternative is worse. Nothing requires a compiler to have a machine description at all, and a compiler with one back end and no ambition to have two would be simpler without. What is forced, once the description exists, is that reading it at compile time rather than at run time is the only way to get a table driven recognizer that costs nothing at run time.
The stamp file idiom is not forced. It is a workaround for make's inability to say "this rule may not change its output", and a build system with content addressed outputs, which several now have, gets the same behaviour for free. A reimplementation on such a system should not copy the stamp files, and should keep the property they exist to provide.
Three separate case statements in config.gcc is not forced and is not defensible. The three arms are CPU, OS and full triple, and the union of what they set is the configuration, so there is no single place to read a target's configuration from. A reimplementation should make a target's configuration one declaration.
The xgcc rename is forced, or near enough. Any build that produces a program with the same name as the tool it is being built with needs to keep the two apart, and renaming is the cheapest way.
bconfig.h against tm.h, with a poisoning check between them, is the load bearing idea in this whole document and the one most worth copying. Two compilers in one build directory need two configurations, and the only reliable way to keep them apart is to make the mistake a compile error.