Come gestisce una pila VM con un solo stack?

0

Ultimamente ho fatto molte domande qui sulle VM. Eccone un altro:

Comprendo che spesso le VM basate su stack utilizzano solo uno stack, lo stack delle chiamate, per tutto. Per esempio. è anche usato per la valutazione delle espressioni aritmetiche.

Quello che non capisco è, in che modo questo non complica molto le cose? Mostrerò cosa intendo con un esempio.

Considerare il seguente programma psuedocode:

func main:
    funcA()

func funcA:
    2 + 4 * 8

Questo sarebbe compilato al seguente codice bytec:

main:
call funcA
end
funcA:
push 2
push 4
push 8
mult
add
end

(in questo bytecode, il programma parte da main: . call spinge il contatore del programma sullo stack e salta all'etichetta specificata. Quando viene raggiunto un end , la parte superiore della pila - si presume che sia un numero di linea - viene spuntato e saltiamo lì.)

Quindi vediamo cosa succede qui:

In call funcA , spingiamo il contatore del programma (cioè il numero di riga successivo) nello stack. Quindi saltiamo su funcA .

In funcA viene eseguito un calcolo. Dopo il calcolo, il numero 34 viene lasciato all'inizio dello stack.

Quando raggiungiamo end , facciamo il punto in cima allo stack, assumendo che sia il numero di riga a cui dovremmo tornare  Ma non lo è, il numero della linea da cui tornare è sepolto sotto. Come dovrebbe sapere end di questo?

Per evitare tutto questo casino, possiamo semplicemente avere uno stack di dati e uno stack di chiamate separati, e non mescolare i due.

Quindi la mia domanda è: perché alcune VM (come la JVM) usano uno stack per tutto, e quando lo fanno: come gestiscono situazioni come quella descritta sopra?

    
posta Aviv Cohn 01.08.2014 - 11:31
fonte

1 risposta

4

When we reach end, we pop the top of the stack, assuming it's the line number we should return to But it isn't, the line number to return to is buried underneath. How should end know about this?

Hai compilato il codice sorgente con gli opcode errati. Ci sono due modi per risolvere questo problema:

  1. Inserisci un swap prima di ogni end . Poiché l'indirizzo di ritorno si trova immediatamente al di sotto del valore di ritorno, questo metterà l'indirizzo di ritorno in cima. Dopo che end ha eseguito il rendimento, il valore restituito sarà il valore più alto.

  2. Definisci un'operazione return che richiama due valori fuori pila. Il secondo valore è l'indirizzo di ritorno, il primo valore è il valore di ritorno. Dopo aver aggiornato il puntatore dell'istruzione per puntare all'indirizzo di ritorno, il valore di ritorno viene reimpostato nello stack. Quindi usa questo% genuino,return invece di end .

Il compilatore è responsabile dell'emissione delle istruzioni corrette.

È possibile scrivere una VM usando solo una pila. Tuttavia, questo restringe notevolmente l'espressività. Quando si usano più stack, diventa molto più facile implementare un flusso di controllo più avanzato come coroutine, continuazioni, gestori di errori. In un'architettura a stack multipli, normalmente c'è uno stack di valori e uno stack di chiamate separato che memorizza non solo l'indirizzo di ritorno ma anche il debug dei dati o le azioni di pulizia da eseguire quando viene lasciato l'ambito corrente. Se stai già utilizzando stack multipli, diventa anche più facile usare stack segmentati, una tecnica che evita lo stack overflow. In sostanza, lo stack è in realtà un elenco collegato di stack più piccoli. Se un segmento è pieno e si sovrapporrà, un altro segmento viene assegnato e aggiunto all'elenco.

Per un VM multistrato ben studiato, guarda la macchina SECD , essenzialmente un interprete di calcolo lambda.

    
risposta data 01.08.2014 - 12:18
fonte

Leggi altre domande sui tag