# Jak se Go kód převádí na strojové instrukce: základy Go Assembleru
Kompilátor Go převádí kód na vysoké úrovni do strojových instrukcí, které provádí procesor. V tomto článku rozebereme, jak je strukturován Go Assembler, jaké instrukce generuje kompilátor a jak souvisí s původním kódem. Zaměříme se na praktickou analýzu abstrakcí nezávislých na architektuře a jejich propojení s reálným hardwarem.
Pseudoregistry a nezávislost na architektuře
Go Assembler používá vlastní syntaxi, která se zásadně liší od standardního GAS/Intel. Klíčovou vlastností jsou pseudoregistry, které skrývají detaily cílové architektury:
- SB (Symbol Base): Globální proměnné a statické symboly
- SP (Stack Pointer): Vrchol aktuálního stack frame
- FP (Frame Pointer): Ukazatel na argumenty funkce
- PC (Program Counter): Adresa následující instrukce
Tyto abstrakce zajišťují:
- Přenositelnost kódu mezi x86, ARM, RISC-V
- Stabilitu ABI mezi verzemi Go
- Integraci s garbage collectorem prostřednictvím stack map
- Zjednodušení práce linkeru
Příklad převodu:
MOVQ AX, main.y(SB) ; GoASM
mov [rip+offset], rax ; x86 ASM
Systém adresování v Go Assembleru používá specifické konstrukce:
name(SB)— globální symbolyx-8(SP)— lokální proměnné ve stackuarg+0(FP)— argumenty funkce
Deklarace funkce v Go Assembleru vypadá takto:
TEXT main.main(SB), ABIInternal, $0-8
Kde ABIInternal označuje interní rozhraní používané runtime a $0-8 — velikost stack frame.
Mechanika operací Load/Store
Procesor pracuje pouze s registry a pamětí. Všechny operace s proměnnými se snižují na:
- Načtení dat z paměti do registru (load)
- Výpočty v registrech
- Uložení výsledku do paměti (store)
Prozkoumejme jednoduchý příklad:
x := 1
y = x
Bez optimalizací kompilátor generuje:
MOVQ $1, main.x-8(SP) ; Zápis konstanty do stacku
MOVQ main.x-8(SP), AX ; Load x do registru
MOVQ AX, main.y(SB) ; Store do y
Instrukce MOVQ (Move Quadword) operuje s 8bajtovými hodnotami. Suřadky označují velikost:
- B (Byte) — 1 bajt
- W (Word) — 2 bajty
- L (Long) — 4 bajty
- Q (Quadword) — 8 bajtů
Při kopírování struktur kompilátor operaci rozkládá:
type Point struct { x, y int }
p1 := Point{1, 2}
p2 := p1
Odpovídající assembler:
MOVQ main.p1(SP), AX ; Kopírujeme x
MOVQ AX, main.p2(SP)
MOVQ main.p1+8(SP), AX ; Kopírujeme y
MOVQ AX, main.p2+8(SP)
Práce s ukazateli používá LEAQ (Load Effective Address):
x := 10
p := &x
*p = 20
LEAQ main.x(SP), AX ; Získáme adresu x
MOVQ AX, main.p(SP) ; Uložíme do ukazatele
MOVQ $20, (AX) ; Dereferencování a zápis
Aritmetika a logické operace
Aritmetické instrukce odpovídají ISA procesoru, ale jsou přizpůsobeny semantice Go. Prozkoumejme základní operace:
Sčítání/odečítání:
ADDQ BX, AX ; AX = AX + BX
SUBQ BX, AX ; AX = AX - BX
Násobení/dělení:
IMULQ BX, AX ; AX = AX * BX
CQO ; Příprava na dělení
IDIVQ BX ; AX = (DX:AX)/BX, DX = zbytek
Bitové operace:
ANDQ BX, AX ; Logické A
ORQ BX, AX ; Logické NEBO
XORQ BX, AX ; Exkluzivní NEBO
SHLQ CL, BX ; Posun vlevo
SHRQ CL, BX ; Posun vpravo
Porovnání:
CMPQ BX, AX ; Nastaví vlajky
JLT less ; Skok pokud AX < BX
Peculiarita TESTQ — bitové A hodnoty se samou sebou. Například kontrola if x == 0:
TESTQ AX, AX
JEQ zero ; Skok pokud výsledek 0
Dělení je nejnáročnější operace. Kompilátor ji často nahrazuje bitovými posuny, pokud je to možné.
Řízení toku provádění
Procesor standardně provádí instrukce sekvenčně. Pro změnu pořadí se používají:
- Podmíněné skoky:
JEQ,JLT,JGTatd. - Nepodmíněné skoky:
JMP - Volání funkcí:
CALL/RET
Příklad větvení:
if x < y {
// ...
}
CMPQ BX, AX ; Porovnáme x a y
JGE skip ; Skok pokud x >= y
... ; Kód bloku if
skip:
V Go Assembleru je řízení toku úzce spojeno s ABI. Například při volání funkce:
- Argumenty se umístí do stacku nebo registrů
- Provede se
CALLs uložením adresy návratu - Po dokončení —
RETs obnovením kontextu
Je důležité pochopit, že kompilátor může přeuspořádat instrukce v rámci garancí jazyka. Například operace čtení/zápisu mohou měnit pořadí, pokud to neovlivní viditelný výsledek.
Co je důležité
- Pseudoregistry — klíč k nezávislosti Go na architektuře. SB, SP, FP abstrakují detaily hardwaru.
- Model Load/Store — všechny operace s pamětí probíhají přes registry. Neexistují přímé operace paměť-paměť.
- Velikost operandů — suřadky B/W/L/Q určují bajtovou šířku operace. Chyba ve výběru vede k nesprávnému fungování.
- Dělení — nejpomalejší aritmetická operace. Vyhněte se jí v hot path pomocí bitových posunů.
- Vlajky procesoru — výsledek porovnání se neukládá do registrů, ale ovlivňuje vlajky používané podmíněnými skoky.
Pro analýzu generovaného kódu použijte:
go tool compile -S main.go
Nebo pro debugovou verzi:
go build -gcflags="-N -l" -o app main.go
go tool objdump -s "main.main" ./app
Pamatujte: Go Assembler není konečným cílem, ale nástrojem pro pochopení toho, jak váš kód interaguje s hardwarem. Tento model pomáhá psát efektivní kód s vědomým zacházením s pamětí a procesorovými zdroji.
— Editorial Team
Zatím žádné komentáře.