abclinuxu.cz AbcLinuxu.cz itbiz.cz ITBiz.cz HDmag.cz HDmag.cz abcprace.cz AbcPráce.cz
Inzerujte na AbcPráce.cz od 950 Kč
Rozšířené hledání
×
    dnes 03:11 | IT novinky

    Společnost Valve publikovala přehled To nej roku 2024 ve službě Steam aneb ohlédnutí nejen za nejprodávanějšími, nejprodávanějšími nově vydanými a nejhranějšími herními tituly roku 2024.

    Ladislav Hagara | Komentářů: 0
    včera 13:00 | Nová verze

    Byla vydána verze 3.4.0, a hned na to verze 3.4.1 řešící pouze číslo verze, programovacího jazyka Ruby (Wikipedie). Podrobný přehled novinek a změn v souboru NEWS na GitHubu. Ruby lze vyzkoušet na webové stránce TryRuby.

    Ladislav Hagara | Komentářů: 4
    včera 03:00 | Komunita

    Všem čtenářkám a čtenářům AbcLinuxu krásné Vánoce.

    Ladislav Hagara | Komentářů: 52
    včera 02:44 | Nová verze

    Byla vydána nová verze 2.9.0 svobodného softwaru ScummVM (Wikipedie) umožňujícího bezproblémový běh mnoha klasických adventur na zařízeních, pro které nebyly nikdy určeny. Přehled novinek v poznámkách k vydání.

    Ladislav Hagara | Komentářů: 0
    včera 02:22 | Nová verze

    Byl vydán Sublime Text 4 Build 4189. Sublime Text (Wikipedie) je proprietární multiplatformní editor textových souborů a zdrojových kódů. Ke stažení a k vyzkoušení je zdarma. Pro další používání je nutná licence v ceně 99 dolarů. Spolu se Sublime Merge je cena 168 dolarů.

    Ladislav Hagara | Komentářů: 0
    24.12. 04:22 | Nová verze

    Vývojáři postmarketOS vydali verzi 24.12 tohoto před sedmi lety představeného operačního systému pro chytré telefony vycházejícího z optimalizovaného a nakonfigurovaného Alpine Linuxu s vlastními balíčky. Přehled novinek v příspěvku na blogu. Na výběr jsou 4 uživatelská rozhraní: GNOME Shell on Mobile, KDE Plasma Mobile, Phosh a Sxmo.

    Ladislav Hagara | Komentářů: 3
    24.12. 03:55 | Nová verze

    Byla vydána nová verze 9.9 z Debianu vycházející linuxové distribuce DietPi pro (nejenom) jednodeskové počítače. Přehled novinek v poznámkách k vydání.

    Ladislav Hagara | Komentářů: 0
    23.12. 12:22 | Nová verze

    Jonathan Thomas oznámil vydání nové verze 3.3.0 video editoru OpenShot (Wikipedie). Představení novinek také v písničce na YouTube. Zdrojové kódy OpenShotu jsou k dispozici na GitHubu. Ke stažení je i balíček ve formátu AppImage. Stačí jej stáhnout, nastavit právo na spouštění a spustit.

    Ladislav Hagara | Komentářů: 4
    22.12. 00:55 | Nová verze

    Byla vydána nová verze 5.0.0 programu na úpravu digitálních fotografií darktable (Wikipedie).

    Ladislav Hagara | Komentářů: 5
    21.12. 20:22 | IT novinky

    Před 50 lety, v prosinci 1974, byl představen počítač Altair 8800 od společnosti MITS (Micro Instrumentation and Telemetry Systems). Založen byl na procesoru Intel 8080. Prodával se jako stavebnice za 439 dolarů. V dnešních cenách by to bylo přes 2500 dolarů. Článek o Altair 8800 byl publikován v lednového čísle, na stáncích bylo už v prosinci, časopisu Popular Electronics (pdf). Videoukázky práce s počítačem Altair 8800 na YouTube: Altair 8800 Instructional Videos.

    Ladislav Hagara | Komentářů: 10
    Rozcestník

    AsmJit - Compiler a jeho zrození...

    4.3.2009 20:42 | Výběrový blog | poslední úprava: 31.8.2009 14:20

    Tento blog byl smazán autorem

           

    Hodnocení: 94 %

            špatnédobré        

    Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

    Komentáře

    Vložit další komentář

    31.8.2009 15:09 backup
    Rozbalit Rozbalit vše Záloha: AsmJit - Compiler a jeho zrození...

    V dnešním zápisku bych chtěl představit pokročilé možnosti generování kódu pomocí knihovny AsmJit. V průběhu práce na grafické knihovně BlitJit jsem došel k závěru, že psát kód na úrovni assembleru je velmi komplikované a při skládání více kousků kódu dohromady může být vytváření výsledného kódu opravdu peklo pro programátora. Abych sobě i ostatním ulehčil život a zrychlil vývoj aplikací (a knihoven) využívajících AsmJit, vytvořil jsem třídu Compiler, která se snaží minimalizovat úsilí věnované na lepení existujících částí kódu a samozřejmě má za cíl ulehčit i vývoj těchto částí.

    Úvod

    První verze knihovny AsmJit obsahovala pouze generátor kódu pro 32bitový x86 procesor. Jednalo se hlavně o jeden velký hlavičkový soubor, který byl založený na kódu z V8 (Google V8). Průběh generování assembleru probíhal tak, že se pomocí metod v hlavní třídě X86 (nyní se jmenuje Assembler) přímo emitoval kód do bufferu, který byl následně překopírován do paměti, kde mohl být kód spuštěn. Mezi největší nevýhody patřilo to, že se dal generovat pouze kód pro 32bitový procesor.

    V průběhu vývoje jsem přidal podporu pro 64 bitů a pár věcí, o které mě požádali ostatní (byl to přepis kódu na konkrétní pozici a relokace). V této fázi jsem byl s už knihovnou celkem spokojený a chtěl jsem si vyzkoušet, jak používá v praxi (tedy napsat něco, co tuto knihovnu využívá). Zkusil jsem tedy začal psát knihovnu BlitJit. Už při psaní prvních funkcí jsem zjistil, že současné možnosti mi nestačí, a že by se mi hodilo používat mnohem vyšší konstrukce – začala práce na třídě Compiler a kompletní reorganizace knihovny AsmJit.

    Co bylo potřeba změnit

    (toto můžete přeskočit, není to důležité)

    Design knihovny AsmJit před reorganizací vypadal nějak takto (organizace tříd):

    X86 – hlavní třída pro generování kódu
    Op  – Operand (Register, Memory, Immediate)
    Register, MMRegister, XMMRegister – registry byly zvláštní třída, fungovaly ale jako Operand.
    Label – návěští
    

    jednalo se prakticky o kód z Google V8, prototypy instrukcí vypadaly nějak takto:

    void X86::mov(const Op& dst, const Op& src)
    {
      ASMJIT_ASSERT() – kontrola vstupů
      ... – kód pro emitování instrukce
    }
    

    Tento design měl 2 velké nevýhody. Mnoho instrukcí si kontrolovalo správnost vstupu až za běhu (tedy ne při kompilaci v C++) a emitování kódu bylo prováděno přímo z funkcí ve třídě X86.

    Rozhodl jsem se, že upustím celý koncept assembleru z V8 a navrhnu vlastní řešení, které bude sice kompatibilní, ale bude umožňovat mnohem více. Prakticky jsem zahodil celý kód a začal jsem psát všechno znovu.

    Změny v bodech:

    • Jedna třída Op reprezentující všechny operandy zanikla, vznikla základní třída Operand, ze které vychází další třídy Register, MMRegister, XMMRegister, Mem a Immediate. Nyní už může provádět kompletní kontrolu při kompilaci i C++ překladač (při kompilování C++ kódu, který využívá knihovnu AsmJit).
    • Vznikla třída Serializer, ve které jsou implementované veškeré instrukce. Z této třídy dědí třída Assembler, která umí generovat strojový kód a Compiler, která si místo generování strojového kódu uchovává instrukce a při zhotovení použije právě Assembler k vygenerování finálního strojového kódu.
    • Generování kódu se přesunulo z inline funkcí do tabulek, kde je uložená skupina instrukce (instruction group), typy operandů a opkódy. Tento princip umožňuje mnohem jednodušší rozšiřování knihovny v budoucnu a taky zjednodušuje hledání chyb.
    • Podpora pro logování emitováného kódu (díky nové architektuře to byla triviální záležitost).

    Rozepisovat reimplementaci asi nemá cenu, důležitý je ale fakt, že nová knihovna je z 99% zpětně kompatibilní a umožňuje toho mnohem víc.

    Organizace tříd v AsmJit:

    Operand:: (operandy)
      - BaseRegMem::
        - BaseReg::
          - Register, 
          - X87Register,
          - MMRegister,
          - XMMRegister,
        - Mem, 
      - Immediate
    Serializer::
      ( obsahuje asm instrukce (x86, x64), všechny instrukce jsou serializováné 
        do jediné pure virtual metody, která je implementovaná v Assembler a Compiler )
      - Assembler – umožňuje generovat x86/x64 kód.
      - Compiler – umožňuje vytvářet funkce, obsahuje alokátor registrů a uchovávání 
        stavů mezi bloky kódu.
    Label - návěští
    

    Vzniklo ještě celkem dost tříd, které se používají spolu s třídou Compiler, budou představeny v příkladech.

    Podle mě je nyní návrh robustní. Knihovna AsmJit se rozpadla na méně částí, které jsou na mnohem lepší úrovni než dříve. Například generování strojového kódu zajišťuje třída Assembler, Serializaci (intrinsics) zajišťuje Serializer a o vyšší konstrukce se stará Compiler a jeho pomocné třídy.

    Compiler

    Mezi nejdůležitější novinku patří třída Compiler, o které měl být původně celý tento zápisek (trochu jsem rozepsal ty změny... no:) ). Umožňuje totiž vytvářet funkce pro 32bitové i 64bitové procesory (x86/x64) bez změn v kódu.

    Compiler vám pomůže z:

    • Calling Conventions – umožňuje vytvářet kód pro všechny možné konvence, pro 32bitové procesory zvládá cdecl, stdcall a fastcall a pro 64bitové procesory zvládá konvenci používanou ve Windows (WIN64 ABI) a konvenci používanou všude jinde (AMD64 ABI).
    • Alokace registrů a používání proměnných – pište kód tak, jak jste zvyklí z C++. Místo registrů můžete používat proměnné a místo ruční alokace registrů automatickou.
    • Uchovávání stavů alokovaných registrů mezi bloky

    Jako první bych chtěl začít definicí funkce, protože funkce bude asi vždycky základ toho, co budeme tvořit. Compiler obsahuje metody newFunction() a EndFunction(), které si představíme na následujícím kódu.

    using namespace AsmJit;
    
    // Vytvoření instance Compiler
    Compiler c;
    
    // Vytvoření funkce prototypu void (*Fn)( int * x, int * y);
    Function& f = * c.newFunction(
      CALL_CONV_DEFAULT, // calling convention
      BuildFunction2<int *, int *>() // prototyp funkce pomocí šablon
    );
    
    ... váš kód
    
    // Konec funkce
    c.endFunction();
    

    Myslím, že kód je celkem jasný, ale i přesto bych chtěl ujasnit pár věcí. Všechno, co vytvoří třída Compiler tak taky v budoucnu uvolní. Není tedy možné si ponechat nějakou instanci vytvořenou pomocí třídy Compiler a pracovat s ní po jeho zániku. To je i důvod, proč Compiler vždy vrací návratové hodnoty jako ukazatele. Další věc je ta, že k vytvoření prototypu funkce slouží šablony BuildFunctionX<>. Díky šablonám se nemůže stát, že by jste se spletli v typu nebo by Compiler váš typ špatně rozpoznal. Identifikaci typu je zjištěná už při kompilaci C++ kódu.

    Ve funkci budeme chtít určitě využít argumenty funkce a proměnné. Mezi argumentem a proměnnou nedělá Compiler rozdíl. Argument má tu vlastnost navíc, že je ihned alokován buď v registru nebo na zásobníku (podle volací konvence, typu argumentu a pořadí). Samozřejmě si můžeme argument, který byl předán na zásobníku alokovat do registru a naopak.

    Následující kód využije argumenty funkce:

    using namespace AsmJit;
    
    // Vytvoření instance Compiler
    Compiler c;
    
    // Vytvoření funkce prototypu void (*Fn)(int* x, int * y);
    Function& f = * c.newFunction(
      CALL_CONV_DEFAULT, // calling convention
      BuildFunction2<int *, int *>() // prototyp funkce pomocí šablon
    );
    
    // Argumenty
    // f.argument(X) vrací Variable*, které vždy obalujeme pomocí tříd, 
    // které mají sufix Ref (PtrRef, Int32Ref, Int64Ref, MMRef, XMMRef, ...)
    PtrRef a1(f.argument(0));
    PtrRef a2(f.argument(1));
    
    // a do ukazatelů můžeme uložit nějaké hodnoty
    c.mov( dword_ptr(a1.r()), imm(10) );
    c.mov( dword_ptr(a2.r()), imm(20) );
    
    // Konec funkce
    c.endFunction();
    

    Takto vytvořená funkce uloží do argumentů funkce x a y hodnoty 10 a 20. Jedná se o celkem primitivní příklad, na kterém bych chtěl ukázat právě to, že se nestaráme o registry. Tím, že jsme použili třídu PtrRef a zavolali metodu PtrRef::r() jsme alokovali proměnnou do registru. Na platformě x86 může vypadat vygenerovaný assembler takto:

    L1:
    push ebp
    mov ebp, esp
    L2:
    mov ecx, [ebp + 8]
    mov [ecx], 0xA
    mov edx, [ebp + 12]
    mov [edx], 0x14
    L3:
    mov esp, ebp
    pop ebp
    ret
    

    Můžeme si všimnout, že Compiler vygeneroval prolog, tělo funkce a epilog. Protože prolog a epilog může být někdy zbytečný, můžeme tuto volbu vypnout (ale jen na vlastní riziko;) ). Pokud zavoláme Function::setNaked(true), vypneme generování prologu a epilogu funkce a Compiler vytvoří následující kód:

    L1:
    L2:
    mov ecx, [esp + 4]
    mov [ecx], 0xA
    mov edx, [esp + 8]
    mov [edx], 0x14
    L3:
    ret
    

    Možností, jak ovlivnit výsledný kód je více. Mezi nejčastější patří alokování některých registrů hned při vstupu do funkce, to můžeme udělat zavoláním VariableRef::alloc() (nebo v našem případě PtrRef::alloc). Ukázka:

    using namespace AsmJit;
    
    // Vytvoření instance Compiler
    Compiler c;
    
    // Vytvoření funkce prototypu void (*Fn)(int* x, int * y);
    Function& f = * c.newFunction(
      CALL_CONV_DEFAULT, // calling convention
      BuildFunction2<int *, int *>() // prototyp funkce pomocí šablon
    );
    
    // Nepoužívat prolog / epilog
    f.setNaked(true):
    
    // Argumenty
    // f.argument(X) vrací Variable*, které vždy obalujeme pomocí tříd, 
    // které mají suffix Ref (PtrRef, Int32Ref, Int64Ref, MMRef, XMMRef, ...)
    PtrRef a1(f.argument(0));
    PtrRef a2(f.argument(1));
    
    // alokujeme si registry hned na začátku
    a1.alloc();
    a2.alloc();
    
    // a do ukazatelů můžeme uložit nějaké hodnoty
    c.mov( dword_ptr(a1.r()), imm(10) );
    c.mov( dword_ptr(a2.r()), imm(20) );
    
    // Konec funkce
    c.endFunction();
    

    Vygenerovaný kód:

    L1:
    L2:
    mov ecx, [esp + 4]
    mov edx, [esp + 8]
    mov [ecx], 0xA
    mov [edx], 0x14
    L3:
    ret
    

    Výsledný kód už je optimální a žádný C překladač v našem případě neudělá lepší (na tak malou funkci je to samozřejmě pochopitelné). Ale jedná se o tak primitivní funkci, kterou by jsme v praxi asi nikdy nekompilovali just-in-time. Nyní bych se chtěl přesunout k vytváření vlastních proměnných. Ty se vytvářejí pomocí metody Compiler::newVariable(typ, priorita), kde typ může nabývat těchto hodnot:

    • VARIABLE_TYPE_INT32 – 32bitová proměnná
    • VARIABLE_TYPE_INT64 – 64bitová proměnná (jen x64)
    • VARIABLE_TYPE_SYSINT – 32bitová nebo 64bitová proměnná (záleží na architektuře)
    • VARIABLE_TYPE_MM – MMX proměnná / registr
    • VARIABLE_TYPE_XMM – SSE proměnná / registr

    Vytvořenou proměnnou opět obalujeme například do PtrRef, Int32Ref, SysIntRef, atd... Když už jsme u proměnných, možná bych mohl vysvětlit, proč se obalují do referenčních typů. Proměnné jsou založené na metodě reference counting, obalení proměnné například do PtrRef znamená zvýšení počtu referencí a destruktor PtrRef zajistí snížení reference. Pokud reference klesne na 0, znamená to, že proměnná ukončila svůj život v současném bloku a Compiler ji může využít později. Zánik také znamená, že se její poslední obsah už nebude ukládat zpátky na zásobník (pro Compiler je v danou chvíli mrtvá). Priorita proměnné slouží alokátoru registrů pro posouzení, kterou proměnnou uložit na zásobník (spill) v případě, že je potřeba alokovat registr a všechny jsou již alokované. Čím nižší priorita, tím větší šance, že nedojde k dealokaci (spill). Pokud je priorita 0, tak alokátor nikdy nesmí danou proměnnou uložit na zásobník (a proměnná nemá na zásobníku ani vyhrazené místo).

    V dalším příkladě bych chtěl ukázat, co se stane, když potřebujeme více proměnných než registrů. V tomto případě totiž alokátor musí některé proměnné přesouvat z registru na zásobník a naopak (v tomto případě už by překladač C/C++ měl generovat lepší kód).

    // Complete compiler example
    
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <string.h>
    
    #include <AsmJit/AsmJitAssembler.h>
    #include <AsmJit/AsmJitCompiler.h>
    #include <AsmJit/AsmJitVM.h>
    #include <AsmJit/AsmJitPrettyPrinter.h>
    
    // This is type of function we will generate
    typedef void (*MyFn)(int*, int*);
    
    int main(int argc, char* argv[])
    {
      using namespace AsmJit;
    
      // ==========================================================================
      // STEP 1: Create function.
      Assembler a;
    
      // Log assembler output
      PrettyPrinter logger;
      a.setLogger(&logger);
    
      // Use compiler to make a function
      {
        Compiler c;
        Function& f = *c.newFunction(CALL_CONV_DEFAULT, BuildFunction2<int*, int*>());
    
        // Possibilities to improve code:
        //   f.setNaked(true);
        //   f.setAllocableEbp(true);
    
        PtrRef a1(f.argument(0));
        PtrRef a2(f.argument(1));
    
        // Create some variables, default variable priority is 10.
        Int32Ref x1(f.newVariable(VARIABLE_TYPE_INT32));
        Int32Ref x2(f.newVariable(VARIABLE_TYPE_INT32));
        Int32Ref x3(f.newVariable(VARIABLE_TYPE_INT32));
        Int32Ref x4(f.newVariable(VARIABLE_TYPE_INT32));
        Int32Ref x5(f.newVariable(VARIABLE_TYPE_INT32));
        Int32Ref x6(f.newVariable(VARIABLE_TYPE_INT32));
        Int32Ref x7(f.newVariable(VARIABLE_TYPE_INT32));
        Int32Ref x8(f.newVariable(VARIABLE_TYPE_INT32));
    
        // Set our variables (use mov with reg/imm to se if register 
        // allocator works)
        // x() means that there will be not read operation, only write
        c.mov(x1.x(), 1);
        c.mov(x2.x(), 2);
        c.mov(x3.x(), 3);
        c.mov(x4.x(), 4);
        c.mov(x5.x(), 5);
        c.mov(x6.x(), 6);
        c.mov(x7.x(), 7);
        c.mov(x8.x(), 8);
    
        // Create temporary variable
        Int32Ref t(f.newVariable(VARIABLE_TYPE_INT32));
        // Set priority to 5 (lower probability to spill)
        t.setPriority(5);
    
        // Make sum (addition)
        // r() means that we can read or write to register, c() means that register
        // is used only for read (compiler can use informations collected by these
        // functions to optimize register allocation and spilling).
        //
        // If you are not sure about .c(), .x() and .r(), use always .r().
        c.xor_(t.r(), t.r());
        c.add(t.r(), x1.c());
        c.add(t.r(), x2.c());
        c.add(t.r(), x3.c());
        c.add(t.r(), x4.c());
        c.add(t.r(), x5.c());
        c.add(t.r(), x6.c());
        c.add(t.r(), x7.c());
        c.add(t.r(), x8.c());
    
        // Store result to a given pointer in first argument
        c.mov(dword_ptr(a1.c()), t.c());
    
        // Make sum (subtraction)
        c.xor_(t.r(), t.r());
        c.sub(t.r(), x1.c());
        c.sub(t.r(), x2.c());
        c.sub(t.r(), x3.c());
        c.sub(t.r(), x4.c());
        c.sub(t.r(), x5.c());
        c.sub(t.r(), x6.c());
        c.sub(t.r(), x7.c());
        c.sub(t.r(), x8.c());
    
        // Store result to a given pointer in second argument
        c.mov(dword_ptr(a2.c()), t.c());
    
        // Finish
        c.endFunction();
        c.build(a);
      }
      // ==========================================================================
    
      // ==========================================================================
      // STEP 2: Alloc execution-enabled memory
      SysUInt vsize;
      void *vmem = VM::alloc(a.codeSize(), &vsize, true);
      if (!vmem)
      {
        printf("AsmJit::VM::alloc() - Failed to allocate execution-enabled memory.\n");
        return 1;
      }
    
      // Relocate generated code to vmem.
      a.relocCode(vmem);
    
      // Cast vmem to our function and call the code.
      int x;
      int y;
      function_cast<MyFn>(vmem)(&x, &y);
      printf("\nResults from JIT function: %d %d\n", x, y);
    
      // Memory should be freed, but use VM::free() to do that.
      VM::free(vmem, vsize);
      // ==========================================================================
    
      return 0;
    }
    

    Tento příklad je už kompletní C++ program, který je součástí knihovny AsmJit (testcompiler.cpp). Jsou v něm použité i věci, o kterých jsem zatím nepsal. Pro logování výsledného kódu je hned na začátku použitá třída PrettyPrinter. Jedná se o volitelnou komponentu, která loguje výsledný kód do stderr (lze samozřejmě změnit). Lze taky vidět, že Compiler používá Assembler k serializaci výsledného kódu (Compiler totiž taky patří mezi volitelné komponenty). Místo VariableRef::r() se v kódu používá VariableRef::x() a VariableRef::c(). Jedná se jen o pomoc pro alokátor registrů, protože pokud použijeme VariableRef::r(), tak mu řekneme, že proměnnou chceme změnit a on ji při dealokaci uloží na zásobník. Pokud místo VariableRef::r() použijeme VariableRef::c(), tak říkáme, že chceme registr jen pro čtení (nemodifikujeme) a VariableRef::x() znamená, že chceme registr jen pro přepis (v takovém případě zase při alokaci proměnné do registru nenačítáme původní hodnotu).

    Druhá část kódu alokuje virtuální paměť a spustí vytvořenou funkci. Výsledek je v našem případě 36 a -36.

    Vygenerovaný kód:

    L1:
    push ebp
    mov ebp, esp
    sub esp, 0x20
    push ebx
    push esi
    push edi
    L2:
    mov ecx, 0x1
    mov edx, 0x2
    mov ebx, 0x3
    mov esi, 0x4
    mov edi, 0x5
    mov eax, 0x6
    mov [esp + 16], ecx
    mov ecx, 0x7
    mov [esp + 20], edx
    mov edx, 0x8
    mov [esp + 24], ebx
    xor ebx, ebx
    mov [esp + 28], esi
    mov esi, [esp + 16]
    add ebx, esi
    mov esi, [esp + 20]
    add ebx, esi
    mov esi, [esp + 24]
    add ebx, esi
    mov esi, [esp + 28]
    add ebx, esi
    add ebx, edi
    add ebx, eax
    add ebx, ecx
    add ebx, edx
    mov esi, [ebp + 8]
    mov [esi], ebx
    xor ebx, ebx
    mov esi, [esp + 16]
    sub ebx, esi
    mov esi, [esp + 20]
    sub ebx, esi
    mov esi, [esp + 24]
    sub ebx, esi
    mov esi, [esp + 28]
    sub ebx, esi
    sub ebx, edi
    sub ebx, eax
    sub ebx, ecx
    sub ebx, edx
    mov esi, [ebp + 12]
    mov [esi], ebx
    L3:
    pop edi
    pop esi
    pop ebx
    mov esp, ebp
    pop ebp
    ret
    

    Je vidět, že vygenerovaný kód na některých místech ukládá proměnné do paměti a zpět, je to z toho důvodu, že množství proměnných přesáhlo množství registrů (to byl účel). Výpočet, který jsme provedli je sice velmi hloupý, ale na demonstraci alokace proměnných snad postačí.

    Pokud by si někdo chtěl trochu experimentovat, tak kromě volby Function::setNaked() existuje ještě Function::setAllocableEbp(), která umožní alokovat ebp/rbp registr, který se normálně používá pro prolog/epilog. Vygenerovaný kód by vypadal takto:

    
    L1:
    push ebx
    push ebp
    push esi
    push edi
    L2:
    mov ecx, 0x1
    mov edx, 0x2
    mov ebx, 0x3
    mov ebp, 0x4
    mov esi, 0x5
    mov edi, 0x6
    mov eax, 0x7
    mov [esp - 12], ecx
    mov ecx, 0x8
    mov [esp - 8], edx
    xor edx, edx
    mov [esp - 4], ebx
    mov ebx, [esp - 12]
    add edx, ebx
    mov ebx, [esp - 8]
    add edx, ebx
    mov ebx, [esp - 4]
    add edx, ebx
    add edx, ebp
    add edx, esi
    add edx, edi
    add edx, eax
    add edx, ecx
    mov ebx, [esp + 20]
    mov [ebx], edx
    xor edx, edx
    mov ebx, [esp - 12]
    sub edx, ebx
    mov ebx, [esp - 8]
    sub edx, ebx
    mov ebx, [esp - 4]
    sub edx, ebx
    sub edx, ebp
    sub edx, esi
    sub edx, edi
    sub edx, eax
    sub edx, ecx
    mov ebx, [esp + 24]
    mov [ebx], edx
    L3:
    pop edi
    pop esi
    pop ebp
    pop ebx
    ret
    

    Můžeme si všimnout, že vygenerovaný kód je kratší a používá o něco málo míň přesunů mezi pamětí a registry. Sám používám tyto volby, ale nechal jsem je defaultně vypnuté, protože při nesprávném použití můžou být nebezpečné.

    Pokračování příště...

    Chtěl bych napsat ještě jeden zápisek, kde bych zakončil své psaní o knihovně AsmJit a taky dokončil možnosti třídy Compiler.

    Nezmínil jsem

    • Práce s návěštím
    • Ukládání stavů mezi bloky
    • Volitelný kód pro alokaci/spill registru – pro mmx a sse programování

    Asi chyba v abc: při náhledu se &entity; převedou na html a není cesty zpět

    ISSN 1214-1267   www.czech-server.cz
    © 1999-2015 Nitemedia s. r. o. Všechna práva vyhrazena.