08. LLVM backend

Nice to read

Introducere

Backend-ul de LLVM primește cod scris în limbajul intermediar și generează asamblare. Binarul este llc.

Pentru a face trecerea de la codul IR la asamblare, este nevoie de 4 transformări:

LLVM IR → SelectionDAG → MachineDAG → MachineInstr → MCInst

SelectionDAG

Reprezintă un graf în care operațiile sunt reprezentate ca noduri. Majoritatea nodurilor sunt independente de arhitectură, dar pot exista și noduri custom. Fiecare nod are un opcode, care este definit (pentru nodurile independente de arhitectură) în enum-ul ISD::NodeType Dacă este nevoie de noduri specifice pentru o arhitectură, ele se definesc începând cu ISD::BUILTIN_OP_END. Se poate observa ca există mai multe opcode-uri decât instrucțiuni llvm.

Arcele din graf reprezintă dependințe. Ele pot fi:

  1. de date, o operație folosește un rezultat produs de o altă operație;
  2. chain, ordonează operații;
  3. glue, operațiile sunt împreună.

Valorile din acest graf au tipuri (e.g. i32).

MachineDAG

Este un graf foarte asemănător cu SelectionDAG, în care nodurile sunt înlocuite cu instrucțiuni.

MachineInstr

MachineDAG-ul este transformat într-o listă de instrucțiuni dependente de arhitectură, cu câteva excepții (e.g. instrucțiunea COPY).

MCInst

Este o reprezentare simplificată, care conține un opcode și niște operanzi.

Structura de fișiere

Pentru a înregistra un backend, trebuie modificate urmatoarele fișiere:

CMakeLists.txt
autoconf/config.sub
autoconf/configure.ac
configure
include/llvm/ADT/Triple.h
include/llvm/Object/ELFObjectFile.h
include/llvm/Support/ELF.h
lib/Support/Triple.cpp
lib/Target/LLVMBuild.txt

Hint: Căutați în aceste fișiere apariții corespunzătoare unui target existent (ex Hexagon).

Implementarea se face în directorul lib/Target/X, care trebuie să conțină, printre altele și următoarele fișiere:

Nume Semnificație
XTargetMachine Implementarea unei clase care extinde LLVMTargetMachine. Conține XPassConfig care adaugă pașii de transformare. Cel mai important pas este createXISelDag
XSubtarget Extinde o clasă generată de tablegen, care definiește un subtarget.
XISelLowering Definește modul in care se face lowering la un SelectionDAG legal.
XISelDAGToDAG Este responsabil cu trecerea de la SelectionDAG la MachineDAG
XRegisterInfo Conține informații despre regiștrii (regștrii rezervați, speciali, salvați de calling convention). Metoda care trebuie implementată este eliminateFrameIndex, care inlocuiește accese abstracte la stivă (e.g. FI -3) cu o adresă concretă (e.g. -20 (SP))
XInstrInfo Informații și transformări pe instrucțiuni. Cele mai importante metode care trebuiesc implementate sunt copyPhysReg, storeRegToStackSlot, loadRegFromStackSlot, expandPostRAPseudo
XFrameLowering Responsabil cu calcularea stivei folosite și modificarea ei la intrarea (emitPrologue) și ieșirile (emitEpilogue) din funcție.
XAsmPrinter Emiterea codului și datelor. Deși conține Asm în nume, dumparea se face la un stream abstract care poate fi text (pentru asamblare) sau obiect (dacă se generează direct elf).

TableGen

Informațiile dependente de arhitectură sunt definite în fișiere speciale cu extensia ``.td`` într-o manieră declarativă.

Aceste fișiere sunt procesate de utilitarul table-gen, care generează cod C++ pe baza acestor fișiere.

Cele 2 concepte importante din limbaj sunt clasele și definițiile.

Clasele sunt folosite pentru a grupa caracteristici comune ale resurselor. O clasă poate să extindă una sau mai multe clase. Cu excepția unor clase de baza (Instruction, Register, CallingConv, CalleeSavedRegs, ș.a.), care definesc semantica unor resurse, clasele nu apar in fișierele C++ generate.

Pentru a defini un registru R0, extindem clasa Register:

// Clasa CplReg extinde clasa Register.
// Parametrii sunt transmiși printr-o manieră care seamana cu template-urile din C++.
class CplReg<bits<5> num, string n, list<string> alt = [],
                   list<Register> alias = []> : Register<n> {
  let Namespace = "Cpl";     // Namespace este un câmp din Register în care
                             // vor fi grupate registrele în codul generat.
  field bits<5> Num;         // Field al clasei curente în care salvăm numărul registrului
  let Aliases = alias;       // Listă de alias-uri pentru registru
  let HWEncoding{4-0} = num; // Codificarea registrului
}

// Ri - 32-bit integer registers.
// Această clasă nu se reflectă în fișierul generat.
class Ri<bits<5> num, string n, list<string> alt = []> : CplReg<num, n, alt> {
  let Num = num;
}

// Definim R0, R1 și R2 prin intermediul claselor Ri și DwarfRegNum
def R0 : Ri<0, "r0">,  DwarfRegNum<[0]>;
def R1 : Ri<1, "r1">,  DwarfRegNum<[1]>;
def R2 : Ri<2, "r2">,  DwarfRegNum<[2]>;
//...

Pentru a defini o instrucțiune, extindem clasa Instruction:

class InstCpl<dag outs, dag ins, string asmstr>
  : Instruction {
  let Namespace = "Cpl";

  dag OutOperandList = outs;  // lista operanzilor destinație
  dag InOperandList = ins;    // lista operanzilor sursă
  let AsmString = asmstr;     // string-ul corespunzător sintaxei
  let Size = 4;               // dimensiunea în octeți (pentru generare obiect)

  bits<32> Inst = 0;          // codificarea binară a instrucțiunii
  bits<32> SoftFail;
}

// Instrucțiunea va fi identificată prin enum-ul Cpl::ADD în codul generat.
def ADD : InstCpl<
     (outs IntRegs:$rc),
     (ins IntRegs:$ra, IntRegs:$rb),
     "add $rc, $ra, $rb"
>;

Generarea de cod se face prin intermediul clasei Pat care mapează noduri din SelectionDAG în MachineDAG:

// Nu avem nevoie de nume pentru pattern, așa că îl definim anonim

// add -> ADD
def : Pat<(add IntRegs:$ra, IntRegs:$rb), (ADD IntRegs:$ra, IntRegs:$rb)>;
// ineg ->
//   t = EOR ra, ra    @ t is zero
//   res = SUB t, ra   @ res is zero - ra
def : Pat<(inot IntRegs:$ra), (SUB (EOR IntRegs:$ra, IntRegs:$ra), IntRegs:$ra)>;

Clasa Pat extinde clasa Pattern și ne permite să definim pattern-uri într-un mod mai simplu. In versiunea de LLVM 3.6, aceasta clasa este definita ca:

 class Pat<dag pattern, dag result> : Pattern<pattern, [result]>;

Calling convention-ul se definește tot prin intermediul fișierelor .td. Calling convention-ul este un contract prin intermediul căruia se specifică modalitatea de transmitere a parametrilor către funcții și întoarcerea rezultatelor de către acestea (folosind registre, stiva sau ambele).

Se pot defini mai multe calling convention-uri, folosing clasa CallingConvention. Această clasă primește ca parametru o listă de acținui (CCAction) care este folosită pentru a determina modul de transmitere a parametrilor.

Pentru lista tuturor acțiunilor, inspectați fișierul include/llvm/Target/TargetCallingConv.td. Mai jos sunt prezentate câteva din cele mai comune acțiuni, din acest fișier.

/// CCIfType - If the current argument is one of the specified types, apply
/// Action A.
class CCIfType<list<ValueType> vts, CCAction A> : CCPredicateAction<A> {
  list<ValueType> VTs = vts;
}

/// CCPromoteToType - If applied, this promotes the specified current value to
/// the specified type.
class CCPromoteToType<ValueType destTy> : CCAction {
  ValueType DestTy = destTy;
}

/// CCAssignToReg - This action matches if there is a register in the specified
/// list that is still available.  If so, it assigns the value to the first
/// available register and succeeds.
class CCAssignToReg<list<Register> regList> : CCAction {
  list<Register> RegList = regList;
}

/// CCAssignToStack - This action always matches: it assigns the value to a
/// stack slot of the specified size and alignment on the stack.  If size is
/// zero then the ABI size is used; if align is zero then the ABI alignment
/// is used - these may depend on the target or subtarget.
class CCAssignToStack<int size, int align> : CCAction {
  int Size = size;
  int Align = align;
}

Este important de menționat că determinarea modului de transmitere a unui parametru se face parcurgând lista până la prima acțiune de assign care se execută.

Pentru a defini un calling convention care primește toți parametrii pe stivăputem folosi:

def CC_Stack : CallingConv<[
  // Promote values to i32
  CCIfType<[i1, i8, i16], CCPromoteToType<i32>>,

  // Use the stack (always works)
  CCAsignToStack<0, 0>
]>;

Setul de instrucțiuni

În cadrul acestui laborator se va folosi o arhitectură care definește un subset al instrucțiunilor de ARMv3.

Arhitectura dispune de un set de 16 registre de 32 biți (R0-R15) și un registru de stare (CPSR). Registrele R13-R15 sunt rezervate pentru stack pointer SP (R13), link register LR (R14) și program counter PC (R15).

Registrul de stare este scris de către instrucțiunile de comparație și citit de către instrucțiunile de salt condiționat.

Setul de instrucțiuni cuprinde:

Tip Sintaxă Efect
ALU add rd, rs1, rs2 rd = rs1 + rs2
add rd, rs1, i8 rd = rs1 + i8
sub rd, rs1, rs2 rd = rs1 - rs2
sub rd, rs1, i8 rd = rs1 - i8
mul rd, rs1, rs2 rd = rs1 * rs2
and rd, rs1, rs2 rd = rs1 AND rs2
and rd, rs1, i8 rd = rs1 AND i8
orr rd, rs1, rs2 rd = rs1 OR rs2
orr rd, rs1, i8 rd = rs1 OR i8
eor rd, rs1, rs2 rd = rs1 XOR rs2
eor rd, rs1, i8 rd = rs1 XOR i8
mul rd, rs1, rs2 rd = rs1 * rs2
Transfer mov rd, rs rd = rs
mvn rd, rs rd = NOT rs
Salt b i24 pc = address
Salt condiționat beq i24 if eq then pc = address
bne i24 if ne then pc = address
bge i24 if ge then pc = address
bgt i24 if gt then pc = address
ble i24 if le then pc = address
blt i24 if lt then pc = address
Apel funcție bl i24 lr = next_address; pc = address
Memorie ldr rd, [ra, i12] rd = [ra + i12]
ldrb rd, [ra, i12] rd = zero_extend [ra + i12]
str rs, [ra, i12] [ra + i12] = rs
strb rs, [ra, i12] [ra + i12] = rs AND 0xFF

Compilare și rulare

Următoarele pachete sunt necesare:

gcc-arm-linux-gnueabi # cross compiler de arm
libc-dev-armel-cross # embedded GNU C library pentru cross-compiling
qemu-user # pt qemu-arm
android-tools-adb # adb
openjdk-7-jre # sau alta varianta de java
lib32z1 lib32ncurses5 lib32stdc++6 # lib-uri pe 32 de biti (daca e cazul)

De asemenea, este necesar SDK-ul de Android. Acesta trebuie dezarhivat, iar calea către directorul obținut în urma dezarhivării trebuie setată în cadrul variabilei de mediu ANDROID_SDK_ROOT. De asemenea trebuie adăugată în PATH calea către directorul $ANDROID_SDK_ROOT/tools.

==== Device pentru android ====
Pentru a instala doar target-ul necesar:
<code>
# Recomandam instalarea celor cu API 22
android list sdk

android update sdk --no-ui --filter 4,25,38,tool,platform-tool
# Numerele pot diferi in functie de output-ul comenzii de mai sus. In cazul nostru:
# 4 - SDK Platform Android 5.1.1, API 22, revision 2
# 25 - Google APIs, Android API 22, revision 1
# 38 - Google APIs ARM EABI v7a System Image, Google Inc. API 22, revision 1

android list targets

android create avd -n myandroid22 \
    -t "Google Inc.:Google APIs:22" \
    --abi google_apis/armeabi-v7a
</code>

==== Compilare ====
Pentru a compila un fișer ''C'' vom folosi cross-compiler-ul de ARM ''arm-linux-gnueabi-gcc''.
<note important>
Pentru temă vom genera cod pentru ARM v3.
Datorită unui bug în gcc, vom rula cu opțiunea ''-march=armv4''.
</note>
Opțiunile sunt similare cu cele date pentru gcc.
Arhitectura dorită va fi specificată cu ''-march=armv4''.

Pentru a obține asamblarea, folosiți ''-S''.

Pentru a putea rula executabilele generate folosind emulatoarele, trebuie ca acestea să fie link-ate static (prin transmiterea opțiunii ''-static'' către ''arm-linux-gnueabi-gcc'')

==== Emulare ====
Pentru testare, vom folosi emulatorul de Android și qemu.
=== Emulatorul de Android ===
<code>
emulator -avd myandroid22 & # porniti emulatorul de android
adb push binar /data/binar  # copiati binarul pe device
adb shell /data/binar       # rulati binarul
</code>
Pentru a putea rula aceste comenzi, trebuie adăugată în ''PATH'' calea către directorul ''$ANDROID_SDK_ROOT/tools. În cazul în care se folosește un sistem de operare pe 32 biți, este nevoie de forțarea folosirii binarelor pe 32 de biți prin exportarea variabilei de mediu ANDROID_EMULATOR_FORCE_32BIT=true. === Qemu === <code> qemu-arm binar # rulati binarul </code> ===== Exerciții ===== Arhiva laboratorului. ==== Exercițiul 1 ==== Scrieți un program C care printează “Hello World!”. Compilați programul și obțineți fișierul .s. Asamblați și link-ați fișierul pentru a obține un fișier ELF. Rulați ELF-ul folosind cele 2 emulatoare (Android și qemu). ==== Exercițiul 2 ==== Completați funcția sum din fișierul ex2.s pentru a întoarce suma elementelor vectorului dat ca parametru. Adresa vectorului este transmisă prin registrul R0, iar numărul elementelor este transmis prin registrul R1. Rezultatul trebuie intors prin registrul R0. Link-ați ex2.s împreună cu ex2.c și rulați executabilul.<note important> Funcția nu trebuie sa suprascrie niciun registru rezervat pentru program counter, return address și stack pointer. </note> ==== Exercițiul 3 ==== Copiați directorul cpl din arhiva în directorul lib/Target al dristribuției de LLVM de mașinile din laborator. Înregistrați target-ul Cpl în cadrul framework-ului de LLVM. După ce ați modificat toate fișierele, compilați llc cu suport pentru noul target: cd ~/packages/llvm-3.8.0/build cmake -DLLVM_TARGETS_TO_BUILD=Cpl path_to_llvm_src make -j2 llc Codul sursa LLVM poate fi descărcat de pe llvm.org Pentru a testa, compilați fișierul ex3.a.ll folosind opțiunea -filetype=null pentru llc. <note warning> Compilarea llc va dura în jur de 20 minute. </note> ==== Exercițiul 4 ==== Completați în target-ul de Cpl pentru a genera cod pentru următoarele funcții echivalente în C. Funcțiile se află în fișierele ex4_<no>.ll. === f === Va trebui să implementați funcția CplInstPrinter::printOperand. <code> void f() {} </code> === ident === Va trebui să completați fisierul CplCallingConv.td <code> int ident(int a) { return a; } </code> === or === Va trebui să adăugați instructiunea or în fișierul CplInstrInfo.td și un pattern pentru nodul or în fișierul CplPatterns.td <code> int or(int a, int b) { return a | b; } </code> === second === Va trebui să implementați funcția CplInstrInfo::copyPhysReg''.

int second(int a, int b) {
  return b;
}
cpl/labs/08.txt · Last modified: 2017/11/21 06:52 by bogdan.nitulescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0