Bytecode ( bytecode ; engelsk bytecode , även ibland p-code , p-code från portable code ) är en standardmellanrepresentation [ till vilken ett datorprogram kan översättas med automatiska medel. Jämfört med mänskligt läsbar källkod är bytecode en kompakt representation av ett program som redan har analyserats och analyserats . Den kodar uttryckligen typer , omfattningar och andra konstruktioner. Ur teknisk synvinkel är en bytekod en maskinoberoende kod på låg nivå som genereras av en översättare från en källkod.
Många moderna programmeringsspråk , särskilt tolkade , använder bytekod för att underlätta och påskynda tolkens arbete . Översättning till bytekod är en metod som ligger mellan i effektivitet mellan direkt tolkning och kompilering till maskinkod.
I form liknar bytecode maskinkod , men är avsedd att exekveras inte av en riktig processor , utan av en virtuell maskin . Den virtuella maskinen är vanligtvis en tolk av motsvarande programmeringsspråk (ibland kompletterat med en JIT- eller AOT-kompilator ). Specifikationerna för bytekoden och de virtuella maskiner som kör den kan variera mycket från språk till språk: bytecode består ofta av instruktioner för en staplad maskin [1] , men registermaskiner [ 2] [3] kan också användas . De flesta bytekodinstruktioner är dock vanligtvis likvärdiga med en eller flera instruktioner för assemblerspråk .
En bytekod heter så eftersom varje opcode traditionellt är en byte lång . Varje instruktion är vanligtvis en en-byte opkod (0 till 255) som kan följas av olika parametrar, såsom ett registernummer eller en minnesadress .
Ett bytekodsprogram exekveras vanligtvis av en bytekodtolkare . Fördelen med bytecode är större effektivitet och portabilitet , det vill säga att samma bytekod kan köras på olika plattformar och arkitekturer för vilka tolken är implementerad. Direkttolkade språk ger dock samma fördel, eftersom bytekod vanligtvis är mindre abstrakt och mer kompakt än källkod, är bytekodtolkning vanligtvis mer effektiv än ren källkodstolkning eller AST -tolkning . Dessutom är en bytekodtolkare ofta enklare än en källkodstolk och är lättare att överföra (porta) till en annan hårdvaruplattform.
Högpresterande implementeringar av virtuella maskiner kan använda en kombination av en tolk och en JIT-kompilator , som översätter ofta använda bytekodfragment till maskinkod under programkörning, samtidigt som olika optimeringar tillämpas. Istället för JIT-kompilering kan en AOT-kompilator användas , som översätter bytekoden till maskinkod i förväg, innan exekvering.
Samtidigt är det möjligt att skapa processorer för vilka den givna bytekoden är direkt maskinkod (sådana experimentella processorer skapades till exempel för språken Java och Forth ).
Bland de första systemen som använde bytekod var O-kod för BCPL (1960-talet), Smalltalk (1976) [4] , SIL (System Implementation Language) för Snobol-4 (1967), p-kod ( p-kod , 1970-talet, med bidrag från Niklaus Wirth ) för bärbara kompilatorer av programmeringsspråket Pascal [5] [6] [7] .
Varianter av p-koden har använts i stor utsträckning i olika implementeringar av Pascal-språket, såsom UCSD p-System ( UCSD Pascal ). [åtta]
Tolkade språk som använder bytekod inkluderar Perl , PHP (som Zend Engine ), Ruby (sedan version 1.9), Python , Erlang och många fler.
Utbredda plattformar som använder bytekod [9] :
Clipper - kompilatorn skapar en körbar fil som inkluderar bytekoden översatt från programmets källkod och en virtuell maskin som exekverar bytekoden.
Java-program kompileras vanligtvis till klassfiler, som innehåller Java-bytekod . Dessa generiska filer överförs till olika målmaskiner.
Tidiga implementeringar av Visual Basic (före version 6) använde högnivå Microsoft p-kod [9]
P-koder och bytekoder på hög nivå användes i DBMS , vissa implementeringar av BASIC och Pascal .
I Open Firmware- standarden från Sun Microsystems representerar bytekoden Forth- operatörer .
Koden:
>>> print ( "Hej, värld!" ) Hej , värld !Bytekod:
>>> importera dis #importera "dis"-modulen - Disassembler av Python-bytekod till mnemonics. >>> dis . dis ( 'print("Hello, World!")' ) 1 0 LOAD_NAME 0 ( print ) 2 LOAD_CONST 0 ( 'Hej världen!' ) 4 CALL_FUNCTION 1 6 RETURN_VALUEKoden:
yttre : för ( int i = 2 ; i < 1000 ; i ++ ) { for ( int j = 2 ; j < i ; j ++ ) { if ( i % j == 0 ) fortsätt yttre ; } System . ut . println ( i ); }Bytekod:
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_21 : iload_1_21 9 : iload_1_21 : iload_1 21 9 : iload_1_21 9 25: iinc 2 , 1 28: goto 11 31: getstatic #84 ; //Fält java/lang/System.out:Ljava/io/PrintStream; 34: iload_1 35: invokevirtual #85 ; //Method java/io/PrintStream.println:(I)V 38: iinc 1 , 1 41: goto 2 44: returnTraditionellt är bytekod utformad i stil med staplade virtuella maskiner, vilket förenklar generering från AST , möjliggör enklare och mer kompakt bytekodning, förenklar tolken och minskar mängden maskinkod som krävs för att exekvera en enkel bytekodinstruktion. Å andra sidan innehåller sådana varianter av bytekoden för ett givet program fler instruktioner än bytekoderna för virtuella registermaskiner, på grund av vilka tolken måste göra fler indirekta hopp, för vilka grenprediktion inte fungerar bra [3] . Bytekoden för virtuella registermaskiner har en något större storlek på maskinkoder, men antalet instruktioner jämfört med stackbytekoden är ungefär två gånger mindre och tolken är tiotals procent snabbare [3] . Dessutom är bytekoden för stackmaskiner svårare att optimera (uttryck blir implicita, relaterade instruktioner grupperas inte, uttryck fördelas över flera grundläggande block ) [12] och kräver verifiering av korrektheten av att använda stacken [13] .
Verifieringsfel för staplingsmaskinbytekod ledde till många extremt farliga sårbarheter, särskilt dussintals i den virtuella AVM2-maskinen som användes i Adobe Flash för att köra ActionScript-skript [14] [15] [16] och flera i de tidiga populära Java-runtime-systemen (JVM ) [ 17] [18]
I slutet av 2000-talet och början av 2010-talet ifrågasatte författarna till V8 (för JavaScript, ofta implementerad via bytecode) [19] och Dart [20] kompilatorerna behovet av mellanliggande bytekoder för snabba och effektiva virtuella maskiner. Dessa projekt implementerade direkt JIT-kompilering (kompilering vid körning) från källkoder direkt till maskinkod. [21]