Il design del compilatore impedisce la sovrascrittura del registro

1

Sto provando a scrivere un compilatore per una CPU auto-progettata con un set di istruzioni di accompagnamento. La CPU ha 3 registri, 2 registri di ingresso (B e C) e un registro di uscita (D). Quando ad esempio viene eseguita un'istruzione ADD, la somma di B e C viene calcolata e memorizzata in D.

Sto cercando di scrivere il compilatore con il modello di progettazione dei visitatori: ho un sacco di classi di corsi di lingue come "IfStatement", "Addition", "Integer" e un visitatore "Compiler". Il visitatore guarderebbe ciascun nodo dell'albero e aggiungerà bytecode alla fine dell'elenco di byte. Non riesco a capire come gestire in modo pulito le sostituzioni del registro: durante la valutazione dell'espressione

2*(7+3)

il codice byte generato è

PUTb 2
PUTb 7
PUTc 3
ADD
MOVE D C
MUL

Come puoi vedere il 2 è sovrascritto dal 7. Voglio che il compilatore capisca che può invertire l'ordine in

(7+3)*2

o che può memorizzare un risultato temporaneo è RAM, usando alcune altre istruzioni, questo sarà certamente necessario per espressioni più complesse come

(7-5)*(8+3)

Esiste un modo pulito / orientato agli oggetti per gestirlo? Il pattern Visitor non è appropriato qui? Devo osservare alcune tecniche avanzate come la colorazione della registrazione? Il compilatore sarà scritto in Java, ma non credo che importi davvero.

    
posta Todd Sewell 31.01.2016 - 16:07
fonte

1 risposta

2

Avrai bisogno di un'area di overflow, forse di uno stack, o di un pezzo di memoria.

Avrai anche bisogno di ottenere il codice generato corretto prima di ottimizzarlo, perché l'ottimizzazione del codice non funzionante è praticamente impossibile.

Quindi, dato uno stack, dovresti avere qualcosa del tipo: PUT b,2; Push b; Put b,7; Put c,3; ADD; Pop b; MUL e se usi la memoria, PUT b,2; Store b @1; Put b,7; Put c,3; ADD; Load b @1; MUL . Successivamente, per ottenere l'ottimizzazione, puoi avere un passaggio separato che equilibra l'albero, cambiando l'ordine degli operandi in base al peso semplice.

EDIT:

Si crea una struttura dati che tiene traccia del contenuto dei registri (si potrebbe assumere vuoto all'inizio dell'istruzione). Ogni volta che generi un'istruzione, aggiorni quella struttura dati per marcare ciò che è in essa (ad es. Quale nodo dell'albero e / o quale variabile operando, cioè variabile temporanea o costante, o overflow).

Prima di generare un'operazione, assicurati che le sue fonti siano nei registri, se necessario, e in caso contrario esegui un carico dall'area di overflow, o area variabile, o area costante / immediatamente, o da un altro registro.

(Dal design, non dovresti avere il caso in cui hai bisogno di generare qualcos'altro a questo punto diverso da un carico di qualche tipo, ad esempio se stai per generare il MUL, non dovrai generare un ADD, perché avrebbe dovuto essere già gestito dall'appropriato traversal in visita.)

Ma - prima di eseguire qualsiasi carico di operandi (che sovrascriverà il registro) si genera un negozio in un'area di overflow se c'è traccia di qualcosa. Fai anche lo stesso per il registro target / output (ad esempio MUL). E per tutto il tempo, aggiorna la tua struttura di tracciamento in modo che la prossima istruzione generata abbia lo stato dei registri & tempistiche di overflow al punto corrente nel codice.

    
risposta data 31.01.2016 - 16:59
fonte

Leggi altre domande sui tag