Obsah
2. Aritmetické operace a porovnání hodnot typu float a double
3. Operace po porovnání hodnot typu float a double
4. Konverze mezi hodnotami různých datových typů
5. Třetí demonstrační příklad: konverze mezi celočíselnými typy bez znaménka
6. Čtvrtý demonstrační příklad: konverze mezi celočíselnými hodnotami se znaménkem
7. Instrukce používané při konverzích celočíselných hodnot se znaménkem i bez znaménka
9. Instrukce používané při sofistikovanějších konverzích
10. Základní operace s ukazateli
11. Kód provádějící rozvětvení
12. Překlad jednoduché programové smyčky s podmínkou (while)
13. Překlad počítané programové smyčky (for)
14. Rozvětvení konstrukcí switch-case
15. Další varianta rozvětvení konstrukcí switch-case
16. Nové instrukce používané při větvení
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 (2)
V dnešním článku budeme pokračovat v tématu, kterému jsme se začali věnovat již v článku předchozím. Popíšeme si další instrukce LLVM IR, tedy například instrukce generované překladačem Clang atd. Pochopitelně ovšem nemá smysl si jednotlivé instrukce pouze vypsat, protože je nutné znát i kontext – tj. ve kterých případech je překladače generují. Z tohoto důvodu si vždy ukážeme zdrojový kód naprogramovaný v jazyku C (většinou je generovaný s pomocí maker) a k němu odpovídající mezikód reprezentovaný v LLVM IR. Seznam všech instrukcí LLVM IR, s nimiž se tímto způsobem seznámíme, je uveden v tabulce vypsané v sedmnácté kapitole.
2. Aritmetické operace a porovnání hodnot typu float a double
V dnešním prvním demonstračním příkladu si ověříme, jakým způsobem se přeloží základní aritmetické operace prováděné s hodnotami s plovoucí řádovou čárkou (floating point), tj. konkrétně s hodnotami typu float a double. Uvidíme, že se v LLVM IR použijí poněkud jiné instrukce, než tomu bylo v případě celočíselných datových typů. V céčkovském zdrojovém kódu opět použijeme makra:
#define NEG(type) type neg_##type(type x) {return -x;}
#define ADD(type) type add_##type(type x, type y) {return x+y;}
#define SUB(type) type sub_##type(type x, type y) {return x-y;}
#define MUL(type) type mul_##type(type x, type y) {return x*y;}
#define DIV(type) type div_##type(type x, type y) {return x/y;}
#define ALL(type) \
NEG(type) \
ADD(type) \
SUB(type) \
MUL(type) \
DIV(type)
ALL(float)
ALL(double)
Po zpracování preprocesorem programovacího jazyka C získáme následující céčkovský kód:
float neg_float(float x) {return -x;}
float add_float(float x, float y) {return x+y;}
float sub_float(float x, float y) {return x-y;}
float mul_float(float x, float y) {return x*y;}
float div_float(float x, float y) {return x/y;}
double neg_double(double x) {return -x;}
double add_double(double x, double y) {return x+y;}
double sub_double(double x, double y) {return x-y;}
double mul_double(double x, double y) {return x*y;}
double div_double(double x, double y) {return x/y;}
A po překladu výše uvedeného kódu do LLVM IR je již patrné, jaké instrukce se v tomto případě volají:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef float @neg_float(float noundef %0) local_unnamed_addr #0 {
%2 = fneg float %0
ret float %2
}
; 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 float @sub_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fsub float %0, %1
ret float %3
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef float @mul_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fmul float %0, %1
ret float %3
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef float @div_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fdiv float %0, %1
ret float %3
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @neg_double(double noundef %0) local_unnamed_addr #0 {
%2 = fneg double %0
ret double %2
}
; 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
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @sub_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fsub double %0, %1
ret double %3
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @mul_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fmul double %0, %1
ret double %3
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @div_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fdiv double %0, %1
ret double %3
}
Všechny nové instrukce (navíc včetně již minule popsané instrukce fadd) si pochopitelně opět můžeme vypsat ve formě tabulky:
| # | Jméno instrukce | Parametry | Stručný popis instrukce |
|---|---|---|---|
| 1 | fneg | float/double | změna znaménka |
| 2 | fadd | hodnota1, hodnota2 | součet dvou hodnot s plovoucí řádovou čárkou |
| 3 | fsub | hodnota1, hodnota2 | rozdíl dvou hodnot s plovoucí řádovou čárkou |
| 4 | fmul | hodnota1, hodnota2 | součin dvou hodnot s plovoucí řádovou čárkou |
| 5 | fdiv | hodnota1, hodnota2 | podíl dvou hodnot s plovoucí řádovou čárkou |
Povšimněte si, že instrukce určené pro zpracování hodnot typu double se od instrukcí s typem float odlišují pouze uvedením modifikátoru se jménem typu.
3. Operace po porovnání hodnot typu float a double
V předchozím článku jsme si popsali i instrukce určené pro porovnání celočíselných hodnot. Připomeňme si, že se ve skutečnosti jednalo o jedinou instrukci nazvanou icmp (integer compare) s variantami určenými dalším modifikátorem eq, ne, slt atd. Zbývá nám tedy zjistit, jakým způsobem dochází k porovnání hodnot typu float a/nebo double. Zdrojový kód z minulého článku upravíme do podoby, která bude pracovat právě s numerickými hodnotami s plovoucí řádovou čárkou:
#define EQ(type) int eq_##type(type x, type y) {return x==y;}
#define NE(type) int ne_##type(type x, type y) {return x!=y;}
#define LT(type) int lt_##type(type x, type y) {return x<y;}
#define LE(type) int le_##type(type x, type y) {return x<=y;}
#define GT(type) int gt_##type(type x, type y) {return x>y;}
#define GE(type) int 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(float)
ALL(double)
Preprocesor programovacího jazyka C z výše uvedeného zdrojového kódu vytvoří následující kód:
int eq_float(float x, float y) {return x==y;}
int ne_float(float x, float y) {return x!=y;}
int lt_float(float x, float y) {return x<y;}
int le_float(float x, float y) {return x<=y;}
int gt_float(float x, float y) {return x>y;}
int ge_float(float x, float y) {return x>=y;}
int eq_double(double x, double y) {return x==y;}
int ne_double(double x, double y) {return x!=y;}
int lt_double(double x, double y) {return x<y;}
int le_double(double x, double y) {return x<=y;}
int gt_double(double x, double y) {return x>y;}
int ge_double(double x, double y) {return x>=y;}
Výsledek překladu tohoto zdrojového kódu do LLVM IR dopadne následovně:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 2) i32 @eq_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fcmp oeq float %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_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fcmp une float %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_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fcmp olt float %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_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fcmp ole float %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_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fcmp ogt float %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_float(float noundef %0, float noundef %1) local_unnamed_addr #0 {
%3 = fcmp oge float %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_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fcmp oeq double %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_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fcmp une double %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_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fcmp olt double %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_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fcmp ole double %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_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fcmp ogt double %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_double(double noundef %0, double noundef %1) local_unnamed_addr #0 {
%3 = fcmp oge double %0, %1
%4 = zext i1 %3 to i32
ret i32 %4
}
Z tohoto výpisu opět můžeme snadno zjistit, které operace se pro porovnávání používají:
| # | Jméno instrukce | Parametry | Stručný popis instrukce |
|---|---|---|---|
| 1 | fcmp oeq | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „rovno“ |
| 2 | fcmp une | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „nerovno“ |
| 3 | fcmp olt | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „menší než“ |
| 4 | fcmp ole | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „menší nebo rovno“ |
| 5 | fcmp ogt | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „větší než“ |
| 6 | fcmp oge | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „větší nebo rovno“ |
4. Konverze mezi hodnotami různých datových typů
Ve zdrojových kódech se velmi často setkáme s konverzemi, při nichž se hodnota určitého typu konvertuje na hodnotu typu jiného. V některých programovacích jazycích mohou být tyto konverze (resp. některé konverze) provedeny implicitně, ovšem například v jazyce Go se musí všechny konverze zapisovat explicitně. Nezávisle na použitém programovacím jazyku je však nutné, aby LLVM IR konverze podporoval, což znamená, že musí obsahovat příslušné instrukce. Většinu těchto instrukcí si ukážeme v následující trojici demonstračních příkladů, které konverze rozdělují na tři skupiny:
- Konverze mezi celočíselnými typy bez znaménka (unsigned)
- Konverze mezi celočíselnými typy se znaménkem (signed)
- Konverze mezi typy s plovoucí řádovou čárkou (float, double)
- Mix konverzí: převod reálného čísla na celé číslo atd.
5. Třetí demonstrační příklad: konverze mezi celočíselnými typy bez znaménka
V dnešním třetím demonstračním příkladu je (opět na základě maker) vygenerováno několik céčkovských funkcí, které provádí všechny kombinace konverzí celočíselných datových typů bez znaménka. Pro úplnost se vytváří i funkce provádějící konverzi hodnoty na stejný typ, což ovšem vede k velmi jednoduchému LLVM IR. Původní zdrojový kód tohoto příkladu vypadá následovně:
#include <stdint.h>
#define CONVERT(type1, type2) type2 convert_##type1##_to_##type2(type1 x) {return (type2)x;}
CONVERT(uint8_t, uint8_t)
CONVERT(uint8_t, uint16_t)
CONVERT(uint8_t, uint32_t)
CONVERT(uint8_t, uint64_t)
CONVERT(uint16_t, uint8_t)
CONVERT(uint16_t, uint16_t)
CONVERT(uint16_t, uint32_t)
CONVERT(uint16_t, uint64_t)
CONVERT(uint32_t, uint8_t)
CONVERT(uint32_t, uint16_t)
CONVERT(uint32_t, uint32_t)
CONVERT(uint32_t, uint64_t)
CONVERT(uint64_t, uint8_t)
CONVERT(uint64_t, uint16_t)
CONVERT(uint64_t, uint32_t)
CONVERT(uint64_t, uint64_t)
Výsledek preprocessingu výše uvedeného zdrojového kódu s makry:
uint8_t convert_uint8_t_to_uint8_t(uint8_t x) {return (uint8_t)x;}
uint16_t convert_uint8_t_to_uint16_t(uint8_t x) {return (uint16_t)x;}
uint32_t convert_uint8_t_to_uint32_t(uint8_t x) {return (uint32_t)x;}
uint64_t convert_uint8_t_to_uint64_t(uint8_t x) {return (uint64_t)x;}
uint8_t convert_uint16_t_to_uint8_t(uint16_t x) {return (uint8_t)x;}
uint16_t convert_uint16_t_to_uint16_t(uint16_t x) {return (uint16_t)x;}
uint32_t convert_uint16_t_to_uint32_t(uint16_t x) {return (uint32_t)x;}
uint64_t convert_uint16_t_to_uint64_t(uint16_t x) {return (uint64_t)x;}
uint8_t convert_uint32_t_to_uint8_t(uint32_t x) {return (uint8_t)x;}
uint16_t convert_uint32_t_to_uint16_t(uint32_t x) {return (uint16_t)x;}
uint32_t convert_uint32_t_to_uint32_t(uint32_t x) {return (uint32_t)x;}
uint64_t convert_uint32_t_to_uint64_t(uint32_t x) {return (uint64_t)x;}
uint8_t convert_uint64_t_to_uint8_t(uint64_t x) {return (uint8_t)x;}
uint16_t convert_uint64_t_to_uint16_t(uint64_t x) {return (uint16_t)x;}
uint32_t convert_uint64_t_to_uint32_t(uint64_t x) {return (uint32_t)x;}
uint64_t convert_uint64_t_to_uint64_t(uint64_t x) {return (uint64_t)x;}
LLVM IR, který vznikne překladem s využitím Clangu, by měl vypadat následovně:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i8 @convert_uint8_t_to_uint8_t(i8 noundef returned zeroext %0) local_unnamed_addr #0 {
ret i8 %0
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local zeroext range(i16 0, 256) i16 @convert_uint8_t_to_uint16_t(i8 noundef zeroext %0) local_unnamed_addr #0 {
%2 = zext i8 %0 to i16
ret i16 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 256) i32 @convert_uint8_t_to_uint32_t(i8 noundef zeroext %0) local_unnamed_addr #0 {
%2 = zext i8 %0 to i32
ret i32 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i64 0, 256) i64 @convert_uint8_t_to_uint64_t(i8 noundef zeroext %0) local_unnamed_addr #0 {
%2 = zext i8 %0 to i64
ret i64 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i8 @convert_uint16_t_to_uint8_t(i16 noundef zeroext %0) local_unnamed_addr #0 {
%2 = trunc i16 %0 to i8
ret i8 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i16 @convert_uint16_t_to_uint16_t(i16 noundef returned zeroext %0) local_unnamed_addr #0 {
ret i16 %0
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 0, 65536) i32 @convert_uint16_t_to_uint32_t(i16 noundef zeroext %0) local_unnamed_addr #0 {
%2 = zext i16 %0 to i32
ret i32 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i64 0, 65536) i64 @convert_uint16_t_to_uint64_t(i16 noundef zeroext %0) local_unnamed_addr #0 {
%2 = zext i16 %0 to i64
ret i64 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i8 @convert_uint32_t_to_uint8_t(i32 noundef %0) local_unnamed_addr #0 {
%2 = trunc i32 %0 to i8
ret i8 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i16 @convert_uint32_t_to_uint16_t(i32 noundef %0) local_unnamed_addr #0 {
%2 = trunc i32 %0 to i16
ret i16 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @convert_uint32_t_to_uint32_t(i32 noundef returned %0) local_unnamed_addr #0 {
ret i32 %0
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i64 0, 4294967296) i64 @convert_uint32_t_to_uint64_t(i32 noundef %0) local_unnamed_addr #0 {
%2 = zext i32 %0 to i64
ret i64 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i8 @convert_uint64_t_to_uint8_t(i64 noundef %0) local_unnamed_addr #0 {
%2 = trunc i64 %0 to i8
ret i8 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef zeroext i16 @convert_uint64_t_to_uint16_t(i64 noundef %0) local_unnamed_addr #0 {
%2 = trunc i64 %0 to i16
ret i16 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @convert_uint64_t_to_uint32_t(i64 noundef %0) local_unnamed_addr #0 {
%2 = trunc i64 %0 to i32
ret i32 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i64 @convert_uint64_t_to_uint64_t(i64 noundef returned %0) local_unnamed_addr #0 {
ret i64 %0
}
6. Čtvrtý demonstrační příklad: konverze mezi celočíselnými hodnotami se znaménkem
V dnešním čtvrtém demonstračním příkladu provedeme stejné operace konverze, jako v příkladu předchozím, ovšem nyní s celočíselnými hodnotami se znaménkem (signed). To nám umožní zjistit, zda se budou instrukce LLVM IR lišit či nikoli:
#include <stdint.h>
#define CONVERT(type1, type2) type2 convert_##type1##_to_##type2(type1 x) {return (type2)x;}
CONVERT(int8_t, int8_t)
CONVERT(int8_t, int16_t)
CONVERT(int8_t, int32_t)
CONVERT(int8_t, int64_t)
CONVERT(int16_t, int8_t)
CONVERT(int16_t, int16_t)
CONVERT(int16_t, int32_t)
CONVERT(int16_t, int64_t)
CONVERT(int32_t, int8_t)
CONVERT(int32_t, int16_t)
CONVERT(int32_t, int32_t)
CONVERT(int32_t, int64_t)
CONVERT(int64_t, int8_t)
CONVERT(int64_t, int16_t)
CONVERT(int64_t, int32_t)
CONVERT(int64_t, int64_t)
Zdrojový kód po zpracování preprocesorem jazyka C:
int8_t convert_int8_t_to_int8_t(int8_t x) {return (int8_t)x;}
int16_t convert_int8_t_to_int16_t(int8_t x) {return (int16_t)x;}
int32_t convert_int8_t_to_int32_t(int8_t x) {return (int32_t)x;}
int64_t convert_int8_t_to_int64_t(int8_t x) {return (int64_t)x;}
int8_t convert_int16_t_to_int8_t(int16_t x) {return (int8_t)x;}
int16_t convert_int16_t_to_int16_t(int16_t x) {return (int16_t)x;}
int32_t convert_int16_t_to_int32_t(int16_t x) {return (int32_t)x;}
int64_t convert_int16_t_to_int64_t(int16_t x) {return (int64_t)x;}
int8_t convert_int32_t_to_int8_t(int32_t x) {return (int8_t)x;}
int16_t convert_int32_t_to_int16_t(int32_t x) {return (int16_t)x;}
int32_t convert_int32_t_to_int32_t(int32_t x) {return (int32_t)x;}
int64_t convert_int32_t_to_int64_t(int32_t x) {return (int64_t)x;}
int8_t convert_int64_t_to_int8_t(int64_t x) {return (int8_t)x;}
int16_t convert_int64_t_to_int16_t(int64_t x) {return (int16_t)x;}
int32_t convert_int64_t_to_int32_t(int64_t x) {return (int32_t)x;}
int64_t convert_int64_t_to_int64_t(int64_t x) {return (int64_t)x;}
Výsledek překladu do LLVM IR bude vypadat následovně:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i8 @convert_int8_t_to_int8_t(i8 noundef returned signext %0) local_unnamed_addr #0 {
ret i8 %0
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local signext range(i16 -128, 128) i16 @convert_int8_t_to_int16_t(i8 noundef signext %0) local_unnamed_addr #0 {
%2 = sext i8 %0 to i16
ret i16 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 -128, 128) i32 @convert_int8_t_to_int32_t(i8 noundef signext %0) local_unnamed_addr #0 {
%2 = sext i8 %0 to i32
ret i32 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i64 -128, 128) i64 @convert_int8_t_to_int64_t(i8 noundef signext %0) local_unnamed_addr #0 {
%2 = sext i8 %0 to i64
ret i64 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i8 @convert_int16_t_to_int8_t(i16 noundef signext %0) local_unnamed_addr #0 {
%2 = trunc i16 %0 to i8
ret i8 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i16 @convert_int16_t_to_int16_t(i16 noundef returned signext %0) local_unnamed_addr #0 {
ret i16 %0
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i32 -32768, 32768) i32 @convert_int16_t_to_int32_t(i16 noundef signext %0) local_unnamed_addr #0 {
%2 = sext i16 %0 to i32
ret i32 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i64 -32768, 32768) i64 @convert_int16_t_to_int64_t(i16 noundef signext %0) local_unnamed_addr #0 {
%2 = sext i16 %0 to i64
ret i64 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i8 @convert_int32_t_to_int8_t(i32 noundef %0) local_unnamed_addr #0 {
%2 = trunc i32 %0 to i8
ret i8 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i16 @convert_int32_t_to_int16_t(i32 noundef %0) local_unnamed_addr #0 {
%2 = trunc i32 %0 to i16
ret i16 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @convert_int32_t_to_int32_t(i32 noundef returned %0) local_unnamed_addr #0 {
ret i32 %0
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local range(i64 -2147483648, 2147483648) i64 @convert_int32_t_to_int64_t(i32 noundef %0) local_unnamed_addr #0 {
%2 = sext i32 %0 to i64
ret i64 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i8 @convert_int64_t_to_int8_t(i64 noundef %0) local_unnamed_addr #0 {
%2 = trunc i64 %0 to i8
ret i8 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef signext i16 @convert_int64_t_to_int16_t(i64 noundef %0) local_unnamed_addr #0 {
%2 = trunc i64 %0 to i16
ret i16 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @convert_int64_t_to_int32_t(i64 noundef %0) local_unnamed_addr #0 {
%2 = trunc i64 %0 to i32
ret i32 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i64 @convert_int64_t_to_int64_t(i64 noundef returned %0) local_unnamed_addr #0 {
ret i64 %0
}
7. Instrukce používané při konverzích celočíselných hodnot se znaménkem i bez znaménka
V tento okamžik již můžeme sepsat všechny konverzní instrukce, které dokážou konvertovat celočíselnou hodnotu se znaménkem nebo bez znaménka na jinou celočíselnou hodnotu s větším nebo menším počtem bitů. Rozšíření počtu bitů je realizováno instrukcemi se jmény zext a sext, zatímco o snížení počtu bitů se stará jediná instrukce nazvaná trunc. Pokud má být provedeno rozšíření bez znaménka (doplnění nejvyšších bitů nulami), využívá se instrukce zext (zero extension), pokud se naopak má provést znaménkové rozšíření (kopie původního znaménka do nejvyšších bitů), využívá se instrukce sext (sign extension):
| # | Jméno instrukce | Parametry | Stručný popis instrukce |
|---|---|---|---|
| 1 | zext | typ1, operand, typ2 | bezznaménkové rozšíření z typu1 na typ2 |
| 2 | sext | typ1, operand, typ2 | znaménkové rozšíření z typu1 na typ2 |
| 2 | trunc | typ1, operand, typ2 | zmenšení bitové šířky z typu1 na typ2 |
8. Pátý demonstrační příklad: konverze mezi hodnotami s plovoucí řádovou čárkou a celočíselnými hodnotami
Ještě se musíme seznámit s konverzemi, které umožňují převádět hodnoty s plovoucí řádovou čárkou (float, double) na celočíselné hodnoty a naopak. Tyto konverze si ověříme díky následujícímu demonstračnímu příkladu, jehož zdrojový kód vypadá následovně:
#include <stdint.h>
#define CONVERT(type1, type2) type2 convert_##type1##_to_##type2(type1 x) {return (type2)x;}
CONVERT(float, float)
CONVERT(float, double)
CONVERT(double, float)
CONVERT(double, double)
CONVERT(int32_t, float)
CONVERT(int32_t, double)
CONVERT(int64_t, float)
CONVERT(int64_t, double)
CONVERT(float, int32_t)
CONVERT(double, int32_t)
CONVERT(float, int64_t)
CONVERT(double, int64_t)
Výsledek po zpracování tohoto zdrojového kódu preprocesorem jazyka C:
float convert_float_to_float(float x) {return (float)x;}
double convert_float_to_double(float x) {return (double)x;}
float convert_double_to_float(double x) {return (float)x;}
double convert_double_to_double(double x) {return (double)x;}
float convert_int32_t_to_float(int32_t x) {return (float)x;}
double convert_int32_t_to_double(int32_t x) {return (double)x;}
float convert_int64_t_to_float(int64_t x) {return (float)x;}
double convert_int64_t_to_double(int64_t x) {return (double)x;}
int32_t convert_float_to_int32_t(float x) {return (int32_t)x;}
int32_t convert_double_to_int32_t(double x) {return (int32_t)x;}
int64_t convert_float_to_int64_t(float x) {return (int64_t)x;}
int64_t convert_double_to_int64_t(double x) {return (int64_t)x;}
Výsledný mezikód v LLVM IR vypadá takto:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef float @convert_float_to_float(float noundef returned %0) local_unnamed_addr #0 {
ret float %0
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @convert_float_to_double(float noundef %0) local_unnamed_addr #0 {
%2 = fpext float %0 to double
ret double %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef float @convert_double_to_float(double noundef %0) local_unnamed_addr #0 {
%2 = fptrunc double %0 to float
ret float %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @convert_double_to_double(double noundef returned %0) local_unnamed_addr #0 {
ret double %0
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef float @convert_int32_t_to_float(i32 noundef %0) local_unnamed_addr #0 {
%2 = sitofp i32 %0 to float
ret float %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @convert_int32_t_to_double(i32 noundef %0) local_unnamed_addr #0 {
%2 = sitofp i32 %0 to double
ret double %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef float @convert_int64_t_to_float(i64 noundef %0) local_unnamed_addr #0 {
%2 = sitofp i64 %0 to float
ret float %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef double @convert_int64_t_to_double(i64 noundef %0) local_unnamed_addr #0 {
%2 = sitofp i64 %0 to double
ret double %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @convert_float_to_int32_t(float noundef %0) local_unnamed_addr #0 {
%2 = fptosi float %0 to i32
ret i32 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i32 @convert_double_to_int32_t(double noundef %0) local_unnamed_addr #0 {
%2 = fptosi double %0 to i32
ret i32 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i64 @convert_float_to_int64_t(float noundef %0) local_unnamed_addr #0 {
%2 = fptosi float %0 to i64
ret i64 %2
}
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local i64 @convert_double_to_int64_t(double noundef %0) local_unnamed_addr #0 {
%2 = fptosi double %0 to i64
ret i64 %2
}
9. Instrukce používané při sofistikovanějších konverzích
Opět si můžeme vypsat konverzní instrukce použité v předchozím mezikódu. Navíc byly do tabulky dopsány i instrukce určené pro konverzi z/do celočíselného typu bez znaménka (unsigned), které sice v demonstračním příkladu použity nebyly, ale doplňují celou sadu dostupných instrukcí:
| # | Jméno instrukce | Parametry | Stručný popis instrukce |
|---|---|---|---|
| 1 | fpext | typ1, hodnota, typ2 | převod FP hodnoty z typu 1 na typ 2 (rozšíření přesnosti/rozsahu) |
| 2 | fptrunc | typ1, hodnota, typ2 | převod FP hodnoty z typu 1 na typ 2 (zmenšení přesnosti/rozsahu) |
| 3 | sitofp | typ1, hodnota, typ2 | převod celočíselné hodnoty se znaménkem na FP hodnotu |
| 4 | uitofp | typ1, hodnota, typ2 | převod celočíselné hodnoty bez znaménka na FP hodnotu |
| 4 | fptosi | typ1, hodnota, typ2 | převod FP hodnoty na celočíselnou hodnotu se znaménkem (signed) |
| 5 | fptoui | typ1, hodnota, typ2 | převod FP hodnoty na celočíselnou hodnotu bez znaménka (unsigned) |
10. Základní operace s ukazateli
V této kapitole si ukážeme, jakým způsobem se přeloží céčkovský kód pracující s ukazateli do mezikódu LLVM IR. Začneme velmi jednoduchou funkcí, která dokáže prohodit hodnoty dvou parametrů předaných referencí (ukazatelem). Nejjednodušší podoba této funkce může vypadat takto:
void swap(int *a, int *b) {
int c = *a;
*a = *b;
*b = c;
}
V mezikódu můžeme vidět, že se používají instrukce load a store, které již dobře známe. Překladač Clang navíc kód do jisté míry upravil tak, že se používají dvě pomocné proměnné %3 a %4, což vede k určité symetrii zápisu:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(argmem: readwrite) uwtable
define dso_local void @swap(ptr nocapture noundef %0, ptr nocapture noundef %1) local_unnamed_addr #0 {
%3 = load i32, ptr %0, align 4, !tbaa !3
%4 = load i32, ptr %1, align 4, !tbaa !3
store i32 %4, ptr %0, align 4, !tbaa !3
store i32 %3, ptr %1, align 4, !tbaa !3
ret void
}
Zkusme nyní práci překladači ztížit, a to takovým způsobem, že do funkce swap budeme předávat dva netypové ukazatele. Přetypování bude provedeno až přímo v těle funkce (toto v žádném případě není pěkný kód, ovšem v praxi se můžeme s netypovými ukazateli relativně často setkat, takže s nimi musí jak Clang, tak i (možná) LLVM IR počítat):
void swap(void *a, void *b) {
int c = *(int*)a;
*(int*)a = *(int*)b;
*(int*)b = c;
}
Zajímavé je, že překlad v tomto případě proběhne naprosto stejným způsobem – mezikód bude obsahovat totožné instrukce:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(argmem: readwrite) uwtable
define dso_local void @swap(ptr nocapture noundef %0, ptr nocapture noundef %1) local_unnamed_addr #0 {
%3 = load i32, ptr %0, align 4, !tbaa !3
%4 = load i32, ptr %1, align 4, !tbaa !3
store i32 %4, ptr %0, align 4, !tbaa !3
store i32 %3, ptr %1, align 4, !tbaa !3
ret void
}
11. Kód provádějící rozvětvení
Další důležitou součástí LLVM IR jsou instrukce provádějící rozvětvení. Tyto instrukce jsou použity i při překladu programových smyček, rekurze atd. Podívejme se tedy na způsob překladu následujícího jednoduchého příkladu, ve kterém je rozvětvení použito ve výrazu s ternárním operátorem:
unsigned int divide(unsigned int u, unsigned int v) {
return v==0 ? -1 : u/v;
}
V mezikódu, který vznikl překladem do LLVM IR, můžeme vidět dvojici nových instrukcí br a phi:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @divide(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
%3 = icmp eq i32 %1, 0
br i1 %3, label %6, label %4
4: ; preds = %2
%5 = udiv i32 %0, %1
br label %6
6: ; preds = %2, %4
%7 = phi i32 [ %5, %4 ], [ -1, %2 ]
ret i32 %7
}
12. Překlad jednoduché programové smyčky s podmínkou (while)
Podobným způsobem si můžeme přeložit i funkci s programovou smyčkou, konkrétně s programovou smyčkou obsahující podmínku. V tomto konkrétním případě se jedná o výpočet největšího společného dělitele:
unsigned int gcd(unsigned int u, unsigned int v) {
while (v) {
int t = u;
u = v;
v = t % v;
}
return u;
}
Výsledek překladu této funkce do LLVM IR bude vypadat následovně:
; Function Attrs: nofree norecurse nosync nounwind optsize memory(none) uwtable
define dso_local i32 @gcd(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
%3 = icmp eq i32 %1, 0
br i1 %3, label %9, label %4
4: ; preds = %2, %4
%5 = phi i32 [ %6, %4 ], [ %0, %2 ]
%6 = phi i32 [ %7, %4 ], [ %1, %2 ]
%7 = urem i32 %5, %6
%8 = icmp eq i32 %7, 0
br i1 %8, label %9, label %4, !llvm.loop !3
9: ; preds = %4, %2
%10 = phi i32 [ %0, %2 ], [ %6, %4 ]
ret i32 %10
}
Opět zde můžeme vidět dvojici instrukcí pojmenovaných br a phi.
13. Překlad počítané programové smyčky (for)
Kromě programových smyček s podmínkou testovanou na začátku nebo na konci každé iterace se v praxi pochopitelně setkáme i se smyčkami, v nichž se používá počitadlo počtu opakování popř. složitější podmínka založená na hodnotách jedné či více proměnných. Takový typ smyčky lze v jazyce C realizovat s využitím klíčového slova for, například takto:
long fibonacciIter(int n) {
if (n <= 1) {
return n;
}
long result = 0;
long n1 = 0;
long n2 = 1;
for(n--; n > 0; n--) {
result = n1 + n2;
n1 = n2;
n2 = result;
}
return result;
}
Ve vygenerovaném mezikódu je opět patrné, že se používají instrukce br a phi:
; Function Attrs: nofree norecurse nosync nounwind optsize memory(none) uwtable
define dso_local i64 @fibonacciIter(i32 noundef %0) local_unnamed_addr #0 {
%2 = icmp slt i32 %0, 2
br i1 %2, label %3, label %5
3: ; preds = %1
%4 = sext i32 %0 to i64
br label %12
5: ; preds = %1, %5
%6 = phi i64 [ %10, %5 ], [ 1, %1 ]
%7 = phi i64 [ %6, %5 ], [ 0, %1 ]
%8 = phi i32 [ %9, %5 ], [ %0, %1 ]
%9 = add nsw i32 %8, -1
%10 = add nsw i64 %6, %7
%11 = icmp samesign ugt i32 %8, 2
br i1 %11, label %5, label %12, !llvm.loop !3
12: ; preds = %5, %3
%13 = phi i64 [ %4, %3 ], [ %10, %5 ]
ret i64 %13
}
14. Rozvětvení konstrukcí switch-case
S instrukcemi LLVM IR provádějícími rozvětvení se setkáme i při překladu programové konstrukce typu switch-case. Ukažme si tuto konstrukci na netriviálním demonstračním příkladu, který překladač nedokáže převést na výpočet s využitím vyhledávací tabulky, takže bude donucen realizovat skutečné rozvětvení:
unsigned int numeric_value(unsigned int x, unsigned int y) {
switch (x) {
case 0:
y++;
break;
case 1:
y+=2;
break;
case 2:
y-=2;
break;
case 3:
y--;
break;
}
return y;
}
Výsledkem překladu bude mezikód, který je dosti zajímavý, protože obsahuje tabulku skoků switch. Tato tabulka obsahuje testované hodnoty a odpovídající cíle skoků:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @numeric_value(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
switch i32 %0, label %11 [
i32 0, label %3
i32 1, label %5
i32 2, label %7
i32 3, label %9
]
3: ; preds = %2
%4 = add i32 %1, 1
br label %11
5: ; preds = %2
%6 = add i32 %1, 2
br label %11
7: ; preds = %2
%8 = add i32 %1, -2
br label %11
9: ; preds = %2
%10 = add i32 %1, -1
br label %11
11: ; preds = %2, %9, %7, %5, %3
%12 = phi i32 [ %1, %2 ], [ %10, %9 ], [ %8, %7 ], [ %6, %5 ], [ %4, %3 ]
ret i32 %12
}
15. Další varianta rozvětvení konstrukcí switch-case
V dnešním posledním demonstračním příkladu bude opět použita programová konstrukce typu switch-case, ovšem nyní nebudou testované hodnoty tvořit číselnou řadu 0, 1, 2, 3. Namísto toho použijeme (pro překladač náhodné) hodnoty:
unsigned int numeric_value(unsigned int x, unsigned int y) {
switch (x) {
case 42:
y++;
break;
case 100:
y+=2;
break;
case 6502:
y-=2;
break;
case 8080:
y--;
break;
}
return y;
}
Překlad této konstrukce do mezikódu LLVM IR dopadne takto:
; Function Attrs: mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) uwtable
define dso_local noundef i32 @numeric_value(i32 noundef %0, i32 noundef %1) local_unnamed_addr #0 {
switch i32 %0, label %11 [
i32 42, label %3
i32 100, label %5
i32 6502, label %7
i32 8080, label %9
]
3: ; preds = %2
%4 = add i32 %1, 1
br label %11
5: ; preds = %2
%6 = add i32 %1, 2
br label %11
7: ; preds = %2
%8 = add i32 %1, -2
br label %11
9: ; preds = %2
%10 = add i32 %1, -1
br label %11
11: ; preds = %2, %9, %7, %5, %3
%12 = phi i32 [ %1, %2 ], [ %10, %9 ], [ %8, %7 ], [ %6, %5 ], [ %4, %3 ]
ret i32 %12
}
16. Nové instrukce používané při větvení
V předchozích výpisech LLVM IR jsme mohli vidět následující pětici instrukcí určených pro řízení větvení, vícenásobného rozvětvení, programových smyček atd.:
| # | Jméno instrukce | Parametry | Stručný popis instrukce |
|---|---|---|---|
| 1 | br | label číslo | nepodmíněný skok na nastavené návěští |
| 2 | br | podmínka label číslo | podmíněný skok na nastavené návěští |
| 3 | ret | hodnota | návrat z funkce |
| 4 | switch | tabulka | rozeskok na základě tabulky s dvojicemi hodnota:návěští |
| 5 | phi | typ [hodnota, hodnota] … | instrukce použitá při konverzi mezi LLVM IR a SSA |
První dvě instrukce br provádí nepodmíněný nebo podmíněný skok, a to prakticky stejným způsobem, jako je tomu v assembleru (za případnou podmínkou vždy následuje cíl skoku). Třetí instrukce ret realizuje výskok z funkce (tuto instrukci již dobře známe). Čtvrtá instrukce switch slouží pro realizaci rozeskoku na základě tabulky s hodnotami a cíli skoku. A konečně pátá instrukce phi je svou sémantikou nejsložitější. Používá se při konverzi z LLVM IR (mezikódu) do SSA (Static single-assignment), což je v oblasti překladačů důležitá problematika, které se budeme věnovat v samostatném článku:
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 | fneg | float/double | změna znaménka hodnoty s plovoucí řádovou čárkou |
| 25 | fadd | hodnota1, hodnota2 | součet dvou hodnot s plovoucí řádovou čárkou |
| 26 | fsub | hodnota1, hodnota2 | rozdíl dvou hodnot s plovoucí řádovou čárkou |
| 27 | fmul | hodnota1, hodnota2 | součin dvou hodnot s plovoucí řádovou čárkou |
| 28 | fdiv | hodnota1, hodnota2 | podíl dvou hodnot s plovoucí řádovou čárkou |
| 29 | fcmp oeq | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „rovno“ |
| 30 | fcmp une | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „nerovno“ |
| 31 | fcmp olt | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „menší než“ |
| 32 | fcmp ole | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „menší nebo rovno“ |
| 33 | fcmp ogt | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „větší než“ |
| 34 | fcmp oge | float/double, float/double | porovnání dvou operandů s plovoucí řádovou čárkou na relaci „větší nebo rovno“ |
| 35 | zext | typ1, operand, typ2 | bezznaménkové rozšíření z typu1 na typ2 |
| 36 | sext | typ1, operand, typ2 | znaménkové rozšíření z typu1 na typ2 |
| 37 | trunc | typ1, operand, typ2 | zmenšení bitové šířky z typu1 na typ2 |
| 38 | fpext | typ1, hodnota, typ2 | převod FP hodnoty z typu 1 na typ 2 (rozšíření přesnosti/rozsahu) |
| 39 | fptrunc | typ1, hodnota, typ2 | převod FP hodnoty z typu 1 na typ 2 (zmenšení přesnosti/rozsahu) |
| 40 | sitofp | typ1, hodnota, typ2 | převod celočíselné hodnoty se znaménkem na FP hodnotu |
| 41 | uitofp | typ1, hodnota, typ2 | převod celočíselné hodnoty bez znaménka na FP hodnotu |
| 41 | fptosi | typ1, hodnota, typ2 | převod FP hodnoty na celočíselnou hodnotu se znaménkem (signed) |
| 42 | fptoui | typ1, hodnota, typ2 | převod FP hodnoty na celočíselnou hodnotu bez znaménka (unsigned) |
| 43 | br | label číslo | nepodmíněný skok na nastavené návěští |
| 44 | br | podmínka label číslo | podmíněný skok na nastavené návěští |
| 45 | switch | tabulka | rozeskok na základě tabulky s dvojicemi hodnota:návěští |
| 46 | phi | typ [hodnota, hodnota] … | instrukce použitá při konverzi mezi LLVM IR a SSA |
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 i v článku předchozím, 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 arith_operators_float.ll \
comparison_operators.ll comparison_operators_float.ll \
branching_1.ll branching_2.ll pointers_1.ll pointers_2.ll \
add_arrays_1.ll add_arrays_2.ll \
fibonacci.ll \
numeric_conversions_1.ll numeric_conversions_2.ll numeric_conversions_3.ll \
switch_1.ll switch_2.ll switch_3.ll switch_4.ll switch_5.ll switch_6.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 předchozím i 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 |
| 32 | numeric_conversions1.c | numerické konverze mezi celočíselnými typy bez znaménka | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/numeric_conversions1.c |
| 33 | numeric_conversions1.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_conversions1.ll |
| 34 | numeric_conversions2.c | numerické konverze mezi celočíselnými typy se znaménkem | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/numeric_conversions2.c |
| 35 | numeric_conversions2.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_conversions2.ll |
| 36 | numeric_conversions3.c | numerické konverze mezi celočíselnými typy a typy s plovoucí řádovou čárkou | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/numeric_conversions3.c |
| 37 | numeric_conversions3.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_conversions3.ll |
| 38 | switch1.c | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch1.c | |
| 39 | switch1.ll | výsledek překladu céčkovského zdrojového kódu do mezijazyka | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch1.ll |
| 40 | switch2.c | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch2.c | |
| 41 | switch2.ll | výsledek překladu céčkovského zdrojového kódu do mezijazyka | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch2.ll |
| 42 | switch3.c | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch3.c | |
| 43 | switch3.ll | výsledek překladu céčkovského zdrojového kódu do mezijazyka | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch3.ll |
| 44 | switch4.c | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch4.c | |
| 45 | switch4.ll | výsledek překladu céčkovského zdrojového kódu do mezijazyka | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch4.ll |
| 46 | switch5.c | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch5.c | |
| 47 | switch5.ll | výsledek překladu céčkovského zdrojového kódu do mezijazyka | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch5.ll |
| 48 | switch6.c | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch6.c | |
| 49 | switch6.ll | výsledek překladu céčkovského zdrojového kódu do mezijazyka | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/switch6.ll |
| 50 | arith_operators_float.c | aritmetické operátory programovacího jazyka C | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/arith_operators_float.c |
| 51 | arith_operators_float.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_float.ll |
| 52 | comparison_operators_float.c | operátory pro porovnání programovacího jazyka C | https://github.com/tisnik/8bit-fame/blob/master/LLVM_IR/comparison_operators_float.c |
| 53 | comparison_operators_float.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_float.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
