Ho provato ad implementare gli operatori * e & in in questo modo:
exp: ...
MUL_OP IDENTIFIER {
t_axe_register val = getNewRegister(program);
t_axe_label id_label = getLabelFromVariableID(program, $2);
gen_load_instruction(program, val, id_label, 0);
$$.value = val;
} |
AND_OP IDENTIFIER {
t_axe_register val = getNewRegister(program);
t_axe_label id_label = getLabelFromVariableID(program, $2);
gen_mova_instruction(program, val, id_label, 0);
$$.value = val;
}
La soluzione proposta è corretta?
Ni. La soluzione proposta contiene un grosso errore e delle inesattezze.
Per prima cosa analizziamo l’implementazione dell’operatore &.
Essa è giusta, infatti viene generata una MOVA per caricare l’indirizzo di una cella di memoria in un registro.
Contiene un paio di inesattezze:
la regola genera delle ambiguità.
Infatti, come deve essere interpretata l’espressione 4 & a?
Sappiamo che essa è un and bit-a-bit, ma il parser potrebbe riconoscerla anche come un costante seguita dall’operatore &.
Il problema nasce dal fatto che il simbolo & è utilizzato anche per descrivere due operatori diversi.
Per ovviare al problema, bisogna forzare la precedenza della regola:
%token ANDAND OROR
%token ADDR_OP
...
%right NOT_OP ADDR_OP
...
AND_OP IDENTIFIER
%prec ADDR_OP { ... }
In questo modo la regola assume la precedenza del token ADDR_op, un token fasullo che serve solo per dettare la precedenza della regola
con l’istruzione $$.value = val; si vuole scrivere nella variabile $$.
Tale variabile, però, contiene anche altri campi che non vengono inizializzati.
Il modo corretto di create una nuova espressione è utilizzare la funzione create_expression:
$$ = create_expression(val, REGISTER);
In questo modo tutti i campi vengono inizializzati correttamente
Questi due errori sono stati commessi anche nell’implementazione dell’operatore *.
L’errore grosso è contenuto all’interno dell’implementazione dell’operatore *.
Esso permette di effettuare un accesso indiretto alla memoria, mentre nella soluzione proposta si va ad effettuare un accesso diretto.
Ne consegue che l’espressione *a invece di ritornare il valore contenuto nella cella di memoria puntata da a, ritorna il valore contenuto nella cella di memoria contente a, cioè il contenuto di a.
Il modo più semplice per implementare l’operatore * è generare una LOAD che sia a conoscenza del fatto che l’indirizzo da caricare deve essere utilizzato per un accesso indiretto.
Tale operazione non è implementata in ACSE, tuttavia tutte le operazioni ternarie hanno la possibilità di avere il registro destinazione e/o il secondo registro sorgente indirizzato indirettamente, perciò si può “simulare” la load indiretta con una ADD:
int id_reg = get_symbol_location(program, $2, 0);
gen_add_instruction(program,
dest_reg,
REG_0,
id_reg,
CG_INDIRECT_SOURCE);
Il parametro flags viene settato a CG_INDIRECT_SOURCE per forzare ad indirizzare indirettamente il secondo parametro sorgente.
Il valore di tale parametro, il registro nella quale è contenuta a in questo caso, viene utilizzato come indirizzo per effettuare una LOAD dalla memoria.
Siccome non si vuole modificare in alcun modo tale valore, il primo parametro della ADD è il registro R0.
La patch per ACSE è disponibile qui.