In che modo le VM implementano le chiamate di funzione?

4

Sto leggendo un manuale del compilatore che viene compilato in qualche forma di assemblaggio. Poiché non conosco questo linguaggio di assemblaggio, ho deciso di inventare il mio semplice "linguaggio assembly" e di implementare una "macchina virtuale" di base che eseguirà queste istruzioni.

Attualmente sto pensando a come implementare la funzione di declinazione e chiamata alle funzioni nel linguaggio e nella VM.

Ho avuto la seguente idea, per favore dimmi cosa ne pensi:

Le funzioni di decoro assomigliano a semplici etichette. Alla fine di una funzione c'è un'istruzione end . Il "programma principale" è una funzione autonoma. Ad esempio:

main:
// some logic
CALL funcA
// more logic
END
funcA:
// .. some logic
END

Tuttavia la differenza tra call <function> e goto <label> è questa:

goto imposta semplicemente il 'puntatore di istruzioni' (il registro che contiene il numero della riga successiva da eseguire) sul numero di riga dell'etichetta.

call fa ciò che goto fa, ma prima di saltare spinge il numero di riga corrente (più 1) in uno stack.

Quando viene raggiunto end , la VM apre la parte superiore dello stack e salta a questo numero di riga. Se non c'è nulla nello stack, il programma termina.

Quindi, ad esempio, per il codice sopra questo è ciò che accade:

main: // this is where the VM starts
// some logic
CALL funcA // push onto the stack the number 4 (3+1), and jump to the label funcA
// more logic .. this is where we return to from funcA
END // pop the top of the stack and jump to this line number. nothing -> terminate
funcA:
// .. some logic
END // pop the top of the stack (the number 4), and jump to this line number

Cosa ne pensi di questo approccio?

    
posta Aviv Cohn 28.07.2014 - 19:45
fonte

1 risposta

5

Dipende dalla VM, per essere onesti.

È un po 'più facile scrivere una VM in un linguaggio OO che funzioni su strutture dati mantenute in memoria piuttosto che su qualche forma di bytecode linearizzato. Se si implementa un linguaggio di programmazione giocattolo, si potrebbe decidere di farlo in questo modo, almeno inizialmente (l'ho già fatto prima), nel qual caso la VM è probabilmente solo un metodo "run ()" polimorfico. Molti linguaggi di scripting funzionano in questo modo, poiché il codice verrà generato nello stesso momento in cui viene eseguito, quindi non è necessario mantenere il bytecode per un secondo momento.

Se si compila fino a un bytecode linearizzato (come la compilazione di Java), il modo in cui gestisci le chiamate operative dipenderà dalla lingua. Se non si consente la ricorsione (la maggior parte dei linguaggi di programmazione precedenti non lo erano), è possibile semplicemente in linea le chiamate all'operazione direttamente nel bytecode. Ciò causa codice gonfiabile ma è "sicuro" altrimenti.

Se si compila fino a un bytecode linearizzato, ma si desidera consentire la ricorsione, è necessario emulare uno stack di chiamate. Ogni frame della pila manterrà un riferimento alla posizione nel segmento di codice in cui è stata richiamata quell'operazione (per i frame basati sulle operazioni). Terrà anche copie di tutte le variabili locali a quella cornice. Gli oggetti a cui tali variabili si riferiscono possono essere tenuti sullo stack o in un segmento heap separato, a seconda del design del linguaggio di programmazione e (più probabilmente) del programma di ottimizzazione del codice utilizzato dal compilatore.

    
risposta data 28.07.2014 - 21:46
fonte

Leggi altre domande sui tag