Laboratorium 8: LLVM

Low Level Virtual Machine, http://llvm.org/

  • maszyna rejestrowa, nieograniczona ilość rejestrów
  • generacja kodu na rzeczywisty procesor prez alokację rejestrów
  • biblioteka C++, ale także format tekstowy (na laboratorium zajmujemy się tylko tym ostatnim)
  • kod czwórkowy:
    %t2 = add i32 %t0, %t1
  • instrukcje są silnie typowane:
    %t5 = add double %t4, %t3
    store i32 %t2, i32* %loc_r
  • nowy rejestr dla każdego wyniku (SSA - Static Single Assignment)

    Prosty przykład

    declare void @printInt(i32) ;  w innym module
    define i32 @main() {
           %i1 = add i32 2, 2
           call void @printInt(i32 %i1)
           ret i32 0
    }

    Uruchomienie

    Narzędzia dla LLVM są w katalogu PUBLIC/MRJP/Llvm (w tym pliki runtime.{ll,bc}

    $ llvm-as t2.ll
    $ llvm-link -o out.bc t2.bc runtime.bc
    $ lli out.bc
    4

    Silnia, rekurencyjnie

    define i32 @fact(i32 %n) {
            %c0 = icmp eq i32 %n, 0
            br i1 %c0, label %L0, label %L1
    L0:
            ret i32 1
    L1:
            %i1 = sub i32 %n, 1
            %i2 = call i32 @fact(i32 %i1)
            %i3 = mul i32 %n, %i2
            ret i32 %i3 
    }

    Uwaga:

    • argumenty funkcji są deklarowane
    • wszystko jest typowane, nawet warunki skoków
    • skoki warunkowe tylko z "else"

    Silnia, iteracyjnie

    Używając zmiennych lokalnych w pamięci

    declare void @printInt(i32)
    define i32 @main() {
    entry: 
    	%i1=call i32 @fact(i32 5)
    	call void @printInt(i32 %i1)
    	ret i32 0
    }
     
    ; r = 1
    ; i = n
    ; while (i > 1):
    ;   r = r * i
    ;   i = i -1
    ; return r
    ;;
     
    define i32 @fact(i32 %n) {
    entry: 
    ; local variables:
            %loc_r = alloca i32
    	%loc_i = alloca i32
    ; r = 1
    	store i32 1, i32* %loc_r
    ; i = n
            store i32 %n, i32* %loc_i
    	br label %L1
    ; while i > 1:
    L1:
     	%tmp_i1 = load i32* %loc_i
    	%c0 = icmp sle i32 %tmp_i1, 1
    	br i1 %c0, label %L3, label %L2
    ; loop body
    L2:
    ; r = r * i
            %tmp_i2 = load i32* %loc_r
    	%tmp_i3 = load i32* %loc_i
    	%tmp_i4 = mul i32 %tmp_i2, %tmp_i3
    	store i32 %tmp_i4, i32* %loc_r 
    ; i = i-1
    	%tmp_i5 = load i32* %loc_i
    	%tmp_i6 = sub i32 %tmp_i5, 1
    	store i32 %tmp_i6, i32* %loc_i
    	br label %L1
    L3:
    	%tmp_i8 = load i32* %loc_r
    	ret i32 %tmp_i8

    Używając rejestrów, w wersji SSA

    declare void @printInt(i32)
    define i32 @main() {
    entry: 
    	%i1=call i32 @fact(i32 5)
    	call void @printInt(i32 %i1)
    	ret i32 0
    }
     
    ; r = 1
    ; i = n
    ; while (i > 1):
    ;   r = r * i
    ;   i = i -1
     
    define i32 @fact(i32 %n) {
    entry: br label %L1 
    L1: 
    	%i.1 = phi i32 [%n, %entry], [%i.2, %L2]
    	%r.1 = phi i32 [1, %entry], [%r.2, %L2]
    	%c0 = icmp sle i32 %i.1, 1
    	br i1 %c0, label %L3, label %L2
    L2:
    	%r.2 = mul i32 %r.1, %i.1
    	%i.2 = sub i32 %i.1, 1
    	br label %L1
    L3:
    	ret i32 %r.1
    }

    Zadanie

    Rozszerz kalkulator o generowanie kodu dla LLVM

    Napisy w LLVM

    @hellostr = internal constant [6 x i8] c"Hello\00"
     
    declare void @printString(i8*)
    define i32 @main() {
    entry: 
           %t0 = bitcast [6 x i8]* @hellostr to i8* ; można też uzyć getelementptr
           call void @printString(i8* %t0)
           ret i32 0
    }