{"article":{"slug":"compiler-bootstrapping-depends-on-more-than-you-might-think","title":"Compiler bootstrapping depends on more than you might think","subtitle":null,"summary":"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.","content_type":"blog_post","language":"en","canonical_url":"https://utcc.utoronto.ca/~cks/space/blog/programming/CompilerBootstrappingVsOSChanges","author":{"name":"Chris Siebenmann","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Wandering Thoughts","url":"https://utcc.utoronto.ca/~cks/space/blog/","listing_slug":null,"listing":null},"topics":[{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"},{"name":"Systems Programming","slug":"systems-programming","url":"https://listedarticles.com/topics/systems-programming"},{"name":"Open Source","slug":"open-source","url":"https://listedarticles.com/topics/open-source"},{"name":"Linux","slug":"linux","url":"https://listedarticles.com/topics/linux"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":627,"reading_minutes":3,"published_at":"2026-10-12T00:00:00.000Z","added_at":"2026-10-14T08:18:01.611Z","updated_at":"2026-10-14T08:18:01.611Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/compiler-bootstrapping-depends-on-more-than-you-might-think","markdown_url":"https://listedarticles.com/articles/compiler-bootstrapping-depends-on-more-than-you-might-think.md","example":false,"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)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://utcc.utoronto.ca/~cks/space/blog/programming/CompilerBootstrappingVsOSChanges"},"body_markdown":"# Compiler bootstrapping depends on more than you might think\n\nLike many languages and their toolchains, the [Go](https://go.dev/)\ncompiler toolchain is written in Go. This presents a little bit of a\nproblem if you want to wind up with a Go compiler without having to\ntrust someone else's pre-built binary of it. Fortunately, the early\nversions of the Go toolchain were written in C, which (in theory)\nallows us to bootstrap a current binary of the Go compiler starting\nfrom the last version written in C. The [Installing Go from source](https://go.dev/doc/install/source) documentation has [a list of what\nversions of Go can be built with what](https://go.dev/doc/install/source#go14) (which gets increasingly\nfine-grained with recent Go releases).\n\nWell, that's the theory. In practice this is only going to work on\nsome platforms, such as x86 Linux, because there's a hidden dependency\nor two in this sequence of bootstrapping. The big hidden dependency is\n*long term operating system compatibility with 'old' binaries*. When\nyou build Go 1.4 with your modern C compiler and OS, you will get a\nmodern binary that calls your modern C standard library and kernel\nsystem calls in the appropriate way. But when you use your newly built\nGo 1.4 to build Go 1.19, the Go binary it produces is one that was\neffectively frozen around the time Go 1.4 was released; it makes the\nsystem calls and expects the ABIs and so on that were current then,\nnot that are current now. Then the binary of Go 1.21 that your Go 1.19\ntoolchain produces will similarly be frozen at the state of ABIs as of\nGo 1.19, and so on.\n\nThe Linux kernel is extremely good about backwards compatibility with\nold binaries; binaries that are literally decades old can and will\nstill run happily. On Linux, you can expect this sequence of 'new old\nstock' Go toolchain binaries to work without problems. But not all\noperating systems consider binary backward compatibility to be\ncritical, or even deeply important. For example, OpenBSD doesn't\nguarantee 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\nmattered for Go](https://utcc.utoronto.ca/~cks/space/blog/programming/Go116OpenBSDUsesLibc). I wouldn't be surprised if\nolder versions of the Go toolchain produce binaries that won't run on\nthe current version of OpenBSD, which would obviously complicate a\nfrom-scratch bootstrapping of Go on OpenBSD.\n\nThe 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.\n\n([As covered in the Go documentation](https://go.dev/doc/install/source#bootstrapFromSource), Go has\nactually had to update the old 'Go 1.4' toolchain in order to keep it\nworking on more modern systems. You can get an idea of the changes\nnecessary by looking at [the Go 1.4 branch's commit log](https://go.googlesource.com/go/+log/refs/heads/release-branch.go1.4).)\n\n[09](https://utcc.utoronto.ca/~cks/space/blog/2026/10/09/)\n\n[October](https://utcc.utoronto.ca/~cks/space/blog/2026/10/)\n\n[2026](https://utcc.utoronto.ca/~cks/space/blog/2026/).\n\n|  |  | \n\n| « | [On modern hardware, who handles what bit of hardware state can be unclear](https://utcc.utoronto.ca/~cks/space/blog/tech/HardwareWhoHandlesWhatNotClear) | \n\n| [Why have policies even if you aren't going to enforce them much](https://utcc.utoronto.ca/~cks/space/blog/sysadmin/PoliciesAreCommunication) | » |","body_html":"<h1 id=\"compiler-bootstrapping-depends-on-more-than-you-might-think\">Compiler bootstrapping depends on more than you might think</h1>\n<p>Like many languages and their toolchains, the <a href=\"https://go.dev/\" rel=\"nofollow ugc noopener\">Go</a>\ncompiler toolchain is written in Go. This presents a little bit of a\nproblem if you want to wind up with a Go compiler without having to\ntrust someone else&#39;s pre-built binary of it. Fortunately, the early\nversions of the Go toolchain were written in C, which (in theory)\nallows us to bootstrap a current binary of the Go compiler starting\nfrom the last version written in C. The <a href=\"https://go.dev/doc/install/source\" rel=\"nofollow ugc noopener\">Installing Go from source</a> documentation has <a href=\"https://go.dev/doc/install/source#go14\" rel=\"nofollow ugc noopener\">a list of what\nversions of Go can be built with what</a> (which gets increasingly\nfine-grained with recent Go releases).</p>\n<p>Well, that&#39;s the theory. In practice this is only going to work on\nsome platforms, such as x86 Linux, because there&#39;s a hidden dependency\nor two in this sequence of bootstrapping. The big hidden dependency is\n<em>long term operating system compatibility with &#39;old&#39; binaries</em>. When\nyou build Go 1.4 with your modern C compiler and OS, you will get a\nmodern binary that calls your modern C standard library and kernel\nsystem calls in the appropriate way. But when you use your newly built\nGo 1.4 to build Go 1.19, the Go binary it produces is one that was\neffectively frozen around the time Go 1.4 was released; it makes the\nsystem calls and expects the ABIs and so on that were current then,\nnot that are current now. Then the binary of Go 1.21 that your Go 1.19\ntoolchain produces will similarly be frozen at the state of ABIs as of\nGo 1.19, and so on.</p>\n<p>The Linux kernel is extremely good about backwards compatibility with\nold binaries; binaries that are literally decades old can and will\nstill run happily. On Linux, you can expect this sequence of &#39;new old\nstock&#39; Go toolchain binaries to work without problems. But not all\noperating systems consider binary backward compatibility to be\ncritical, or even deeply important. For example, OpenBSD doesn&#39;t\nguarantee it will maintain ABI compatibility between releases (<a href=\"https://utcc.utoronto.ca/~cks/space/blog/programming/GoWhatOpenBSDs-2020-03\" rel=\"nofollow ugc noopener\">cf</a>) and <a href=\"https://utcc.utoronto.ca/~cks/space/blog/programming/Go116OpenBSDUsesLibc\" rel=\"nofollow ugc noopener\">in the past it&#39;s made changes that\nmattered for Go</a>. I wouldn&#39;t be surprised if\nolder versions of the Go toolchain produce binaries that won&#39;t run on\nthe current version of OpenBSD, which would obviously complicate a\nfrom-scratch bootstrapping of Go on OpenBSD.</p>\n<p>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&#39;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&#39;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&#39;t consider that sort of thing a stable API that has to be preserved over the long term.</p>\n<p>(<a href=\"https://go.dev/doc/install/source#bootstrapFromSource\" rel=\"nofollow ugc noopener\">As covered in the Go documentation</a>, Go has\nactually had to update the old &#39;Go 1.4&#39; toolchain in order to keep it\nworking on more modern systems. You can get an idea of the changes\nnecessary by looking at <a href=\"https://go.googlesource.com/go/+log/refs/heads/release-branch.go1.4\" rel=\"nofollow ugc noopener\">the Go 1.4 branch&#39;s commit log</a>.)</p>\n<p><a href=\"https://utcc.utoronto.ca/~cks/space/blog/2026/10/09/\" rel=\"nofollow ugc noopener\">09</a></p>\n<p><a href=\"https://utcc.utoronto.ca/~cks/space/blog/2026/10/\" rel=\"nofollow ugc noopener\">October</a></p>\n<p><a href=\"https://utcc.utoronto.ca/~cks/space/blog/2026/\" rel=\"nofollow ugc noopener\">2026</a>.</p>\n<p>|  |  | </p>\n<p>| « | <a href=\"https://utcc.utoronto.ca/~cks/space/blog/tech/HardwareWhoHandlesWhatNotClear\" rel=\"nofollow ugc noopener\">On modern hardware, who handles what bit of hardware state can be unclear</a> | </p>\n<p>| <a href=\"https://utcc.utoronto.ca/~cks/space/blog/sysadmin/PoliciesAreCommunication\" rel=\"nofollow ugc noopener\">Why have policies even if you aren&#39;t going to enforce them much</a> | » |</p>","headings":[{"level":1,"text":"Compiler bootstrapping depends on more than you might think","id":"compiler-bootstrapping-depends-on-more-than-you-might-think"}]}}