Che cosa è esattamente il Bytecode della macchina virtuale? [chiuso]

2

Voglio lavorare su un compilatore molto semplice per un linguaggio molto semplice. Il compilatore compilerà il codice con un codice byte di base. Successivamente, un programma di macchina virtuale eseguirà il bytecode ed eseguirà il programma.

Ho diverse domande su cosa sia in realtà bytecode:

  • Il bytecode deve essere di formato binario, alias composto solo da 1 e 0 sequenze? Oppure può includere più tipi di numeri e persino parole?

  • Quanto esattamente, più comunemente, è il bytecode di un programma composto? Uno idea che avevo per ciò che il bytecode conterrà e come sarà composto, usando il mio compilatore, è: Il compilatore scansionerà il codice dello sviluppatore e convertirà ogni comando in qualche equivalente bytecode. Ad esempio, il comando print verrà convertito in 1 nel bytecode. Successivamente, quando la VM esegue il bytecode, ogni elemento nel bytecode verrà convertito in un'esecuzione nel programma. Ad esempio, se la VM esegue la scansione del bytecode e arriva 1 di acors, verrà stampato qualcosa. Questo approccio è comune? È valido? Qual è l'approccio più comune a come e cosa compone il bytecode?

posta Aviv Cohn 03.03.2014 - 12:31
fonte

4 risposte

4
  • non c'è bisogno di fare binari, puoi scrivere del testo e interpretarlo direttamente. La maggior parte delle macchine virtuali utilizzerà il binario basato su ottetti per efficienza.

  • che dipende interamente dal designer. tuttavia di solito le istruzioni comuni (aggiungendo 2 numeri, GOTO, ...) saranno più brevi di quelle non comuni (chiamando una funzione, sollevando un'eccezione, ...)

    un modo per farlo è lasciare che i primi pochi bit di un codice operativo decidano la famiglia (aritmetica, confronto, salti, ...) dell'istruzione, i bit successivi saranno l'istruzione specifica e gli ultimi bit indicherà gli operandi utilizzati (se applicabile)

risposta data 03.03.2014 - 12:39
fonte
3

Quello che stai facendo, in effetti, è la progettazione di nuove istruzioni per computer, insieme a un nuovo computer (la tua macchina virtuale) implementato come un programma.

Generalmente, le istruzioni (che sono i codici byte) hanno una parte iniziale che l'interprete esamina per capire cosa verrà dopo. Ad esempio, come suggerito, 1 potrebbe significare stampare la seguente stringa . 2 potrebbe significare aggiungere i seguenti 2 numeri interi .

Mentre elabori le istruzioni che ti servono, scoprirai che hai bisogno di un modo di conservare i dati (chiamato organizzazione della memoria) e un modo di organizzare le istruzioni. Le istruzioni faranno cose molto complesse o cose molto semplici? Se fanno cose complesse (stampa questa stringa), allora saranno necessari molti, ma i dati possono essere organizzati in un modo più semplice. Se le istruzioni fanno cose semplici (carica questo numero intero per area di lavoro 1) ci possono essere meno istruzioni, ma l'organizzazione dei dati dovrà supportare strutture più ricche e più tipi di dati.

Inizia semplice, ad esempio con codici op. 1 byte. Aspettati di passare attraverso molte versioni di codice mentre lavori.

Enjoy. Imparerai di più su come funzionano i computer di quanto immagini.

    
risposta data 03.03.2014 - 13:33
fonte
1

Per un semplice esempio diamo un'occhiata a Zend Engine di PHP. Questa è una VM relativamente semplice che funziona esclusivamente in memoria, quindi non deve riguardare la serializzazione ecc.

In PHP il bytecode è composto da 167 diversi codici opcode che possono essere trovati in un'intestazione: link

Tutti questi opcode sono memorizzati in una struttura dati insieme ad alcune meta informazioni in forma di un array (rappresentato come puntatore al primo elemento)

struct _zend_op_array {
    /* ... */
    zend_op *opcodes;
    /* ... */
 }

Ogni operazione ha quindi la sua struttura dati memorizzata in tale posizione:

struct _zend_op {
    opcode_handler_t handler;
    znode_op op1;
    znode_op op2;
    znode_op result;
    ulong extended_value;
    uint lineno;
    zend_uchar opcode;
    zend_uchar op1_type;
    zend_uchar op2_type;
    zend_uchar result_type;
};

Qui possiamo vedere più cose:

  • Il numero di opcode dall'elenco lungo di opcode è memorizzato nel campo opcode .
  • Ogni operazione richiede due operandi ( op1 e op2 ) e ha un valore di ritorno ( result )
  • Gli operandi e il valore restituito hanno tipi, che indicano se rappresentano variabili, costanti o valori temporanei. Nell'implementazione di PHP questi sono memorizzati in luoghi diversi e sono accessibili in modo diverso.
  • Esiste un extended_value che viene utilizzato, ad esempio, in foreach loop per memorizzare ulteriori informazioni
  • Il numero di riga del file di origine per riportare errori (il nome del file è memorizzato nella strucuture zend_op_array sopra)
  • Il handler è una piccola ottimizzazione, che punta direttamente alla funzione che implementa questo.

Durante la compilazione dello script il compilatore creerà queste strutture dati e assicurerà che gli operandi e il valore restituito vengano visualizzati nello stesso posto, quindi con una chiamata come foo(bar()) il valore di ritorno della chiamata a bar sarà lo stesso temporaneo come operando dell'opcode che spinge un argomento sullo stack di argomenti delle funzioni. Aspetta cosa - stack argomento !? potresti chiedere, a destra: poiché ogni codice operativo accetta solo due operandi, non possiamo passare direttamente i parametri di funzione, ma le prime operazioni di ZEND_SEND_* riempiono uno stack, quindi un'operazione ZEND_DO_FCALL / ZEND_DO_FCALL_BY_NAME verrà eseguita utilizzando tale stack.

Usando lo strumento vld possiamo scaricare questo modulo compilato:

php -dextension=modules/vld.so -dvld.active -r 'foo(bar());'
Finding entry points
Branch analysis from position: 0
Return found
filename:       Command line code
function name:  (null)
number of ops:  6
compiled vars:  none
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   1     0  >   INIT_FCALL_BY_NAME                                       'foo', 'foo'
         1      INIT_FCALL_BY_NAME                                       'bar', 'bar'
         2      DO_FCALL_BY_NAME                              0          
         3      SEND_VAR_NO_REF                               4          $0
         4      DO_FCALL_BY_NAME                              1          
         5    > RETURN                                                   null

branch: #  0; line:     1-    1; sop:     0; eop:     5
path #1: 0, 

Il prossimo passo è l'esecuzione. Questo è essenzialmente questo ciclo (abbreviato):

    while (1) {
            if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
                    switch (ret) {
                            case 1:
                                    return;
                                    /* ... */
                            default:
                                    break;
                    }
            }

    }

OPLINE rappresenta l'elemento corrente della nostra matrice di strutture zend_op. Ogni op ha un gestore che viene eseguito. (per vedere il ciclo corretto: è in zend_vm_execute.h generato da zend_vm_gen.php da zend_vm_execute.skl e zend_vm_def.h) Se il gestore restituisce uno script / funzione / ... stiamo attualmente eseguendo dei ritorni, come il ciclo finisce.

Un semplice gestore di codice operativo può essere definito in questo modo (vedi zend_vm_def.h):

ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV)
{
        USE_OPLINE
        zend_free_op free_op1, free_op2;

        SAVE_OPLINE();
        fast_add_function(&EX_T(opline->result.var).tmp_var,
                GET_OP1_ZVAL_PTR(BP_VAR_R),
                GET_OP2_ZVAL_PTR(BP_VAR_R) TSRMLS_CC);
        FREE_OP1();
        FREE_OP2();
        CHECK_EXCEPTION();
        ZEND_VM_NEXT_OPCODE();
}

Questo codice è simile a C ma è inserito nello script gen menzionato in precedenza. Vediamo che questo è l'opcode numero 1, che va sotto il nome di ZEND_ADD che rappresenta un'aggiunta. Richiede entrambi gli operandi, che possono essere di entrambi i tipi (costanti, temporanee, variabili arbitrarie, variabili memorizzate nella cache del compilatore). Al termine dell'operazione il puntatore viene impostato sul prossimo codice operativo dell'array.

    
risposta data 03.03.2014 - 15:17
fonte
1

Bytecode della macchina virtuale è semplicemente un altro set di istruzioni. Dove vediamo casi di questi set di istruzioni è generalmente per la portabilità. Fondamentalmente hai solo bisogno di un compilatore o almeno di un back-end.

La portabilità deriva dalla scelta di un nuovo set di istruzioni che non è troppo difficile da implementare sui set di istruzioni reali target. Un set di istruzioni basato sullo stack è solitamente la risposta qui per la portabilità ma non sempre ciò che viene scelto.

Come con qualsiasi set di istruzioni, nuovo o vecchio, puoi scegliere di rappresentare le tue istruzioni in qualsiasi forma, fondamentalmente ascii o binario, entrambe possono avere molte implementazioni diverse e comportare lo stesso codice macchina in esecuzione.

Se si pianifica che questa istruzione sia sempre basata su VM e in particolare su un sistema operativo, è possibile, se si sceglie, implementare funzioni di livello superiore come stampa o file i / o nel set di istruzioni . Qualcosa che non si dovrebbe necessariamente fare quando si mira a un set di istruzioni di codice macchina reale implementato nell'hardware, poiché l'hardware stesso ha raramente funzioni così complicate nel set di istruzioni. chiamate di sistema ad altre pile di codice sì, ma non a un'implementazione diretta. L'istruzione virtuale impostata fino a quel set di istruzioni ha qualsiasi cosa tu definisca come il livello di funzionalità di una singola istruzione / operazione. se non si dispone di queste funzioni di stampa di alto livello, i / o, ecc nel set di istruzioni virtuali, sarà necessario implementare qualche altra soluzione come uno spazio di indirizzi virtuale con dispositivi virtuali che devono essere implementati come il set di istruzioni virtuali voglio sapere dettagli hardware reali, che sconfiggono la portabilità e il vantaggio di un compilatore per tutte le piattaforme.

    
risposta data 04.03.2014 - 06:22
fonte

Leggi altre domande sui tag