Перейти к содержимому

Plan 9 Assembly, go:linkname, pragma-арсенал, ABIInternal, SIMD

Этот документ — про low-level Go: Plan 9 assembly, register-based ABI 1.17+, pragma directives, go:linkname (со strict mode 1.23+), SIMD-оптимизации в crypto и hashes. Уровень Middle 3: читать и понимать runtime/asm_amd64.s, писать собственный asm-stub, осознавать стоимость и риски linkname к runtime. Это уровень разработчика, который коммитит в Go runtime / pebble / etcd / hot-path libs.

  1. Краткое введение (для разогрева)
  2. Глубочайшее погружение
    • 2.1. Plan 9 assembly vs Intel/AT&T
    • 2.2. Pseudo-instructions и pseudo-registers
    • 2.3. TEXT directive: name, frame size, args size
    • 2.4. Calling conventions: ABI0 vs ABIInternal (1.17+)
    • 2.5. Зачем asm в Go
    • 2.6. Реальный asm-стаб: чтение runtime
    • 2.7. SIMD в Go: AVX2, AVX-512, NEON
    • 2.8. go:noescape pragma
    • 2.9. go:linkname pragma
    • 2.10. go:nosplit, go:noinline, go:registerparams
    • 2.11. Cgo vs assembly: когда что
    • 2.12. go tool objdump, DWARF, build IDs
  3. Подводные камни
  4. Реальные production-кейсы
  5. Вопросы на собесе Middle 3
  6. Practice
  7. Источники

Go использует Plan 9 assembly — диалект, унаследованный от операционной системы Plan 9 из Bell Labs. Это не AT&T и не Intel, хотя более похож на AT&T.

Зачем вообще ассемблер в Go-проектах:

  • Crypto hot paths (crypto/aes, crypto/sha256, crypto/chacha20).
  • Hash функции (runtime/asm_*.s для maphash, fastrand).
  • Atomic primitives (sync/atomic — вся реализация в asm для архитектур, где нужны спец-инструкции).
  • System calls (syscall.Syscall, runtime·syscall).
  • Runtime internals (gogo, mcall, systemstack, async preempt).

На Middle 3 ожидается, что вы:

  • Различаете ABI0 (старый stack-based) и ABIInternal (register-based с 1.17).
  • Можете прочесть простой stub в runtime/asm_amd64.s.
  • Понимаете //go:linkname и его опасности (strict mode 1.23+).
  • Знаете, когда //go:noescape корректен (только с asm-реализацией).
  • Понимаете отличия go tool objdump от objdump -d.

Сходства/различия для AMD64:

АспектPlan 9AT&T (gcc -S)Intel (NASM)
Порядок операндовsrc, dstsrc, dstdst, src
Размер операндовсуффикс инструкции (Q/L/W/B)суффикс инстр. (q/l/w/b)суффикс операнда (qword)
РегистрыAX, BX, CX%rax, %rbx, %rcxrax, rbx
Immediate$42$4242
Memory access8(BX), (BX)(AX*8)8(%rbx), (%rbx,%rax,8)[rbx+8], [rbx+rax*8]
Labelsdone:.done:done:
Comments// или /* */# или /* */;

Пример — MOV 8 байт из регистра в память:

Plan 9: MOVQ AX, 16(SP)
AT&T: movq %rax, 16(%rsp)
Intel: mov qword ptr [rsp+16], rax

В Plan 9 есть четыре псевдо-регистра, не существующих в hardware:

Псевдо-регистрЧто это
SBStatic base — для глобальных символов: runtime·foo(SB).
FPFrame pointer — для доступа к аргументам функции: arg+0(FP).
SPStack pointer — virtual local SP (с учётом фрейма).
PCProgram counter — для labels и переходов внутри функции.

⚠️ SP в Plan 9 — это VIRTUAL SP, не hardware %rsp. Адрес 0(SP) указывает на первое локальное хранилище функции, а не на текущий top stack. Compile-time компилятор пересчитывает в hw-offset.

Если в Plan 9 вы пишете SP БЕЗ символа (типа 0(SP)) — это виртуальный. Если SP С символом (foo+0(SP)) — тоже виртуальный. Hardware SP в Plan 9 — это SP или просто RSP в некоторых синтаксисах ассемблера, нативно недоступен (есть workaround через BP/DI etc).

Pseudo-instructions (не реальные CPU инструкции, обрабатываются Go-ассемблером):

Pseudo-инстр.Что делает
TEXTОбъявляет функцию.
DATAИнициализирует data в RO-сегменте.
GLOBLГлобальный символ (для DATA или TEXT).
FUNCDATAМетаданные для GC (stackmap, argmap).
PCDATAPer-instruction metadata (call site IDs).
NOPNo-op (для alignment).
RETReturn (компилируется в RET AMD64).
CALLFunction call.

FUNCDATA/PCDATA обычно не пишутся вручную — компилятор вставляет автоматически для Go-функций. В чистом asm их добавляет cmd/asm если есть pseudo-impl. Когда пишете asm-обёртку для Go-функции, вы должны указать NOPTR (no pointers) или объявить argmap корректно — иначе GC сломается.

Полный синтаксис:

TEXT package·funcName(SB), flags, $frame_size-args_size

Пример:

// func Add(a, b int) int
TEXT ·Add(SB), NOSPLIT, $0-24
MOVQ a+0(FP), AX
MOVQ b+8(FP), BX
ADDQ BX, AX
MOVQ AX, ret+16(FP)
RET

Разбор:

  • ·Add — символ Add в текущем пакете (символ · — это U+00B7 middle dot, разделитель pkg·name).
  • (SB) — относительно static base.
  • NOSPLIT — флаг: не вставлять stack-grow check (опасно — используется для маленьких функций).
  • $0 — frame size = 0 (нет локалов).
  • -24 — args+return size = 24 (3 × 8 байт: a, b, ret).
  • a+0(FP) — аргумент a по offset 0 от frame pointer.
  • b+8(FP) — аргумент b по offset 8.
  • ret+16(FP) — возвращаемое значение по offset 16.

Флаги для TEXT:

ФлагЗначение
NOSPLITНе делать stack split check (только если функция малая и не вызывает other).
NOFRAMEНе аллоцировать frame (для leaf functions).
NEEDCTXTClosure context передаётся в DX.
WRAPPERТонкая обёртка (специальное обращение в stack trace).
TLSBSSThread-local BSS data.
TOPFRAMETop of stack (например, goexit).
ABIInternalИспользует ABIInternal (register-based, 1.17+).

До Go 1.17 все аргументы передавались на стеке (ABI0). Это медленно — каждый аргумент = MOVQ из памяти в регистр.

С Go 1.17 введён ABIInternal: первые аргументы — в регистрах.

AMD64 ABIInternal (Go 1.17+):

Integer/pointer args: AX, BX, CX, DI, SI, R8, R9, R10, R11 (9 регистров)
Float args: X0 — X14 (15 регистров)
Integer/pointer returns: AX, BX, CX, DI, SI, R8, R9, R10, R11
Float returns: X0 — X14
Closure context: DX
G pointer (TLS): R14 ← важно
Stack base: BP
Frame pointer: SP (hardware)

ARM64 ABIInternal: R0—R15 для integer, F0—F15 для float, R28 для G, и т.д.

⚠️ Это internal ABI — между Go-функциями. Asm-функции по умолчанию остаются ABI0 (stack-based) для совместимости.

Чтобы asm использовала ABIInternal:

TEXT ·FastAdd<ABIInternal>(SB), NOSPLIT, $0
// первые два arg в AX, BX (по ABIInternal)
ADDQ BX, AX // result в AX
RET

Или для wrapper (как Go это делает для legacy asm):

TEXT ·OldAsm(SB), NOSPLIT, $0-16
// ABI0: args на стеке, ret — в стеке

Compiler автоматически генерирует ABI wrapper между ABI0 asm и ABIInternal Go-вызовом. Это лишний overhead — заставьте критичные asm-функции использовать ABIInternal явно.

Категория 1: Performance-critical paths.

  • crypto/aes — AES-NI (hw acceleration).
  • crypto/sha256 — SHA-NI или AVX2.
  • crypto/chacha20poly1305 — AVX2/AVX-512.
  • crypto/blake2b — AVX2.
  • crypto/sha3 — vectorized.
  • runtime/memclr_amd64.s — REP STOSQ + SIMD.
  • runtime/memmove_amd64.s — оптимизирован под кеш-линии.

Категория 2: Hardware-specific.

  • SIMD (AVX, AVX2, AVX-512, NEON).
  • Atomic instructions (CMPXCHG, LOCK prefix).
  • TSC, RDRAND, RDSEED.
  • Cache prefetch (PREFETCHT0).

Категория 3: System calls.

  • syscall.Syscall* — прямой SYSCALL инструкция.
  • На Linux это самый эффективный путь (быстрее cgo).

Категория 4: Runtime internals.

  • runtime·gogo — context switch между G.
  • runtime·mcall — переключение на g0.
  • runtime·systemstack — выполнение на системном стеке.
  • runtime·asyncPreempt — preempt handler.

Посмотрим runtime/asm_amd64.s, функцию runtime·gogo:

// func gogo(buf *gobuf)
// restore state from gobuf; longjmp.
TEXT runtime·gogo(SB), NOSPLIT, $0-8
MOVQ buf+0(FP), BX // load *gobuf into BX
MOVQ gobuf_g(BX), DX // load g from gobuf into DX
MOVQ 0(DX), CX // touch *g to make sure it's not nil (panic if so)
JMP gogo<>(SB)
TEXT gogo<>(SB), NOSPLIT, $0
get_tls(CX) // load TLS base into CX
MOVQ DX, g(CX) // store new g pointer in TLS slot
MOVQ DX, R14 // also set R14 (ABIInternal g register)
MOVQ gobuf_sp(BX), SP // restore stack pointer
MOVQ gobuf_ret(BX), AX // restore return value
MOVQ gobuf_ctxt(BX), DX // restore closure context
MOVQ gobuf_bp(BX), BP // restore frame pointer
MOVQ $0, gobuf_sp(BX) // clear to help GC (no longer holds stack)
MOVQ $0, gobuf_ret(BX)
MOVQ $0, gobuf_ctxt(BX)
MOVQ $0, gobuf_bp(BX)
MOVQ gobuf_pc(BX), BX // load PC
JMP BX // jump to it (resume goroutine)

Это context switch между горутинами на уровне M. Никаких syscalls — чистый user-space.

Разбор:

  • get_tls(CX) — макрос, загружает thread-local storage в CX (на Linux: MOVQ FS:0, CX).
  • g(CX) — offset в TLS для текущего g pointer.
  • Восстановление SP, BP, AX (return), DX (ctxt), R14 (g) — всё что нужно для возобновления goroutine.
  • JMP BX — переход на сохранённый PC.

SIMD instructions позволяют обрабатывать несколько данных за инструкцию. В Go runtime/crypto обильно используется AVX2 (256-bit), реже AVX-512 (512-bit).

Пример — XOR’ing двух byte slices (упрощённый):

// func xorBytesAVX2(dst, a, b []byte, n int)
TEXT ·xorBytesAVX2(SB), NOSPLIT, $0-...
MOVQ dst_base+0(FP), DI
MOVQ a_base+24(FP), SI
MOVQ b_base+48(FP), R8
MOVQ n+72(FP), CX
SHRQ $5, CX // n / 32 (256 bits = 32 bytes)
JZ done
loop:
VMOVDQU (SI), Y0 // 32 bytes from a
VMOVDQU (R8), Y1 // 32 bytes from b
VPXOR Y0, Y1, Y2 // y2 = y0 XOR y1
VMOVDQU Y2, (DI) // store
ADDQ $32, SI
ADDQ $32, R8
ADDQ $32, DI
DECQ CX
JNZ loop
done:
VZEROUPPER // clean AVX state for AVX↔SSE transition
RET

Где это в реальном Go:

  • crypto/cipher/xor_amd64.s — XOR для CTR mode.
  • crypto/chacha20poly1305/chacha20poly1305_amd64.s — ChaCha20 polyhash, ~1500 строк AVX2 ассемблера.
  • crypto/blake2b/blake2b_amd64.s — BLAKE2b с AVX2 (~3x ускорение).
  • runtime/memmove_amd64.s — для копирований 16+ байт использует SSE.

ARM64 NEON аналогично: crypto/aes/asm_arm64.s использует ARMv8 cryptographic extensions (AESE, AESD инструкции).

Detection в runtime: internal/cpu пакет инициализирует флаги при старте программы:

import "internal/cpu"
if cpu.X86.HasAVX2 { useAVX2() } else { useScalar() }

В bench/crypto-пакетах вы увидите эти ветвления на верхнем уровне.

Pragma говорит компилятору: «эта функция гарантирует, что её указатели-аргументы НЕ убегут».

Use case: asm-функция, которая работает с pointer arguments.

xor_amd64.go
//go:noescape
func xorBytesSSE2(dst, a, b *byte, n int)
// xor_amd64.s
TEXT ·xorBytesSSE2(SB), NOSPLIT, $0-...
// ... asm impl

Без noescape компилятор escape-аналитики увидит указатель, идущий в external функцию, и предположит escape → heap allocation вызывающего объекта.

⚠️ Опасность: если ваша asm-функция фактически сохраняет указатель куда-то (в глобал, в hash table), pragma — ложь. GC может collect объект, а указатель в asm-внутренностях останется dangling → silent corruption.

⚠️ Правило: //go:noescape ОБЯЗАНО иметь asm- или cgo-реализацию. Применять к pure Go-функции = UB.

Самая мощная и опасная pragma в Go. Позволяет «прозвонить» в unexported символ из другого пакета.

Синтаксис:

import _ "unsafe" // обязательно для linkname
//go:linkname localFn pkg.RemoteFn
func localFn(arg int) int

Это создаёт alias: localFn в текущем пакете указывает на pkg.RemoteFn (даже если она unexported, типа pkg.remoteFn).

Use cases:

  • sync/atomic использует функции из runtime.
  • time пакет тянет таймеры из runtime через linkname.
  • os/signal — runtime.signalM.
  • Сторонние libs: gopsutil, pebble, mongo-driver, prometheus client — все используют linkname.

Пример из stdlib (time/sleep.go):

// startTimer adds t to the timer heap.
//go:linkname startTimer time.startTimer
func startTimer(*runtimeTimer)

Здесь time.startTimer фактически — runtime.startTimer (unexported в runtime).

Go 1.23+ STRICT MODE:

Раньше любой пакет мог linkname к чему угодно. С 1.23 — restricted: только если runtime-side символ имеет соответствующую разрешающую pragma или находится в специальном allowlist.

Что произошло:

  • Команда Go устала от того, что 3rd-party libs ломаются при изменениях private API.
  • Введён cmd/link -checklinkname=1 (по умолчанию).
  • Чтобы отключить (escape hatch): go build -ldflags="-checklinkname=0".

Affected libs: golang.org/x/sys, k8s.io/apimachinery, pebble, gvisor, cilium-ebpf — многие срочно патчили или переходили на public API.

//go:nosplit — функция не делает stack split check. Используется в runtime для функций, которые работают на g0 или в signal handler, где stack growth не работает.

⚠️ Размер функции ограничен (~512 bytes). Если превысит → compile error.

//go:nosplit
func tinyHelper(x int) int { return x + 1 }

В user-коде использовать не нужно. Только runtime internals.

//go:noinline — компилятор гарантированно не инлайнит. Use cases:

  • Тестирование profile (отдельный frame в pprof).
  • Бенчмарки (предотвратить inline + dead code elim).
  • Debugging.

//go:registerparams — тест ABIInternal. Применяется автоматически с 1.17, явно не нужна.

КритерийCgoAssembly
Overhead на вызов~200ns (cgocall + thread)~1-5ns (просто CALL)
Поддержка SIMDЧерез intrinsics в CПрямо, полный контроль
Сложность написанияПростая (любой C-код)Высокая, нужно знать ABI
Cross-compileСложно (нужен cross-compiler)Тривиально (GOOS GOARCH)
GC interactionПараметры escape, дорогие//go:noescape — на stack
Stack growthCgo стек — отдельный, большойИспользует Go stack
Async preemptionНе работает в cgoРаботает в asm

Правило: для maximum perf hot path — asm. Для серьёзных вычислений с большим C-кодом (libsodium, snappy) — cgo, но изолируйте через worker pool.

go tool objdump — Go’s disassembler, заточенный под Go binaries.

Окно терминала
go tool objdump -s "main\.foo" ./binary

Покажет:

  • Имя функции (с учётом mangling Go).
  • Машинный код.
  • Plan 9 mnemonics (привычные).
  • Source line numbers (если есть DWARF).

Отличие от objdump -d (binutils): Go’s objdump парсит Go’s symbol table, понимает pkg·func синтаксис, знает Plan 9 mnemonics.

DWARF:

  • DWARF — формат debug info.
  • Go генерирует DWARF v4 (или v5 с опцией).
  • Содержит: типы, переменные, line numbers, inline frames.
  • Размер: ~30-40% от total binary size.
  • Удаление: -ldflags="-s -w" (-s strip symbol table, -w strip DWARF).

Build IDs:

  • Уникальный hash от source + compile config.
  • В go version -m binary.
  • Помогает: detection того, какая версия скомпилирована; voiding cache.

-trimpath:

  • Убирает абсолютные пути из binary (/home/user/proj/foo.gofoo.go).
  • Reproducible builds (одинаковый source → одинаковый binary).
  • Должен использоваться в CI.

go tool nm — symbol table dump.

Окно терминала
go tool nm ./binary | grep -i "myfunc"

  1. Plan 9 SP — виртуальный. 0(SP) — это локалы. Hardware top stack недоступен напрямую без trick’ов.

  2. //go:noescape без asm-импл = UB. Pragma — это контракт. Compile-time проверки нет.

  3. //go:linkname к runtime может сломаться в любой минорной версии. Реальные кейсы: 1.20 переименовал runtime.fastrandruntime.cheaprand — много libs сломалось одномоментно.

  4. //go:nosplit функция слишком большая → compile error «nosplit stack overflow». Размер ~512 bytes max.

  5. ABIInternal asm требует знать какие регистры clobber-safe. Если забыли сохранить R12 в not-leaf функции — silent corruption.

  6. VZEROUPPER обязательна после AVX-блоков перед SSE-инструкциями (transition penalty ~70 cycles).

  7. AVX-512 throttle. На Skylake-X использование AVX-512 снижает CPU frequency глобально для всего ядра. Trade-off в hot path: ускорение AVX-512 vs slowdown остальных потоков.

  8. get_tls(CX) платформо-специфично. На Windows другой механизм TLS (MOVQ GS:0x28, CX для thread block). Учитывайте.

  9. Plan 9 mnemonics ≠ Intel. MOVL в Plan 9 — это 32-bit move (MOVL = movl). Не путать с long mode 64-bit.

  10. Argmap для GC. Если asm-функция принимает pointer-аргумент, GC должен знать его offset для scan. Если нет — может пропустить и crash. Compiler автогенерирует argmap из Go-объявления, но если объявлений нет (только asm) — нужен явный FUNCDATA.

  11. //go:linkname exposes private API. Если 3rd-party library использует linkname к runtime — backward compat вашего проекта зависит от стабильности этого внутреннего API. Минимизируйте.

  12. NOSPLIT функции не могут вызывать non-NOSPLIT. Compiler проверяет — иначе stack overflow без preempt check.

  13. runtime/internal/atomic vs sync/atomic — разные пакеты с разными гарантиями. Один — runtime-internal, второй — public. Не путать.

  14. DWARF stripping (-w) ломает stack traces в runtime/debug.Stack в части источников. Function names остаются, но line numbers могут пропасть.

  15. go tool objdump -gnu показывает GNU mnemonics, но это не идеально — есть несоответствия для специфичных Plan 9 инструкций.


  • Стандартный compress/gzip был bottleneck на edge.
  • Перепеисали critical path (huffman encoding, CRC32) на asm с AVX2/AVX-512.
  • Throughput +180% на компресс, +90% на декомпресс.
  • Затраты: 1 senior-инженер × 3 месяца, форк github.com/klauspost/compress.
  • Auth-токены пересчитывали SHA256 100k+ раз в секунду.
  • Switch на crypto/sha256 (SHA-NI hardware) дал 7x speedup из коробки.
  • Бэк: важно проверить CPU flags (cpu.X86.HasSHA). На Atom/Celeron SHA-NI нет, fallback на scalar.
  • Использовали runtime.fastrand через linkname для быстрого random.
  • В 1.20 переименовали → бинарь не собирался.
  • Workaround: добавили версию-зависимый build tag, для 1.20+ — cheaprand, для старых — fastrand.
  • В 1.23 strict mode полностью запретил → перешли на math/rand/v2 (новая стдлиб в 1.22+).
  • Кастомный asm для bloom filter (был на 1.13).
  • После обновления на 1.17 perf упал на 6%.
  • Причина: компилятор оборачивал каждый вызов asm-функции в ABI wrapper (ABIInternal → ABI0).
  • Fix: добавили <ABIInternal> к TEXT decl, переписали prologue/epilogue.
  • Восстановили + ещё +3% (без wrapper’а).
  • Использовали gvisor для sandboxing.
  • gvisor имеет ~60 linkname-хаков в runtime.
  • При попытке обновиться на Go 1.23 — десятки compile errors из-за strict mode.
  • Решили: остаться на 1.22 до выхода gvisor 0.0.20240XXX с обновлёнными хаками.
  • pebble использует vectorized.go стратегию: hot loops в crc32, sst-block decoding — asm.
  • Их benchmark показывает 30-40% throughput vs pure Go.
  • Цена: ~5k строк asm на arch, поддержка AMD64+ARM64. Каждый release — testing на 4-6 платформах.
  • Реализация BLAKE3 на Go с avx512-asm.
  • Skylake-X: 6 GB/s hash throughput (vs scalar 600 MB/s).
  • Стало основой crypto/blake3 proposals в Go.

5. Вопросы на собесе Middle 3 (экспертный уровень)

Заголовок раздела «5. Вопросы на собесе Middle 3 (экспертный уровень)»
  1. Что такое Plan 9 assembly? Чем отличается от AT&T?

  2. Назовите 4 псевдо-регистра в Plan 9. Что они означают?

  3. Почему SP в Plan 9 «виртуальный»?

  4. Объясните TEXT ·Foo(SB), NOSPLIT, $16-24. Что такое 16, 24?

  5. Какие флаги для TEXT: NOSPLIT, NOFRAME, WRAPPER — когда нужны?

  6. ABI0 vs ABIInternal — разница. С какой версии ABIInternal?

  7. Какие регистры используются для аргументов в ABIInternal на AMD64?

  8. Что такое R14 в ABIInternal? Почему именно R14?

  9. Что такое DX в ABIInternal? (Closure context.)

  10. Что такое ABI wrapper? Когда компилятор его вставляет?

  11. Зачем asm в Go? Назовите 5 use cases.

  12. Где в stdlib используется AVX2? AVX-512?

  13. Объясните VZEROUPPER. Когда обязательна?

  14. AVX-512 frequency throttle — что это и как избежать?

  15. Pragma //go:noescape — когда корректно применять? Что произойдёт при misuse?

  16. Pragma //go:linkname — для чего? Назовите 3 примера в stdlib.

  17. Что изменилось в //go:linkname в Go 1.23 (strict mode)? Как отключить?

  18. //go:nosplit — назначение. Ограничение по размеру.

  19. Чем //go:noinline отличается от -gcflags=-l?

  20. //go:registerparams — что это? Нужно ли применять в обычном коде?

  21. Cgo vs assembly — overhead на вызов. Когда что выбрать?

  22. runtime·gogo — что делает? Опишите кратко.

  23. Что такое systemstack? Когда вызывается?

  24. Async preempt: какая asm-функция в runtime/preempt_amd64.s отвечает за сохранение регистров?

  25. Что такое FUNCDATA, PCDATA? Кто их вставляет?


  1. Простой asm-stub. Напишите asm-функцию func Mul(a, b int64) int64, использующую IMULQ. Сравните perf с pure Go аналогом (бенчмарк должен показать, что Go-версия инлайнится и быстрее — это покажет нюанс).

  2. go tool objdump исследование. Соберите hello-world. Сделайте go tool objdump -s "main\.main". Идентифицируйте: prologue (stack check), вызов fmt.Println, epilogue.

  3. ABIInternal vs ABI0. Напишите две версии одной asm-функции — одна <ABIInternal>, другая обычная. Бенчмарк. Замерьте wrapper overhead.

  4. linkname к stdlib. Используя //go:linkname, добавьте в свой пакет alias к runtime.nanotime. Получите наносекундное время быстрее, чем через time.Now(). Сравните бенчмарком.

  5. //go:noescape correctness. Напишите asm-функцию с pointer arg. Без noescape — посмотрите escape report. С noescape — без heap alloc. Подтвердите.

  6. AVX2 XOR. Напишите asm func xorAVX2(dst, a, b []byte, n int). Бенчмарк vs pure Go. Ожидаем 4-8x ускорение на больших буферах.

  7. internal/cpu detection. Распечатайте все флаги CPU (HasAVX2, HasSHA, HasAES, …). Сделайте утилиту, которая проверяет, доступны ли все нужные для вашего сервиса инструкции.

  8. DWARF strip experiment. Соберите программу с/без -ldflags="-s -w". Сравните размер. Запустите панику в обоих — проверьте, какая информация теряется.


  1. Go Assembly Language Manualcmd/asm/doc.go + golang.org/doc/asm.
  2. «A Quick Guide to Go’s Assembler» — официальный doc (go.dev/doc/asm).
  3. runtime/asm_amd64.s — читать целиком. Особенно gogo, mcall, systemstack, asyncPreempt.
  4. «Plan 9 Assembly» — Ken Thompson, оригинальный doc Plan 9.
  5. «Go register ABI» — Cherry Zhang, design doc 40724-register-calling.md.
  6. «Writing assembly in Go» — Dave Cheney, dave.cheney.net (2013, но всё ещё актуален).
  7. «Avoid Go runtime traps» — Roberto Clapis на linkname/noescape тонкости.
  8. «Linkname strict mode in Go 1.23» — Go Blog (2024).
  9. «SIMD в Go» — Filippo Valsorda, blog.filippo.io (про crypto asm).
  10. «Klauspost’s compress library» — github.com/klauspost/compress (best-in-class asm для compression).
  11. «Zeebo/blake3» — пример full AVX-512 implementation.
  12. cmd/asm/internal/arch/*.go — Go assembler internals.
  13. «Go’s Hidden Pragmas» — Dave Cheney, dave.cheney.net.
  14. «How Go Implements time.Now()» — Filippo, ссылка на linkname к runtime.nanotime.
  15. DWARF v4/v5 specifications — dwarfstd.org.