Bytecode ( bytecode ; engleză bytecode , de asemenea, uneori p-code , p-code din codul portabil ) este o reprezentare intermediară standard în care un program de calculator poate fi tradus prin mijloace automate. În comparație cu codul sursă care poate fi citit de om , bytecode este o reprezentare compactă a unui program care a fost deja analizat și analizat . Codifică în mod explicit tipuri , domenii și alte constructe. Din punct de vedere tehnic, un bytecode este un cod de nivel scăzut, independent de mașină, generat de un traducător dintr-un cod sursă.
Multe limbaje de programare moderne , în special cele interpretate , folosesc bytecode pentru a facilita și accelera munca interpretului . Traducerea în bytecode este o metodă intermediară ca eficiență între interpretarea directă și compilarea în codul mașină.
În formă, bytecode este similar cu codul de mașină , dar este destinat să fie executat nu de un procesor real , ci de o mașină virtuală . Mașina virtuală este de obicei un interpret al limbajului de programare corespunzător (uneori completat de un compilator JIT sau AOT ). Specificațiile bytecode-ului și ale mașinilor virtuale care îl execută pot varia foarte mult de la o limbă la alta: bytecode-ul constă adesea în instrucțiuni pentru o mașină stivuită [ 1] , dar pot fi utilizate și mașinile de înregistrare [2] [3] . Cu toate acestea, majoritatea instrucțiunilor bytecode sunt de obicei echivalente cu una sau mai multe instrucțiuni în limbaj de asamblare .
Un bytecode este numit astfel deoarece fiecare opcode are în mod tradițional un octet . Fiecare instrucțiune este de obicei un cod operațional de un octet (de la 0 la 255) care poate fi urmat de diferiți parametri, cum ar fi un număr de registru sau o adresă de memorie .
Un program bytecode este de obicei executat de un interpret de bytecode . Avantajul bytecode este o mai mare eficiență și portabilitate , adică același bytecode poate fi executat pe diferite platforme și arhitecturi pentru care este implementat interpretul. Limbile interpretate direct oferă același avantaj, însă, deoarece bytecode este de obicei mai puțin abstract și mai compact decât codul sursă, interpretarea bytecode este de obicei mai eficientă decât interpretarea pură a codului sursă sau interpretarea AST . În plus, un interpret de cod de octet este adesea mai simplu decât un interpret de cod sursă și este mai ușor de transferat (port) pe o altă platformă hardware.
Implementările de înaltă performanță ale mașinilor virtuale pot folosi o combinație între un interpret și un compilator JIT , care traduce fragmentele de bytecode utilizate frecvent în codul mașinii în timpul execuției programului, aplicând în același timp diverse optimizări. În loc de compilare JIT, poate fi folosit un compilator AOT , care traduce bytecode în cod mașină în prealabil, înainte de execuție.
În același timp, este posibil să se creeze procesoare pentru care bytecode dat este direct cod de mașină (astfel de procesoare experimentale au fost create, de exemplu, pentru limbajele Java și Forth ).
Printre primele sisteme care au folosit bytecode au fost O-code pentru BCPL (anii 1960), Smalltalk (1976) [4] , SIL (System Implementation Language) pentru Snobol-4 (1967), p-code ( p-code , anii 1970, cu contribuții de la Niklaus Wirth ) pentru compilatoare portabile ale limbajului de programare Pascal [5] [6] [7] .
Variante ale codului p au fost utilizate pe scară largă în diferite implementări ale limbajului Pascal, cum ar fi sistemul p UCSD ( UCSD Pascal ). [opt]
Limbile interpretate care folosesc bytecode includ Perl , PHP (cum ar fi Zend Engine ), Ruby (din versiunea 1.9), Python , Erlang și multe altele.
Platforme răspândite folosind bytecode [9] :
Compilatorul Clipper creează un fișier executabil care include bytecode tradus din codul sursă al programului și o mașină virtuală care execută bytecode.
Programele Java sunt de obicei compilate în fișiere de clasă, care conține bytecode Java . Aceste fișiere generice sunt transferate pe diferite mașini țintă.
Implementările timpurii ale Visual Basic (înainte de versiunea 6) au folosit cod p Microsoft de nivel înalt [9]
În DBMS au fost utilizate coduri p de nivel înalt și coduri de octeți , unele implementări ale BASIC și Pascal .
În standardul Open Firmware de la Sun Microsystems , bytecode reprezintă operatorii Forth .
Codul:
>>> print ( "Bună, lume!" ) Bună , lume !Cod octet:
>>> import dis #import modulul "dis" - Dezasamblarea codului de octeți Python în mnemonici. >>> dis . dis ( 'print("Bună, lume!")' ) 1 0 LOAD_NAME 0 ( print ) 2 LOAD_CONST 0 ( 'Bună, lume!' ) 4 CALL_FUNCTION 1 6 RETURN_VALUECodul:
exterior : for ( int i = 2 ; i < 1000 ; i ++ ) { for ( int j = 2 ; j < i ; j ++ ) { if ( i % j == 0 ) continua exterior ; } Sistem . afară . println ( i ); }Cod octet:
0: iconst_2 1: istore_1 2: iload_1 3: sipush 1000 6: if_icmpge 44 9: iconst_2 10: istore_2 11: iload_2 12: iload_1 13: if_icmpge 31 16: iload_1 22: 16: iload_1 22 : 19 : iload_1 2 2 3 25: iinc 2 , 1 28: goto 11 31: getstatic #84 ; //Câmp java/lang/System.out:Ljava/io/PrintStream; 34: iload_1 35: invokevirtual #85 ; //Metoda java/io/PrintStream.println:(I)V 38: iinc 1 , 1 41: goto 2 44: returnÎn mod tradițional, bytecode este proiectat în stilul mașinilor virtuale stivuite, ceea ce simplifică generarea din AST , permite o codificare bytecode mai simplă și mai compactă, simplifică interpretul și reduce cantitatea de cod de mașină necesară pentru a executa o singură instrucțiune bytecode. Pe de altă parte, astfel de variante ale codului de octet pentru un program dat conțin mai multe instrucțiuni decât codurile de octet ale mașinilor virtuale de registru, din cauza cărora interpretul trebuie să facă mai multe salturi indirecte, pentru care predicția de ramificație nu funcționează bine [3] . Codul de octet pentru mașinile virtuale de înregistrare are o dimensiune puțin mai mare a codurilor de mașină, dar numărul de instrucțiuni în comparație cu codul de octet al stivei este de aproximativ două ori mai mic, iar interpretul este cu zeci de procente mai rapid [3] . De asemenea, bytecode-ul mașinilor stive este mai greu de optimizat (expresiile devin implicite, instrucțiunile aferente nu sunt grupate, expresiile sunt distribuite pe mai multe blocuri de bază ) [12] și necesită verificarea corectitudinii utilizării stivei [13] .
Erorile de verificare a codului octet al mașinii stive au condus la multe vulnerabilități extrem de periculoase, în special zeci în mașina virtuală AVM2 utilizată în Adobe Flash pentru a executa scripturi ActionScript [14] [15] [16] și câteva din primele sisteme populare de rulare Java (JVM) [ 17] [18]
La sfârșitul anilor 2000 și începutul anilor 2010, autorii compilatorilor V8 (pentru JavaScript, implementat adesea prin bytecode) [19] și Dart [20] au pus la îndoială necesitatea unor bytecoduri intermediare pentru mașini virtuale rapide și eficiente. Aceste proiecte au implementat compilarea JIT directă (compilare la timpul rulării) de la codurile sursă direct la codul mașinii. [21]