Zpět na domů

Go Assembler: jak se kód mění v strojové instrukce | Základy

Článek odhaluje mechanismus překladu Go kódu do strojových instrukcí skrz Go Assembler. Jsou probrány pseudoregistry, operace load/store, aritmetické instrukce a řízení toku provádění s praktickými příklady.

Ponoření do Go Assembleru: od kódu ke strojovým instrukcím
Advertisement 728x90

# 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í:

Google AdInline article slot
  • 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í symboly
  • x-8(SP) — lokální proměnné ve stacku
  • arg+0(FP) — argumenty funkce

Deklarace funkce v Go Assembleru vypadá takto:

Google AdInline article slot
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:

Google AdInline article slot
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, JGT atd.
  • 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 CALL s uložením adresy návratu
  • Po dokončení — RET s 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

Advertisement 728x90

Číst dál