Obsah
1. Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python (4.část)
2. Překlad modulu do bajtkódu, obsah vygenerovaného binárního souboru
3. Překlad aritmetických výrazů do bajtkódu
4. Zásobníkový efekt (stack effect)
5. Naznačení operací prováděných virtuálním strojem Pythonu zásobníkovým efektem
6. Porovnání s bajtkódem virtuálního stroje jazyka Lua
7. Překlad výrazů s Booleovskými operátory
8. Překlad výrazů s relačními operátory
9. Překlad řídicí struktury typu if-then
10. Překlad řídicí struktury typu if-then-else
11. Jednoduchá programová smyčka typu while
12. Programová smyčka typu while se složitějším tělem
13. Dvě vnořené smyčky typu while – výpis řady prvočísel
14. Jednoduchá programová smyčka typu do-while/repeat-until
15. Příloha A: základní instrukce pro manipulaci se zásobníkem
16. Příloha B: aritmetické a logické operace
17. Příloha C: podmíněné a nepodmíněné skoky, větvení kódu
18. Příloha D: Tabulka všech operačních kódů instrukcí Python VM
19. Repositář s demonstračními příklady
1. Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python (4.část)
Na konci předchozího článku jsme se zmínili o tom, jakým způsobem se překládá kód reprezentovaný v abstraktním syntaktickém stromu (AST) do bajtkódu. Jedná se o poměrně mechanickou operaci, jejímž výsledkem je sekvence instrukcí bajtkódu vytvořená pro každou funkci, resp. metodu. Sekvence těchto instrukcí je buď přímo interpretována (standardní CPython), nebo ji lze přeložit do nativního kódu (AOT, JIT, což bude téma samostatného článku).
Překlad do bajtkódu si můžeme vyzkoušet přímo v REPLu jazyka Python:
import dis dis.dis("1+2*3") 1 0 LOAD_CONST 0 (7) 2 RETURN_VALUE
Z předchozího výsledku je patrné, že došlo k optimalizaci výrazu za jeho výsledek – hodnotu 7.
Nyní si vyzkoušejme překlad funkce se dvěma parametry do bajtkódu:
def add(x,y): ... return x+y ... dis.dis(add) 2 0 LOAD_FAST 0 (x) 2 LOAD_FAST 1 (y) 4 BINARY_ADD 6 RETURN_VALUE
V tomto případě nemá Python k dispozici žádné informace o typech, což ostatně platí i pro bajtkód, kde operace BINARY_ADD pouze reprezentuje operátor +, který lze pochopitelně přetížit (a už v samotném Pythonu je několikrát přetížen).
V bajtkódu jsou uloženy i další metainformace o přeložené funkci. Ty lze získat následovně:
>>> print(dis.code_info(add)) Name: add Filename: <stdin> Argument count: 2 Positional-only arguments: 0 Kw-only arguments: 0 Number of locals: 2 Stack size: 2 Flags: OPTIMIZED, NEWLOCALS, NOFREE Constants: 0: None Variable names: 0: x 1: y
2. Překlad modulu do bajtkódu, obsah vygenerovaného binárního souboru
Díky existenci modulu py_compile je možné zajistit překlad jakéhokoli modulu do bajtkódu. K tomuto účelu slouží následující volání (posledním argumentem je pochopitelně překládaný zdrojový kód):
$ python3 -m py_compile dis_0.py
Po překladu do bajtkódu získáme binární soubor nazvaný dis0.{python_version}.pyc, jehož obsah si můžeme prohlédnout libovolným hexa prohlížečem. Standardním hexa prohlížečem na Linuxu je nástroj od (nenechte se zmást jeho jménem, které klame – jedná se i o hexa prohlížeč):
$ od -t x1 dis_0.cpython-38.pyc 0000000 55 0d 0d 0a 00 00 00 00 ac 9b 00 63 3c 00 00 00 0000020 e3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000040 00 02 00 00 00 40 00 00 00 73 0c 00 00 00 64 00 0000060 64 01 84 00 5a 00 64 02 53 00 29 03 63 02 00 00 0000100 00 00 00 00 00 00 00 00 00 03 00 00 00 02 00 00 0000120 00 43 00 00 00 73 0c 00 00 00 7c 00 7c 01 17 00 0000140 7d 02 7c 02 53 00 29 01 4e a9 00 29 03 da 01 78 0000160 da 01 79 da 06 72 65 73 75 6c 74 72 01 00 00 00 0000200 72 01 00 00 00 fa 08 64 69 73 5f 30 2e 70 79 da 0000220 0b 61 64 64 5f 6e 75 6d 62 65 72 73 01 00 00 00 0000240 73 04 00 00 00 00 01 08 01 72 06 00 00 00 4e 29 0000260 01 72 06 00 00 00 72 01 00 00 00 72 01 00 00 00 0000300 72 01 00 00 00 72 05 00 00 00 da 08 3c 6d 6f 64 0000320 75 6c 65 3e 01 00 00 00 f3 00 00 00 00 0000335
Na začátku tohoto binárního souboru se nachází dvojice bajtů, jejichž hodnoty se mění s každou verzí Pythonu. Za těmito dvěma bajty je dvojice 0×0d 0×0a kvůli detekci manipulace se souborem jako s textem (předpokládá se, že se jeden z těchto bajtů buď ztratí nebo naopak zduplikuje). Následuje binárně uložený objekt, který je výsledkem překladu a který kromě tabulky symbolů (viz předchozí článek) obsahuje i sekvenci operačních kódů instrukcí následovaných operačním kódem STOP s hodnotou 0.
3. Překlad aritmetických výrazů do bajtkódu
Nyní se již pojďme věnovat poněkud praktičtějšímu tématu – konkrétně způsobu překladu různých typických programových konstrukcí do bajtkódu virtuálního stroje jazyka Python. V této kapitole si ukážeme, jak se konkrétně překládají základní aritmetické výrazy, a to zejména z toho důvodu, že právě na překladu aritmetických výrazů bude patrný největší rozdíl mezi virtuálními stroji založenými na zásobníku (Python VM) a operacích s operandy uloženými na tomto zásobníku na jedné straně a s virtuálními stroji založenými na sadě registrů na straně druhé (Lua VM).
Budeme analyzovat bajtkód tohoto programu napsaného v Pythonu:
# # Modul s nekolika jednoduchymi funkcemi # pro otestovani zakladnich vlastnosti bajtkodu jazyka Python # prekladu aritmetickych vyrazu. # def vyraz1(x, y): result = x + y return result def vyraz2(x, y): result = x - y return result def vyraz3(x, y): result = x * y return result def vyraz4(x, y): result = x / y return result def vyraz5(x, y): result = x % y return result def vyraz6(x, y, z): result = x + y * z return result def vyraz7(x, y, z, w): result = x + y * z + w return result def vyraz8(x, y, z, w): result = 2 * (x + y) * (z + w) * ((x + z) / (y + w)) return result # # Vse je nutne otestovat. # def main(): print(vyraz1(4, 3)) print(vyraz2(4, 3)) print(vyraz3(4, 3)) print(vyraz4(4, 3)) print(vyraz5(4, 3)) print(vyraz6(4, 3, 2)) print(vyraz7(4, 3, 2, 1)) print(vyraz8(4, 3, 2, 1)) def disassemble(): from dis import dis print("\nvyraz1:") dis(vyraz1) print("\nvyraz2:") dis(vyraz2) print("\nvyraz3:") dis(vyraz3) print("\nvyraz4:") dis(vyraz4) print("\nvyraz5:") dis(vyraz5) print("\nvyraz6:") dis(vyraz6) print("\nvyraz7:") dis(vyraz7) print("\nvyraz8:") dis(vyraz8) main() disassemble()
V Pythonu je použit, jak zajisté po přečtení předchozí části tohoto miniseriálu očekáváte, zásobníkový kód. Pro aritmetické operace se tedy nejdříve načtou oba operandy na zásobník instrukcí LOAD_FAST, provede se příslušná binární operace BINARY_? a následně se hodnota uloží zpět do lokální proměnné instrukcí STORE_FAST:
vyraz1: 8 0 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 2 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 4 BINARY_ADD ; provedení zvolené binární operace 6 STORE_FAST 2 (result) ; přečtení vypočteného výsledku s jeho uložením do lokální proměnné 9 8 LOAD_FAST 2 (result) ; načtení hodnoty lokální proměnné 10 RETURN_VALUE ; která bude současně návratovou hodnotou funkce vyraz2: 13 0 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 2 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 4 BINARY_SUBTRACT ; provedení zvolené binární operace 6 STORE_FAST 2 (result) ; přečtení vypočteného výsledku s jeho uložením do lokální proměnné 14 8 LOAD_FAST 2 (result) ; načtení hodnoty lokální proměnné 10 RETURN_VALUE ; která bude současně návratovou hodnotou funkce vyraz3: 18 0 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 2 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 4 BINARY_MULTIPLY ; provedení zvolené binární operace 6 STORE_FAST 2 (result) ; přečtení vypočteného výsledku s jeho uložením do lokální proměnné 19 8 LOAD_FAST 2 (result) ; načtení hodnoty lokální proměnné 10 RETURN_VALUE ; která bude současně návratovou hodnotou funkce vyraz4: 23 0 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 2 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 4 BINARY_TRUE_DIVIDE ; provedení zvolené binární operace 6 STORE_FAST 2 (result) ; přečtení vypočteného výsledku s jeho uložením do lokální proměnné 24 8 LOAD_FAST 2 (result) ; načtení hodnoty lokální proměnné 10 RETURN_VALUE ; která bude současně návratovou hodnotou funkce vyraz5: 28 0 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 2 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 4 BINARY_MODULO ; provedení zvolené binární operace 6 STORE_FAST 2 (result) ; přečtení vypočteného výsledku s jeho uložením do lokální proměnné 29 8 LOAD_FAST 2 (result) ; načtení hodnoty lokální proměnné 10 RETURN_VALUE ; která bude současně návratovou hodnotou funkce vyraz6: 33 0 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 2 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 4 LOAD_FAST 2 (z) ; uložení třetího parametru na zásobník operandů 6 BINARY_MULTIPLY ; provedení zvolené binární operace, výsledek se uloží na zásobník 8 BINARY_ADD ; provedení zvolené binární operace 10 STORE_FAST 3 (result) ; přečtení vypočteného výsledku s jeho uložením do lokální proměnné 34 12 LOAD_FAST 3 (result) ; načtení hodnoty lokální proměnné 14 RETURN_VALUE ; která bude současně návratovou hodnotou funkce vyraz7: 38 0 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 2 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 4 LOAD_FAST 2 (z) ; uložení třetího parametru na zásobník operandů 6 BINARY_MULTIPLY ; provedení zvolené binární operace, výsledek se uloží na zásobník 8 BINARY_ADD ; provedení zvolené binární operace 10 LOAD_FAST 3 (w) ; uložení čtvrtého parametru na zásobník operandů 12 BINARY_ADD ; provedení zvolené binární operace 14 STORE_FAST 4 (result) ; přečtení vypočteného výsledku s jeho uložením do lokální proměnné 39 16 LOAD_FAST 4 (result) ; načtení hodnoty lokální proměnné 18 RETURN_VALUE ; která bude současně návratovou hodnotou funkce vyraz8: 43 0 LOAD_CONST 1 (2) ; uložení konstanty na zásobník operandů 2 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 4 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 6 BINARY_ADD ; provedení zvolené binární operace 8 BINARY_MULTIPLY ; provedení zvolené binární operace, výsledek se uloží na zásobník 10 LOAD_FAST 2 (z) ; uložení třetího parametru na zásobník operandů 12 LOAD_FAST 3 (w) ; uložení čtvrtého parametru na zásobník operandů 14 BINARY_ADD ; provedení zvolené binární operace 16 BINARY_MULTIPLY ; provedení zvolené binární operace, výsledek se uloží na zásobník 18 LOAD_FAST 0 (x) ; uložení prvního parametru na zásobník operandů 20 LOAD_FAST 2 (z) ; uložení třetího parametru na zásobník operandů 22 BINARY_ADD ; provedení zvolené binární operace 24 LOAD_FAST 1 (y) ; uložení druhého parametru na zásobník operandů 26 LOAD_FAST 3 (w) ; uložení čtvrtého parametru na zásobník operandů 28 BINARY_ADD 30 BINARY_TRUE_DIVIDE 32 BINARY_MULTIPLY ; provedení zvolené binární operace, výsledek se uloží na zásobník 34 STORE_FAST 4 (result) ; přečtení vypočteného výsledku s jeho uložením do lokální proměnné 44 36 LOAD_FAST 4 (result) ; načtení hodnoty lokální proměnné 38 RETURN_VALUE ; která bude současně návratovou hodnotou funkce
4. Zásobníkový efekt (stack effect)
Už před několika desítkami let programátoři píšící aplikace v programovacím jazyku Forth používali pro popis vlivu slov (příkazů) na položky uložené na zásobníku takzvaný zásobníkový efekt (stack effect). Pro další čtení je užitečné si čtení zásobníkového efektu osvojit.
Nejedná se v podstatě o nic složitého: uvnitř kulatých závorek, které ve Forthu ale i Factoru značí začátek a konec poznámky se zásobníkovým efektem, je symbolicky zapsán stav části zásobníku před provedením operace a po dvojici znaků „–“ stav zásobníku po provedení dané operace. Vzhledem k tomu, že na zásobníku může být uloženo teoreticky libovolné množství hodnot a daná operace většinou ovlivňuje pouze hodnoty umístěné blízko jeho vrcholu, je zásobníkový efekt zapsán pouze pro ty pozice na zásobníku, které jsou operací nějakým způsobem dotčeny, tj. operace tyto hodnoty přečte, zruší či modifikuje. Položky umístěné níže nemá cenu zapisovat, jen by zápis zbytečně komplikovaly. Položka umístěná nejvíce vlevo je ve skutečnosti uložena na spodnějších místech zásobníku, než položky napravo od ní.
Následuje jednoduchý příklad zápisu zásobníkového efektu pro operaci součtu dvou čísel. Nejprve je zapsán název operace, v tomto případě nazvané plus. Poté je zapsána otevírací kulatá závorka značící začátek poznámky, za níž následuje stav nejvyšších položek zásobníku před provedením operace (tedy těch položek, které jsou operací dotčeny – ale můžeme jich zapsat i více). Po oddělovači představovaném dvojicí znaků „–“ je uveden stav nejvyšších položek po provedení operace, za nímž následuje uzavírací kulatá závorka značící konec poznámky (ve skutečnosti jsou i kulaté závorky běžnými slovy jazyka a proto musí být od okolních slov odděleny alespoň jednou mezerou).
Vzhledem k tomu, že operace sčítání načte dvě hodnoty ze zásobníku a zpět vloží jejich součet, jsou použity odlišné symboly x, y a z, přičemž samozřejmě platí, že z=x+y:
# "hlavička" operátoru plus plus ( x y -- z )
Pokud by nějaká operace například prohazovala hodnoty uložené na zásobníku, ale jinak by je žádným způsobem neměnila, byly by použity stejné symboly, což je případ operátoru swap či rot (což mohou být i instrukce bajtkódu):
# "hlavička" operátorů swap a rot swap ( x y -- y x ) rot ( x y z -- y z x )
5. Naznačení operací prováděných virtuálním strojem Pythonu zásobníkovým efektem
Výše popsaný koncept zásobníkového efektu můžeme použít i při popisu operací prováděných instrukcemi bajtkódu. Jednoduchý příklad z předchozích kapitol:
vyraz1: ; ( -- ) 8 0 LOAD_FAST 0 (x) ; ( -- x ) 2 LOAD_FAST 1 (y) ; ( x -- x y ) 4 BINARY_ADD ; ( x y -- z ) 6 STORE_FAST 2 (result) ; ( z -- ) 9 8 LOAD_FAST 2 (result) ; ( -- z ) 10 RETURN_VALUE ; ( -- )
A pro nejsložitější funkci z předchozích kapitol:
vyraz8: ; ( -- ) 43 0 LOAD_CONST 1 (2) ; ( -- 2 ) 2 LOAD_FAST 0 (x) ; ( 2 -- 2 x ) 4 LOAD_FAST 1 (y) ; ( 2 x -- 2 x y ) 6 BINARY_ADD ; ( 2 x y -- 2 x+y ) 8 BINARY_MULTIPLY ; ( 2 x+y -- 2*(x+y) ) 10 LOAD_FAST 2 (z) ; ( 2*(x+y) -- 2*(x+y) z ) 12 LOAD_FAST 3 (w) ; ( 2*(x+y) z -- 2*(x+y) z w ) 14 BINARY_ADD ; ( 2*(x+y) z w -- 2*(x+y) z+w ) 16 BINARY_MULTIPLY ; ( 2*(x+y) z+w -- 2*(x+y)*(z+w) ) 18 LOAD_FAST 0 (x) ; ( 2*(x+y)*(z+w) -- 2*(x+y)*(z+w) x ) 20 LOAD_FAST 2 (z) ; ( 2*(x+y)*(z+w) x -- 2*(x+y)*(z+w) x z ) 22 BINARY_ADD ; ( 2*(x+y)*(z+w) x z -- 2*(x+y)*(z+w) x+z ) 24 LOAD_FAST 1 (y) ; ( 2*(x+y)*(z+w) x+z -- 2*(x+y)*(z+w) x+z y ) 26 LOAD_FAST 3 (w) ; ( 2*(x+y)*(z+w) x+z y -- 2*(x+y)*(z+w) x+z y w ) 28 BINARY_ADD ; ( 2*(x+y)*(z+w) x+z y w -- 2*(x+y)*(z+w) x+z y+w ) 30 BINARY_TRUE_DIVIDE ; ( 2*(x+y)*(z+w) x+z y+w -- 2*(x+y)*(z+w) (x+z)/(y+w) ) 32 BINARY_MULTIPLY ; ( 2*(x+y)*(z+w) (x+z)/(y+w) -- 2*(x+y)*(z+w)*((x+z)/(y+w)) ) 34 STORE_FAST 4 (result) ; ( 2*(x+y)*(z+w)*((x+z)/(y+w)) -- ) 44 36 LOAD_FAST 4 (result) 38 RETURN_VALUE
6. Porovnání s bajtkódem virtuálního stroje jazyka Lua
Pro zajímavost se podívejme, jak bude podobný zdrojový kód přeložen v jazyku Lua, jehož bajtkód není založen na zásobníkovém stroji, ale na stroji registrovém:
-- -- Modul s nekolika jednoduchymi funkcemi -- pro otestovani zakladnich vlastnosti bajtkodu jazyka Lua -- prekladu aritmetickych vyrazu. -- function vyraz1(x, y) local result = x + y return result end function vyraz2(x, y) local result = x - y return result end function vyraz3(x, y) local result = x * y return result end function vyraz4(x, y) local result = x / y return result end function vyraz5(x, y) local result = x % y return result end function vyraz6(x, y, z) local result = x + y * z return result end function vyraz7(x, y, z, w) local result = x + y * z + w return result end function vyraz8(x, y, z, w) local result = 2 * (x + y) * (z + w) * ((x + z) / (y + w)) return result end -- -- Vse je nutne otestovat. -- function main() print(vyraz1(4, 3)) print(vyraz2(4, 3)) print(vyraz3(4, 3)) print(vyraz4(4, 3)) print(vyraz5(4, 3)) print(vyraz6(4, 3, 2)) print(vyraz7(4, 3, 2, 1)) print(vyraz8(4, 3, 2, 1)) end main()
Ve virtuálním stroji jazyka Lua je použit kód využívající „registry“ virtuálního stroje a proto je počet instrukcí menší, než u kódu zásobníkového. Za tuto efektivitu platíme omezeným počtem registrů (což v praxi nemusí vadit):
function <Test2.lua:7,10> (3 instructions at 0x9e07c88) 2 params, 3 slots, 0 upvalues, 3 locals, 0 constants, 0 functions 1 [8] ADD 2 0 1 ; provést aritmetickou operaci s parametrem 0 a 1 2 [9] RETURN 2 2 ; návrat z funkce a vrácení hodnoty lokální proměnné 2 3 [10] RETURN 0 1 ; automaticky vkládaná instrukce návratu z funkce function <Test2.lua:12,15> (3 instructions at 0x9e07f20) 2 params, 3 slots, 0 upvalues, 3 locals, 0 constants, 0 functions 1 [13] SUB 2 0 1 ; provést aritmetickou operaci s parametrem 0 a 1 2 [14] RETURN 2 2 ; návrat z funkce a vrácení hodnoty lokální proměnné 2 3 [15] RETURN 0 1 ; automaticky vkládaná instrukce návratu z funkce function <Test2.lua:17,20> (3 instructions at 0x9e080f0) 2 params, 3 slots, 0 upvalues, 3 locals, 0 constants, 0 functions 1 [18] MUL 2 0 1 ; provést aritmetickou operaci s parametrem 0 a 1 2 [19] RETURN 2 2 ; návrat z funkce a vrácení hodnoty lokální proměnné 2 3 [20] RETURN 0 1 ; automaticky vkládaná instrukce návratu z funkce function <Test2.lua:22,25> (3 instructions at 0x9e07ec8) 2 params, 3 slots, 0 upvalues, 3 locals, 0 constants, 0 functions 1 [23] DIV 2 0 1 ; provést aritmetickou operaci s parametrem 0 a 1 2 [24] RETURN 2 2 ; návrat z funkce a vrácení hodnoty lokální proměnné 2 3 [25] RETURN 0 1 ; automaticky vkládaná instrukce návratu z funkce function <Test2.lua:27,30> (3 instructions at 0x9e084f8) 2 params, 3 slots, 0 upvalues, 3 locals, 0 constants, 0 functions 1 [28] MOD 2 0 1 ; provést aritmetickou operaci s parametrem 0 a 1 2 [29] RETURN 2 2 ; návrat z funkce a vrácení hodnoty lokální proměnné 2 3 [30] RETURN 0 1 ; automaticky vkládaná instrukce návratu z funkce function <Test2.lua:32,35> (4 instructions at 0x9e07e18) 3 params, 4 slots, 0 upvalues, 4 locals, 0 constants, 0 functions 1 [33] MUL 3 1 2 ; provést aritmetickou operaci s parametrem 1 a 2 2 [33] ADD 3 0 3 ; provést aritmetickou operaci s parametrem 0 a lokální proměnnou 3 3 [34] RETURN 3 2 ; návrat z funkce a vrácení hodnoty lokální proměnné 3 4 [35] RETURN 0 1 ; automaticky vkládaná instrukce návratu z funkce function <Test2.lua:37,40> (5 instructions at 0x9e08458) 4 params, 5 slots, 0 upvalues, 5 locals, 0 constants, 0 functions 1 [38] MUL 4 1 2 ; provést aritmetickou operaci s parametrem 1 a 2 2 [38] ADD 4 0 4 ; provést další aritmetickou operaci s parametrem 0 a lokální proměnnou 4 3 [38] ADD 4 4 3 ; provést další aritmetickou operaci s parametrem 3 a lokální proměnnou 4 4 [39] RETURN 4 2 ; návrat z funkce a vrácení hodnoty lokální proměnné 4 5 [40] RETURN 0 1 ; automaticky vkládaná instrukce návratu z funkce function <Test2.lua:42,45> (10 instructions at 0x9e08720) 4 params, 7 slots, 0 upvalues, 5 locals, 1 constant, 0 functions 1 [43] ADD 4 0 1 ; provést aritmetickou operaci s parametrem 0 a 1 2 [43] MUL 4 -1 4 ; 2 - ; zde se adresuje konstanta -2 !!! 3 [43] ADD 5 2 3 ; provést další aritmetickou operaci, výsledek do lok. proměnné 5 4 [43] MUL 4 4 5 5 [43] ADD 5 0 2 6 [43] ADD 6 1 3 7 [43] DIV 5 5 6 8 [43] MUL 4 4 5 9 [44] RETURN 4 2 ; návrat z funkce a vrácení hodnoty lokální proměnné 4 10 [45] RETURN 0 1 ; automaticky vkládaná instrukce návratu z funkce
7. Překlad výrazů s Booleovskými operátory
Booleovské (logické) výrazy se v bajtkódu programovacího jazyka Python většinou překládají s využitím podmíněného skoku. Před samotnou instrukcí skoku se vyhodnotí podmínka, v našem případě první část logického výrazu, a na základě této podmínky se zjistí hodnota druhé části logického výrazu (pokud se samozřejmě jedná o operace se dvěma operandy). Podívejme se nyní na způsob překladu výrazů obsahujících negaci, logický součet, logický součin, operaci nonekvivalence a různé kombinace těchto operací:
# # Modul s nekolika jednoduchymi funkcemi # pro otestovani zakladnich vlastnosti bajtkodu jazyka Python # prekladu Booleovskych vyrazu. # def vyraz1(x): result = not x return result def vyraz2(x, y): result = x and y return result def vyraz3(x, y): result = x or y return result def vyraz4(x, y): result = x ^ y return result def vyraz5(x, y, z): result = x or y and z return result def vyraz6(x, y, z, w): result = (x or y) and (z or w) return result # # Vse je nutne otestovat. # def main(): print(vyraz1(True)) print(vyraz2(True, False)) print(vyraz3(True, False)) print(vyraz4(True, False)) print(vyraz5(True, False, True)) print(vyraz6(True, False, True, False)) def disassemble(): from dis import dis print("\nvyraz1:") dis(vyraz1) print("\nvyraz2:") dis(vyraz2) print("\nvyraz3:") dis(vyraz3) print("\nvyraz4:") dis(vyraz4) print("\nvyraz5:") dis(vyraz5) print("\nvyraz6:") dis(vyraz6) main() disassemble()
Povšimněte si, že se kromě první funkce (negace) skutečně používají podmíněné skoky, tj. instrukce JUMP_IF_?, s nimiž se ostatně setkáme i v navazujícím textu:
vyraz1: 8 0 LOAD_FAST 0 (x) 2 UNARY_NOT 4 STORE_FAST 1 (result) 9 6 LOAD_FAST 1 (result) 8 RETURN_VALUE vyraz2: 12 0 LOAD_FAST 0 (x) 2 JUMP_IF_FALSE_OR_POP 6 4 LOAD_FAST 1 (y) >> 6 STORE_FAST 2 (result) 13 8 LOAD_FAST 2 (result) 10 RETURN_VALUE vyraz3: 16 0 LOAD_FAST 0 (x) 2 JUMP_IF_TRUE_OR_POP 6 4 LOAD_FAST 1 (y) >> 6 STORE_FAST 2 (result) 17 8 LOAD_FAST 2 (result) 10 RETURN_VALUE vyraz4: 20 0 LOAD_FAST 0 (x) 2 LOAD_FAST 1 (y) 4 BINARY_XOR 6 STORE_FAST 2 (result) 21 8 LOAD_FAST 2 (result) 10 RETURN_VALUE vyraz5: 24 0 LOAD_FAST 0 (x) 2 JUMP_IF_TRUE_OR_POP 10 4 LOAD_FAST 1 (y) 6 JUMP_IF_FALSE_OR_POP 10 8 LOAD_FAST 2 (z) >> 10 STORE_FAST 3 (result) 25 12 LOAD_FAST 3 (result) 14 RETURN_VALUE vyraz6: 28 0 LOAD_FAST 0 (x) 2 POP_JUMP_IF_TRUE 8 4 LOAD_FAST 1 (y) 6 JUMP_IF_FALSE_OR_POP 14 >> 8 LOAD_FAST 2 (z) 10 JUMP_IF_TRUE_OR_POP 14 12 LOAD_FAST 3 (w) >> 14 STORE_FAST 4 (result) 29 16 LOAD_FAST 4 (result) 18 RETURN_VALUE
8. Překlad výrazů s relačními operátory
Výrazy s relačními operátory se překládají do značné míry stejným způsobem, jako aritmetické výrazy (jen se používají odlišné binární instrukce), o čemž se ostatně přesvědčíme po pohledu na bajtkód vygenerovaný pro následující skript:
# # Modul s nekolika jednoduchymi funkcemi # pro otestovani zakladnich vlastnosti bajtkodu jazyka Python # prekladu relacnich vyrazu. # def vyraz1(x, y): result = x <= y return result def vyraz2(x, y): result = x < y return result def vyraz3(x, y): result = x == y return result def vyraz4(x, y): result = x != y return result def vyraz5(x, y): result = x >= y return result def vyraz6(x, y): result = x > y return result # # Vse je nutne otestovat. # def main(): print(vyraz1(1, 2)) print(vyraz2(1, 2)) print(vyraz3(1, 2)) print(vyraz4(1, 2)) print(vyraz5(1, 2)) print(vyraz6(1, 2)) def disassemble(): from dis import dis print("\nvyraz1:") dis(vyraz1) print("\nvyraz2:") dis(vyraz2) print("\nvyraz3:") dis(vyraz3) print("\nvyraz4:") dis(vyraz4) print("\nvyraz5:") dis(vyraz5) print("\nvyraz6:") dis(vyraz6) main() disassemble()
Z bajtkódu je patrné, že se používá „univerzální“ instrukce COMPARE_OP, jejímž celočíselným parametrem je číslo (index) prováděné operace:
vyraz1: 8 0 LOAD_FAST 0 (x) 2 LOAD_FAST 1 (y) 4 COMPARE_OP 1 (<=) 6 STORE_FAST 2 (result) 9 8 LOAD_FAST 2 (result) 10 RETURN_VALUE vyraz2: 12 0 LOAD_FAST 0 (x) 2 LOAD_FAST 1 (y) 4 COMPARE_OP 0 (<) 6 STORE_FAST 2 (result) 13 8 LOAD_FAST 2 (result) 10 RETURN_VALUE vyraz3: 16 0 LOAD_FAST 0 (x) 2 LOAD_FAST 1 (y) 4 COMPARE_OP 2 (==) 6 STORE_FAST 2 (result) 17 8 LOAD_FAST 2 (result) 10 RETURN_VALUE vyraz4: 20 0 LOAD_FAST 0 (x) 2 LOAD_FAST 1 (y) 4 COMPARE_OP 3 (!=) 6 STORE_FAST 2 (result) 21 8 LOAD_FAST 2 (result) 10 RETURN_VALUE vyraz5: 24 0 LOAD_FAST 0 (x) 2 LOAD_FAST 1 (y) 4 COMPARE_OP 5 (>=) 6 STORE_FAST 2 (result) 25 8 LOAD_FAST 2 (result) 10 RETURN_VALUE vyraz6: 28 0 LOAD_FAST 0 (x) 2 LOAD_FAST 1 (y) 4 COMPARE_OP 4 (>) 6 STORE_FAST 2 (result) 29 8 LOAD_FAST 2 (result) 10 RETURN_VALUE
9. Překlad řídicí struktury typu if-then
Nyní se již konečně dostáváme k mnohem zajímavějšímu tématu – konkrétně k podmíněným skokům, kterými jsou realizovány strukturované příkazy (rozeskoky) typu if-then a samozřejmě i úplné rozhodovací konstrukce typu if-then-else. Jedná se o tak časté programové konstrukce, že pro ně byly v bajtkódu vyhrazeny speciální operace. Nejprve se podívejme na zdrojový kód dvou funkcí, jejichž bajtkód budeme zkoumat:
def prikaz1(x): if x: return 10 return 20 def prikaz2(x, y): if x: if y: return 10 return 20 # # Vse je nutne otestovat. # def main(): print(prikaz1(True)) print(prikaz1(False)) print(prikaz2(True, True)) print(prikaz2(True, False)) print(prikaz2(False, True)) print(prikaz2(False, False)) def disassemble(): from dis import dis print("\nprikaz1:") dis(prikaz1) print("\nprikaz2:") dis(prikaz2) main() disassemble()
Nejzajímavější částí bajtkódu jsou v tomto případě instrukce POP_JUMP_IF_FALSE (resp. POP_JUMP_IF_TRUE), které načtou hodnotu a pokud je rovna False (tedy vlastně 0), provede se podmíněný skok na konec rozvětvení:
prikaz1: 8 0 LOAD_FAST 0 (x) 2 POP_JUMP_IF_FALSE 8 9 4 LOAD_CONST 1 (10) 6 RETURN_VALUE 10 >> 8 LOAD_CONST 2 (20) 10 RETURN_VALUE prikaz2: 13 0 LOAD_FAST 0 (x) 2 POP_JUMP_IF_FALSE 12 14 4 LOAD_FAST 1 (y) 6 POP_JUMP_IF_FALSE 12 15 8 LOAD_CONST 1 (10) 10 RETURN_VALUE 16 >> 12 LOAD_CONST 2 (20) 14 RETURN_VALUE
10. Překlad řídicí struktury typu if-then-else
Úplná programová konstrukce if-then-else vznikne z neúplné konstrukce typu if-then doplněním programové větve else, což je samozřejmě podporováno i bajtkódem virtuálního stroje Pythonu – musí se jednat o úplné rozvětvení a tedy typicky o kombinaci podmíněného skoku se skokem nepodmíněným:
def prikaz1(x): if x: return 10 else: return 20 def prikaz2(x, y): if x: if y: return 10 else: return 20 else: return 30 # # Vse je nutne otestovat. # def main(): print(prikaz1(True)) print(prikaz1(False)) print(prikaz2(True, True)) print(prikaz2(True, False)) print(prikaz2(False, True)) print(prikaz2(False, False)) def disassemble(): from dis import dis print("\nprikaz1:") dis(prikaz1) print("\nprikaz2:") dis(prikaz2) main() disassemble()
V bajtkódu nyní uvidíme dvě možnosti. První variantou je plný rozeskok, přičemž každá větev končí instrukcí RETURN_VALUE:
prikaz1: 8 0 LOAD_FAST 0 (x) 2 POP_JUMP_IF_FALSE 8 9 4 LOAD_CONST 1 (10) 6 RETURN_VALUE 11 >> 8 LOAD_CONST 2 (20) 10 RETURN_VALUE 12 LOAD_CONST 0 (None) 14 RETURN_VALUE
Druhá varianta spočívá ve využití již výše zmíněné kombinace podmíněného a nepodmíněného skoku, tedy zde konkrétně instrukcí POP_JUMP_IF_FALSE a JUMP_FORWARD:
prikaz2: 14 0 LOAD_FAST 0 (x) 2 POP_JUMP_IF_FALSE 18 15 4 LOAD_FAST 1 (y) 6 POP_JUMP_IF_FALSE 12 16 8 LOAD_CONST 1 (10) 10 RETURN_VALUE 18 >> 12 LOAD_CONST 2 (20) 14 RETURN_VALUE 16 JUMP_FORWARD 4 (to 22) 20 >> 18 LOAD_CONST 3 (30) 20 RETURN_VALUE >> 22 LOAD_CONST 0 (None) 24 RETURN_VALUE
11. Jednoduchá programová smyčka typu while
Základním a ve skutečnosti i univerzálním typem smyčky je ve strukturovaném programování smyčka typu while. U tohoto typu smyčky se podmínka pro ukončení smyčky kontroluje vždy na začátku každé iterace, což mj. znamená, že se tělo smyčky nemusí provést ani jednou, a to v případě, kdy podmínka není splněna již před provedením první iterace, tj. před vstupem do smyčky. Z tohoto důvodu se konstrukce while používá například v situacích, kdy se mají zpracovávat vstupní data (čtená ze souboru, z databázové tabulky atd.), u nichž není zřejmé, kolik údajů se bude načítat a zda se vůbec nějaký údaj na vstupu bude nacházet. Zajímavé je, že ostatní typy smyček lze většinou přepsat právě na programovou smyčku typu while, i když – jak uvidíme na příkladu Pythonu – se nemusí vždy jednat o elegantní řešení. Pojďme si tedy na velmi jednoduchých příkladech ukázat způsob překladu této programové smyčky do bajtkódu virtuálního stroje Pythonu. Nejprve si ukážeme překlad smyčky, v níž se pouze snižuje hodnota počitadla kontrolovaného v podmínce pro další iteraci.
def loop(x): while x > 0: x = x - 1 return x # # Vse je nutne otestovat. # def main(): print(loop(10)) # # Vypsani bajtkodu testovane funkce # def disassemble(): from dis import dis print("\nloop:") dis(loop) main() disassemble()
Bajtkód vytvořený pro virtuální stroj Pythonu je kupodivu poměrně dlouhý (což prakticky znamená i pomalejší vyhodnocení v porovnání s Luou). Můžeme zde vidět jak nepodmíněný skok JUMP_ABSOLUTE, tak i podmíněný skok JUMP_IF_FALSE, pro nějž se podmínka vyhodnocuje instrukcí COMPARE_OP (již známe). Povšimněte si taktéž nutnosti použití instrukce POP_TOP sloužící pro „uklizení“ obsahu zásobníku operandů:
loop: 8 >> 0 LOAD_FAST 0 (x) 2 LOAD_CONST 1 (0) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 18 9 8 LOAD_FAST 0 (x) 10 LOAD_CONST 2 (1) 12 BINARY_SUBTRACT 14 STORE_FAST 0 (x) 16 JUMP_ABSOLUTE 0 10 >> 18 LOAD_FAST 0 (x) 20 RETURN_VALUE
12. Programová smyčka typu while se složitějším tělem
Pro zajímavost se ještě podívejme na poněkud složitější demonstrační příklady, v nichž je opět použita programová smyčka typu while. Tyto příklady slouží k výpočtu exponenciální funkce 2x, popř. y2x (což je v Pythonu vlastně zbytečné protože pro tuto operaci máme k dispozici operátor **). Vzhledem k tomu, že hodnoty x a y nejsou dopředu známé, nemůže překladač provádět prakticky žádné optimalizace smyčky (to mohl teoreticky udělat v předchozím příkladu, což se však, jak již víme, ve skutečnosti nestalo, alespoň nikoli na úrovni bajtkódu).
def loop(x, y): while x > 0: x = x - 1 y = y * 2 return y # # Vse je nutne otestovat. # def main(): print(loop(10, 1)) def disassemble(): from dis import dis print("\nloop:") dis(loop) main() disassemble()
Opět můžeme vidět, že se test na ukončení smyčky provádí v úvodní části kódu (porovnání+podmíněný skok), zatímco na konci smyčky je použit nepodmíněný skok na začátek:
loop: 8 >> 0 LOAD_FAST 0 (x) 2 LOAD_CONST 1 (0) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 26 9 8 LOAD_FAST 0 (x) 10 LOAD_CONST 2 (1) 12 BINARY_SUBTRACT 14 STORE_FAST 0 (x) 10 16 LOAD_FAST 1 (y) 18 LOAD_CONST 3 (2) 20 BINARY_MULTIPLY 22 STORE_FAST 1 (y) 24 JUMP_ABSOLUTE 0 11 >> 26 LOAD_FAST 1 (y) 28 RETURN_VALUE
13. Dvě vnořené smyčky typu while – výpis řady prvočísel
Nastává čas ukázat si způsob překladu algoritmu, v němž se používají dvě vnořené programové smyčky typu while. Zde je již situace zajímavější, protože delší zdrojový kód dává překladači alespoň teoretickou možnost provedení různých optimalizací. Jako příklad jsem vybral výpočet řady prvočísel od 2 do zadané maximální hodnoty, přičemž se pro výpočet používá ta nejjednodušší a současně i nejpomalejší :-) metoda založená na postupném zjišťování, zda je zadaná hodnota n dělitelná nějakým celým číslem v rozsahu 2..n-1 (teoreticky je možné smyčku ukončit dříve, ovšem nechtěl jsem zbytečně do demonstračního algoritmu přidávat volání funkcí).
V Pythonu je možné tento algoritmus zapsat následujícím způsobem:
def primeNumbers(min, max): i = min while i <= max: # projit vsechny hodnoty od min do max j = 2 while j < i: # (lze optimalizovat a zkratit smycku!) if i % j == 0: # je mozne celociselne delit? break # - pak ovsem nejde o prvocislo j = j + 1 # vyzkouset dalsiho kandidata na celociselne deleni if j == i: # pokud jsme dosli az k cislu i print(i) # nedoslo nikdy k celociselnemu deleni i = i + 1 # dalsi hodnota v posloupnosti # # Vse je nutne otestovat. # def main(): primeNumbers(2, 1000) def disassemble(): from dis import dis print("\nprimeNumbers:") dis(primeNumbers) main() disassemble()
Překlad do bajtkódu je v tomto případě dosti těžkopádný a jedná se o jeden z důvodů, proč je interpret Pythonu tak pomalý v porovnání s nejbližší (technologickou) konkurencí:
primeNumbers: 9 0 LOAD_FAST 0 (min) # i = min 2 STORE_FAST 2 (i) 10 >> 4 LOAD_FAST 2 (i) 6 LOAD_FAST 1 (max) 8 COMPARE_OP 1 (<=) 10 POP_JUMP_IF_FALSE 74 # while(i <= max): 11 12 LOAD_CONST 1 (2) 14 STORE_FAST 3 (j) 12 >> 16 LOAD_FAST 3 (j) # j = 2 18 LOAD_FAST 2 (i) 20 COMPARE_OP 0 (<) 22 POP_JUMP_IF_FALSE 48 13 24 LOAD_FAST 2 (i) # if i % j == 0: 26 LOAD_FAST 3 (j) 28 BINARY_MODULO 30 LOAD_CONST 2 (0) 32 COMPARE_OP 2 (==) 34 POP_JUMP_IF_FALSE 38 # break 14 36 JUMP_ABSOLUTE 48 15 >> 38 LOAD_FAST 3 (j) # j = j + 1 40 LOAD_CONST 3 (1) 42 BINARY_ADD 44 STORE_FAST 3 (j) 46 JUMP_ABSOLUTE 16 # konec vnitřní smyčky 16 >> 48 LOAD_FAST 3 (j) # if (j == i): 50 LOAD_FAST 2 (i) 52 COMPARE_OP 2 (==) 54 POP_JUMP_IF_FALSE 64 17 56 LOAD_GLOBAL 0 (print) 58 LOAD_FAST 2 (i) # print i 60 CALL_FUNCTION 1 62 POP_TOP 18 >> 64 LOAD_FAST 2 (i) # i = i + 1 66 LOAD_CONST 3 (1) 68 BINARY_ADD 70 STORE_FAST 2 (i) 72 JUMP_ABSOLUTE 4 # konec vnější smyčky >> 74 LOAD_CONST 0 (None) 76 RETURN_VALUE
14. Jednoduchá programová smyčka typu do-while/repeat-until
Kromě programové smyčky typu while se ve strukturovaném programování velmi často setkáme i se smyčkou typu do-while, popř. s ekvivalentní smyčkou repeat-until. Zatímco se ve smyčce while provádí test na ukončení smyčky před každou iterací, je v případě do-while i repeat-until test prováděn na konci těla smyčky a tedy již po dokončení jedné iterace, což mimochodem znamená, že tělo smyčky bude vždy provedeno alespoň jedenkrát. Rozdíl mezi smyčkami do-while (céčková větev programovacích jazyků) a repeat-until (Pascal, Modula-2, Lua) spočívá pouze v tom, zda je splnění zapsané podmínky považováno za vstup do další iterace či naopak za důvod pro ukončení celé programové smyčky. Zatímco programovací jazyk Java podporuje smyčku do-while, najdeme v jazyku Lua smyčku repeat-until a v Pythonu je nutné tuto smyčku emulovat s využitím smyčky typu while doplněnou o podmínku na konci, popř. doplněnou o další lokální proměnnou:
def loop(x): while True: x = x - 1 if x <= 0: break return x # # Vse je nutne otestovat. # def main(): print(loop(10)) # # Vypsani bajtkodu testovane funkce # def disassemble(): from dis import dis print("\nloop:") dis(loop) main() disassemble()
V případě Pythonu je smyčka do-while ve skutečnosti pouze emulována, čemuž odpovídá i (relativně přímočarý) překlad do bajtkódu:
loop: 10 >> 0 LOAD_FAST 0 (x) 2 LOAD_CONST 1 (1) 4 BINARY_SUBTRACT # x=x-1 6 STORE_FAST 0 (x) 11 8 LOAD_FAST 0 (x) 10 LOAD_CONST 2 (0) 12 COMPARE_OP 1 (<=) # vyhodnocení podmínky 14 POP_JUMP_IF_FALSE 0 # podmíněný skok na začátek smyčky 16 JUMP_ABSOLUTE 20 # výskok ze smyčky 18 JUMP_ABSOLUTE 0 # nepodmíněný skok na začátek smyčky 12 >> 20 LOAD_FAST 0 (x) # návratová hodnota 22 RETURN_VALUE
15. Příloha A: základní instrukce pro manipulaci se zásobníkem
Před provedením aritmetických, logických i bitových instrukcí jakož i před zavoláním jiné funkce, popř. metody je mnohdy nutné data vložit do vhodných registrů a/nebo na správné místo v zásobníku operandů. K tomu slouží instrukce popsané v této příloze.
Python VM je zásobníkový virtuální stroj, proto jeho instrukční sada obsahuje několik instrukcí, které se nápadně podobají slovům používaným v programovacím jazyku Forth (ono se ostatně není čemu divit, protože se jedná skutečně o základní „zásobníkové“ instrukce). Důležité je, že tyto instrukce již nepotřebují být rozděleny podle toho s jakými daty pracují, protože každému prvku uloženém na zásobníku operandů je stejně přiřazen datový tag, který je těmito instrukcemi využíván:
# | Instrukce | Operand 1 | Operand 2 | Operace |
---|---|---|---|---|
1 | POP_TOP | implicitní | implicitní | odstraní jednu položku z vrcholu zásobníku operandů |
2 | ROT_TWO | implicitní | implicitní | rotace (swap) dvou položek |
3 | ROT_THREE | implicitní | implicitní | rotace (roll) tří položek |
4 | ROT_FOUR | implicitní | implicitní | rotace čtyř položek |
5 | DUP_TOP | implicitní | implicitní | duplikace položky na zásobníku operandů |
16. Příloha B: aritmetické a logické operace
Začněme popisem aritmetických instrukcí, které jsou velmi jednoduché a snadno pochopitelné.
Aritmetické instrukce pracují s hodnotami umístěnými na zásobníku operandů, podobně jako je tomu například i v JVM. Všechny instrukce navíc mohou pracovat i pro různé typy objektů (to odpovídá přetížení příslušných operandů):
# | Instrukce | Operand 1 | Operand 2 | Operace |
---|---|---|---|---|
1 | BINARY_ADD | číslo/objekt | číslo/objekt | součet |
2 | BINARY_SUBTRACT | číslo/objekt | číslo/objekt | rozdíl |
3 | BINARY_MULTIPLY | číslo/objekt | číslo/objekt | součin |
4 | BINARY_DIVIDE | číslo/objekt | číslo/objekt | podíl |
5 | BINARY_MODULO | číslo/objekt | číslo/objekt | podíl modulo |
6 | BINARY_POWER | číslo/objekt | číslo/objekt | umocnění |
7 | UNARY_NEGATIVE | číslo/objekt | × | změna znaménka |
Druhou skupinou instrukcí jsou instrukce, pomocí nichž se implementují téměř všechny základní bitové a logické operace.
# | Instrukce | Operand 1 | Operand 2 | Operace |
---|---|---|---|---|
1 | BINARY_AND | číslo/objekt | číslo/objekt | bitový/logický součin |
2 | BINARY_OR | číslo/objekt | číslo/objekt | bitový/logický součet |
3 | BINARY_XOR | číslo/objekt | číslo/objekt | bitová/logická nonekvivalence |
7 | UNARY_NOT | číslo/objekt | × | negace |
V Python VM nalezneme i následující instrukce pro posuny (pro celočíselné operandy se jedná o aritmetický posun doleva a doprava):
# | Instrukce | Operand 1 | Operand 2 | Operace |
---|---|---|---|---|
1 | BINARY_LSHIFT | číslo/objekt | číslo/objekt | posun doleva |
2 | BINARY_RSHIFT | číslo/objekt | číslo/objekt | posun doprava |
17. Příloha C: podmíněné a nepodmíněné skoky, větvení kódu
Ve virtuálním stroji programovacího jazyka Python nalezneme celkem šest základních instrukcí skoku – konkrétně dvě instrukce určené pro provedení nepodmíněných skoků a tři instrukce pro provedení skoků podmíněných:
# | Instrukce | Operand 1 | Operand 2 | Prováděná operace |
---|---|---|---|---|
1 | JUMP_ABSOLUTE | index instrukce, na kterou skok vede | × | skok na zadaný index (lokální adresu) |
2 | JUMP_FORWARD | index instrukce, na kterou skok vede | × | skok na relativní index (je zadána delta oproti současné adrese) |
3 | POP_JUMP_IF_TRUE | index instrukce, na kterou skok vede | × | podmíněný skok na základě obsahu TOS; obsah TOS je odstraněn |
4 | POP_JUMP_IF_FALSE | index instrukce, na kterou skok vede | × | podmíněný skok na základě obsahu TOS (opačná podmínka); obsah TOS je odstraněn |
5 | JUMP_IF_TRUE_OR_POP | index instrukce, na kterou skok vede | × | pokud TOS==true, provede se skok, v opačném případě se TOS odstraní |
6 | JUMP_IF_FALSE_OR_POP | index instrukce, na kterou skok vede | × | opačné chování, než je tomu v předchozí instrukci |
18. Příloha D: Tabulka všech operačních kódů instrukcí Python VM
Virtuální stroj programovacího jazyka Python využívá v bajtkódu instrukce, jejichž operační kód má šířku osmi bitů (jeden bajt). To znamená, že je teoreticky možné využít 256 různých instrukcí. Ve skutečnosti jsou však některé operační kódy neobsazeny, což je ostatně patrné i při pohledu na tabulku se seznamem všech (v současnosti používaných) instrukcí Python VM.
Operační kód | Instrukce | Význam |
---|---|---|
0×00 | STOP_CODE | konec bajtkódu, není využito interpretrem |
0×01 | POP_TOP | odstraní položku ze zásobníku operandů |
0×02 | ROT_TWO | rotace (swap) dvou položek |
0×03 | ROT_THREE | rotace (roll) tří položek |
0×04 | DUP_TOP | duplikace položky na zásobníku operandů |
0×05 | ROT_FOUR | rotace čtyř položek |
0×06 | neobsazeno | (neobsazeno) |
0×07 | neobsazeno | (neobsazeno) |
0×08 | neobsazeno | (neobsazeno) |
0×09 | NOP | žádná operace (výplň) |
0×0a | UNARY_POSITIVE | unární operátor + |
0×0b | UNARY_NEGATIVE | změna znaménka |
0×0c | UNARY_NOT | negace |
0×0d | UNARY_CONVERT | implementace konstrukce backtick (`) |
0×0e | neobsazeno | (neobsazeno) |
0×0f | UNARY_INVERT | bitová negace |
0×10 | BINARY_MATRIX_MULTIPLY | (nově obsazeno maticovým operátorem násobení) |
0×11 | INBINARY_MATRIX_MULTIPLY | (nově obsazeno maticovým operátorem násobení) |
0×12 | neobsazeno | (neobsazeno) |
0×13 | BINARY_POWER | umocnění |
0×14 | BINARY_MULTIPLY | součin |
0×15 | BINARY_DIVIDE | podíl |
0×16 | BINARY_MODULO | podíl modulo |
0×17 | BINARY_ADD | součet |
0×18 | BINARY_SUBTRACT | rozdíl |
0×19 | BINARY_SUBSCR | přístup k prvkům složeného datového typu (řetězec, seznam, n-tice) |
0×1a | BINARY_FLOOR_DIVIDE | podíl (//) |
0×1b | BINARY_TRUE_DIVIDE | podíl |
0×1c | INPLACE_FLOOR_DIVIDE | podíl (odlišný přístup k prvkům na zásobníku) |
0×1d | INPLACE_TRUE_DIVIDE | podíl (odlišný přístup k prvkům na zásobníku) |
0×1e | GET_LEN | získání délky sekvence (řetězec, seznam, n-tice) |
0×1f | MATCH_MAPPING | pattern matching |
0×20 | MATCH_SEQUENCE | pattern matching |
0×21 | MATCH_KEYS | pattern matching |
0×22 | COPY_DICT_WITHOUT_KEYS | (nově obsazeno) |
0×23 | neobsazeno | (neobsazeno) |
0×24 | neobsazeno | (neobsazeno) |
0×25 | neobsazeno | (neobsazeno) |
0×26 | neobsazeno | (neobsazeno) |
0×27 | neobsazeno | (neobsazeno) |
0×28 | STORE_SLICE+0 | přístup k prvkům složeného datového typu (typicky seznamu) |
0×29 | STORE_SLICE+1 | přístup k prvkům složeného datového typu (typicky seznamu) |
0×2a | STORE_SLICE+2 | přístup k prvkům složeného datového typu (typicky seznamu) |
0×2b | STORE_SLICE+3 | přístup k prvkům složeného datového typu (typicky seznamu) |
0×2c | WITH_EXCEPT_START | (nově obsazeno) |
0×2d | GET_AITER | (nově obsazeno, await/async) |
0×2e | GET_ANEXT | (nově obsazeno, await/async) |
0×2f | BEFORE_ASYNC_WITH | (nově obsazeno, await/async) |
0×30 | END_ASYNC_FOR | (nově obsazeno, await/async) |
0×31 | neobsazeno | (neobsazeno) |
0×32 | DELETE_SLICE+0 | přístup k prvkům složeného datového typu (typicky seznamu) |
0×33 | DELETE_SLICE+1 | přístup k prvkům složeného datového typu (typicky seznamu) |
0×34 | DELETE_SLICE+2 | přístup k prvkům složeného datového typu (typicky seznamu) |
0×35 | DELETE_SLICE+3 | přístup k prvkům složeného datového typu (typicky seznamu) |
0×36 | STORE_MAP | uloží do mapy další dvojici klíč:hodnota |
0×37 | INPLACE_ADD | varianta instrukce BINARY_ADD (odlišný přístup k prvkům na zásobníku) |
0×38 | INPLACE_SUBTRACT | varianta instrukce BINARY_SUBTRACT (odlišný přístup k prvkům na zásobníku) |
0×39 | INPLACE_MULTIPLY | varianta instrukce BINARY_MULTIPLY (odlišný přístup k prvkům na zásobníku) |
0×3a | INPLACE_DIVIDE | varianta instrukce BINARY_DIVIDE (odlišný přístup k prvkům na zásobníku) |
0×3b | INPLACE_MODULO | varianta instrukce BINARY_MODULO (odlišný přístup k prvkům na zásobníku) |
0×3c | STORE_SUBSCR | přístup k prvkům n-tice, seznamu či řetězce |
0×3d | DELETE_SUBSCR | vymazání prvku složeného datového typu |
0×3e | BINARY_LSHIFT | posun doleva |
0×3f | BINARY_RSHIFT | posun doprava |
0×40 | BINARY_AND | bitový/logický součin |
0×41 | BINARY_XOR | bitová/logická nonekvivalence |
0×42 | BINARY_OR | bitový/logický součet |
0×43 | INPLACE_POWER | operace ** |
0×44 | GET_ITER | získání iterátoru pro prvek uložený na TOS |
0×45 | GET_YIELD_FROM_ITER | získání reference na pozastavený kód v korutině |
0×46 | PRINT_EXPR | tisk výrazu |
0×47 | LOAD_BUILD_CLASS | (nově obsazeno, předtím použito pro varianty operace PRINT) |
0×48 | YIELD_FROM | (nově obsazeno, předtím použito pro varianty operace PRINT) |
0×49 | GET_AWAITABLE | (nově obsazeno, předtím použito pro varianty operace PRINT) |
0×4a | LOAD_ASSERTION_ERROR | (nově obsazeno, předtím použito pro varianty operace PRINT) |
0×4b | INPLACE_LSHIFT | posun doleva |
0×4c | INPLACE_RSHIFT | posun doprava |
0×4d | INPLACE_AND | bitový/logický součin |
0×4e | INPLACE_XOR | bitová/logická nonekvivalence |
0×4f | INPLACE_OR | bitový/logický součet |
0×50 | BREAK_LOOP | přerušení programové smyčky |
0×51 | WITH_CLEANUP | vymazání zásobníku při ukončení bloku with |
0×52 | LOAD_LOCALS | použito při definici tříd |
0×53 | RETURN_VALUE | návrat z funkce a vrácení hodnoty z TOS |
0×54 | IMPORT_STAR | nahraje všechny (nelokální) symboly do jmenného prostoru |
0×55 | EXEC_STMT | volá exec |
0×56 | YIELD_VALUE | použito v generátoru pro vrácení (další) hodnoty |
0×57 | POP_BLOCK | odstraní blok ze zásobníku (konec smyčky atd.) |
0×58 | END_FINALLY | ukončí klauzuli finally |
0×59 | BUILD_CLASS | vytvoří novou třídu |
0×5a | STORE_NAME | práce se jmény uloženými v co_names |
0×5b | DELETE_NAME | práce se jmény uloženými v co_names |
0×5c | UNPACK_SEQUENCE | rozdělení sekvence na jednotlivé prvky a jejich uložení na zásobník |
0×5d | FOR_ITER | použito při implementaci programové smyčky |
0×5e | LIST_APPEND/UNPACK_EX | volá list.append() |
0×5f | STORE_ATTR | práce se jmény uloženými v co_names |
0×60 | DELETE_ATTR | práce se jmény uloženými v co_names |
0×61 | STORE_GLOBAL | práce s globálními jmény |
0×62 | DELETE_GLOBAL | práce s globálními jmény |
0×63 | ROTN | instrukce ROT opakovaná 1× až 5× |
0×64 | LOAD_CONST | práce s hodnotami uloženými v co_const |
0×65 | LOAD_NAME | práce s hodnotami uloženými v co_names |
0×66 | BUILD_TUPLE | vytvoření nové n-tice |
0×67 | BUILD_LIST | vytvoření nového seznamu |
0×68 | BUILD_SET | vytvoření nové množiny |
0×69 | BUILD_MAP | vytvoření nové mapy |
0×6a | LOAD_ATTR | přečtení atributu objektu |
0×6b | COMPARE_OP | provedení zvolené Booleovské operace |
0×6c | IMPORT_NAME | import modulu |
0×6d | IMPORT_FROM | import modulu |
0×6e | JUMP_FORWARD | skok na relativní index (je zadána delta oproti současné adrese) |
0×6f | JUMP_IF_FALSE_OR_POP | opačné chování, než je tomu v předchozí instrukci |
0×70 | JUMP_IF_TRUE_OR_POP | pokud TOS==true, provede se skok, v opačném případě se TOS odstraní |
0×71 | JUMP_ABSOLUTE | skok na zadaný index (lokální adresu) |
0×72 | POP_JUMP_IF_FALSE | podmíněný skok na základě obsahu TOS; obsah TOS je odstraněn |
0×73 | POP_JUMP_IF_TRUE | podmíněný skok na základě obsahu TOS (opačná podmínka); obsah TOS je odstraněn |
0×74 | LOAD_GLOBAL | uložení zvoleného globálního jména na TOS |
0×75 | IS_OP | (nově obsazeno) |
0×76 | CONTAINS_OP | (nově obsazeno) |
0×77 | CONTINUE_LOOP | implementace příkazu continue |
0×78 | SETUP_LOOP | příprava programové smyčky (vytvoření bloku atd.) |
0×79 | SETUP_EXCEPT | vytvoření kopie bloku try na zásobníku |
0×7a | SETUP_FINALLY | vytvoření kopie bloku try na zásobníku |
0×7b | neobsazeno | (neobsazeno) |
0×7c | LOAD_FAST | uložení reference lokální proměnné na zásobník |
0×7d | STORE_FAST | uložení hodnoty do lokální proměnné z TOS |
0×7e | DELETE_FAST | vymazání lokální proměnné |
0×7f | neobsazeno | (neobsazeno) |
0×80 | neobsazeno | (neobsazeno) |
0×81 | GEN_START | (nově obsazeno) |
0×82 | RAISE_VARARGS | vyhození výjimky |
0×83 | CALL_FUNCTION | volání funkce |
0×84 | MAKE_FUNCTION | uložení reference na novou funkci na TOS |
0×85 | BUILD_SLICE | implementace funkce slice() |
0×86 | MAKE_CLOSURE | vytvoření uzávěru |
0×87 | LOAD_CLOSURE | uloží referenci na uzávěr |
0×88 | LOAD_DEREF | práce s uzávěry a/nebo objekty |
0×89 | STORE_DEREF | práce s uzávěry a/nebo objekty |
0×8a | DELETE_DEREF | práce s uzávěry a/nebo objekty |
0×8b | neobsazeno | (neobsazeno) |
0×8c | CALL_FUNCTION_VAR | zavolání funkce s proměnným počtem argumentů |
0×8d | CALL_FUNCTION_KW | zavolání funkce s pojmenovanými argumenty |
0×8e | CALL_FUNCTION_VAR_KW | zavolání funkce – kombinace obou předchozích variant |
0×8f | SETUP_WITH | začátek bloku with |
0×90 | EXTENDED_ARG | podpora pro argumenty funkcí, jichž je více než 65535 |
0×91 | LIST_APPEND | (nově obsazeno, přidání elementu do seznamu) |
0×92 | SET_ADD | rozšíření bajtkódu: práce s množinou |
0×93 | MAP_ADD | rozšíření bajtkódu: práce s mapou |
0×94 | LOAD_CLASSDEREF | (nově obsazeno) |
0×95 | neobsazeno | (neobsazeno) |
0×96 | neobsazeno | (neobsazeno) |
0×97 | neobsazeno | (neobsazeno) |
0×98 | MATCH_CLASS | pattern matching |
0×99 | neobsazeno | (neobsazeno) |
0×9a | SETUP_ASYNC_WITH | (nově obsazeno, async/await) |
0×9b | FORMAT_VALUE | (nově obsazeno pro formátovací řetězec) |
0×9c | BUILD_CONST_KEY_MAP | (konstruktory) |
0×9d | BUILD_STRING | (konstruktory) |
0×9e | neobsazeno | (neobsazeno) |
0×9f | neobsazeno | (neobsazeno) |
0×a0 | LOAD_METHOD | (nově obsazeno) |
0×a1 | CALL_METHOD | (nově obsazeno) |
0×a2 | LIST_EXTEND | (nově obsazeno) |
0×a3 | SET_UPDATE | (nově obsazeno) |
0×a4 | DICT_MERGE | (nově obsazeno) |
0×a5 | DICT_UPDATE | (nově obsazeno) |
0×a6 | neobsazeno | (neobsazeno) |
0×a7 | neobsazeno | (neobsazeno) |
0×a8 | neobsazeno | (neobsazeno) |
0×a9 | neobsazeno | (neobsazeno) |
0×aa | neobsazeno | (neobsazeno) |
0×ab | neobsazeno | (neobsazeno) |
0×ac | neobsazeno | (neobsazeno) |
0×ad | neobsazeno | (neobsazeno) |
0×ae | neobsazeno | (neobsazeno) |
0×af | neobsazeno | (neobsazeno) |
0×b0 | neobsazeno | (neobsazeno) |
0×b1 | neobsazeno | (neobsazeno) |
0×b2 | neobsazeno | (neobsazeno) |
0×b3 | neobsazeno | (neobsazeno) |
0×b4 | neobsazeno | (neobsazeno) |
0×b5 | neobsazeno | (neobsazeno) |
0×b6 | neobsazeno | (neobsazeno) |
0×b7 | neobsazeno | (neobsazeno) |
0×b8 | neobsazeno | (neobsazeno) |
0×b9 | neobsazeno | (neobsazeno) |
0×ba | neobsazeno | (neobsazeno) |
0×bb | neobsazeno | (neobsazeno) |
0×bc | neobsazeno | (neobsazeno) |
0×bd | neobsazeno | (neobsazeno) |
0×be | neobsazeno | (neobsazeno) |
0×bf | neobsazeno | (neobsazeno) |
0×c0 | neobsazeno | (neobsazeno) |
0×c1 | neobsazeno | (neobsazeno) |
0×c2 | neobsazeno | (neobsazeno) |
0×c3 | neobsazeno | (neobsazeno) |
0×c4 | neobsazeno | (neobsazeno) |
0×c5 | neobsazeno | (neobsazeno) |
0×c6 | neobsazeno | (neobsazeno) |
0×c7 | neobsazeno | (neobsazeno) |
0×c8 | neobsazeno | (neobsazeno) |
0×c9 | neobsazeno | (neobsazeno) |
0×ca | neobsazeno | (neobsazeno) |
0×cb | neobsazeno | (neobsazeno) |
0×cc | neobsazeno | (neobsazeno) |
0×cd | neobsazeno | (neobsazeno) |
0×ce | neobsazeno | (neobsazeno) |
0×cf | neobsazeno | (neobsazeno) |
0×d0 | neobsazeno | (neobsazeno) |
0×d1 | neobsazeno | (neobsazeno) |
0×d2 | neobsazeno | (neobsazeno) |
0×d3 | neobsazeno | (neobsazeno) |
0×d4 | neobsazeno | (neobsazeno) |
0×d5 | neobsazeno | (neobsazeno) |
0×d6 | neobsazeno | (neobsazeno) |
0×d7 | neobsazeno | (neobsazeno) |
0×d8 | neobsazeno | (neobsazeno) |
0×d9 | neobsazeno | (neobsazeno) |
0×da | neobsazeno | (neobsazeno) |
0×db | neobsazeno | (neobsazeno) |
0×dc | neobsazeno | (neobsazeno) |
0×dd | neobsazeno | (neobsazeno) |
0×de | neobsazeno | (neobsazeno) |
0×df | neobsazeno | (neobsazeno) |
0×e0 | neobsazeno | (neobsazeno) |
0×e1 | neobsazeno | (neobsazeno) |
0×e2 | neobsazeno | (neobsazeno) |
0×e3 | neobsazeno | (neobsazeno) |
0×e4 | neobsazeno | (neobsazeno) |
0×e5 | neobsazeno | (neobsazeno) |
0×e6 | neobsazeno | (neobsazeno) |
0×e7 | neobsazeno | (neobsazeno) |
0×e8 | neobsazeno | (neobsazeno) |
0×e9 | neobsazeno | (neobsazeno) |
0×ea | neobsazeno | (neobsazeno) |
0×eb | neobsazeno | (neobsazeno) |
0×ec | neobsazeno | (neobsazeno) |
0×ed | neobsazeno | (neobsazeno) |
0×ee | neobsazeno | (neobsazeno) |
0×ef | neobsazeno | (neobsazeno) |
0×f0 | neobsazeno | (neobsazeno) |
0×f1 | neobsazeno | (neobsazeno) |
0×f2 | neobsazeno | (neobsazeno) |
0×f3 | neobsazeno | (neobsazeno) |
0×f4 | neobsazeno | (neobsazeno) |
0×f5 | neobsazeno | (neobsazeno) |
0×f6 | neobsazeno | (neobsazeno) |
0×f7 | neobsazeno | (neobsazeno) |
0×f8 | neobsazeno | (neobsazeno) |
0×f9 | neobsazeno | (neobsazeno) |
0×fa | neobsazeno | (neobsazeno) |
0×fb | neobsazeno | (neobsazeno) |
0×fc | neobsazeno | (neobsazeno) |
0×fd | neobsazeno | (neobsazeno) |
0×fe | neobsazeno | (neobsazeno) |
0×ff | neobsazeno | (neobsazeno) |
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 (některé přímo pro Python 3.10) byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- 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/ - Byterun
https://github.com/nedbat/byterun - Python Byte Code Instructions
http://document.ihg.uni-duisburg.de/Documentation/Python/lib/node56.html - Python Byte Code Instructions
https://docs.python.org/3.2/library/dis.html#python-bytecode-instructions - dis – Python module
https://docs.python.org/2/library/dis.html - Comparison of Python virtual machines
http://polishlinux.org/apps/cli/comparison-of-python-virtual-machines/ - O-code
http://en.wikipedia.org/wiki/O-code_machine - 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 - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Pygments – Python syntax highlighter
http://pygments.org/ - Pygments (dokumentace)
http://pygments.org/docs/ - 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/ - Jazyky podporované knihovnou Pygments
http://pygments.org/languages/ - Pygments FAQ
http://pygments.org/faq/ - 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