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.
Содержание
Заголовок раздела «Содержание»- Краткое введение (для разогрева)
- Глубочайшее погружение
- 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
- Подводные камни
- Реальные production-кейсы
- Вопросы на собесе Middle 3
- Practice
- Источники
1. Краткое введение (для разогрева)
Заголовок раздела «1. Краткое введение (для разогрева)»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.
2. Глубочайшее погружение
Заголовок раздела «2. Глубочайшее погружение»2.1. Plan 9 assembly vs Intel/AT&T
Заголовок раздела «2.1. Plan 9 assembly vs Intel/AT&T»Сходства/различия для AMD64:
| Аспект | Plan 9 | AT&T (gcc -S) | Intel (NASM) |
|---|---|---|---|
| Порядок операндов | src, dst | src, dst | dst, src |
| Размер операндов | суффикс инструкции (Q/L/W/B) | суффикс инстр. (q/l/w/b) | суффикс операнда (qword) |
| Регистры | AX, BX, CX | %rax, %rbx, %rcx | rax, rbx |
| Immediate | $42 | $42 | 42 |
| Memory access | 8(BX), (BX)(AX*8) | 8(%rbx), (%rbx,%rax,8) | [rbx+8], [rbx+rax*8] |
| Labels | done: | .done: | done: |
| Comments | // или /* */ | # или /* */ | ; |
Пример — MOV 8 байт из регистра в память:
Plan 9: MOVQ AX, 16(SP)AT&T: movq %rax, 16(%rsp)Intel: mov qword ptr [rsp+16], rax2.2. Pseudo-instructions и pseudo-registers
Заголовок раздела «2.2. Pseudo-instructions и pseudo-registers»В Plan 9 есть четыре псевдо-регистра, не существующих в hardware:
| Псевдо-регистр | Что это |
|---|---|
SB | Static base — для глобальных символов: runtime·foo(SB). |
FP | Frame pointer — для доступа к аргументам функции: arg+0(FP). |
SP | Stack pointer — virtual local SP (с учётом фрейма). |
PC | Program 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). |
PCDATA | Per-instruction metadata (call site IDs). |
NOP | No-op (для alignment). |
RET | Return (компилируется в RET AMD64). |
CALL | Function call. |
FUNCDATA/PCDATA обычно не пишутся вручную — компилятор вставляет автоматически для Go-функций. В чистом asm их добавляет cmd/asm если есть pseudo-impl. Когда пишете asm-обёртку для Go-функции, вы должны указать NOPTR (no pointers) или объявить argmap корректно — иначе GC сломается.
2.3. TEXT directive: name, frame size, args size
Заголовок раздела «2.3. TEXT directive: name, frame size, args size»Полный синтаксис:
TEXT package·funcName(SB), flags, $frame_size-args_sizeПример:
// func Add(a, b int) intTEXT ·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). |
NEEDCTXT | Closure context передаётся в DX. |
WRAPPER | Тонкая обёртка (специальное обращение в stack trace). |
TLSBSS | Thread-local BSS data. |
TOPFRAME | Top of stack (например, goexit). |
ABIInternal | Использует ABIInternal (register-based, 1.17+). |
2.4. Calling conventions: ABI0 vs ABIInternal (1.17+)
Заголовок раздела «2.4. Calling conventions: ABI0 vs ABIInternal (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, R11Float returns: X0 — X14Closure context: DXG pointer (TLS): R14 ← важноStack base: BPFrame 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 явно.
2.5. Зачем asm в Go
Заголовок раздела «2.5. Зачем asm в Go»Категория 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.
2.6. Реальный asm-стаб: чтение runtime
Заголовок раздела «2.6. Реальный asm-стаб: чтение runtime»Посмотрим 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.
2.7. SIMD в Go: AVX2, AVX-512, NEON
Заголовок раздела «2.7. SIMD в Go: AVX2, AVX-512, NEON»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 doneloop: 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 loopdone: 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-пакетах вы увидите эти ветвления на верхнем уровне.
2.8. go:noescape pragma
Заголовок раздела «2.8. go:noescape pragma»Pragma говорит компилятору: «эта функция гарантирует, что её указатели-аргументы НЕ убегут».
Use case: asm-функция, которая работает с pointer arguments.
//go:noescapefunc xorBytesSSE2(dst, a, b *byte, n int)
// xor_amd64.sTEXT ·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.
2.9. go:linkname pragma
Заголовок раздела «2.9. go:linkname pragma»Самая мощная и опасная pragma в Go. Позволяет «прозвонить» в unexported символ из другого пакета.
Синтаксис:
import _ "unsafe" // обязательно для linkname
//go:linkname localFn pkg.RemoteFnfunc 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.startTimerfunc 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.
2.10. go:nosplit, go:noinline, go:registerparams
Заголовок раздела «2.10. go:nosplit, go:noinline, go:registerparams»//go:nosplit — функция не делает stack split check. Используется в runtime для функций, которые работают на g0 или в signal handler, где stack growth не работает.
⚠️ Размер функции ограничен (~512 bytes). Если превысит → compile error.
//go:nosplitfunc 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, явно не нужна.
2.11. Cgo vs assembly: когда что
Заголовок раздела «2.11. Cgo vs assembly: когда что»| Критерий | Cgo | Assembly |
|---|---|---|
| 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 growth | Cgo стек — отдельный, большой | Использует Go stack |
| Async preemption | Не работает в cgo | Работает в asm |
Правило: для maximum perf hot path — asm. Для серьёзных вычислений с большим C-кодом (libsodium, snappy) — cgo, но изолируйте через worker pool.
2.12. go tool objdump, DWARF, build IDs
Заголовок раздела «2.12. go tool objdump, DWARF, build IDs»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"(-sstrip symbol table,-wstrip DWARF).
Build IDs:
- Уникальный hash от source + compile config.
- В
go version -m binary. - Помогает: detection того, какая версия скомпилирована; voiding cache.
-trimpath:
- Убирает абсолютные пути из binary (
/home/user/proj/foo.go→foo.go). - Reproducible builds (одинаковый source → одинаковый binary).
- Должен использоваться в CI.
go tool nm — symbol table dump.
go tool nm ./binary | grep -i "myfunc"3. Подводные камни
Заголовок раздела «3. Подводные камни»-
Plan 9 SP — виртуальный.
0(SP)— это локалы. Hardware top stack недоступен напрямую без trick’ов. -
//go:noescapeбез asm-импл = UB. Pragma — это контракт. Compile-time проверки нет. -
//go:linknameк runtime может сломаться в любой минорной версии. Реальные кейсы: 1.20 переименовалruntime.fastrand→runtime.cheaprand— много libs сломалось одномоментно. -
//go:nosplitфункция слишком большая → compile error «nosplit stack overflow». Размер ~512 bytes max. -
ABIInternal asm требует знать какие регистры clobber-safe. Если забыли сохранить R12 в not-leaf функции — silent corruption.
-
VZEROUPPERобязательна после AVX-блоков перед SSE-инструкциями (transition penalty ~70 cycles). -
AVX-512 throttle. На Skylake-X использование AVX-512 снижает CPU frequency глобально для всего ядра. Trade-off в hot path: ускорение AVX-512 vs slowdown остальных потоков.
-
get_tls(CX)платформо-специфично. На Windows другой механизм TLS (MOVQ GS:0x28, CXдля thread block). Учитывайте. -
Plan 9 mnemonics ≠ Intel.
MOVLв Plan 9 — это 32-bit move (MOVL = movl). Не путать с long mode 64-bit. -
Argmap для GC. Если asm-функция принимает pointer-аргумент, GC должен знать его offset для scan. Если нет — может пропустить и crash. Compiler автогенерирует argmap из Go-объявления, но если объявлений нет (только asm) — нужен явный
FUNCDATA. -
//go:linknameexposes private API. Если 3rd-party library использует linkname к runtime — backward compat вашего проекта зависит от стабильности этого внутреннего API. Минимизируйте. -
NOSPLIT функции не могут вызывать non-NOSPLIT. Compiler проверяет — иначе stack overflow без preempt check.
-
runtime/internal/atomicvssync/atomic— разные пакеты с разными гарантиями. Один — runtime-internal, второй — public. Не путать. -
DWARF stripping (
-w) ломает stack traces вruntime/debug.Stackв части источников. Function names остаются, но line numbers могут пропасть. -
go tool objdump -gnuпоказывает GNU mnemonics, но это не идеально — есть несоответствия для специфичных Plan 9 инструкций.
4. Реальные production-кейсы
Заголовок раздела «4. Реальные production-кейсы»4.1. Cloudflare: gzip в asm
Заголовок раздела «4.1. Cloudflare: gzip в asm»- Стандартный
compress/gzipбыл bottleneck на edge. - Перепеисали critical path (huffman encoding, CRC32) на asm с AVX2/AVX-512.
- Throughput +180% на компресс, +90% на декомпресс.
- Затраты: 1 senior-инженер × 3 месяца, форк
github.com/klauspost/compress.
4.2. Авито: SHA256 для service-mesh signatures
Заголовок раздела «4.2. Авито: SHA256 для service-mesh signatures»- Auth-токены пересчитывали SHA256 100k+ раз в секунду.
- Switch на
crypto/sha256(SHA-NI hardware) дал 7x speedup из коробки. - Бэк: важно проверить CPU flags (
cpu.X86.HasSHA). На Atom/Celeron SHA-NI нет, fallback на scalar.
4.3. Тинькофф: linkname к runtime сломался в 1.20
Заголовок раздела «4.3. Тинькофф: linkname к runtime сломался в 1.20»- Использовали
runtime.fastrandчерез linkname для быстрого random. - В 1.20 переименовали → бинарь не собирался.
- Workaround: добавили версию-зависимый build tag, для 1.20+ —
cheaprand, для старых —fastrand. - В 1.23 strict mode полностью запретил → перешли на
math/rand/v2(новая стдлиб в 1.22+).
4.4. Яндекс: ABI0 → ABIInternal миграция
Заголовок раздела «4.4. Яндекс: ABI0 → ABIInternal миграция»- Кастомный asm для bloom filter (был на 1.13).
- После обновления на 1.17 perf упал на 6%.
- Причина: компилятор оборачивал каждый вызов asm-функции в ABI wrapper (ABIInternal → ABI0).
- Fix: добавили
<ABIInternal>к TEXT decl, переписали prologue/epilogue. - Восстановили + ещё +3% (без wrapper’а).
4.5. Сбер: gvisor зависит от linkname
Заголовок раздела «4.5. Сбер: gvisor зависит от linkname»- Использовали
gvisorдля sandboxing. - gvisor имеет ~60 linkname-хаков в runtime.
- При попытке обновиться на Go 1.23 — десятки compile errors из-за strict mode.
- Решили: остаться на 1.22 до выхода gvisor 0.0.20240XXX с обновлёнными хаками.
4.6. Open source: pebble (CockroachDB) и asm
Заголовок раздела «4.6. Open source: pebble (CockroachDB) и asm»- 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 платформах.
4.7. Open source: github.com/zeebo/blake3 (assembly-heavy)
Заголовок раздела «4.7. Open source: github.com/zeebo/blake3 (assembly-heavy)»- Реализация BLAKE3 на Go с avx512-asm.
- Skylake-X: 6 GB/s hash throughput (vs scalar 600 MB/s).
- Стало основой
crypto/blake3proposals в Go.
5. Вопросы на собесе Middle 3 (экспертный уровень)
Заголовок раздела «5. Вопросы на собесе Middle 3 (экспертный уровень)»-
Что такое Plan 9 assembly? Чем отличается от AT&T?
-
Назовите 4 псевдо-регистра в Plan 9. Что они означают?
-
Почему SP в Plan 9 «виртуальный»?
-
Объясните
TEXT ·Foo(SB), NOSPLIT, $16-24. Что такое 16, 24? -
Какие флаги для TEXT: NOSPLIT, NOFRAME, WRAPPER — когда нужны?
-
ABI0 vs ABIInternal — разница. С какой версии ABIInternal?
-
Какие регистры используются для аргументов в ABIInternal на AMD64?
-
Что такое R14 в ABIInternal? Почему именно R14?
-
Что такое DX в ABIInternal? (Closure context.)
-
Что такое ABI wrapper? Когда компилятор его вставляет?
-
Зачем asm в Go? Назовите 5 use cases.
-
Где в stdlib используется AVX2? AVX-512?
-
Объясните
VZEROUPPER. Когда обязательна? -
AVX-512 frequency throttle — что это и как избежать?
-
Pragma
//go:noescape— когда корректно применять? Что произойдёт при misuse? -
Pragma
//go:linkname— для чего? Назовите 3 примера в stdlib. -
Что изменилось в
//go:linknameв Go 1.23 (strict mode)? Как отключить? -
//go:nosplit— назначение. Ограничение по размеру. -
Чем
//go:noinlineотличается от-gcflags=-l? -
//go:registerparams— что это? Нужно ли применять в обычном коде? -
Cgo vs assembly — overhead на вызов. Когда что выбрать?
-
runtime·gogo— что делает? Опишите кратко. -
Что такое
systemstack? Когда вызывается? -
Async preempt: какая asm-функция в
runtime/preempt_amd64.sотвечает за сохранение регистров? -
Что такое
FUNCDATA,PCDATA? Кто их вставляет?
6. Practice
Заголовок раздела «6. Practice»-
Простой asm-stub. Напишите asm-функцию
func Mul(a, b int64) int64, использующуюIMULQ. Сравните perf с pure Go аналогом (бенчмарк должен показать, что Go-версия инлайнится и быстрее — это покажет нюанс). -
go tool objdumpисследование. Соберите hello-world. Сделайтеgo tool objdump -s "main\.main". Идентифицируйте: prologue (stack check), вызовfmt.Println, epilogue. -
ABIInternal vs ABI0. Напишите две версии одной asm-функции — одна
<ABIInternal>, другая обычная. Бенчмарк. Замерьте wrapper overhead. -
linknameк stdlib. Используя//go:linkname, добавьте в свой пакет alias кruntime.nanotime. Получите наносекундное время быстрее, чем черезtime.Now(). Сравните бенчмарком. -
//go:noescapecorrectness. Напишите asm-функцию с pointer arg. Безnoescape— посмотрите escape report. Сnoescape— без heap alloc. Подтвердите. -
AVX2 XOR. Напишите asm
func xorAVX2(dst, a, b []byte, n int). Бенчмарк vs pure Go. Ожидаем 4-8x ускорение на больших буферах. -
internal/cpudetection. Распечатайте все флаги CPU (HasAVX2, HasSHA, HasAES, …). Сделайте утилиту, которая проверяет, доступны ли все нужные для вашего сервиса инструкции. -
DWARF strip experiment. Соберите программу с/без
-ldflags="-s -w". Сравните размер. Запустите панику в обоих — проверьте, какая информация теряется.
7. Источники
Заголовок раздела «7. Источники»- Go Assembly Language Manual —
cmd/asm/doc.go+ golang.org/doc/asm. - «A Quick Guide to Go’s Assembler» — официальный doc (go.dev/doc/asm).
runtime/asm_amd64.s— читать целиком. Особенно gogo, mcall, systemstack, asyncPreempt.- «Plan 9 Assembly» — Ken Thompson, оригинальный doc Plan 9.
- «Go register ABI» — Cherry Zhang, design doc 40724-register-calling.md.
- «Writing assembly in Go» — Dave Cheney, dave.cheney.net (2013, но всё ещё актуален).
- «Avoid Go runtime traps» — Roberto Clapis на linkname/noescape тонкости.
- «Linkname strict mode in Go 1.23» — Go Blog (2024).
- «SIMD в Go» — Filippo Valsorda, blog.filippo.io (про crypto asm).
- «Klauspost’s compress library» — github.com/klauspost/compress (best-in-class asm для compression).
- «Zeebo/blake3» — пример full AVX-512 implementation.
cmd/asm/internal/arch/*.go— Go assembler internals.- «Go’s Hidden Pragmas» — Dave Cheney, dave.cheney.net.
- «How Go Implements
time.Now()» — Filippo, ссылка на linkname к runtime.nanotime. - DWARF v4/v5 specifications — dwarfstd.org.