В этом материале собраны дополнительные аспекты компилятора Go, которые редко обсуждаются даже среди опытных Go-разработчиков. Базовые этапы и SSA разобраны в первой статье про компилятор Go.
Inline-функции: не всё так просто
Go выполняет инлайнинг функций ещё до SSA-прохода. Однако:
- Инлайн-лист ведётся в структуре
ir.Node, а не в SSA. - Компилятор учитывает глубину вложенности, чтобы избежать бесконтрольного роста кода.
- Даже простые функции могут не быть заинлайнены, если они используют
defer,recoverилиgo.
func trivial() int { return 42 } // будет inlined
func withDefer() int {
defer fmt.Println("hi")
return 42
} // не будет
Go-оптимизатор: больше про rewrite.go
В каталоге rewrite*.go находятся архитектурно-зависимые правила оптимизации SSA:
- Например,
rewriteAMD64.goсодержит:(Add x (Neg y)) => (Sub x y) - Это даёт математические оптимизации на уровне IR, до генерации ассемблера.
Эти правила — причина, почему
x + (-y)становитсяx - yбез вашего ведома.
Zero-cost defer? Не всегда
Go пытается оптимизировать defer, но:
- Внутри циклов
deferостаётся медленным. - Если
deferможно статически устранить (например, вreturn-единственной функции), он может быть превращён в inline-вызов.
SSA-представление умеет «упрощать» код
Во время SSA компилятор упрощает выражения:
x := a * 0 // → x = 0
y := a + 0 // → y = a
z := a * 1 // → z = a
Но только если не теряется семантика типов или вызовов (например, a + 0 не упрощается, если a — интерфейс).
Escape-анализ и heap allocation
Escape-анализ выполняется на стадии IR и определяет:
- Переменные, которые «убегают» из функции, попадают в heap.
- Влияет на производительность радикально.
func foo() *int {
x := 5
return &x // escape: x в heap
}
Инструмент для анализа:
go build -gcflags="-m" file.go
Как Go выбирает инструкции процессора?
Go использует структуру Prog из cmd/internal/obj, которая:
- Содержит поле
As— код инструкции (например,AMOVQ,AADDQ). - Работает с абстракцией, которая потом переводится в байты архитектурой (amd64, arm, riscv и т.д.)
Это делает Go компилятор по-настоящему портируемым — он не зависит от внешнего ассемблера (в отличие от C).
Символьная таблица: скрытые зависимости
Во время компиляции typechecking создаёт символьную таблицу:
- Типы, переменные, функции — всё помещается в
types.Sym. - Именно тут происходят конфликты имён, даже если они не используются.
Go на других архитектурах
Go поддерживает:
amd64,arm,arm64,ppc64,riscv64,wasm,loong64,s390x- Каждая архитектура имеет:
- Свои правила SSA (
rewriteARCH.go) - Свой план инструкций (
obj/ARCH/asm.go)
- Свои правила SSA (
Всё это собирается одним компилятором, без изменений исходного кода.
Заключение
Компилятор Go — это не просто frontend кода. Он включает полноценную ассемблерную модель, правило-движок переписывания инструкций, SSA-инфраструктуру и архитектурную независимость. Это делает его мощным и быстрым — зачастую быстрее компиляторов C/C++.