LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)

Pavel Tišnovský 25. 11. 2014

V sedmé části článku o Just in Time překladači LuaJIT dokončíme popis mezijazyka, který v LuaJITu plní funkci bajtkódu. Minule jsme se zmínili o způsobu překladu programových smyček, dnes na toto téma navážeme a popíšeme si způsob překladu smyčky for-each a následně pak práci s uzávěry (closures).

Obsah

1. LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)

2. Šablony (templates) použité při překladu programových smyček while, repeat-until a počítané smyčky for

   2.1 Šablona pro programovou smyčku typu while

   2.2 Šablona pro programovou smyčku typu repeat-until

   2.3 Šablona pro počítanou programovou smyčku typu for

3. Šablony použité při překladu programové smyčky typu for-each

   3.1 Šablona pro programovou smyčku typu for-each při průchodu polem

   3.2 Šablona pro programovou smyčku typu for-each při průchodu asociativním polem

4. Programová smyčka typu for-each při průchodu polem

   4.1 Zdrojový kód příkladu test35.lua

   4.2 Překlad příkladu test35.lua do mezijazyka LuaJITu

5. Programová smyčka typu for-each při průchodu asociativním polem

   5.1 Zdrojový kód příkladu test36.lua

   5.2 Překlad příkladu test36.lua do mezijazyka LuaJITu

6. Vytvoření a následné použití (zavolání) uzávěru (closure)

   6.1 Zdrojový kód příkladu test37.lua

   6.2 Překlad příkladu test37.lua do mezijazyka LuaJITu

7. Externí lokální proměnné většího množství uzávěrů

   7.1 Zdrojový kód příkladu test38.lua

   7.2 Překlad příkladu test38.lua do mezijazyka LuaJITu

8. Předání parametrů volanému uzávěru

   8.1 Zdrojový kód příkladu test39.lua

   8.2 Překlad příkladu test39.lua do mezijazyka LuaJITu

9. Zdrojové kódy všech pěti dnešních demonstračních příkladů

10. Obsah dalších částí tohoto seriálu

11. Seznam všech popsaných instrukcí mezijazyka LuaJITu

   11.1 První formát instrukcí

   11.2 Druhý formát instrukcí

   11.3 Seznam instrukcí

12. Odkazy na Internetu

1. LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)

V šesté části článku o vlastnostech LuaJITu jsme se převážně zabývali způsobem překladu různých typů programových smyček do mezijazyka (IR) využívaného LuaJITem. Připomeňme si, že se jednalo o programové smyčky typu while (test na ukončení smyčky je prováděn před každou iterací), repeat-until (test na ukončení smyčky je prováděn po každé iteraci) a taktéž o počítanou programovou smyčku typu for. LuaJIT překládá všechny tyto smyčky podle jednotné šablony zopakované ve druhé kapitole. Na ukázce šablon uvedených v navazující kapitole si povšimněte především použití speciální instrukce nazvané LOOP, která pouze označuje rozsah instrukcí, které smyčku tvoří – tato informace je následně použita just-in-time překladačem k detekci, která část aplikace se má v čase jejího běhu přeložit do nativního kódu. V případě počítané smyčky for není zapotřebí LOOP využívat, neboť její funkci zastupují další typy instrukcí: FORI na začátku smyčky (tato instrukce zajišťuje vstup do smyčky) a FORL na konci smyčky (tato instrukce zajišťuje řízení další iterace).

2. Šablony (templates) použité při překladu programových smyček while, repeat-until a počítané smyčky for

Ve stručnosti si zopakujme tvar šablon (posloupnost instrukcí IR) použitou při překladu typických programových smyček do mezijazyka LuaJITu:

2.1 Šablona pro programovou smyčku typu while

Při překladu programové smyčky typu while do mezijazyka LuaJITu se používá čtveřice instrukcí – podmíněný skok na začátku smyčky realizovaný dvojicí instrukcí IS??+JMP, nepodmíněný skok na konci smyčky realizovaný instrukcí JMP a „přebytečnou“ instrukcí LOOP označující tělo smyčky (tedy zjednodušeně řečeno rozsah instrukcí od-do):

+---> IS??     ; podmínka odvozená z invertované podmínky zapsané ve zdrojovém kódu
|     JMP --+  ; podmíněný skok ZA konec programové smyčky
|     LOOP  |  ; označení generické programové smyčky (pro detekci hot spotů)
|     ?     |
|     ?     |  ; instrukce tvořící tělo smyčky
|     ?     |  ; instrukce tvořící tělo smyčky
|     ?     |  ; instrukce tvořící tělo smyčky
|     ?     |
|     ?     |  ; end loop
+---- JMP   |  ; nepodmíněný skok na začátek programové smyčky
      <-----+

2.2 Šablona pro programovou smyčku typu repeat-until

Překlad programové smyčky typu repeat-until do mezijazyka LuaJITu je jednodušší, neboť se podmíněný skok realizovaný dvojicí IS??+JMP přesouvá na konec smyčky. Na jejím začátku tak zbývá místo jen pro instrukci LOOP:

+---> LOOP     ; označení generické programové smyčky (pro detekci hot spotů)
|     ?
|     ?        ; instrukce tvořící tělo smyčky
|     ?        ; instrukce tvořící tělo smyčky
|     ?        ; instrukce tvořící tělo smyčky
|     ?
|     IS??     ; podmínka odvozená z podmínky zapsané ve zdrojovém kódu
+---- JMP      ; podmíněný skok na začátek programové smyčky

2.3 Šablona pro počítanou programovou smyčku typu for

Šablona použitá pro počítanou programovou smyčku typu for při překladu do mezijazyka LuaJITu je poněkud odlišná, neboť zde již nenajdeme instrukci LOOP. Je tomu tak z toho důvodu, že „označení“ začátku a konce tohoto typu smyčky nám zajistí instrukce FORI (začátek smyčky) a FORL (konec smyčky).

+--->  FORI --+  ; vstup do počítané programové smyčky typu for
|      ?      |
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |
+----- FORL   |  ; další iterace, skok na začátek programové smyčky
      <-------+

3. Šablony použité při překladu programové smyčky typu for-each

Oproti trojici programových smyček představených v předchozí kapitole se práce překladače LuaJITu stává poněkud složitější ve chvíli, kdy se ve zdrojovém kódu vyskytne (velmi často používaná) programová smyčka typu for-each určená pro průchod polem popř. pro průchod asociativním polem. Tyto typy smyček by se teoreticky daly přeložit stejným způsobem jako programová smyčka while, přičemž by se před každou iterací zavolala funkce next(). Smyčka for-each by tedy byla do značné míry ekvivalentní zápisu:

-- inicializace - získání prvního prvku a jeho indexu z tabulky
local index,value = next(my_table, nil)
 
-- programová smyčka typu while
while index do
    index,value = next(my_table, index)
end

Ve skutečnosti však překladač LuaJITu generuje dosti odlišný IR, v němž nalezneme nové typy instrukcí, což si ukážeme v navazujících podkapitolách a kapitolách.

3.1 Šablona pro programovou smyčku typu for-each při průchodu polem

Při průchodu polem, tj. tabulkou, v níž mají všechny prvky přiřazen celočíselný index od 1 do n, se ve zdrojovém kódu využívá iterátoru ipairs() a v přeloženém kódu nalezneme jak volání tohoto iterátoru, tak i dvojici nových instrukcí nazvaných ITERCITERL. Instrukce ITERC zavolá znovu iterátor, přičemž parametry tohoto iterátoru jsou většinou již připraveny v rezervovaných slotech po celou dobu „života“ programové smyčky. Vlastní test, zda se má provést další iterace a tím pádem i skok, je představován funkcí ITERL:

       GGET      ; získání reference na funkci se jménem "ipairs"
       MOV       ; předání iterátoru jako parametru funkce
       CALL      ; zavolání funkce ipairs()
       JMP  --+  ; přímý skok na instrukci ITERC
+--->  ?      |
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |
|      ITERC<-+
+----  ITERL    ; návrat na začátek smyčky (další iterace)

3.2 Šablona pro programovou smyčku typu for-each při průchodu asociativním polem

Podobným způsobem je přeložena i programová smyčka typu for-each ve chvíli, kdy se prochází všemi prvky asociativního pole, tj. takové tabulky, v níž je každý prvek indexován klíčem (typicky řetězcem, ale může se jednat i o klíč jiného typu, kromě hodnoty nil). Při překladu do bajtkódu se používají instrukce ISNEXT, ITERNITERL (tato instrukce je tedy stejná, jako tomu bylo i v předchozím případě). Instrukce ITERN dokáže spolupracovat (volat) funkce next() či pairs() a tím pádem implementovat jak načtení dalšího prvku z asociativního pole, tak i přípravu pro test, zda se má provést další iterace:

       GGET      ; získání reference na funkci se jménem "pairs"
       MOV       ; předání iterátoru jako parametru funkce
       CALL      ; zavolání funkce pairs()
       ISNEXT-+  ; přímý skok na instrukci ITERN
+--->  ?      |
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |  ; instrukce tvořící tělo smyčky
|      ?      |
|      ITERN<-+
+----  ITERL     ; návrat na začátek smyčky (další iterace)

4. Programová smyčka typu for-each při průchodu polem

V dnešním prvním demonstračním příkladu nazvaném test35.lua je nejprve vytvořena a současně i inicializována desetiprvková tabulka. Následně se v trojici programových smyček postupně vypíšou indexy prvků (první smyčka), hodnoty prvků (druhá smyčka) a indexy+hodnoty prvků (třetí smyčka) této tabulky.

4.1 Zdrojový kód příkladu test35.lua

--
-- LuaJIT: demonstrační příklad číslo 35.
--
-- Práce s tabulkami:
--    * vytvoření a inicializace neprázdné tabulky
--    * programová smyčka typu for-each využívající
--      funkci ipairs použitá pro průchod tabulkou.
--
 
 
 
-- vytvoření desetiprvkové tabulky
local tbl = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
 
-- programová smyčka typu for-each využívající funkci ipairs
for i,val in ipairs(tbl) do
    print(i)
end
 
-- odřádkování
print()
 
-- programová smyčka typu for-each využívající funkci ipairs
for i,val in ipairs(tbl) do
    print(val)
end
 
-- odřádkování
print()
 
-- programová smyčka typu for-each využívající funkci ipairs
for i,val in ipairs(tbl) do
    print(i, val)
end
 
 
 
-- finito

4.2 Překlad příkladu test35.lua do mezijazyka LuaJITu

V IR přeloženého příkladu test35.lua můžeme vidět použití instrukcí ITERCITERL:

; Překlad demonstračního příkladu test35.lua
; do IR využívaného virtuálním strojem a JIT
; překladačem LuaJIT.
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test35.lua:0-40
 
 
 
; vytvoření a inicializace tabulky
; local tbl = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
0001    TDUP     0   0        ; vytvoření a inicializace tabulky
 
; programová smyčka typu for-each
; for i,val in ipairs(tbl) do
0002    GGET     1   1        ; získání reference na funkci se jménem "ipairs"
0003    MOV      2   0        ; předání iterátoru
0004    CALL     1   4   2    ; volání funkce ipairs()
0005    JMP      4 => 0009    ; přímý skok na instrukci ITERC
 
; print(i)
0006 => GGET     6   2        ; získání reference na funkci se jménem "print"
0007    MOV      7   4        ; bude se tisknout index
0008    CALL     6   1   2    ; volání funkce print()
0009 => ITERC    4   3   3    ; volání iterátoru a zjištění. zda existuje další prvek v tabulce
 
; end loop
0010    ITERL    4 => 0006    ; další iterace (pokud k ní dojde) začne na instrukci 0006
 
; print()
0011    GGET     1   2        ; získání reference na funkci se jménem "print"
0012    CALL     1   1   1    ; volání funkce print()
 
; programová smyčka typu for-each
; for i,val in ipairs(tbl) do
0013    GGET     1   1        ; získání reference na funkci se jménem "ipairs"
0014    MOV      2   0        ; předání iterátoru
0015    CALL     1   4   2    ; volání funkce ipairs()
0016    JMP      4 => 0020    ; přímý skok na instrukci ITERC
 
; print(val)
0017 => GGET     6   2        ; získání reference na funkci se jménem "print"
0018    MOV      7   5        ; bude se tisknout hodnota
0019    CALL     6   1   2    ; volání funkce print()
0020 => ITERC    4   3   3    ; volání iterátoru a zjištění. zda existuje další prvek v tabulce
 
; end loop
0021    ITERL    4 => 0017    ; další iterace (pokud k ní dojde) začne na instrukci 0017
 
; print()
0022    GGET     1   2        ; získání reference na funkci se jménem "print"
0023    CALL     1   1   1    ; volání funkce print()
 
; programová smyčka typu for-each
; for i,val in ipairs(tbl) do
0024    GGET     1   1        ; získání reference na funkci se jménem "ipairs"
0025    MOV      2   0        ; předání iterátoru
0026    CALL     1   4   2    ; volání funkce ipairs()
0027    JMP      4 => 0032    ; přímý skok na instrukci ITERC
 
; print(i, val)
0028 => GGET     6   2        ; získání reference na funkci se jménem "print"
0029    MOV      7   4        ; bude se tisknout index
0030    MOV      8   5        ; a současně i hodnota
0031    CALL     6   1   3    ; volání funkce print()
0032 => ITERC    4   3   3    ; volání iterátoru a zjištění. zda existuje další prvek v tabulce
 
; end loop
0033    ITERL    4 => 0028    ; další iterace (pokud k ní dojde) začne na instrukci 0028
 
; každý program je automaticky ukončen následující instrukcí
0034    RET0     0   1
 
; konec

5. Programová smyčka typu for-each při průchodu asociativním polem

V dnešním druhém demonstračním příkladu s názvem test36.lua si ukážeme způsob průchodu asociativním polem ve smyčce typu for-each spojené s funkcí pairs(). Podobně jako v předchozím příkladu, i zde jsou použity tři programové smyčky, přičemž v první smyčce se na standardní výstup vytisknou pouze jednotlivé klíče (v pořadí, které obvykle neodpovídá pořadí vložení prvků do tabulky), ve smyčce druhé se vytisknou jen hodnoty prvků tabulky a konečně ve smyčce třetí se vytisknou jak hodnoty klíčů, tak i hodnoty prvků přiřazených k těmto klíčům.

5.1 Zdrojový kód příkladu test36.lua

--
-- LuaJIT: demonstrační příklad číslo 36.
--
-- Práce s tabulkami:
--    * vytvoření a inicializace prázdné tabulky
--    * přidání prvku do tabulky s použitím klíčů
--    * programová smyčka typu for-each využívající
--      funkci ipairs použitá pro průchod tabulkou
--
 
 
 
-- vytvoření prázdné tabulky
local tbl = {}
 
-- nastavit hodnotu prvku tabulky
tbl["first"] = 777
 
-- nastavit hodnotu prvku tabulky
tbl["second"] = 999
 
-- nastavit hodnotu prvku tabulky
tbl["tenth"] = 1000
 
-- programová smyčka typu for-each využívající funkci ipairs
for key,val in pairs(tbl) do
    print(key)
end
 
-- odřádkování
print()
 
-- programová smyčka typu for-each využívající funkci ipairs
for key,val in pairs(tbl) do
    print(val)
end
 
-- odřádkování
print()
 
-- programová smyčka typu for-each využívající funkci ipairs
for key,val in pairs(tbl) do
    print(key, val)
end
 
 
 
-- finito

5.2 Překlad příkladu test36.lua do mezijazyka LuaJITu

Při překladu tohoto demonstračního příkladu do IR jsou pro implementaci smyčky použity instrukce ISNEXTITERN:

; Překlad demonstračního příkladu test36.lua
; do IR využívaného virtuálním strojem a JIT
 
 
 
; překladačem LuaJIT.
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test36.lua:0-50
 
 
 
; vytvoření prázdné tabulky
; local tbl = {}
0001    TNEW     0   0        ; vytvoření prázdné tabulky
 
; nastavit hodnotu prvku tabulky
; s využitím klíče reprezentovaného řetězcem
; tbl["first"] = 777
0002    KSHORT   1 777        ; ukládaná konstanta
0003    TSETS    1   0   0    ; uložení hodnoty do tabulky
 
; nastavit hodnotu prvku tabulky
; s využitím klíče reprezentovaného řetězcem
; tbl["second"] = 999
0004    KSHORT   1 999        ; ukládaná konstanta
0005    TSETS    1   0   1    ; uložení hodnoty do tabulky
 
; nastavit hodnotu prvku tabulky
; s využitím klíče reprezentovaného řetězcem
; tbl["tenth"] = 1000
0006    KSHORT   1 1000       ; ukládaná konstanta
0007    TSETS    1   0   2    ; uložení hodnoty do tabulky
 
; programová smyčka typu for-each
; for key,val in pairs(tbl) do
0008    GGET     1   3        ; získání reference na funkci se jménem "pairs"
0009    MOV      2   0        ; předání iterátoru
0010    CALL     1   4   2    ; volání funkce pairs()
0011    ISNEXT   4 => 0015    ; přímý skok na instrukci ITERN
 
; print(key)
0012 => GGET     6   4        ; získání reference na funkci se jménem "print"
0013    MOV      7   4        ; bude se tisknout klíč
0014    CALL     6   1   2    ; volání funkce print()
0015 => ITERN    4   3   3    ; volání iterátoru a zjištění. zda existuje další prvek v tabulce
 
; end loop
0016    ITERL    4 => 0012    ; další iterace (pokud k ní dojde) začne na instrukci 0012
 
; print()
0017    GGET     1   4        ; získání reference na funkci se jménem "print"
0018    CALL     1   1   1    ; volání funkce print()
 
; programová smyčka typu for-each
; for key,val in pairs(tbl) do
0019    GGET     1   3        ; získání reference na funkci se jménem "pairs"
0020    MOV      2   0
0021    CALL     1   4   2
0022    ISNEXT   4 => 0026    ; volání iterátoru a zjištění. zda existuje další prvek v tabulce
 
; print(val)
0023 => GGET     6   4        ; získání reference na funkci se jménem "print"
0024    MOV      7   5        ; bude se tisknout hodnota
0025    CALL     6   1   2    ; volání funkce print()
0026 => ITERN    4   3   3    ; volání iterátoru a zjištění. zda existuje další prvek v tabulce
 
; end loop
0027    ITERL    4 => 0023    ; další iterace (pokud k ní dojde) začne na instrukci 0023
 
; print()
0028    GGET     1   4        ; získání reference na funkci se jménem "print"
0029    CALL     1   1   1    ; volání funkce print()
 
; programová smyčka typu for-each
; for key,val in pairs(tbl) do
0030    GGET     1   3        ; získání reference na funkci se jménem "pairs"
0031    MOV      2   0
0032    CALL     1   4   2
0033    ISNEXT   4 => 0038    ; volání iterátoru a zjištění. zda existuje další prvek v tabulce
 
; print(key, val)
0034 => GGET     6   4        ; získání reference na funkci se jménem "print"
0035    MOV      7   4        ; bude se tisknout klíč
0036    MOV      8   5        ; a současně se bude tisknout i hodnota
0037    CALL     6   1   3    ; volání funkce print()
0038 => ITERN    4   3   3    ; volání iterátoru a zjištění. zda existuje další prvek v tabulce
 
; end loop
0039    ITERL    4 => 0034    ; další iterace (pokud k ní dojde) začne na instrukci 0034
 
; každý program je automaticky ukončen následující instrukcí
0040    RET0     0   1
 
; konec

6. Vytvoření a následné použití (zavolání) uzávěru (closure)

Poslední problematikou, kterou se v souvislosti s překladem zdrojových kódů jazyka Lua do mezikódu LuaJITu budeme zabývat, je způsob práce s takzvanými uzávěry (closures). Programovací jazyk Lua převzal mnoho vlastností z klasických funkcionálních programovacích jazyků, které práci s uzávěry podporují; jedná se současně o velmi důležitou vlastnost umožňující efektivnější implementaci některých typů algoritmů. U většiny funkcionálních jazyků – a taktéž u jazyka Lua – si každý vytvořený objekt (včetně funkce, zde speciálně uzávěru) uchovává odkazy na všechny proměnné, které jsou uvnitř objektu použity, nehledě na jejich lexikální kontext (tyto proměnné se nazývají „externí lokální proměnné“ popř. „upvalues“).

To ovšem znamená, že pokud je uvnitř nějaké funkce vytvořena nová anonymní funkce (uzávěr) přistupující k lokálním proměnným své „obalující“ funkce a pokud je následně tato anonymní funkce vrácena příkazem return, jsou všechny odkazované lokální proměnné zachovány minimálně po tu dobu, po kterou existuje vrácený uzávěr (ten může být uložen jak do lokální, tak i do globální proměnné). Každé volání uzávěru může s těmito lokálními (a zdánlivě už neexistujícími) proměnnými pracovat, tj. číst i zapisovat do nich hodnoty.

Vzhledem k tomuto chování není ve funkcionálních jazycích podporujících uzávěry obecně možné všechny lokální proměnné ukládat na zásobník (jeho rámec je po opuštění funkce zapomenut), ale je nutné využít spíše paměť alokovanou na haldě (heap), pro jejíž uvolňování je použita nějaká forma automatického uvolňování nepoužívané paměti (garbage collectoru).

V dnešním třetím demonstračním příkladu pojmenovaném test37.lua je ve funkci createCounter() vytvořen uzávěr, tj. anonymní funkce, s níž je svázána externí lokální proměnná counter. Při každém zavolání tohoto uzávěru se hodnota proměnné counter zvýší o jedničku a nová hodnota je vrácena jako návratová hodnota uzávěru. Ve funkci main() je uzávěr nejprve vytvořen a následně třikrát zavolán.

6.1 Zdrojový kód příkladu test37.lua

--
-- LuaJIT: demonstrační příklad číslo 37
--
-- Vytvoření a následné použití uzávěru (closure).
--
 
 
 
-- Vytvoření a vrácení uzávěru, tj. funkce na níž je navázána
-- externí lokální proměnná - upvalue.
function createCounter()
    -- lokální proměnná, jejíž "životnost" v čase běhu aplikace přesahuje
    -- pouhé zavolání a provedeni bloku funkce createCounter()
    local counter = 0
    -- návratovou hodnotou funkce createCounter() je anonymní
    -- funkce pracující s proměnnou cnt, která je na tuto
    -- anonymní funkci navázána
    return function()
        -- counter se označuje jako "externí lokální proměnná"
        -- popř. v terminologii jazyka Lua "upvalue"
        counter = counter + 1
        return counter
    end
end
 
 
 
--
-- Spuštění testu.
--
function main()
    -- získáme "instanci" anonymní funkce i na ni navázanou
    -- externí lokální proměnnou nazvanou "counter"
    -- -& closure
    local mycounter = createCounter()
    print(mycounter())
    print(mycounter())
    print(mycounter())
end
 
 
 
main()
 
 
 
--
-- Finito.
--

6.2 Překlad příkladu test37.lua do mezijazyka LuaJITu

Ve vygenerované sekvenci IR demonstračního příkladu test37.lua můžeme najít čtveřici nových instrukcí: FNEW, UCLO, UGETUSETV. Instrukce FNEW slouží pro vytvoření nového uzávěru z prototypu specifikovaného v operandu D, instrukce UCLO k uzavření hodnot a skoku na následující instrukci (tou je ve většině případů JMP či RET, skok se zde využije při dalších optimalizacích). V samotném uzávěru (tedy funkci využívající externí vázané proměnné) nalezneme instrukce UGETUSETV, které slouží pro přečtení či k zápisu do externí vázané proměnné (právě díky těmto instrukcím dokáže uzávěr přistupovat k vázaným proměnným):

; Překlad demonstračního příkladu test37.lua
; do IR využívaného virtuálním strojem a JIT
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test37.lua:18-23
 
 
 
; implementace uzávěru
; counter = counter + 1
0001    UGET     0   0        ; přečtení hodnoty externí vázané proměnné counter
0002    ADDVN    0   0   0    ; přičtení jedničky
0003    USETV    0   0        ; zápis hodnoty externí vázané proměnné counter
0004    UGET     0   0        ; přečtení hodnoty externí vázané proměnné counter
; return counter
0005    RET1     0   2        ; vrácení nové hodnoty
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test37.lua:11-24
 
 
 
; implementace funkce createCounter()
; local counter = 0
0001    KSHORT   0   0        ; nastavení hodnoty lokální proměnné counter
; return function()
0002    FNEW     1   0        ; vytvoření uzávěru
0003    UCLO     0 => 0004
0004 => RET1     1   2        ; vrácení uzávěru
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test37.lua:31-39
; implementace funkce main()
 
 
 
; local mycounter = createCounter()
0001    GGET     0   0        ; získat referenci na funkci "createCounter"
0002    CALL     0   2   1    ; volání funkce "createCounter"
 
; print(mycounter())
0003    GGET     1   1        ; získat referenci na funkci "print"
0004    MOV      2   0
0005    CALL     2   0   1    ; volání funkce "mycounter"
0006    CALLM    1   1   0    ; volání funkce "print"
 
; print(mycounter())
0007    GGET     1   1        ; získat referenci na funkci "print"
0008    MOV      2   0
0009    CALL     2   0   1    ; volání funkce "mycounter"
0010    CALLM    1   1   0    ; volání funkce "print"
 
; print(mycounter())
0011    GGET     1   1        ; získat referenci na funkci "print"
0012    MOV      2   0
0013    CALL     2   0   1    ; volání funkce "mycounter"
0014    CALLM    1   1   0    ; volání funkce "print"
 
0015    RET0     0   1        ; návrat z funkce "main"
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test37.lua:0-51
 
 
 
; inicializace a volání funkce main
0001    FNEW     0   0        ; test37.lua:11
0002    GSET     0   1        ; "createCounter"
0003    FNEW     0   2        ; test37.lua:31
0004    GSET     0   3        ; "main"
0005    GGET     0   3        ; "main"
0006    CALL     0   1   1    ; volání funkce "main"
 
; každý program je automaticky ukončen následující instrukcí
0007    RET0     0   1
 
; konec

7. Externí lokální proměnné většího množství uzávěrů

V případě dalšího volání funkce createCounter() se vytvoří i nová lokální proměnná counter i nový uzávěr, tj. jednotlivé uzávěry obsahují vazby na své vlastní kopie původních lokálních proměnných (lokální proměnné tedy nejsou statické ve smyslu „statičnosti“ známém například z céčka). Každé volání funkce createCounter() tedy vede k alokaci paměti na haldě; do této paměti je uložena počáteční hodnota lokální proměnné counter a vytvořený uzávěr obsahuje odkaz na tuto hodnotu. Tuto vlastnost si otestujeme na dnešním čtvrtém demonstračním příkladu nazvaném test38.lua, v němž se vytvoří dva uzávěry, které se následně volají v počítané programové smyčce typu for.

7.1 Zdrojový kód příkladu test38.lua

--
-- LuaJIT: demonstrační příklad číslo 38.
--
-- Vytváření a následné použití uzávěru (closure).
--
 
 
 
-- Vytvoření a vraceni uzávěru
function createCounter()
    -- lokální proměnná, jejíž "životnost" v čase běhu aplikace přesahuje
    -- pouhé zavolání a provedeni bloku funkce createCounter()
    local counter = 0
    -- návratovou hodnotou funkce createCounter() je anonymní
    -- funkce pracující s proměnnou cnt, která je na tuto
    -- anonymní funkci navázána
    return function()
        -- counter se označuje jako "externí lokální proměnná"
        -- popř. v terminologii jazyka Lua "upvalue"
        counter = counter + 1
        return counter
    end
end
 
 
 
--
-- Spuštění testu.
--
function main()
    -- získáme dvojici uzávěru
    local counter1 = createCounter()
    local counter2 = createCounter()
 
    -- volání uzávěru
    for i = 1, 10 do
        print("iteration #" .. i)
        print("    counter1: " .. counter1())
        print("    counter2: " .. counter2())
        print("    counter1: " .. counter1())
        print()
    end
end
 
 
 
main()
 
 
 
--
-- Finito.
--

7.2 Překlad příkladu test38.lua do mezijazyka LuaJITu

; Překlad demonstračního příkladu test38.lua
; do IR využívaného virtuálním strojem a JIT
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test38.lua:17-22
 
 
 
; implementace uzávěru
; counter = counter + 1
0001    UGET     0   0        ; přečtení hodnoty externí vázané proměnné counter
0002    ADDVN    0   0   0    ; přičtení jedničky
0003    USETV    0   0        ; zápis hodnoty externí vázané proměnné counter
0004    UGET     0   0        ; přečtení hodnoty externí vázané proměnné counter
; return counter
0005    RET1     0   2        ; vrácení nové hodnoty
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test38.lua:10-23
 
 
 
; implementace funkce createCounter()
; local counter = 0
0001    KSHORT   0   0        ; nastavení hodnoty lokální proměnné counter
; return function()
0002    FNEW     1   0        ; vytvoření uzávěru
0003    UCLO     0 => 0004
0004 => RET1     1   2        ; vrácení uzávěru
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test38.lua:30-43
; implementace funkce main()
 
 
 
; local mycounter1 = createCounter()
0001    GGET     0   0        ; získat referenci na funkci "createCounter"
0002    CALL     0   2   1    ; volání funkce "createCounter"
 
; local mycounter2 = createCounter()
0003    GGET     1   0        ; získat referenci na funkci "createCounter"
0004    CALL     1   2   1    ; volání funkce "createCounter"
 
; příprava počítané programové smyčky for
0005    KSHORT   2   1
0006    KSHORT   3  10
0007    KSHORT   4   1
0008    FORI     2 => 0035
 
; tělo počítané programové smyčky for
 
; print("iteration #" .. i)
0009 => GGET     6   1        ; "print"
0010    KSTR     7   2        ; "iteration #"
0011    MOV      8   5
0012    CAT      7   7   8    ; spojení řetězců
0013    CALL     6   1   2    ; volání funkce "print"
 
; print("    counter1: " .. counter1())
0014    GGET     6   1        ; "print"
0015    KSTR     7   3        ; "    counter1: "
0016    MOV      8   0
0017    CALL     8   2   1    ; volání uzávěru
0018    CAT      7   7   8    ; spojení řetězců
0019    CALL     6   1   2    ; volání funkce "print"
 
; print("    counter2: " .. counter2())
0020    GGET     6   1        ; "print"
0021    KSTR     7   4        ; "    counter2: "
0022    MOV      8   1
0023    CALL     8   2   1    ; volání uzávěru
0024    CAT      7   7   8    ; spojení řetězců
0025    CALL     6   1   2    ; volání funkce "print"
 
; print("    counter1: " .. counter1())
0026    GGET     6   1        ; "print"
0027    KSTR     7   3        ; "    counter1: "
0028    MOV      8   0
0029    CALL     8   2   1    ; volání uzávěru
0030    CAT      7   7   8    ; spojení řetězců
0031    CALL     6   1   2    ; volání funkce "print"
 
; print()
0032    GGET     6   1       ; "print"
0033    CALL     6   1   1
 
; konec těla programové smyčky for
0034    FORL     2 => 0009
 
; návrat z funkce
0035 => RET0     0   1
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test38.lua:0-55
 
 
 
; inicializace a volání funkce main
0001    FNEW     0   0        ; test38.lua:10
0002    GSET     0   1        ; "createCounter"
0003    FNEW     0   2        ; test38.lua:30
0004    GSET     0   3        ; "main"
0005    GGET     0   3        ; "main"
0006    CALL     0   1   1    ; volání funkce "main"
 
; každý program je automaticky ukončen následující instrukcí
0007    RET0     0   1
 
; konec

8. Předání parametrů volanému uzávěru

Dnešní pátý a současně i poslední demonstrační příklad, který je pojmenovaný test39.lua, se v mnoha ohledech podobá třetímu příkladu nazvanému test37.lua. Je zde však jedna podstatná odlišnost – při volání uzávěru se mu předává parametr určující, jakým způsobem se má změnit hodnota externí lokální proměnné navázané na uzávěr. Tato změna ve zdrojovém kódu samozřejmě povede i ke změnám ve vygenerovaném IR.

8.1 Zdrojový kód příkladu test39.lua

--
-- LuaJIT: demonstrační příklad číslo 39.
--
-- Vytváření a následné použití uzávěru (closure).
--
 
 
 
-- Vytvoření a vraceni uzávěru
function createCounter()
    -- lokální proměnná, jejíž "životnost" v čase běhu aplikace přesahuje
    -- pouhé zavolání a provedeni bloku funkce createCounter()
    local counter = 0
    -- návratovou hodnotou funkce createCounter() je anonymní
    -- funkce pracující s proměnnou cnt, která je na tuto
    -- anonymní funkci navázána
    return function(delta)
        -- counter se označuje jako "externí lokální proměnná"
        -- popř. v terminologii jazyka Lua "upvalue"
        counter = counter + delta
        return counter
    end
end
 
 
 
--
-- Spuštění testu.
--
function main()
    -- získáme "instanci" anonymní funkce i na ni navázanou
    -- externí lokální proměnnou "counter"
    -- -> closure
    local mycounter = createCounter()

    -- volání uzávěru
    for i = 1, 10 do
        print("iteration #" .. i)
        print("    mycounter(1):  " .. mycounter(1))
        print("    mycounter(10): " .. mycounter(10))
        print("    mycounter(-2): " .. mycounter(-2))
        print()
    end
end
 
 
 
main()
 
 
 
--
-- Finito.
--

8.2 Překlad příkladu test39.lua do mezijazyka LuaJITu

; Překlad demonstračního příkladu test39.lua
; do IR využívaného virtuálním strojem a JIT
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test39.lua:17-22
 
 
 
; implementace uzávěru
; counter = counter + 1
0001    UGET     1   0        ; přečtení hodnoty externí vázané proměnné counter
0002    ADDVV    1   1   0    ; přičtení jedničky
0003    USETV    0   1        ; zápis hodnoty externí vázané proměnné counter
0004    UGET     1   0        ; přečtení hodnoty externí vázané proměnné counter
; return counter
0005    RET1     1   2        ; vrácení nové hodnoty
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test39.lua:10-23
 
 
 
; implementace funkce createCounter()
; local counter = 0
0001    KSHORT   0   0        ; nastavení hodnoty lokální proměnné counter
; return function()
0002    FNEW     1   0        ; vytvoření uzávěru
0003    UCLO     0 => 0004
0004 => RET1     1   2        ; vrácení uzávěru
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test39.lua:30-44
; implementace funkce main()
 
 
 
; local mycounter = createCounter()
0001    GGET     0   0        ; získat referenci na funkci "createCounter"
0002    CALL     0   2   1    ; volání funkce "createCounter"
 
 
; příprava počítané programové smyčky for
0003    KSHORT   1   1
0004    KSHORT   2  10
0005    KSHORT   3   1
0006    FORI     1 => 0036
 
; tělo počítané programové smyčky for
 
; print("iteration #" .. i)
0007 => GGET     5   1        ; "print"
0008    KSTR     6   2        ; "iteration #"
0009    MOV      7   4
0010    CAT      6   6   7    ; spojení řetězců
0011    CALL     5   1   2
 
; print("    counter1: " .. counter1())
0012    GGET     5   1        ; "print"
0013    KSTR     6   3        ; "    mycounter(1):  "
0014    MOV      7   0
0015    KSHORT   8   1
0016    CALL     7   2   2    ; volání uzávěru
0017    CAT      6   6   7    ; spojení řetězců
0018    CALL     5   1   2    ; volání funkce "print"
 
; print("    counter2: " .. counter2())
0019    GGET     5   1        ; "print"
0020    KSTR     6   4        ; "    mycounter(10): "
0021    MOV      7   0
0022    KSHORT   8  10
0023    CALL     7   2   2    ; volání uzávěru
0024    CAT      6   6   7    ; spojení řetězců
0025    CALL     5   1   2    ; volání funkce print
 
0026    GGET     5   1        ; "print"
0027    KSTR     6   5        ; "    mycounter(-2): "
0028    MOV      7   0
0029    KSHORT   8  -2
0030    CALL     7   2   2    ; volání funkce "print"
 
0031    CAT      6   6   7    ; spojení řetězců
0032    CALL     5   1   2    ; volání funkce "print"
 
0033    GGET     5   1        ; "print"
0034    CALL     5   1   1    ; volání funkce "print"
 
; konec těla programové smyčky for
0035    FORL     1 => 0007
 
; návrat z funkce
0036 => RET0     0   1        ; návrat z funkce "main"
 
 
 
; metadata se základními informacemi o zdrojovém kódu,
; který byl použit pro vygenerování tohoto IR
-- BYTECODE -- test39.lua:0-56
 
 
 
; inicializace a volání funkce main
0001    FNEW     0   0        ; vytvoření funkce z definice na řádku test39.lua:10
0002    GSET     0   1        ; zápis "createCounter" do globální tabulky
0003    FNEW     0   2        ; vytvoření funkce z definice na řádku test39.lua:30
0004    GSET     0   3        ; zápis "main" do globální tabulky
0005    GGET     0   3        ; "main"
0006    CALL     0   1   1    ; volání funkce "main"
 
; každý program je automaticky ukončen následující instrukcí
0007    RET0     0   1
 
; konec

9. Zdrojové kódy všech pěti dnešních demonstračních příkladů

V dnešní části i v šesti předchozích částech článku o LuaJITu jsme si řekli všechny podstatné informace o způsobu překladu zdrojových kódů naprogramovaných v jazyku Lua do mezijazyka LuaJITu. Ve skutečnosti však tento překlad není z programátorského hlediska příliš zajímavý, neboť je založen na předpřipravených šablonách. Mnohem zajímavější a taktéž složitější je způsob just-in-time překladu z mezijazyka LuaJITu do strojového kódu konkrétního typu mikroprocesoru. Tento překlad již musí brát v úvahu jak rozdíly mezi jednotlivými typy instrukčních sad (i386, x86_64, ARM, …), tak i počet použitelných pracovních registrů, adresovací režimy (a jejich efektivitu), způsob předávání parametrů volaným subrutinám atd. Navíc se již od dob prvních JIT překladačů ukazuje, že poměrně velkou část kódu reálných aplikací ani nemá cenu složitě překládat, ale postačuje interpretace. Pouze často volané funkce a samozřejmě též programové smyčky (speciálně vnořené smyčky) se „JITují“. Příště si ukážeme, jak je tato problematika řešená v LuaJITu.

10. Obsah dalších částí tohoto seriálu

Všechny dnes použité demonstrační příklady byly, jak je tomu ostatně v tomto seriálu již dlouhodobějším zvykem, uloženy do Git (přesněji řečeno do GitHub) repositáře umístěného na adrese https://github.com/tisnik/luajit-examples:

11. Seznam všech popsaných instrukcí mezijazyka LuaJITu

Na závěr si ještě uvedeme tabulku se všemi doposud popsanými instrukcemi mezijazyka LuaJITu. Připomeňme si, že IR používá takzvaný tříadresový kód a instrukce o pevné šířce třiceti dvou bitů. Přístup k instrukcím je tedy obecně mnohem rychlejší, než při použití instrukcí proměnné délky, tak jako je tomu v JVM. Existují dva formáty instrukcí, přesněji řečeno dva způsoby rozdělení 32bitového slova na jednotlivá bitová pole. V obou formátech má operační kód instrukce šířku osmi bitů, obsazení dalších 24 bitů je však odlišné.

11.1 První formát instrukcí

První formát se používá u instrukcí s trojicí operandů. Typické použití tohoto formátu je u aritmetických instrukcí:

widgety

Označení Šířka (bitů) Popis
B 8 první vstupní operand (zdrojová proměnná)
C 8 druhý vstupní operand (zdrojová proměnná, numerická konstanta atd.)
A 8 výsledek operace (proměnná pro uložení výsledku)
OP 8 operační kód instrukce
Celkem 32  

11.2 Druhý formát instrukcí

Druhý formát, resp. instrukce používající druhý formát dokážou adresovat pouze dva operandy, ovšem operand označený písmenem D má šířku šestnáct bitů a lze ho v některých instrukcích použít například k přímému uložení šestnáctibitové celočíselné konstanty se znaménkem (srov. s poměrně velkým množstvím instrukcí v bajtkódu JVM, které se pokouší o totéž, ale mnohem komplikovanějším způsobem):

Označení Šířka (bitů) Popis
D 16 vstupní operand (zdrojová proměnná)
A 8 první operand nebo proměnná pro uložení výsledku
OP 8 operační kód instrukce
Celkem 32  

11.3 Seznam instrukcí

Nyní již následuje slíbená tabulka se seznamem instrukcí IR LuaJITu:

# Instrukce Operandy Popis
1 KNIL base, base nastaví sloty číslo A až D na hodnotu nil
2 KPRI dest, pri nastaví dest na hodnotu specifikovanou v D
3 KSHORT dest, lits přenese do dest šestnáctibitovou celočíselnou konstantu
4 KNUM dest, num přenese do dest zvolenou numerickou konstantu
5 KSTR dest, str přenese do dest zvolený řetězec
       
6 ADDVV slot, slot aritmetická operace: součet
7 SUBVV slot, slot aritmetická operace: rozdíl
8 MULVV slot, slot aritmetická operace: součin
9 DIVVV slot, slot aritmetická operace: podíl
10 MODVV slot, slot aritmetická operace: podíl modulo
11 ADDVN slot, num aritmetická operace s numerickou konstantou: součet
12 SUBVN slot, num aritmetická operace s numerickou konstantou: rozdíl
13 MULVN slot, num aritmetická operace s numerickou konstantou: součin
14 DIVVN slot, num aritmetická operace s numerickou konstantou: podíl
15 MODVN slot, num aritmetická operace s numerickou konstantou: podíl modulo
16 ADDNV slot, num aritmetická operace s numerickou konstantou: součet (otočené operandy)
17 SUBNV slot, num aritmetická operace s numerickou konstantou: rozdíl
18 MULNV slot, num aritmetická operace s numerickou konstantou: součin
19 DIVNV slot, num aritmetická operace s numerickou konstantou: podíl
20 MODNV slot, num aritmetická operace s numerickou konstantou: podíl modulo
       
21 JMP adresa nepodmíněný skok, popř. podmíněný skok, pokud mu předchází instrukce I*
22 IST slot následuje skok provedený při splnění podmínky A = true
23 ISF slot následuje skok provedený při splnění podmínky A = false
24 ISLT slot, slot následuje skok provedený při splnění podmínky A < D
25 ISGE slot, slot následuje skok provedený při splnění podmínky A ≥ D
26 ISLE slot, slot následuje skok provedený při splnění podmínky A ≤ D
27 ISGT slot, slot následuje skok provedený při splnění podmínky A > D
28 ISEQV slot, slot následuje skok provedený při splnění podmínky A = D
29 ISNEV slot, slot následuje skok provedený při splnění podmínky A ≠ D
30 ISEQN slot, konstanta následuje skok provedený při splnění podmínky A = konstanta
31 ISNEN slot, konstanta následuje skok provedený při splnění podmínky A ≠ konstanta
32 ISEQP slot, pri následuje skok provedený při splnění podmínky A = pri_type
33 ISNEP slot, pri následuje skok provedený při splnění podmínky A ≠ pri_type
34 ISEQS slot, string následuje skok provedený při splnění podmínky A = string
34 ISNES slot, string následuje skok provedený při splnění podmínky A ≠ string
       
35 MOV kopie dat z D do A
       
36 TNEW dst, lit vytvoření nové tabulky o velikosti specifikované v D (lit), reference na vytvořenou tabulku se uloží do A (dst)
37 TDUP dst, tab vytvoření nové tabulky na základě šablony specifikované v D (tab), reference na vytvořenou tabulku se uloží do A (dst)
38 GGET dst, str přečtení prvku z globální tabulky _G, v D (str) je uložen klíč (řetězec)
39 GSET var, str zápis prvku do globální tabulky _G, v D (str) je uložen klíč (řetězec)
40 TGETV dst, var, var čtení prvku z tabulky specifikované v B, klíč je uložen v proměnné
41 TGETS dst, var, str čtení prvku z tabulky specifikované v B, klíčem je řetězec
42 TGETB dst, var, lit čtení prvku z tabulky specifikované v B, klíčem je literál (konstanta, typicky celé číslo)
43 TSETV var, var, var zápis prvku do tabulky specifikované v B, klíč je uložen v proměnné
44 TSETS var, var, str zápis prvku do tabulky specifikované v B, klíčem je řetězec
45 TSETB var, var, lit zápis prvku do tabulky specifikované v B, klíčem je literál (konstanta, typicky celé číslo)
46 TSETM base, num* nastavení většího množství prvků dle vztahu: (A-1)[D], (A-1)[D+1], … = A, A+1, …
       
47 FNEW dst, func vytvoření uzávěru (closure) z prototypu funkce
       
48 CALL base, lit, lit volání funkce s předáním parametrů (standardní forma volání)
49 CALLM base, lit, lit volání funkce s předáním parametrů (speciální forma volání)
       
50 RET0 A, D návrat z funkce bez předání návratového hodnoty (A a D se ignorují)
51 RET1 A, D návrat z funkce s předáním jedné návratové hodnoty A (D se ignoruje)
52 RET A, D návrat z funkce s předáním jedné návratových hodnot A, A+1, … A+D-2
       
53 FORI A, D test i≤max pro krok≥0 či i≥min pro krok<0
skok ZA tělo smyčky při nesplnění této podmínky
54 FORL A, D i=i+krok
test i≤max pro krok≥0 či i≥min pro krok<0
skok na začátek smyčky při splnění této podmínky
       
55 ITERC base, lit, lit volání iterátoru A s parametry v A+1 a A+2
56 ITERN base, lit, lit varianta instrukce ITERC pro funkci next() či pairs()
     
57 LOOP rbase, jump použití při JITování a detekci hot-spotů, neprovádí se skok!
58 ITERL base, jump iterátor pro smyčku typu for-each, kontrola na hodnotu odlišnou od nil a podmíněný skok
59 ISNEXT base, jump použito se speciální instrukcí ITERN pro podmíněný skok
     
60 FNEW vytvoření nového uzávěru
61 UCLO uzavření (navázání) externích proměnných k funkci
     
62 UGET přečtení hodnoty externí vázané proměnné
62 USETV zápis nové hodnoty externí vázané proměnné
63 USETS zápis řetězce do externí vázané proměnné
64 USETN zápis čísla do externí vázané proměnné
65 USETP zápis vybrané primitivní hodnoty do externí vázané proměnné

12. Odkazy na Internetu

  1. Wikipedia: Mezijazyk
    http://cs.wikipedia.org/wi­ki/Mezijazyk
  2. The LuaJIT Project
    http://luajit.org/index.html
  3. LuaJIT FAQ
    http://luajit.org/faq.html
  4. LuaJIT Performance Comparison
    http://luajit.org/performance.html
  5. LuaJIT 2.0 intellectual property disclosure and research opportunities
    http://article.gmane.org/gma­ne.comp.lang.lua.general/58908
  6. LuaJIT Wiki
    http://wiki.luajit.org/Home
  7. LuaJIT 2.0 Bytecode Instructions
    http://wiki.luajit.org/Bytecode-2.0
  8. Programming in Lua 9.1 – Coroutine Basics,
    http://www.lua.org/pil/9.1.html
  9. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  10. Programming in Lua: 6 – More about Functions
    http://www.lua.org/pil/6.html
  11. Lua Lanes
    http://kotisivu.dnainternet­.net/askok/bin/lanes/
  12. Programming in Lua: 6.1 – Closures
    http://www.lua.org/pil/6.1.html
  13. Programming in Lua: 9.1 – Coroutine Basics
    http://www.lua.org/pil/9.1.html
  14. Programming in Lua: Numeric for
    http://www.lua.org/pil/4.3.4.html
  15. Programming in Lua: break and return
    http://www.lua.org/pil/4.4.html
  16. Programming in Lua: Tables
    http://www.lua.org/pil/2.5.html
  17. Programming in Lua: Table Constructors
    http://www.lua.org/pil/3.6.html
  18. Programovací jazyk Lua
    http://palmknihy.cz/web/kni­ha/programovaci-jazyk-lua-12651.htm
  19. Lua: Tables Tutorial
    http://lua-users.org/wiki/TablesTutorial
  20. Lua: Control Structure Tutorial
    http://lua-users.org/wiki/ControlStruc­tureTutorial
  21. Lua Types Tutorial
    http://lua-users.org/wiki/LuaTypesTutorial
  22. Goto Statement in Lua
    http://lua-users.org/wiki/GotoStatement
  23. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  24. Lua 5.2 sources – lopcodes.h
    http://www.lua.org/source/5­.2/lopcodes.h.html
  25. Lua 5.2 sources – lopcodes.c
    http://www.lua.org/source/5­.2/lopcodes.c.html
  26. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  27. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  28. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
Našli jste v článku chybu?
DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

120na80.cz: Co je padesátkrát sladší než cukr?

Co je padesátkrát sladší než cukr?

Vitalia.cz: dTest odhalil ten nejlepší kečup

dTest odhalil ten nejlepší kečup

DigiZone.cz: Sony MP-CL1A: miniaturní projektor

Sony MP-CL1A: miniaturní projektor

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

Vitalia.cz: Muž, který miluje příliš. Ženám neimponuje

Muž, který miluje příliš. Ženám neimponuje

Lupa.cz: Blíží se konec Wi-Fi sítí bez hesla?

Blíží se konec Wi-Fi sítí bez hesla?

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

DigiZone.cz: Wimbledon na Nova Sport až do 2019

Wimbledon na Nova Sport až do 2019

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

Vitalia.cz: Jsou vegani a vyrábějí nemléko

Jsou vegani a vyrábějí nemléko

Podnikatel.cz: Takhle se prodávají mražené potraviny

Takhle se prodávají mražené potraviny

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

Lupa.cz: Hackeři mají data z půlmiliardy účtů Yahoo

Hackeři mají data z půlmiliardy účtů Yahoo

Vitalia.cz: 5 chyb, které děláme při skladování potravin

5 chyb, které děláme při skladování potravin

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků