Compiladores (CC3001)
Aula 11: Implementação de código intermédio
Pedro VasconcelosDCC/FCUP
Esta aula
Geração de código intermédio Implementação em C
Re-lembrar: fases dum compilador
texto do programa ↓ Análise lexical ↓ sequência de tokens ↓ Análise sintática ↓árvore sintática abstrata ↓
Análise semântica ↓
AST & tabela de símbolos
Geração de código ↓
código intermédio
Seleção de instruções ↓
código assembly simbólico ↓
Alocação de registos ↓
código assembly concreto ↓
Assembler & linker ↓
Geração de código intermédio
I Na aula passada: esquemas de tradução de uma linguagem imperativa para código intermédio de 3 endereços
I Nesta aula: vamos ver algumas formas para implementar esses esquemas de tradução em C e Haskell
Representação de instruções em C
#include <stdint.h>typedef struct { Opcode opcode;
Addr arg1, arg2, arg3, ...; // argumentos } Instr;
typedef enum { MOVE, MOVEI, ... } Opcode; // código da instrução typedef intptr_t Addr; // endereço (inteiro ou apontador)
I Estruturas com número xo de campos
I Denimos um código distinto para cada tipo de instrução (MOVE, MOVEI, etc.) I A maioria da instruções usa 2 ou 3 argumentos (exceção: COND)
Temporários e etiquetas
I Representar variáveis e etiquetas temporárias por inteiros
I Usamos contadores globais para gerar novos temporários e etiquetas: int temp_count = 0, label_count = 0;
int newTemp() { return temp_count ++; } int newLabel () { return label_count ++; }
Geração de instruções
I Em vez de listas podemos gerar um vector de instruçõesglobal
I Funções auxiliares para acrescentar uma nova instrução ao nal (marcado pelo instruction pointer)
I Intercalando emit com as chamadas recursivas podemos evitar a necessidade de concatenações de listas void emit2(opc,arg1,arg2); void emit3(opc,arg1,arg2,arg3); ... instr 1 instr 2 ... instr n IP → =⇒ instr 1 instr 2 ... instr n instr n+1 IP →
Geração de instruções (cont.)
void transExp(Exp exp, dest) {switch(...) { case BINOP:
t1 = newTemp(); t2 = newTemp();
transExp(exp->binop.left, t1); // lado esquerdo transExp(exp->binop.right, t2); // lado direito
emit3(opcode(exp->binop.op), dest, t1, t2); // instrução final break;
} }
Implementação em Haskell
I Representar instruções como um tipo algébrico I As funções de tradução retornam listas de instruções I Geração de variáveis temporárias e etiquetas
I passar explicitamente contadores; ou
Representação de instruções
data Instr = MOVE Temp Temp - temp1 := temp2
| MOVEI Temp Int - temp1 := num
| OP BinOp Temp Temp Temp - temp1 := temp2 op temp3 | OPI BinOp Temp Temp Int - temp1 := temp2 op num | LABEL Label
| JUMP Label
| COND Temp RelOp Temp Label Label type Temp = String
type Label = String
- operadores da linguagem data BinOp = Plus | Minus | ... data RelOp = Lt | Lteq | Eq | ...
Geração de variáveis e etiquetas
type Count = (Int, Int) - contadores de temporários e etiquetas newTemp :: Count -> (Temp, Count)
newTemp (temps, labels) = ("t"++show temps, (temps+1,labels)) newLabel :: Count -> (Label, Count)
Exemplo de tradução
Com passagem explicita dos contadores.
transExpr :: Expr -> Table -> Temp -> Count -> ([Instr], Count) transExpr (Num n) table dest count
= ([MOVEI dest n], count)
transExpr (Op op e1 e2) table dest count0 = let (t1, count1) = newTemp count0
(t2, count2) = newTemp count1
(code1, count3) = transExpr e1 table t1 count2 (code2, count4) = transExpr e2 table t2 count3 code = code1 ++ code2 ++ [OP op dest t1 t2] in (code, count4)
Passagem implícita dos contadores
I Passagem explícita étrabalhosae dada a erros
I usar count0 para obter count1, count1 para obter count2, . . .
I em caso de engano vamos re-utilizar temporários e gerar código errado I Podemos modicar o programa para que apassagem seja implicita
Passagem implícita dos contadores (cont.)
I Importamos Control.Monad.StateI Alteramos o tipo da função
transExpr :: Expr -> Table -> Temp -> Count -> ([Instr], Count)
para
transExpr :: Expr -> Table -> Temp -> State Count [Instr]
I O tipo State Count signica que a função tem:
I um argumento implícito (o contador inicial)
Passagem implícita dos contadores (cont.)
Casos terminais: removemos os contadores e introduzimos return. - versão explícita
transExpr (Num n) table dest count = ([MOVEI dest n], count)
- versão implícita
Passagem implícita dos contadores (cont.)
Casos recursivos: removemos os contadores e usamos notação-do em vez de let. - versão explícita
transExpr (Op op e1 e2) table dest count0
= let (t1, count1) = newTemp count0
(t2, count2) = newTemp count1
(code1, count3) = transExpr e1 table t1 count2
(code2, count4) = transExpr e2 table t2 count3
in (code1 ++ code2 ++ [OP op dest t1 t2], count4) - versão implícita
transExpr (Op op e1 e2) table dest = do t1 <- newTemp
t2 <- newTemp
code1 <- transExpr e1 table t1 code2 <- transExpr e2 table t2
Passagem implícita dos contadores (cont.)
Para executar a tradução usamosrunStatecom os valores iniciais dos contadores. > let expr = Op Mult (Num 3) (Op Plus (Num 4) (Num 5))
> runState (transExpr expr Map.empty "x") (0,0) ([MOVEI "t0" 3, MOVEI "t2" 4, MOVEI "t3" 5,
OP Plus "t1" "t2" "t3", OP Mult "x" "t0" "t1"], (4,0)) Se não queremos o valor nal dos contadores podemos usarevalState. > evalState (transExpr expr Map.empty "x") (0,0)
[MOVEI "t0" 3, "MOVEI "t2" 4, MOVEI "t3" 5, OP Plus "t1" "t2" "t3", OP Mult "x" "t0" "t1"]
Passagem implícita dos contadores (cont.)
Temos ainda de mudar ligeiramente as funções auxiliares. type Count = (Int,Int)
newTemp :: State Count Temp
newTemp = do (temps,labels)<-get put (temps+1,labels) return ("t"++show temps) newLabel :: State Count Label
newLabel = do (temps,labels)<-get put (temps,labels+1) return ("L"++show labels)