---
title: "Compiler bootstrapping depends on more than you might think"
slug: compiler-bootstrapping-depends-on-more-than-you-might-think
url: https://listedarticles.com/articles/compiler-bootstrapping-depends-on-more-than-you-might-think
canonical_url: https://utcc.utoronto.ca/~cks/space/blog/programming/CompilerBootstrappingVsOSChanges
content_type: blog_post
language: en
published_at: 2026-10-12T00:00:00.000Z
updated_at: 2026-10-14T08:18:01.611Z
author: "Chris Siebenmann"
authored_by: human
publisher: "Wandering Thoughts"
publisher_url: https://utcc.utoronto.ca/~cks/space/blog/
topics: ["Programming", "Systems Programming", "Open Source", "Linux"]
license: all-rights-reserved
word_count: 627
reading_minutes: 3
citation: "Chris Siebenmann, Wandering Thoughts. \"Compiler bootstrapping depends on more than you might think.\" 12 Oct 2026. https://utcc.utoronto.ca/~cks/space/blog/programming/CompilerBootstrappingVsOSChanges (all-rights-reserved)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Compiler bootstrapping depends on more than you might think

> Chris Siebenmann on bootstrapping the Go toolchain from C sources, and how OS and environment changes complicate the “start from source” story more than the language docs suggest.

# Compiler bootstrapping depends on more than you might think

Like many languages and their toolchains, the [Go](https://go.dev/)
compiler toolchain is written in Go. This presents a little bit of a
problem if you want to wind up with a Go compiler without having to
trust someone else's pre-built binary of it. Fortunately, the early
versions of the Go toolchain were written in C, which (in theory)
allows us to bootstrap a current binary of the Go compiler starting
from the last version written in C. The [Installing Go from source](https://go.dev/doc/install/source) documentation has [a list of what
versions of Go can be built with what](https://go.dev/doc/install/source#go14) (which gets increasingly
fine-grained with recent Go releases).

Well, that's the theory. In practice this is only going to work on
some platforms, such as x86 Linux, because there's a hidden dependency
or two in this sequence of bootstrapping. The big hidden dependency is
*long term operating system compatibility with 'old' binaries*. When
you build Go 1.4 with your modern C compiler and OS, you will get a
modern binary that calls your modern C standard library and kernel
system calls in the appropriate way. But when you use your newly built
Go 1.4 to build Go 1.19, the Go binary it produces is one that was
effectively frozen around the time Go 1.4 was released; it makes the
system calls and expects the ABIs and so on that were current then,
not that are current now. Then the binary of Go 1.21 that your Go 1.19
toolchain produces will similarly be frozen at the state of ABIs as of
Go 1.19, and so on.

The Linux kernel is extremely good about backwards compatibility with
old binaries; binaries that are literally decades old can and will
still run happily. On Linux, you can expect this sequence of 'new old
stock' Go toolchain binaries to work without problems. But not all
operating systems consider binary backward compatibility to be
critical, or even deeply important. For example, OpenBSD doesn't
guarantee it will maintain ABI compatibility between releases ([cf](https://utcc.utoronto.ca/~cks/space/blog/programming/GoWhatOpenBSDs-2020-03)) and [in the past it's made changes that
mattered for Go](https://utcc.utoronto.ca/~cks/space/blog/programming/Go116OpenBSDUsesLibc). I wouldn't be surprised if
older versions of the Go toolchain produce binaries that won't run on
the current version of OpenBSD, which would obviously complicate a
from-scratch bootstrapping of Go on OpenBSD.

The other hidden dependency is on how any outside programs used by the toolchain work, for example what specific arguments they accept or require. If your language's toolchain at some point uses the standard system linker, assembler, C compiler, or whatever, it depends on being able to pass them the right arguments and in general for them to operate the way it expects. Old versions of your toolchain will be using the command line arguments for outside programs that were current when those old toolchains were supported, not the ones you may need today, possibly years after the fact. Not all language toolchains are self-contained the way Go (mostly) is; it's perfectly reasonable to use the system assembler or linker or whatever rather than write your own (in addition to your actual compiler system). But this leaves you depending on their command line arguments and how they work, and often the programs don't consider that sort of thing a stable API that has to be preserved over the long term.

([As covered in the Go documentation](https://go.dev/doc/install/source#bootstrapFromSource), Go has
actually had to update the old 'Go 1.4' toolchain in order to keep it
working on more modern systems. You can get an idea of the changes
necessary by looking at [the Go 1.4 branch's commit log](https://go.googlesource.com/go/+log/refs/heads/release-branch.go1.4).)

[09](https://utcc.utoronto.ca/~cks/space/blog/2026/10/09/)

[October](https://utcc.utoronto.ca/~cks/space/blog/2026/10/)

[2026](https://utcc.utoronto.ca/~cks/space/blog/2026/).

|  |  | 

| « | [On modern hardware, who handles what bit of hardware state can be unclear](https://utcc.utoronto.ca/~cks/space/blog/tech/HardwareWhoHandlesWhatNotClear) | 

| [Why have policies even if you aren't going to enforce them much](https://utcc.utoronto.ca/~cks/space/blog/sysadmin/PoliciesAreCommunication) | » |
