Technologie mezijazyků (mezikódů) a bajtkódů: LLVM IR

Dnes
Doba čtení: 37 minut

Sdílet

Dva kolegové pracují na svých dvou počítačích
Autor: Shutterstock
Mezijazyky se často využívají také v oblasti sofistikovaných překladačů. Do této kategorie spadají překladače, které jsou součástí projektu LLVM, které využívají mezijazyk LLVM IR.

Obsah

1. Technologie mezijazyků (mezikódů) a bajtkódů v moderních interpretrech a překladačích: LLVM IR

2. LLVM IR

3. Nejjednodušší kód přeložitelný do LLVM IR: prázdná funkce bez parametrů

4. Překlad funkce noop do LLVM IR

5. Rozdíly ve výsledném mezikódu

6. Překlad do bitkódu

7. Způsob překladu funkce provádějící součet dvou celých čísel bez znaménka

8. Základní instrukce použité v LLVM IR

9. Jak číst kód v LLVM IR (první část)

10. Překlad funkce pro součet dvou celočíselných hodnot se znaménkem: nedefinované chování

11. Základní aritmetické operace a operace pro bitové posuny

12. Instrukce pro aritmetické operace a pro provedení bitových posunů

13. Operace provádějící porovnání celočíselných operandů

14. Instrukce pro porovnání celočíselných operandů

15. Modifikace instrukcí LLVM IR při operacích s různými datovými typy

16. Výsledný kód reprezentovaný v LLVM IR

17. Příloha A: seznam doposud popsaných instrukcí používaných v LLVM IR

18. Příloha B: Makefile soubor pro překlad všech demonstračních příkladů

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Technologie mezijazyků (mezikódů) a bajtkódů v moderních interpretrech a překladačích: LLVM IR

V úvodním článku o technologii mezijazyků a bajtkódu jsme si ve stručnosti popsali struktury bajtkódů využívaných virtuálními stroji programovacích jazyků Python (respektive přesněji řečeno CPython), Lua (klasická Lua, nikoli LuaJIT) a taktéž Javy. Ovšem mezijazyky se v současnosti velmi často využívají i v oblasti sofistikovaných překladačů. Do této kategorie spadají i překladače, které jsou součástí infrastruktury postavené okolo projektu LLVM. Dnes si nejdříve ve stručnosti popíšeme, jakým způsobem vlastně probíhá překlad programů napsaných v programovacím jazyku C a překládaných s využitím Clangu do mezijazyka nazvaného LLVM IR. Posléze si popíšeme základní vlastnosti LLVM IR, které byly navrženy (a postupně rozšiřovány) takovým způsobem, aby na jedné straně pokryly sémantiku všech podporovaných programovacích jazyků (včetně například Rustu) a na straně druhé umožnily optimalizovaný překlad pro všechny cílové platformy (z nichž mnohé podporují pokročilé SIMD operace atd.).

V současnosti je LLVM IR velmi dobře zdokumentován a nejméně důležité je, že je i velmi stabilní. Jsou do něj sice postupně přidávány další vlastnosti, které jsou vyžadovány například moderním C++ či Rustem, ovšem základ LLVM IR se již po několik let prakticky nezměnil. To umožnilo vznik dalších předních i zadních překladačů (přední překladač neboli frontend pracuje s konkrétním programovacím jazykem a generuje LLVM IR zatímco zadní překladač neboli backend naopak z LLVM IR generuje výsledný strojový kód popř. čitelný kód v assembleru nebo bajtkód pro WebAssembly atd.).

Mezikód

Obrázek 1: Pohled na činnost překladače, který je zde chápán jako černá skříňka, jež transformuje zdrojový kód na strojový kód nebo bajtkód. 

Autor: tisnik, podle licence: Rights Managed

2. LLVM IR

Samotný LLVM IR je možné reprezentovat dvěma zcela odlišnými způsoby. První způsob je založen na textové (a nutno říci, že i velmi dobře čitelné) reprezentaci. Tímto způsobem zobrazení LLVM IR se budeme zabývat primárně. Ovšem existuje alternativní způsob reprezentace, a to konkrétně reprezentace binární, která se nazývá bitkód (bitcode). Binární reprezentace je (pochopitelně) nečitelná resp. je nutné při jejím zkoumání používat různé pomocné nástroje. Na druhou stranu může být programové zpracování bitkódu efektivnější, a to jak z hlediska spotřeby operační paměti, tak i rychlosti generování, transformací (optimalizací) či výsledném překladu do strojového kódu. V dnešním článku si pouze ukážeme, jakým způsobem je možné bitkód generovat, ale nebudeme se jím (alespoň prozatím) podrobněji zabývat. Podrobný popis bitkódu lze v případě potřeby nalézt na adrese https://llvm.org/docs/Bit­CodeFormat.html.

LLVM

Obrázek 2: Fáze překladu v rodině Clang+LLVM s rozdělením na frontend a backend fáze (tedy na přední a zadní překladač). Uprostřed se nachází reprezentace založená na LLVM IR. 

Autor: tisnik, podle licence: Rights Managed

3. Nejjednodušší kód přeložitelný do LLVM IR: prázdná funkce bez parametrů

V úvodní části dnešního článku si ukážeme, jak vlastně LLVM IR vypadá. Začneme tím nejjednodušším zdrojovým kódem, který je přeložitelný do LLVM IR bez toho, aby byl výsledkem kód bez instrukcí. Jedná se o céčkovskou funkci bez parametrů, bez návratové hodnoty a s prázdným tělem. Z pohledu programovacího jazyka C je to však plnohodnotná funkce, která je potenciálně volatelná a bude tedy přeložena do LLVM IR (později však může být v dalších fázích překladu zcela odstraněna):

void noop(void) {
}

Jméno noop použité v tomto kódu znamená no-op neboli no operation, někdy též psáno nop (například v assembleru atd.)

Poznámka: zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/noop.c.

4. Překlad funkce noop do LLVM IR

Pro překlad zdrojových kódů z programovacího jazyka C do mezijazyka LLVM IR se používá „přední“ překladač Clang. Musíme ovšem použít přepínače -S (překlad do čitelného textového formátu) a -emit-llvm. Výsledkem překladu bude v takovém případě soubor s koncovkou .ll.

Výsledek ovlivňují parametry překladu, především pak zvolené optimalizace (nebo naopak jejich zákaz) a cílová architektura. Informace o cílové architektuře se předávají do zadního překladače a současně mohou ovlivnit i to, zde bude do LLVM IR zahrnut i „mrtvý“ kód.

Při zákazu optimalizací a překladu na aktuální platformu (x86–64) získáme tento soubor:

; ModuleID = 'noop.c'
source_filename = "noop.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-redhat-linux-gnu"
 
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @noop() #0 {
  ret void
}
 
attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
 
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
 
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"uwtable", i32 2}
!2 = !{i32 7, !"frame-pointer", i32 2}
!3 = !{!"clang version 20.1.8 (Fedora 20.1.8-4.fc42)"}

Překladem s povolením optimalizací na velikost, tj. -Os vznikne soubor:

; ModuleID = 'noop.c'
source_filename = "noop.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-redhat-linux-gnu"
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local void @noop() local_unnamed_addr #0 {
  ret void
}
 
attributes #0 = { mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
 
!llvm.module.flags = !{!0, !1}
!llvm.ident = !{!2}
 
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"uwtable", i32 2}
!2 = !{!"clang version 20.1.8 (Fedora 20.1.8-4.fc42)"}

Ovšem můžeme provést i překlad pro jiné platformy, například přepínačem –target=aarch64 překlad pro ARMovskou architekturu AArch64. Mezikód bude opět odlišný:

; ModuleID = 'noop.c'
source_filename = "noop.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64"
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local void @noop() local_unnamed_addr #0 {
  ret void
}
 
attributes #0 = { mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+v8a,-fmv" }
 
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
 
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"uwtable", i32 2}
!2 = !{i32 7, !"frame-pointer", i32 1}
!3 = !{!"clang version 20.1.8 (Fedora 20.1.8-4.fc42)"}

A konečně si ukažme překlad pro (pseudo)architekturu WebAssembly, kterou zajistíme přepínačem –target=wasm32:

; ModuleID = 'noop.c'
source_filename = "noop.c"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
target triple = "wasm32"
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none)
define hidden void @noop() local_unnamed_addr #0 {
  ret void
}
 
attributes #0 = { mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+multivalue,+mutable-globals,+nontrapping-fptoint,+reference-types,+sign-ext" }
 
!llvm.module.flags = !{!0}
!llvm.ident = !{!1}
 
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 20.1.8 (Fedora 20.1.8-4.fc42)"}

5. Rozdíly ve výsledném mezikódu

Jak je z předchozích ukázek patrné, obsahuje mezikód kromě dalších údajů i velké množství metainformací, které se předávají zadnímu překladači. Nás ovšem bude zajímat především ta část LLVM IR, ve které je vypsán mezikód překládané funkce noop. Uveďme si tedy výsledky jednotlivých variant překladu.

Překlad bez povolení optimalizací:

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @noop() #0 {
  ret void
}

Překlad s povolením optimalizací. Zde se objevuje klauzule local_unnamed_addr, která zadnímu překladači oznamuje, že funkce není volána a mohl by ji tedy při optimalizacích vynechat:

; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local void @noop() local_unnamed_addr #0 {
  ret void
}

Překlad pro platformu AArch64 s povolením optimalizací povede k naprosto totožné funkci reprezentované v mezikódu:

; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local void @noop() local_unnamed_addr #0 {
  ret void
}

Překlad pro platformu WebAssembly s povolením optimalizací. Chybí zde atribut uwtable, který je vyžadován pro formát ELF, ale nikoli pro WebAssemly:

; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none)
define hidden void @noop() local_unnamed_addr #0 {
  ret void
}

Samotné tělo funkce a do značné míry i její hlavička, jsou ve všech čtyřech případech totožné, ovšem poměrně zásadně se odlišují atributy funkce vypsané na řádku před její hlavičkou.

6. Překlad do bitkódu

LLVM IR má dvě formy reprezentace. První forma je textová a viděli jsme ji v předchozí dvojici kapitol. Druhá forma je binární a nazývá se bitkód (bitcode). Tato forma je popsána zde a získat ji můžeme překladem při použití přepínačů -c a -emit-llvm:

$ clang -c -emit-llvm noop.c

Výsledkem bude v tomto případě soubor nazvaný noop.bc s velikostí přibližně dvou kilobajtů:

$ file noop.bc
noop.bc: LLVM IR bitcode
 
$ ls -l noop.bc
-rw-r--r--. 1 ptisnovs ptisnovs 2196 Jan 29 14:28 noop.bc

Obsah tohoto souboru je obecně nečitelný (binární):

$ od -tx1 noop.bc | head
 
0000000 42 43 c0 de 35 14 00 00 05 00 00 00 62 0c 30 24
0000020 4a 59 be a6 4d fb b5 cf 0b 51 80 4c 01 00 00 00
0000040 21 0c 00 00 eb 01 00 00 0b 02 21 00 02 00 00 00
0000060 19 00 00 00 07 81 23 91 41 c8 04 49 06 10 32 39
0000100 92 01 84 0c 25 05 08 19 1e 04 8b 62 80 0c 45 02
0000120 42 92 0b 42 64 10 32 14 38 08 18 4b 0a 32 32 88
0000140 48 70 c4 21 23 44 12 87 8c 10 41 92 02 64 c8 08
0000160 b1 14 20 43 46 88 20 c9 01 32 32 84 58 0e 90 91
0000200 21 44 90 a1 82 a2 02 19 c3 07 cb 15 19 32 8c 8c
0000220 25 10 1d 3a 74 c8 00 00 89 20 00 00 0a 00 00 00
Poznámka: bitkódem se prozatím nebudeme zabývat. V dalším textu budeme vždy využívat textovou reprezentaci LLVM IR.

7. Způsob překladu funkce provádějící součet dvou celých čísel bez znaménka

Pro studium mezijazyka LLVM IR je pochopitelně vhodnější použít funkce se složitější vnitřní strukturou, než jakou má výše zmíněná funkce noop. Naprogramujeme tedy funkci nazvanou jednoduše add, která provede součet svých dvou parametrů typu celé číslo bez znaménka a vrátí výsledek tohoto součtu:

unsigned int add(unsigned int x, unsigned int y) {
    return x+y;
}
Poznámka: zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/ad­d_unsigned.c.

Pokud jsou při překladu zakázány optimalizace, bude výsledek v mezikódu vypadat takto:

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @add(i32 noundef %0, i32 noundef %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  store i32 %0, ptr %3, align 4
  store i32 %1, ptr %4, align 4
  %5 = load i32, ptr %3, align 4
  %6 = load i32, ptr %4, align 4
  %7 = add i32 %5, %6
  ret i32 %7
}

Při povolení optimalizací (a je jedno, jestli na velikost nebo na rychlost) získáme následující výsledek:

; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @add(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = add i32 %1, %0
  ret i32 %3
}
Poznámka: povšimněte si, že před jménem funkce add je uveden symbol @, který se v LLVM IR nazývá sigil a je použit před každým symbolem získaným z původního zdrojového kódu. Taktéž si povšimněte, že LLVM IR je typovaný – typ má jak funkce add, tak i oba její (nepojmenované) parametry označované symboly %0 a %1.

8. Základní instrukce použité v LLVM IR

V obou variantách funkce add přeložené do LLVM IR je možné najít hned šest různých typů instrukcí. Tyto instrukce jsou vypsány v následující tabulce:

# Jméno instrukce Parametry Stručný popis instrukce
1 alloca typ (velikost) alokace místa na zásobníku
2 load adresa načtení hodnoty z paměti
3 store hodnota, adresa uložení hodnoty do paměti
4 add hodnota1, hodnota2 součet dvou hodnot
5 ret hodnota návrat z funkce

Některé instrukce připomínají klasický assembler, ovšem s tím rozdílem, že jejich operandy jsou typovány (i32, ptr atd.)::

  store i32 %0, ptr %3, align 4
  ...
  ...
  ...
  ret i32 %7

Povšimněte si dále, že další instrukce jsou použity na levé straně přiřazovacího výrazu. Jedná se o takové instrukce, které nějakým způsobem vyhodnocují výslednou hodnotu. Ta se ukládá do (v tomto případě nepojmenovaného) slotu označovaného symbolem %x. Může se jednat buď o globální hodnotu, hodnotu naalokovanou na zásobníkovém rámci, nebo o (lokální) pracovní registr – o tom se do značné míry rozhoduje až v další fázi překladu:

  %3 = alloca i32, align 4
  ...
  ...
  ...
  %5 = load i32, ptr %3, align 4
  ...
  ...
  ...
  %7 = add i32 %5, %6

9. Jak číst kód v LLVM IR (první část)

S pomocí výše uvedeného popisu instrukcí LLVM IR je již poměrně snadné přečíst mezikód reprezentovaný v LLVM IR. Vyzkoušíme si to na mezikódu funkce add, která byla přeložena bez povolení optimalizací. Kód je tedy zbytečně dlouhý, ovšem o to pochopitelnější:

define dso_local i32 @add(i32 noundef %0, i32 noundef %1) #0 {
  %3 = alloca i32, align 4         ; alokace 32bitové hodnoty na zásobníku, zarovnání je nastaveno na 32bitové adresy
  %4 = alloca i32, align 4         ; alokace 32bitové hodnoty na zásobníku, zarovnání je nastaveno na 32bitové adresy 
  store i32 %0, ptr %3, align 4    ; uložení prvního 32bitového parametru funkce na adresu uloženou v %3
  store i32 %1, ptr %4, align 4    ; uložení druhého 32bitového parametru funkce na adresu uloženou v %4
  %5 = load i32, ptr %3, align 4   ; načtení 32bitové hodnoty z adresy uložené v %3 do %5 (hodnota prvního parametru funkce)
  %6 = load i32, ptr %4, align 4   ; načtení 32bitové hodnoty z adresy uložené v %4 do %6 (hodnota druhého parametru funkce)
  %7 = add i32 %5, %6              ; součet s uložením výsledku do %7
  ret i32 %7                       ; výsledek uložený v %7 je současně i návratovou hodnotou funkce
Poznámka: z komentářů je patrné, jak je vlastně celý kód neefektivní. Zadní překladač z tohoto mezikódu vytvoří tuto sekvenci instrukcí:
    push    rbp
    mov rbp, rsp                    ; konstrukce zásobníkového rámce
    mov dword ptr [rbp - 4], edi    ; uložení prvního 32bitového parametru funkce na zásobníkový rámec
    mov dword ptr [rbp - 8], esi    ; uložení druhého 32bitového parametru funkce na zásobníkový rámec 
    mov eax, dword ptr [rbp - 4]    ; načtení 32bitové hodnoty do registru EAX (hodnota prvního parametru funkce)
    add eax, dword ptr [rbp - 8]    ; součet s uložením výsledku do registru EAX
    pop rbp
    ret                             ; návrat z funkce; ABI specifikuje návratovou hodnotu v EAX

Jen pro připomenutí: V ABI Linuxu se první dva (32bitové) celočíselné parametry předávají v pracovních registrech EDI a ESI (tedy nikoli přes zásobník) a návratová hodnota je uložena do pracovního registru EAX.

10. Překlad funkce pro součet dvou celočíselných hodnot se znaménkem: nedefinované chování

Předchozí příklad nyní upravíme do takové podoby, aby se namísto celočíselných hodnot bez znaménka (unsigned) sčítaly celočíselné hodnoty se znaménkem (signed). Jedná se tedy o (alespoň zdánlivě) triviální úpravu, která by neměla přinášet žádné další problémy:

int add(int x, int y) {
    return x+y;
}
Poznámka: zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/add_signed.c.

Překlad do LLVM IR bude v tomto případě vypadat následovně:

; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @add(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = add nsw i32 %1, %0
  ret i32 %3
}

Překlad je prakticky totožný, ovšem namísto instrukce:

  %3 = add i32 %1, %0

zde můžeme vidět instrukci:

  %3 = add nsw i32 %1, %0

Modifikátor nsw označuje instrukci, která má (pro některé vstupní operandy) nedefinované chování neboli undefined behaviour. To zadnímu překladači umožňuje provádět různé více či méně sofistikované optimalizace, například neprovádět test na přetečení (overflow) na některých architekturách atd. Vše je přitom naprosto korektní, protože z pohledu specifikace programovacího jazyka C se opravdu o nedefinované chování jedná.

11. Základní aritmetické operace a operace pro bitové posuny

LLVM IR musí obsahovat i instrukce určené pro provádění aritmetických operací popř. bitových posunů. Prozatím jsme se seznámili jen s instrukcí add, takže si vyzkoušejme, jakým způsobem se přeloží zdrojový kód obsahující další aritmetické operace a operace pro bitové posuny. Použijeme makra, aby nebylo nutné ručně zapisovat funkce pro všechny datové typy parametrů atd.:

#define NEGNEG(type) type neg_##type(type x) {return -x;}
#define ADDADD(type) type add_##type(type x, type y) {return x+y;}
#define SUBSUB(type) type sub_##type(type x, type y) {return x-y;}
#define MULMUL(type) type mul_##type(type x, type y) {return x*y;}
#define DIVDIV(type) type div_##type(type x, type y) {return x/y;}
#define SHLSHL(type) type shl_##type(type x, type y) {return x<<y;}
#define SHRSHR(type) type shr_##type(type x, type y) {return x>>y;}
 
#define ALL(type) \
    NEG(type) \
    ADD(type) \
    SUB(type) \
    MUL(type) \
    DIV(type) \
    SHL(type) \
    SHR(type)
 
ALL(int)
ALL(unsigned)

Výsledek činnosti preprocesoru jazyka C:

int neg_int(int x) {return -x;}
int add_int(int x, int y) {return x+y;}
int sub_int(int x, int y) {return x-y;}
int mul_int(int x, int y) {return x*y;}
int div_int(int x, int y) {return x/y;}
int shl_int(int x, int y) {return x<<y;}
int shr_int(int x, int y) {return x>>y;}
 
unsigned neg_unsigned(unsigned x) {return -x;}
unsigned add_unsigned(unsigned x, unsigned y) {return x+y;}
unsigned sub_unsigned(unsigned x, unsigned y) {return x-y;}
unsigned mul_unsigned(unsigned x, unsigned y) {return x*y;}
unsigned div_unsigned(unsigned x, unsigned y) {return x/y;}
unsigned shl_unsigned(unsigned x, unsigned y) {return x<<y;}
unsigned shr_unsigned(unsigned x, unsigned y) {return x>>y;}
Poznámka: zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/a­rith_operators.c.

Výsledek překladu do LLVM IR:

; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 -2147483647, -2147483648) i32 @neg_int(i32 noundef %0) local_unnamed_addr #0 {
  %2 = sub nsw i32 0, %0
  ret i32 %2
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @add_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = add nsw i32 %1, %0
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @sub_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = sub nsw i32 %0, %1
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @mul_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = mul nsw i32 %1, %0
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @div_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = sdiv i32 %0, %1
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @shl_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = shl i32 %0, %1
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @shr_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = ashr i32 %0, %1
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @neg_unsigned(i32 noundef %0) local_unnamed_addr #0 {
  %2 = sub i32 0, %0
  ret i32 %2
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @add_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = add i32 %1, %0
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @sub_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = sub i32 %0, %1
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @mul_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = mul i32 %1, %0
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @div_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = udiv i32 %0, %1
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @shl_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = shl i32 %0, %1
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @shr_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = lshr i32 %0, %1
  ret i32 %3
}
Poznámka: zajímavá je hlavička první funkce s modifikátorem range, který bude popsán příště:
define dso_local range(i32 -2147483647, -2147483648) i32 @neg_int(i32 noundef %0) local_unnamed_addr #0 {

12. Instrukce pro aritmetické operace a pro provedení bitových posunů

Z předchozího kódu reprezentovaného v LLVM IR lze snadno vyčíst názvy všech instrukcí určených pro provádění aritmetických operací i bitových posunů. Všechny tyto instrukce jsou shrnuty v následující tabulce:

# Jméno instrukce Parametry Stručný popis instrukce
1 icmp eq hodnota1, hodnota2 porovnání dvou operandů na relaci „rovno“
2 icmp ne hodnota1, hodnota2 porovnání dvou operandů na relaci „nerovno“
3 icmp slt hodnota1, hodnota2 porovnání dvou operandů se znaménkem na relaci „menší než“
4 icmp sle hodnota1, hodnota2 porovnání dvou operandů se znaménkem na relaci „menší nebo rovno“
5 icmp sgt hodnota1, hodnota2 porovnání dvou operandů se znaménkem na relaci „větší než“
6 icmp sge hodnota1, hodnota2 porovnání dvou operandů se znaménkem na relaci „větší nebo rovno“
7 icmp ult hodnota1, hodnota2 porovnání dvou operandů bez znaménka na relaci „menší než“
8 icmp ule hodnota1, hodnota2 porovnání dvou operandů bez znaménka na relaci „menší nebo rovno“
9 icmp ugt hodnota1, hodnota2 porovnání dvou operandů bez znaménka na relaci „větší než“
10 icmp uge hodnota1, hodnota2 porovnání dvou operandů bez znaménka na relaci „větší nebo rovno“
Poznámka: jak je z tabulky patrné, jedná se vlastně o jedinou instrukci, která však obsahuje modifikátor s popisem typu relace, která se má vyhodnotit.

13. Operace provádějící porovnání celočíselných operandů

V dalším demonstračním příkladu zjistíme, jaké operace se v LLVM IR využívají pro realizaci operací porovnání celočíselných operandů. Pro tento účel upravíme předchozí zdrojový kód do podoby, ve které budou s využitím maker zkonstruovány funkce pro porovnání dvou operandů stejných typů:

#define EQ(type) type eq_##type(type x, type y) {return x==y;}
#define NE(type) type ne_##type(type x, type y) {return x!=y;}
#define LT(type) type lt_##type(type x, type y) {return x<y;}
#define LE(type) type le_##type(type x, type y) {return x<=y;}
#define GT(type) type gt_##type(type x, type y) {return x>y;}
#define GE(type) type ge_##type(type x, type y) {return x>=y;}
 
#define ALL(type) \
    EQ(type) \
    NE(type) \
    LT(type) \
    LE(type) \
    GT(type) \
    GE(type)
 
ALL(int)
ALL(unsigned)
Poznámka: zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/com­parison_operators.c.

Po expanzi všech maker, které jsou ve zdrojovém kódu použity, získáme tento zdrojový kód, který je předán přednímu překladači:

int eq_int(int x, int y) {return x==y;}
int ne_int(int x, int y) {return x!=y;}
int lt_int(int x, int y) {return x<y;}
int le_int(int x, int y) {return x<=y;}
int gt_int(int x, int y) {return x>y;}
int ge_int(int x, int y) {return x>=y;}
 
unsigned eq_unsigned(unsigned x, unsigned y) {return x==y;}
unsigned ne_unsigned(unsigned x, unsigned y) {return x!=y;}
unsigned lt_unsigned(unsigned x, unsigned y) {return x<y;}
unsigned le_unsigned(unsigned x, unsigned y) {return x<=y;}
unsigned gt_unsigned(unsigned x, unsigned y) {return x>y;}
unsigned ge_unsigned(unsigned x, unsigned y) {return x>=y;}

Výsledek překladu do LLVM IR s povolením optimalizací kódu na velikost:

; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @eq_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp eq i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @ne_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp ne i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @lt_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp slt i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @le_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp sle i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @gt_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp sgt i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @ge_int(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp sge i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @eq_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp eq i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @ne_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp ne i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @lt_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp ult i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @le_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp ule i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @gt_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = icmp ugt i32 %0, %1
  %4 = zext i1 %3 to i32
  ret i32 %4
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @ge_unsigned(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
%3 = icmp uge i32 %0, %1
%4 = zext i1 %3 to i32
ret i32 %4
}
Poznámka: instrukci nazvanou zext provádějící znaménkové rozšíření výsledku na určenou bitovou šířku (zde konkrétně na 32 bitů) si podrobněji popíšeme příště.

14. Instrukce pro porovnání celočíselných operandů

Z mezikódu vypsaného v předchozí kapitole můžeme opět poměrně snadno vyčíst názvy a operandy všech instrukcí, které popisují porovnání celočíselných operandů:

# Jméno instrukce Parametry Stručný popis instrukce
1 add hodnota1, hodnota2 součet dvou hodnot
2 sub hodnota1, hodnota2 rozdíl dvou hodnot
3 mul hodnota1, hodnota2 součin dvou hodnot
4 sdiv hodnota1, hodnota2 podíl dvou hodnot, které mají znaménko (signed)
5 udiv hodnota1, hodnota2 podíl dvou hodnot, které jsou bezznaménkové (unsigned)
6 shl hodnota, posun aritmetický či bitový posun doleva
7 ashr hodnota, posun aritmetický posun doprava
8 lshr hodnota, posun bitový posun doprava

15. Modifikace instrukcí LLVM IR při operacích s různými datovými typy

Dozvěděli jsme se, jak se jmenují instrukce LLVM IR, které provádí základní aritmetické operace. Ovšem prozatím nevíme, jestli jsou tyto instrukce dostupné i pro operandy, které nemají šířku třiceti dvou bitů a už vůbec nevíme, jestli jsou podobné operace definovány i pro datové typy float a double (které se v mnoha ohledech od celočíselných typů odlišují). To však bude relativně snadné zjistit, protože například můžeme definovat makro určené pro konstrukci funkce pro součet dvou operandů různých typů a následně si nechat toto makro expandovat tak, aby skutečně vznikly funkce pro součet operandů různých typů. V případě programovacího jazyka C je realizace poměrně snadná:

#include <stdint.h>
 
#define ADD(type) type add_##type(type x, type y) {return x+y;}
 
ADD(int8_t)
ADD(int16_t)
ADD(int32_t)
ADD(int64_t)
 
ADD(uint8_t)
ADD(uint16_t)
ADD(uint32_t)
ADD(uint64_t)
 
ADD(float)
ADD(double)
Poznámka: zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/nu­meric_types.c.

Výsledný zdrojový kód získaný po expanzi makra ADD:

int8_t add_int8_t(int8_t x, int8_t y) {return x+y;}
int16_t add_int16_t(int16_t x, int16_t y) {return x+y;}
int32_t add_int32_t(int32_t x, int32_t y) {return x+y;}
int64_t add_int64_t(int64_t x, int64_t y) {return x+y;}
 
uint8_t add_uint8_t(uint8_t x, uint8_t y) {return x+y;}
uint16_t add_uint16_t(uint16_t x, uint16_t y) {return x+y;}
uint32_t add_uint32_t(uint32_t x, uint32_t y) {return x+y;}
uint64_t add_uint64_t(uint64_t x, uint64_t y) {return x+y;}
 
float add_float(float x, float y) {return x+y;}
double add_double(double x, double y) {return x+y;}

16. Výsledný kód reprezentovaný v LLVM IR

Výsledek získaný po překladu zdrojového kódu pomocí Clangu do LLVM IR:

; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i8 @add_int8_t(i8 noundef signext %0, i8 noundef signext %1) local_unnamed_addr #0 {
  %3 = add i8 %1, %0
  ret i8 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i16 @add_int16_t(i16 noundef signext %0, i16 noundef signext %1) local_unnamed_addr #0 {
  %3 = add i16 %1, %0
  ret i16 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @add_int32_t(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = add nsw i32 %1, %0
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i64 @add_int64_t(i64 noundef %0, i64 noundef %1) local_unnamed_addr #0 {
  %3 = add nsw i64 %1, %0
  ret i64 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i8 @add_uint8_t(i8 noundef zeroext %0, i8 noundef zeroext %1) local_unnamed_addr #0 {
  %3 = add i8 %1, %0
  ret i8 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i16 @add_uint16_t(i16 noundef zeroext %0, i16 noundef zeroext %1) local_unnamed_addr #0 {
  %3 = add i16 %1, %0
  ret i16 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @add_uint32_t(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
  %3 = add i32 %1, %0
  ret i32 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i64 @add_uint64_t(i64 noundef %0, i64 noundef %1) local_unnamed_addr #0 {
  %3 = add i64 %1, %0
  ret i64 %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef float @add_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
  %3 = fadd float %0, %1
  ret float %3
}
 
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @add_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
  %3 = fadd double %0, %1
  ret double %3
}

Můžeme zde vidět použití nám již známé instrukce add:

  %3 = add i8 %1, %0
  %3 = add i16 %1, %0
  %3 = add nsw i32 %1, %0
  %3 = add nsw i64 %1, %0
  %3 = add i8 %1, %0
  %3 = add i16 %1, %0
  %3 = add i32 %1, %0
  %3 = add i64 %1, %0
Poznámka: dvě z výše uvedených operací mají nedefinované chování při přetečení výsledků.

Pro součet operandů typu float a double se však používá nová instrukce nazvaná fadd:

  %3 = fadd float %0, %1
  %3 = fadd double %0, %1

17. Příloha A: seznam doposud popsaných instrukcí používaných v LLVM IR

V tabulce vypsané pod tímto odstavcem jsou vypsány všechny doposud popsané a použité instrukce LLVM IR:

# Jméno instrukce Parametry Stručný popis instrukce
1 alloca typ (velikost) alokace místa na zásobníku
2 load adresa načtení hodnoty z paměti
3 store hodnota, adresa uložení hodnoty do paměti
5 ret hodnota návrat z funkce
       
6 add hodnota1, hodnota2 součet dvou celočíselných hodnot
7 sub hodnota1, hodnota2 rozdíl dvou celočíselných hodnot
8 mul hodnota1, hodnota2 součin dvou celočíselných hodnot
9 sdiv hodnota1, hodnota2 podíl dvou celočíselných hodnot, které mají znaménko (signed)
10 udiv hodnota1, hodnota2 podíl dvou celočíselných hodnot, které jsou bezznaménkové (unsigned)
       
11 shl hodnota, posun aritmetický či bitový posun doleva
12 ashr hodnota, posun aritmetický posun doprava
13 lshr hodnota, posun bitový posun doprava
       
14 icmp eq hodnota1, hodnota2 porovnání dvou operandů na relaci „rovno“
15 icmp ne hodnota1, hodnota2 porovnání dvou operandů na relaci „nerovno“
16 icmp slt hodnota1, hodnota2 porovnání dvou operandů se znaménkem na relaci „menší než“
17 icmp sle hodnota1, hodnota2 porovnání dvou operandů se znaménkem na relaci „menší nebo rovno“
18 icmp sgt hodnota1, hodnota2 porovnání dvou operandů se znaménkem na relaci „větší než“
19 icmp sge hodnota1, hodnota2 porovnání dvou operandů se znaménkem na relaci „větší nebo rovno“
20 icmp ult hodnota1, hodnota2 porovnání dvou operandů bez znaménka na relaci „menší než“
21 icmp ule hodnota1, hodnota2 porovnání dvou operandů bez znaménka na relaci „menší nebo rovno“
22 icmp ugt hodnota1, hodnota2 porovnání dvou operandů bez znaménka na relaci „větší než“
23 icmp uge hodnota1, hodnota2 porovnání dvou operandů bez znaménka na relaci „větší nebo rovno“
       
24 fadd hodnota1, hodnota2 součet dvou hodnot s plovoucí řádovou čárkou

18. Příloha B: Makefile soubor pro překlad všech demonstračních příkladů

Všechny demonstrační příklady využívající překladač Clang, které byly použity v dnešním článku, je možné přeložit do mezijazyka LLVM IR s využitím souboru Makefile, jehož obsah je vypsán pod tímto odstavcem:

CC=clang
 
outputs := noop.ll noop_no_opt.ll noop_wasm.ll noop_aarch64.ll \
           add_unsigned.ll add_unsigned_no_opt.ll \
           add_signed.ll numeric_types.ll \
           arith_operators.ll comparison_operators.ll \
           branching_1.ll branching_2.ll pointers_1.ll pointers_2.ll \
           add_arrays_1.ll add_arrays_2.ll \
           fibonacci.ll
 
all:    $(outputs)
 
clean:
        rm -f *.ll
 
.PHONY: all clean
 
 
%.ll:   %.c
        $(CC) -Os -S -emit-llvm $< -o $@
 
add_unsigned_no_opt.ll: add_unsigned.c
        $(CC) -S -emit-llvm $< -o $@
 
noop_no_opt.ll: noop.c
        $(CC) -S -emit-llvm $< -o $@
 
noop_wasm.ll:   noop.c
        $(CC) -Os -S -emit-llvm --target=wasm32 $< -o $@
 
noop_aarch64.ll:        noop.c
        $(CC) -Os -S -emit-llvm --target=aarch64 $< -o $@

Pro překlad všech demonstračních příkladů postačuje zadat příkaz:

Školení Zabbix

$ make

Pro smazání všech vytvářených souborů s LLVM IR (mají koncovku .ll) použijte příkaz:

$ make clean

19. Repositář s demonstračními příklady

Demonstrační příklady, s nimiž jsme se v dnešním článku seznámili a které jsou určeny pro překlad s využitím Clangu, jsou dostupné, jak je zvykem, na GitHubu. V tabulce níže jsou uvedeny odkazy na jednotlivé zdrojové kódy psané v jazyku C i soubory v mezijazyku LLVM IR získané překladem Clangem:

# Příklad Stručný popis příkladu Adresa
1 Makefile definice cílů pro překlad všech demonstračních příkladů z této tabulky https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/Makefile
       
2 noop.c prázdná funkce bez parametrů nevracející žádnou hodnotu https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/noop.c
3 noop_no_opt.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka bez provádění optimalizací https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/no­op_no_opt.ll
4 noop.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka s provedením optimalizací na velikost https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/noop.ll
5 noop_aarch64.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka se specifikami platformy AArch64 https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/no­op_aarch64.ll
6 noop_wasm.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka se specifikami platformy WebAssembly https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/noop_wasm.ll
       
7 add_unsigned.c funkce pro součet dvou celých čísel bez znaménka (unsigned) https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/ad­d_unsigned.c
8 add_unsigned_no_opt.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka bez provedení optimalizací https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/ad­d_unsigned_no_opt.ll
9 add_unsigned.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka s provedením optimalizací na velikost https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/ad­d_unsigned.ll
       
10 add_signed.c funkce pro výpočet dvou celých čísel se znaménkem (signed) https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/add_signed.c
11 add_signed.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/add_signed.ll
       
12 arith_operators.c aritmetické operátory programovacího jazyka C https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/a­rith_operators.c
13 arith_operators.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/a­rith_operators.ll
       
14 numeric_types.c práce se základními numerickými datovými typy v jazyku C https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/nu­meric_types.c
15 numeric_types.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/nu­meric_types.ll
       
16 comparison_operators.c relační operátory programovacího jazyka C použité ve výrazech https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/com­parison_operators.c
17 comparison_operators.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/com­parison_operators.ll
       
18 pointers1.c základní práce s ukazateli v programovacím jazyku C, první demonstrační příklad https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/pointers1.c
19 pointers1.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/po­inters1.ll
       
20 pointers2.c základní práce s ukazateli v programovacím jazyku C, druhý demonstrační příklad https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/pointers2.c
21 pointers2.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/po­inters2.ll
       
22 branching1.c rozeskoky vzniklé překladem podmíněných konstrukcí a programových smyček, první demonstrační příklad https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/bran­ching1.c
23 branching1.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/bran­ching1.ll
       
24 branching2.c rozeskoky vzniklé překladem podmíněných konstrukcí a programových smyček, druhý demonstrační příklad https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/bran­ching2.c
25 branching2.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/bran­ching2.ll
       
26 add_arrays1.c součet prvků polí, předání polí odkazem https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/ad­d_arrays1.c
27 add_arrays1.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/ad­d_arrays1.ll
       
28 add_arrays2.c součet prvků polí, specifikace aliasingu předávaných odkazů https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/ad­d_arrays2.c
29 add_arrays2.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/ad­d_arrays2.ll
       
30 fibonacci.c výpočet Fibonacciho posloupnosti https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/fibonacci.c
31 fibonacci.ll výsledek překladu céčkovského zdrojového kódu do mezijazyka https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/fibonacci.ll

20. Odkazy na Internetu

  1. Is intermediate representation (such as bytecodes or .net IL) still an advantage?
    https://stackoverflow.com/qu­estions/35061333/is-intermediate-representation-such-as-bytecodes-or-net-il-still-an-advantage
  2. Intermediate Representation vs Byte Code
    https://cs.stackexchange.com/qu­estions/163398/intermedia­te-representation-vs-byte-code
  3. Getting the intermediate representation in gcc
    https://forum.osdev.org/vi­ewtopic.php?t=22845&sid=11f6e77­d24bda7fcc2c9ef6a5be4e6b2
  4. Intermediate Representations
    https://www.cs.cornell.edu/cou­rses/cs4120/2023sp/notes/ir/
  5. Why do we use intermediate representations / languages?
    https://mortoray.com/why-we-use-intermediate-representations/
  6. Unwrapping intermediate representations
    https://mortoray.com/unwrapping-intermediate-representations/
  7. Understanding Python Code Flow From Source to Execution
    https://medium.com/@azan96593/un­derstanding-python-code-flow-from-source-to-execution-ebeea870ef83
  8. Why most compilers use AST, instead generate IR directly?
    https://stackoverflow.com/qu­estions/60870622/why-most-compilers-use-ast-instead-generate-ir-directly#60902159
  9. A Gentle Introduction to LLVM IR
    https://mcyoung.xyz/2023/08/01/llvm-ir/
  10. Why does the compiler need the intermediate representations for link time optimization?
    https://stackoverflow.com/qu­estions/75586563/why-does-the-compiler-need-the-intermediate-representations-for-link-time-optimi
  11. pyrefact na PyPi
    https://pypi.org/project/pyrefact/
  12. Repositář projektu pyrefact
    https://github.com/OlleLin­dgren/pyrefact
  13. pyrefact jako plugin do VSCode
    https://marketplace.visual­studio.com/items?itemName=o­lleln.pyrefact
  14. pyrefact-vscode-extension (repositář)
    https://github.com/OlleLin­dgren/pyrefact-vscode-extension
  15. Best Python Refactoring Tools for 2023
    https://www.developer.com/lan­guages/python/best-python-refactoring-tools/
  16. Python Refactoring: Techniques, Tools, and Best Practices
    https://www.codesee.io/learning-center/python-refactoring
  17. Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python
    https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python/
  18. Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python (2.část)
    https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python-2-cast/
  19. Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python (3.část)
    https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python-3-cast/
  20. Lexikální a syntaktická analýza zdrojových kódů jazyka Python (4.část)
    https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-jazyka-python-4-cast/
  21. Knihovna LibCST umožňující snadnou modifikaci zdrojových kódů Pythonu
    https://www.root.cz/clanky/knihovna-libcst-umoznujici-snadnou-modifikaci-zdrojovych-kodu-pythonu/
  22. LibCST – dokumentace
    https://libcst.readthedoc­s.io/en/latest/index.html
  23. libCST na PyPi
    https://pypi.org/project/libcst/
  24. libCST na GitHubu
    https://github.com/Instagram/LibCST
  25. Inside The Python Virtual Machine
    https://leanpub.com/insidet­hepythonvirtualmachine
  26. module-py_compile
    https://docs.python.org/3­.8/library/py_compile.html
  27. Given a python .pyc file, is there a tool that let me view the bytecode?
    https://stackoverflow.com/qu­estions/11141387/given-a-python-pyc-file-is-there-a-tool-that-let-me-view-the-bytecode
  28. The structure of .pyc files
    https://nedbatchelder.com/blog/200804/the_str­ucture_of_pyc_files.html
  29. Python Bytecode: Fun With Dis
    http://akaptur.github.io/blog/2013/08/14/pyt­hon-bytecode-fun-with-dis/
  30. Python's Innards: Hello, ceval.c!
    http://tech.blog.aknin.na­me/category/my-projects/pythons-innards/
  31. Golang Compilation and Execution
    https://golangtutorial.com/golang-compilation-and-execution/
  32. Mezijazyk (Wikipedie)
    https://cs.wikipedia.org/wi­ki/Mezijazyk
  33. The LLVM Compiler Infrastructure
    https://www.llvm.org/
  34. GCC internals
    https://gcc.gnu.org/online­docs/gccint/index.html
  35. GCC Developer Options
    https://gcc.gnu.org/online­docs/gcc/Developer-Options.html
  36. What is Gimple?
    https://mschiralli1.wordpres­s.com/2024/12/01/what-is-gimple/
  37. The Conceptual Structure of GCC
    https://www.cse.iitb.ac.in/grc/in­tdocs/gcc-conceptual-structure.html
  38. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  39. ASM Home page
    http://asm.ow2.org/
  40. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  41. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  42. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  43. BCEL Home page
    http://commons.apache.org/bcel/
  44. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  45. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  46. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  47. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  48. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  49. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  50. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  51. Javassist
    http://www.jboss.org/javassist/
  52. Byteman
    http://www.jboss.org/byteman
  53. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  54. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  55. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  56. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  57. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  58. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  59. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  60. Abstract syntax tree
    https://en.wikipedia.org/wi­ki/Abstract_syntax_tree
  61. Lexical analysis
    https://en.wikipedia.org/wi­ki/Lexical_analysis
  62. Parser
    https://en.wikipedia.org/wi­ki/Parsing#Parser
  63. Parse tree
    https://en.wikipedia.org/wi­ki/Parse_tree
  64. Derivační strom
    https://cs.wikipedia.org/wi­ki/Deriva%C4%8Dn%C3%AD_strom
  65. Python doc: ast — Abstract Syntax Trees
    https://docs.python.org/3/li­brary/ast.html
  66. Python doc: tokenize — Tokenizer for Python source
    https://docs.python.org/3/li­brary/tokenize.html
  67. SymbolTable
    https://docs.python.org/3­.8/library/symtable.html
  68. 5 Amazing Python AST Module Examples
    https://www.pythonpool.com/python-ast/
  69. Intro to Python ast Module
    https://medium.com/@wshanshan/intro-to-python-ast-module-bbd22cd505f7
  70. Golang AST Package
    https://golangdocs.com/golang-ast-package
  71. AP8, IN8 Regulární jazyky
    http://statnice.dqd.cz/home:inf:ap8
  72. AP9, IN9 Konečné automaty
    http://statnice.dqd.cz/home:inf:ap9
  73. AP10, IN10 Bezkontextové jazyky
    http://statnice.dqd.cz/home:inf:ap10
  74. AP11, IN11 Zásobníkové automaty, Syntaktická analýza
    http://statnice.dqd.cz/home:inf:ap11
  75. Introduction to YACC
    https://www.geeksforgeeks­.org/introduction-to-yacc/
  76. Introduction of Lexical Analysis
    https://www.geeksforgeeks­.org/introduction-of-lexical-analysis/?ref=lbp
  77. Write your own filter
    http://pygments.org/docs/fil­terdevelopment/
  78. Write your own lexer
    http://pygments.org/docs/le­xerdevelopment/
  79. Write your own formatter
    http://pygments.org/docs/for­matterdevelopment/
  80. Compiler Construction/Lexical analysis
    https://en.wikibooks.org/wi­ki/Compiler_Construction/Le­xical_analysis
  81. Compiler Design – Lexical Analysis
    https://www.tutorialspoin­t.com/compiler_design/com­piler_design_lexical_analy­sis.htm
  82. Lexical Analysis – An Intro
    https://www.scribd.com/do­cument/383765692/Lexical-Analysis
  83. Python AST Visualizer
    https://github.com/pombredanne/python-ast-visualizer
  84. What is an Abstract Syntax Tree
    https://blog.bitsrc.io/what-is-an-abstract-syntax-tree-7502b71bde27
  85. Why is AST so important
    https://medium.com/@obernar­dovieira/why-is-ast-so-important-b1e7d6c29260
  86. Emily Morehouse-Valcarcel – The AST and Me – PyCon 2018
    https://www.youtube.com/wat­ch?v=XhWvz4dK4ng
  87. Python AST Parsing and Custom Linting
    https://www.youtube.com/wat­ch?v=OjPT15y2EpE
  88. Chase Stevens – Exploring the Python AST Ecosystem
    https://www.youtube.com/wat­ch?v=Yq3wTWkoaYY
  89. Full Grammar specification
    https://docs.python.org/3/re­ference/grammar.html
  90. Playing with GCC’s GIMPLE: How to Generate, Save, and Modify Intermediate Code (Tutorial + Examples)
    https://www.tutorialpedia­.org/blog/playing-with-gcc-s-intermediate-gimple-format/
  91. A Deep Dive Into LLVM IR
    https://medium.com/@abdulraheembeigh/a-deep-dive-into-llvm-ir-b5aa81beb474
  92. Nulová operace
    https://cs.wikipedia.org/wi­ki/Nulov%C3%A1_operace
  93. NOP (code)
    https://en.wikipedia.org/wi­ki/NOP_(code)
  94. LLVM Bitcode File Format
    https://llvm.org/docs/Bit­CodeFormat.html
  95. LLVM IR Language Reference
    https://deepwiki.com/llvm-mirror/llvm/2.1-llvm-ir-language-reference

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.