Obsah
1. Technologie mezijazyků (mezikódů) a bajtkódů v moderních interpretrech a překladačích: 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
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
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.).
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.
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/BitCodeFormat.html.
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.
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.)
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
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;
}
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
}
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
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;
}
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;}
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
}
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“ |
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)
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
}
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)
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
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:
$ 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/noop_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/noop_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/add_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/add_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/add_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/arith_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/arith_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/numeric_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/numeric_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/comparison_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/comparison_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/pointers1.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/pointers2.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/branching1.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/branching1.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/branching2.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/branching2.ll |
| 26 | add_arrays1.c | součet prvků polí, předání polí odkazem | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/add_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/add_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/add_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/add_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
- Is intermediate representation (such as bytecodes or .net IL) still an advantage?
https://stackoverflow.com/questions/35061333/is-intermediate-representation-such-as-bytecodes-or-net-il-still-an-advantage - Intermediate Representation vs Byte Code
https://cs.stackexchange.com/questions/163398/intermediate-representation-vs-byte-code - Getting the intermediate representation in gcc
https://forum.osdev.org/viewtopic.php?t=22845&sid=11f6e77d24bda7fcc2c9ef6a5be4e6b2 - Intermediate Representations
https://www.cs.cornell.edu/courses/cs4120/2023sp/notes/ir/ - Why do we use intermediate representations / languages?
https://mortoray.com/why-we-use-intermediate-representations/ - Unwrapping intermediate representations
https://mortoray.com/unwrapping-intermediate-representations/ - Understanding Python Code Flow From Source to Execution
https://medium.com/@azan96593/understanding-python-code-flow-from-source-to-execution-ebeea870ef83 - Why most compilers use AST, instead generate IR directly?
https://stackoverflow.com/questions/60870622/why-most-compilers-use-ast-instead-generate-ir-directly#60902159 - A Gentle Introduction to LLVM IR
https://mcyoung.xyz/2023/08/01/llvm-ir/ - Why does the compiler need the intermediate representations for link time optimization?
https://stackoverflow.com/questions/75586563/why-does-the-compiler-need-the-intermediate-representations-for-link-time-optimi - pyrefact na PyPi
https://pypi.org/project/pyrefact/ - Repositář projektu pyrefact
https://github.com/OlleLindgren/pyrefact - pyrefact jako plugin do VSCode
https://marketplace.visualstudio.com/items?itemName=olleln.pyrefact - pyrefact-vscode-extension (repositář)
https://github.com/OlleLindgren/pyrefact-vscode-extension - Best Python Refactoring Tools for 2023
https://www.developer.com/languages/python/best-python-refactoring-tools/ - Python Refactoring: Techniques, Tools, and Best Practices
https://www.codesee.io/learning-center/python-refactoring - 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/ - 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/ - 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/ - 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/ - 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/ - LibCST – dokumentace
https://libcst.readthedocs.io/en/latest/index.html - libCST na PyPi
https://pypi.org/project/libcst/ - libCST na GitHubu
https://github.com/Instagram/LibCST - Inside The Python Virtual Machine
https://leanpub.com/insidethepythonvirtualmachine - module-py_compile
https://docs.python.org/3.8/library/py_compile.html - Given a python .pyc file, is there a tool that let me view the bytecode?
https://stackoverflow.com/questions/11141387/given-a-python-pyc-file-is-there-a-tool-that-let-me-view-the-bytecode - The structure of .pyc files
https://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html - Python Bytecode: Fun With Dis
http://akaptur.github.io/blog/2013/08/14/python-bytecode-fun-with-dis/ - Python's Innards: Hello, ceval.c!
http://tech.blog.aknin.name/category/my-projects/pythons-innards/ - Golang Compilation and Execution
https://golangtutorial.com/golang-compilation-and-execution/ - Mezijazyk (Wikipedie)
https://cs.wikipedia.org/wiki/Mezijazyk - The LLVM Compiler Infrastructure
https://www.llvm.org/ - GCC internals
https://gcc.gnu.org/onlinedocs/gccint/index.html - GCC Developer Options
https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html - What is Gimple?
https://mschiralli1.wordpress.com/2024/12/01/what-is-gimple/ - The Conceptual Structure of GCC
https://www.cse.iitb.ac.in/grc/intdocs/gcc-conceptual-structure.html - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - Abstract syntax tree
https://en.wikipedia.org/wiki/Abstract_syntax_tree - Lexical analysis
https://en.wikipedia.org/wiki/Lexical_analysis - Parser
https://en.wikipedia.org/wiki/Parsing#Parser - Parse tree
https://en.wikipedia.org/wiki/Parse_tree - Derivační strom
https://cs.wikipedia.org/wiki/Deriva%C4%8Dn%C3%AD_strom - Python doc: ast — Abstract Syntax Trees
https://docs.python.org/3/library/ast.html - Python doc: tokenize — Tokenizer for Python source
https://docs.python.org/3/library/tokenize.html - SymbolTable
https://docs.python.org/3.8/library/symtable.html - 5 Amazing Python AST Module Examples
https://www.pythonpool.com/python-ast/ - Intro to Python ast Module
https://medium.com/@wshanshan/intro-to-python-ast-module-bbd22cd505f7 - Golang AST Package
https://golangdocs.com/golang-ast-package - AP8, IN8 Regulární jazyky
http://statnice.dqd.cz/home:inf:ap8 - AP9, IN9 Konečné automaty
http://statnice.dqd.cz/home:inf:ap9 - AP10, IN10 Bezkontextové jazyky
http://statnice.dqd.cz/home:inf:ap10 - AP11, IN11 Zásobníkové automaty, Syntaktická analýza
http://statnice.dqd.cz/home:inf:ap11 - Introduction to YACC
https://www.geeksforgeeks.org/introduction-to-yacc/ - Introduction of Lexical Analysis
https://www.geeksforgeeks.org/introduction-of-lexical-analysis/?ref=lbp - Write your own filter
http://pygments.org/docs/filterdevelopment/ - Write your own lexer
http://pygments.org/docs/lexerdevelopment/ - Write your own formatter
http://pygments.org/docs/formatterdevelopment/ - Compiler Construction/Lexical analysis
https://en.wikibooks.org/wiki/Compiler_Construction/Lexical_analysis - Compiler Design – Lexical Analysis
https://www.tutorialspoint.com/compiler_design/compiler_design_lexical_analysis.htm - Lexical Analysis – An Intro
https://www.scribd.com/document/383765692/Lexical-Analysis - Python AST Visualizer
https://github.com/pombredanne/python-ast-visualizer - What is an Abstract Syntax Tree
https://blog.bitsrc.io/what-is-an-abstract-syntax-tree-7502b71bde27 - Why is AST so important
https://medium.com/@obernardovieira/why-is-ast-so-important-b1e7d6c29260 - Emily Morehouse-Valcarcel – The AST and Me – PyCon 2018
https://www.youtube.com/watch?v=XhWvz4dK4ng - Python AST Parsing and Custom Linting
https://www.youtube.com/watch?v=OjPT15y2EpE - Chase Stevens – Exploring the Python AST Ecosystem
https://www.youtube.com/watch?v=Yq3wTWkoaYY - Full Grammar specification
https://docs.python.org/3/reference/grammar.html - 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/ - A Deep Dive Into LLVM IR
https://medium.com/@abdulraheembeigh/a-deep-dive-into-llvm-ir-b5aa81beb474 - Nulová operace
https://cs.wikipedia.org/wiki/Nulov%C3%A1_operace - NOP (code)
https://en.wikipedia.org/wiki/NOP_(code) - LLVM Bitcode File Format
https://llvm.org/docs/BitCodeFormat.html - LLVM IR Language Reference
https://deepwiki.com/llvm-mirror/llvm/2.1-llvm-ir-language-reference
