Skip to content

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.