Seleção de Instruções
Obj: gerar instruções para cada nó:
- Nós de IR implementam uma operação;
- muitas máquinas podem adicionar e ler da memória na mesma instrução;
- Selecção de Instruções escolhe as melhores instruções
para implementar uma IR.
2 | LOAD | r1 <- M[fp+a] |
4 | ADDI | r2 <- r0+4 |
5 | MUL | r2 <- ri ×r2 |
6 | ADD | r1 <- r1 + r2 |
8 | LOAD | r2 <- M[fp+x] |
9 | STORE | M[r1+0] <- r2 |
|
2 | LOAD | r1 <- M[fp+a] |
4 | ADDI | r2 <- r0+4 |
5 | MUL | r2 <- ri ×r2 |
6 | ADD | r1 <- r1 + r2 |
8 | ADDI | r2 <- fp+x |
9 | MOVEM | M[r1] <- M[r2] |
|
2 | ADDI | r1 <- r0+a |
3 | ADD | r1 <- fp+r0 |
4 | LOAD | r1 <- M[r1+0] |
6 | ADDI | r2 <- r0+4 |
7 | MUL | r2 <- ri ×r2 |
8 | ADD | r1 <- r1 + r2 |
9 | LOAD | r1 <- r1 + r2 |
11 | ADDI | r2 <- r0 + x |
12 | LOAD | r2 <- M[r2+0] |
13 | STORE | M[r1+0] <- r2 |
|
O melhor tiling corresponde à sequência de menor custo:
- Menor número de instruções;
- Instruções que levam menor tempo;
- Óptimo: soma o menor custo possível;
- Optimal: duas telhas adjacentes não podem ser
substituidas por telhas de melhor custo;
- Óptimo contém optimal.
- Problema: interacção de instruções.
- ISAs:
- RISC não tem muita flexibilidade;
- CISC tem telhas grandes e muitas opções.
Algoritmo guloso:
- Gerar instruções em order inversa (visita pósfixa);
- Escolher a telha com mais nós;
- Senão, escolha arbitrária;
- Implementação em C:
- munchStm() percorre frases;
- munchExp() percorre expressões;
- Algoritmo termina bem: para cada nó existe pelo menos uma telha.
Maximal Munch não é óptimo:
- Programação Dinâmica encontra óptimo encontrando óptimos para
sub-problemas;
- Cada nó n tem um custo, que é o custo total da árvore
com n por raiz;
- Trabalha bottom-up;
- Para cada nó n:
- Encontra custos dos filhos, netos, recusivamente;
- Aplica todos os padrões contra n;
- O custo da telha t será c + SUMci.
- Faz emissão de instruções:
- para cada folha lf da telha selecionada para n, emite
para li;
- Emite instrução para n.
- CONST 1 é um ADDI de custo 1;
- CONST 1 é um ADDI de custo 1;
- ADD
Telha | Instrução | Nó | Folhas | Total |
| ADD | 1 | 1+1 | 3 |
| ADDI | 1 | 1 | 2 |
| ADDI | 1 | 1 | 2 |
|
Telha | Instrução | Nó | Folhas | Total |
| LOAD | 1 | 2 | 3 |
| LOAD | 1 | 1 | 2 |
| LOAD | 1 | 1 | 2 |
|
Para máquinas com conjuntos complexos de instruções, e muitos tipos
de registos e modos de endereçamento, existe uma generalização:
- Schizo-Jouette tem registos a para endereços, e d para
dados:
- ADD(I), MUL, SUB(I), DIV usam d;
- LOAD, STORE, MOVEM usam a;
- MOVEA faz dj <- ai;
- MOVED faz aj <- di;
- Programação dinâmica deve saber do custo mínimo usando registo
a e registo d;
Implementam este algoritmo:
- cada regra tem um custo e uma acção:
- custos para solução óptima;
- ações para emitir instruções.
- Output é um programa em C;
- Convenientes para máquinas CISC:
- VAX tem 112 regras e 20 não terminais;
- MC68020 141 regras e 35 não terminais.
- instruções com mais de um resultado são dificeis.
- Exemplos: Twig e BURG.
Todas as telhas que unificam têm que ser testadas:
- Examinar todas as telhas;
- Usar um case e comparar pelo tipo do nó;
Complexidade:
- N nós;
- T telhas, K telhas unificam em média, K' número máximo de
nós a examinar por telha, T' padrões por nó;
- RISC: T = 50, K = 2, K'= 4, T'=5;
- Maximum Munch:
- considera N/K nós (não tem que ver não-folhas);
- Para cada nó vê K' nós e testa se optimal T' vezes:
K'+T';
- Total: (K'+T')N/K).
- Programação dinâmica: (K'+T')N.
Na prática são muito rápidos.
Características:
- 32 registos;
- uma classe de registos inteiros;
- aritmética entre registos;
- instruções de 3 endereços: r1 <- r2 OPr3;
- load e store com M[r+c];
- instruções de 32 bits;
- um resultado por instrução.
Características:
- Registos: 6, 8,16;
- classes diferentes de registos inteiros;
- aritmética pode usar memória;
- instruções de 2 endereços: r1 <- r1 OPr2;
- muitos modos de endereçamento;
- instruções de tamanho variável;
- side-effects em instruções.
32 bits:
- 6 registos gerais;
- multiplicação e divisão usam eax;
- operações podem mexer em memória:
- r1 <- r1 OPr2,
- M[r1+c] <- M[r1+c] OPr2,
- r1 <- r1 OPM[r2+c],
- M[r1+c1] NOT <- M[r1+c1] OPM[r2+c2],
- Poucos registos:
- gerar temporários livremente;
- problema do alocador de registos.
- Classes de registos:
- Multiplicação precisa de eax;
- Mover o operando e resultado explicitamente:
mov eax,t1 | | eax | <- t1 |
mul t2 | | eax | <- eax
×t2; |
| | edx | <- lixo |
mov t3,eax | | t3 | <- eax |
|
- Dois endereços:
- Adicionamos instruções extra:
mov t1,t2 | | t1 | <- t2 |
add t1,t3 | | t1 | <- t1 + t3 |
|
- Esperamos que t1 e t2 no mesmo registo.
- mov seria então removido.
- Aritmética pode usar memória:
- Cada TEMP é um "registo", mas pode ser MEM;
- Alternativa, forçar registos:
mov | eax,[ebp-8] | | | |
add | eax,exc | | add | [ebp-8],ecx |
mov | [ebp-8],eax | | | |
|
- Duas sequências demoram o mesmo tempo;
- Esquerda mexe em eax
- Múltiplos modos de endereçamento:
- Não ganham velocidade, codificação + curta, mexem em menos registos;
- usar padrões mais complexos.
- Tamanho variável de instruções não afecta compilador.
- Side effects r2 <- M[r1]; r1 <- r1+4:
- Ignorar auto-incremento;
- Ad-hoc;
- Usar padrões-DAG.
Alocação de registos?
- Alocação de registos pode ser feita ao mesmo tempo;
- Alg. independente de máquina: fazer antes ou depois;
- Antes não se pode ser muito preciso, mas feito às vezes.
Usamos tipo para assembly sem registos:
- OPER: instrução assem, lista de operandos
src, lista de resultados dst, e um jumps;
- LABEL é um ponto de salto: assem mostra como é a label
em assembly, e label com símbolo;
- MOVE: como OPER, mas move dados (sem jumps).
- As_printf escreve em arquivo.
Temp_map
- Tabela cuja chave é Temp_temp e cujo resultado é uma
string;
- Mapeamentos podem ser encadeados:
layer(sigma1,sigma2) tenta
look(sigma1,t) e depois look(sigma2,t);
- Alocador de registos vai usar este mapa para descrever os nomes
dos registos;
- Tb usado para registos especiais (fp, sp) e
debugging.
Esquema independente do tipo de máquina:
- apenas assem diz qual o nome da instrução;
As_oper("LOAD 'd0 <- M['s0+8]",
Temp_TempList(Temp_newtemp(), NULL),
Temp_TempList(T_Temp(F_FP()), NULL),
NULL)
- Obs: saltos condicionais têm várias labels em jumps mas
habitualmente referem apenas uma.
- Todos os argumentos são primeiro colocados nas suas posições
correctas;
- munchArgs coloca os argumentos e retorna lista de
temporários:
- Usados para análise de vida;
- Destinos de call são registos que são destruídos:
caller-save, endereço de retorno e valor de retorno.
- Semelhante para mul no Pentium: destinos são
eax e edx;
- Se frame pointer não é usado:
- usar fp virtual baseado em sp;
- problema é que sp só é conhecido mais tarde.
vitor@cos.ufrj.br