92
Geração de Código Simão Melo de Sousa RELEASE - RELiablE And SEcure Computation Group Computer Science Department University of Beira Interior, Portugal [email protected] http://www.di.ubi.pt/˜desousa/ S. Melo de Sousa (DIUBI) Geração de Código 1 / 92

Geração de Código - Departamento de Informáticadesousa/2012-2013/LFC/codegen.pdf · A geração de código produz um novo programa, executável. S. Melo de Sousa (DIUBI) Geração

  • Upload
    lamdan

  • View
    217

  • Download
    0

Embed Size (px)

Citation preview

Geração de Código

Simão Melo de Sousa

RELEASE - RELiablE And SEcure Computation Group

Computer Science Department

University of Beira Interior, Portugal

[email protected]

http://www.di.ubi.pt/˜desousa/

S. Melo de Sousa (DIUBI) Geração de Código 1 / 92

Este documento é uma tradução adaptada do capítulo "Generation de Code" dasebenta "Cours de Compilation" de Christine Paulin (http://www.lri.fr/˜paulin).

S. Melo de Sousa (DIUBI) Geração de Código 2 / 92

Ponto da situação

As análises léxica e sintáctica permitam construir uma primeira versão daárvore abstracta e eliminar programas incorrectas.

A análise semântica opera recursivamente sobre a árvore de sintaxeabstracta para calcular informações actualizadas (porte, tipos, sobrecarga deoperadores, etc.) que são arquivadas na árvore ou fora. Novos erros sãodetectados nesta fase.

Outras representações como o grafo de fluxo (que não abordamos,infelizmente, aqui) levantarão informações ligadas à execução do programa.

A geração de código produz um novo programa, executável.

S. Melo de Sousa (DIUBI) Geração de Código 3 / 92

Programa para este capítulo

Geração de código para o Assembly MIPS.

Introdução: modelo de execução com base numa pilha

Código intermédio: modelo baseado numa pilhaI InstruçõesI Casos de baseI Compilação das expressões condicionais

Chamadas a procedimentos

Linguagens funcionais (se tivermos tempo)

Linguagens objectos (se tivermos tempo)

S. Melo de Sousa (DIUBI) Geração de Código 4 / 92

Plano

1 IntroduçãoModelo de Execução

2 Geração de código na presença de uma pilha

3 Dados Compostos

4 Compilação de chamadas a funções

S. Melo de Sousa (DIUBI) Geração de Código 5 / 92

Modelo de Execução

O tamanho do programa é conhecido durante a fase de compilação

Uma parte da memória fica atribuída às instruções do programa

um apontador (pc — program counter) aponta para o ponto actual noprograma onde se encontra a execução

o programa executa-se de forma sequencial. Excepto no caso explícito deinstruções de salto, as instruções do programa são executadas de formasequencial, uma a seguir a outra.

S. Melo de Sousa (DIUBI) Geração de Código 6 / 92

Modelo de Execução

Os dados do programa são arquivadas na memória ou nos registos damáquina.

A memória encontra-se organizada em palavra (32 ou 64 bits). É acedidapelo meio de um endereço (inteiro).

Os valores simples são arquivados numa unidade de memória

Os valores complexos são arquivados em unidades de memória consecutivas(estruturas, vectores, etc.), ou então em estruturas encadeadas de unidadesde memória (listas encadeadas de unidades de memória, onde se arquivauma componente do valor por arquivar e aponta-se para o resto da lista, i.e.a próxima unidade que arquiva a componente seguinte, etc.).

Os registos permitam aceder rapidamente aos dados simples

Mas, o número de registos depende da arquitectura alvo e em geral ébastante limitado.

S. Melo de Sousa (DIUBI) Geração de Código 7 / 92

Alocação

Certos dados são explicitamente manipulados pelo programa fonte pelointermédio de variáveis.

Outros são criados pelo próprio compilador:I Valores intermédios num cálculo aritmético não trivialI Variáveis aquando de uma chamada a funçãoI Valores objecto, valores funcionais...

Certos dados têm um tempo de vida/de utilização conhecido na fase decompilação: regras de porte, analise de liveness.

S. Melo de Sousa (DIUBI) Geração de Código 8 / 92

Alocação

As variáveis globais do programa podem ficar alocadas para endereçosfixos, desde que se conheça antecipadamente o tamanho destes.

A gestão das variáveis locais, dos blocos, dos procedimentos ou ainda doarquivo dos valores intermédios podem ser feitos alocando memória na pilha(stack)

Outros dados podem ter um tempo de vida que não é conhecida na fase decompilação. Este é o caso quando se manipulam apontadores (que sãoexpressões cujo valor é um endereço). Estes dados vão ser alocados em geralnuma outra parte da memória, organizada em heap (a estrutura de dadosHeap na wikipedia).

S. Melo de Sousa (DIUBI) Geração de Código 9 / 92

Memória

O espaço reservado na heap deverá ser liberto quando deixa de ser utilizado.

De forma explícita: C, Pascal.

De forma autómatica e imlpícita: a execução de um programa utiliza umprograma geral de recuperação de memória, comunamente designado de GCou Garbage Collector (Caml, Java, etc.)

S. Melo de Sousa (DIUBI) Geração de Código 10 / 92

Esquema organizativo da memória

sp : stack pointergp : global pointerpc : program counter

S. Melo de Sousa (DIUBI) Geração de Código 11 / 92

Código intermedio

Código/programa independente da máquina alvo

factoriza grande parte do trabalho de compilação

torna o compilador mais modular/adaptável/genérico

A escolha de uma linguage intermédia é muito importante

deve ser suficientemente rico para permitir uma fácil codificação dasoperações da linguagem sem criar uma longa sequência de código

deve ser suficientemente limitada (baixo nível) para que a tradução final nãoseja demasiada custosa

S. Melo de Sousa (DIUBI) Geração de Código 12 / 92

Linguagem assembly

numero de registos limitado, mas indispensáveis para os cáculos

Necessidade da salveguarda de determinados em pilha.

Passagem da linguagem de alto nível para o assembly via várias linguagensintermédias

O próprio assembly utiliza pseudo-instruções de mais alto nível do quecódigo máquina e labels simbólicos

S. Melo de Sousa (DIUBI) Geração de Código 13 / 92

Plano

1 Introdução

2 Geração de código na presença de uma pilhaModelo de execução na presença de uma pilhaMIPSExpressões aritméticas simplesVariáveisCondicionais

3 Dados Compostos

4 Compilação de chamadas a funções

S. Melo de Sousa (DIUBI) Geração de Código 14 / 92

Notações

Vamos especificar uma função code que aceita em argumento uma árvore desintaxe abstracta e devolve uma sequência de instrução para a máquina de pilhas.Notação

Se E ::= E1 + E2 é uma regra da gramática da linguagem entãocode(E1 + E2) = . . . code(E1) . . . code(E2)

especifica o valor da função code sobre a árvore de sintaxe abstracta quecorresonde à soma de duas expressões.

Se C1 e C2 representam sequências de instruções então C1|C2 é a notaçãopara a sequencia de instruções resultante da concatenação de C1 com C2.

A notação para a sequência vazia é [].

S. Melo de Sousa (DIUBI) Geração de Código 15 / 92

Máquina de pilhas

Arquivar os valores intermédios na pilha

Trocar os valores entre memória e registos para os cálculos

A pilha pode conter inteiros, flutuantes ou endereços

Certos dados de grande tamanho como cadeias de caracteres são alocadosnum espaço suplementar. Quando for necessário manipular uma cadeia decaracteres, manipular-se-á na pilha o seu endereço.

S. Melo de Sousa (DIUBI) Geração de Código 16 / 92

Arquitectura e assembly MIPS

Máquina alvo para estas aulas.

MIPS na wikipedia

Um bom livro de Arquitectura de computador que introduz a arquitecturaMIPs:Computer Organization & Design: The Hardware/Software Interface,Second Edition. By John Hennessy and David Patterson. Published byMorgan Kaufmann, 1997. ISBN 1-55860-428-6.

uns acetatos completos e pedagógicos sobre o MIPS: (link1)(link2)

SPIM: Simulador MIPSI (link1)I (link2)

S. Melo de Sousa (DIUBI) Geração de Código 17 / 92

MIPS em resumo

li $r0, C $r0 <- Clui $r0, C $r0 <- 2^16*Cmove $r0, $r1 $r0 <- $r1

add $r0, $r1, $r2 $r0 <- $r1 + $r2addi $r0, $r1, C $r0 <- $r1 + Csub $r0, $r1, $r2 $r0 <- $r1 - $r2div $r0, $r1, $r2 $r0 <- $r1 / $r2div $r1, $r2 $lo <- $r1 / $r2, $hi <- $r1 mod $r2mul $r0, $r1, $r2 $r0 <- $r1 * $r2 (sem overflow)neg $r0, $r1 $r0 <- -$r1

slt $r0, $r1, $r2 $r0 <- 1 se $r1 < $r2, $r0 <- 0 senãoslti $r0, $r1, C $r0 <- 1 se $r1 < C, $r0 <- 0 senãosle $r0, $r1, $r2 $r0 <- 1 se $r1 <= $r2, $r0 <- 0 senãoseq $r0, $r1, $r2 $r0 <- 1 se $r1 = $r2, $r0 <- 0 senãosne $r0, $r1, $r2 $r0 <- 1 se $r1 <> $r2, $r0 <- 0 senão

S. Melo de Sousa (DIUBI) Geração de Código 18 / 92

MIPS em resumola $r0, adr $r0 <- adr

lw $r0, adr $r0 <- mem[adr]

sw $r0, adr mem[adr] <- $r0

beq $r0, $r1, label salto se $r0 = $r1beqz $r0, label salto se $r0 = 0bgt $r0, $r1, label salto se $r0 > $r1bgtz $r0, label salto se $r0 > 0

beqzal $r0, label salto se $r0 = 0, $ra <- $pc + 1bgtzal $r0, label salto se $r0 > 0, $ra <- $pc + 1

j label salto para labeljal label salto para label, $ra <- $pc + 1jr $r0 salto para $r0jalr $r0 salto para $r0, $ra <- $pc + 1

S. Melo de Sousa (DIUBI) Geração de Código 19 / 92

MIPS em resumo

Registers Calling ConventionName Number Use Callee must preserve?$zero $0 constant 0 N/A$at $1 assembler temporary No$v0-$v1 $2-$3 values for function

returns and expressionevaluation No

$a0-$a3 $4-$7 function arguments No$t0-$t7 $8-$15 temporaries No$s0-$s7 $16-$23 saved temporaries Yes$t8-$t9 $24-$25 temporaries No$k0-$k1 $26-$27 reserved for OS kernel N/A$gp $28 global pointer Yes$sp $29 stack pointer Yes$fp $30 frame pointer Yes$ra $31 return address N/A

S. Melo de Sousa (DIUBI) Geração de Código 20 / 92

MIPS - syscall - Chamadas ao sistema

Pode depender do simulador utilizado (consultar documentação)

Serviço Código

em $v0

Argumentos Resultados

print_int 1 $a0 = o inteiro por imprimir

print_float 2 $f12 = o float por imprimir

print_double 3 $f12 = o double por imprimir

print_string 4 $a0 = endereço da string por imprimir

read_int 5 $v0 = o inteiro de-

volvido

read_float 6 $f0 = o float devolvido

read_double 7 $f0 = o double de-

volvido

read_string 8 $a0 = endereço da string por ler $a1 =

comprimento da string

sbrk/malloc 9 $a0 = quantidade de memória por alocar endereço em $v0

exit 10 $v0 = o código devolvido

S. Melo de Sousa (DIUBI) Geração de Código 21 / 92

Simão Melo de Sousa

MIPS - syscall - Chamadas ao sistema

#Print out integer value contained in register $t2

li $v0, 1 # load appropriate system call code into register $v0;

# code for printing integer is 1

move $a0, $t2 # move integer to be printed into $a0: $a0 = $t2

syscall # call operating system to perform operation

#Read integer value, store in RAM location with label int_value

#(presumably declared in data section)

li $v0, 5 # load appropriate system call code into register $v0;

# code for reading integer is 5

syscall # call operating system to perform operation

sw $v0, int_value # value read from keyboard returned in register $v0;

# store this in desired location

#Print out string (useful for prompts)

.data

string1 .asciiz "Print this.\n" # declaration for string variable,

# .asciiz directive makes string null terminated

.text

main: li $v0, 4 # load appropriate system call code into register $v0;

# code for printing string is 4

la $a0, string1 # load address of string to be printed into $a0

syscall # call operating system to perform print operation

S. Melo de Sousa (DIUBI) Geração de Código 22 / 92

MIPS - um exemplo : Fib

#------------------------------------------------# fib - recursive Fibonacci function.# http://www.cs.bilkent.edu.tr/~will/courses/# CS224/MIPS%20Programs/fib_a.htm## a0 - holds parameter n# s0 - holds fib(n-1)# v0 - returns result#------------------------------------------------# Code segment

.textfib: sub $sp,$sp,12 # save registers on stack

sw $a0,0($sp)sw $s0,4($sp)sw $ra,8($sp)

bgt $a0,1,notOnemove $v0,$a0 # fib(0)=0, fib(1)=1b fret # if n<=1

S. Melo de Sousa (DIUBI) Geração de Código 23 / 92

MIPS - um exemplo : Fib

notOne: sub $a0,$a0,1 # param = n-1jal fib # compute fib(n-1)move $s0,$v0 # save fib(n-1)

sub $a0,$a0,1 # set param to n-2jal fib # and make recursive calladd $v0,$v0,$s0 # add fib(n-2)

fret: lw $a0,0($sp) # restore registerslw $s0,4($sp)lw $ra,8($sp)add $sp,$sp,12jr $ra

# data segment.data

endl: .asciiz "\n"

S. Melo de Sousa (DIUBI) Geração de Código 24 / 92

Instruções macros para a máquina de pilhas

Um registo $sp (stack pointer) que aponta para a primeira célula livre dapilha

O endereçamento faz-se com base em bytes: uma palavra de 32 bits cabeem 4 bytes.

Funções de arquivo e de carregamento na pilha:

l e t pushr r = sub $sp , $sp , 4 |

sw r , 0 ( $sp )

l e t popr r = lw r , 0( $sp ) |

add $sp , $sp , 4

S. Melo de Sousa (DIUBI) Geração de Código 25 / 92

Esquema de compilação de expressões

O resultado da expressão por compilar está no registo $a0.Os valores intermédios são arquivados na pilhaO código correspondente é:

code ( i n t e i r o ) = l i $a0 i n t e i r o

code (E1 + E2) = code (E1 ) | pushr $a0 |

code (E2 ) | popr $t0 |

add $a0 $t0 $a0

Funciona, mas ... código ineficiente

Podemos melhorar a situação utilizando registos temporários $ti (i = 0 . . . 9)e $si (i = 0 . . . 7)

Pode ser realizado de forma simplista (passando uma pilha de registosdisponíveis como parâmetro) ou pela utilização de analises estáticas muitomais pertinentes mais também mais complexas.

S. Melo de Sousa (DIUBI) Geração de Código 26 / 92

Geração de código

A função de geração de código é recursiva sobre a AST das expressões aritméticas.Invariante: Execução de code(E) num estado em que sp = n

O valor da expressão E é calculada no registo $a0

Os valores de P[m] para m<n não foram modificados.

S. Melo de Sousa (DIUBI) Geração de Código 27 / 92

As variáveis globais e locais

As instruções lw e sw permitam deslocar valores entre um endereço memória eum registo.

sw: atribuir um valor calculado para um registo reservado para as variáveisglobais;

lw carregar num registo um valor contido numa variável global

Notação: se $r é um registo cujo valor é um endereço a na pilha e b é umaconstante inteira, então n($r) designa o endereço a+n.

S. Melo de Sousa (DIUBI) Geração de Código 28 / 92

Código para as variáveis globais

E ::= identUma variável x poderá ser arquivada na zona de dados associada a um labellab(x)

.datalab_x:

.word 0.text

Assim

code(id) = lw $a0 lab(id)

Podemos também localizar uma variável (global, mas também local) x pelo offset

adr(x) relativamente a um apontador global $gp.

code(id) = lw k ($gp) se k=adr(id)

O espaço necessário às variáveis é reservado antes da execução retirando a $sp 4vezes o numero de variáveis por arquivar.

S. Melo de Sousa (DIUBI) Geração de Código 29 / 92

Atribuições

Imaginemos uma linguagem composta de sequências I de atribuições da formaid:=E

code(epsilon) = []code(I1 id:=E;) = code(I1) | code(id:=E)Code(id:=E) = code(E) | sw $a0 lab(id) se id global

code(E) | sw $a0 k($gp) se k=adr(id)

S. Melo de Sousa (DIUBI) Geração de Código 30 / 92

Condicionais

As expressões condicionais e os ciclos usam instruções de saltos para navegarnas instruções

As instruções poderão ser referenciadas por endereços simbólicos inseridosno programa.label: introduz um endereço simbólico labelque corresponde ao endereçoreal da instrução que segue.

salto incondicional: j label (existe também outras variantes como b label)

salto condicional (uma das variantes...) beqz $r label (salto se $r = 0)

Convenção: false = 0, true = qualquer inteiro positivo (1, em particular)

S. Melo de Sousa (DIUBI) Geração de Código 31 / 92

Condicionais e ciclos

code(if E then I)= code(E) | beqz $a0 next| code(I) | next:

code(if E then I1 else I2)= code(E) | beqz $a0 lab1 | code(I1)| j lab2 | lab1: code(I2) | lab2:

code(while E do I done)= lab1: code(E) | beqz $a0 next | code(I)| j lab1 | next:

Estas traduções introduzem novas etiquetas (next, lab1, lab2).

S. Melo de Sousa (DIUBI) Geração de Código 32 / 92

Expressões booleanas

A tradução de expressões booleanas processa-se como para o caso as expressõesaritméticas. Vamos manipular os valores 0 e 1 (i.e. colocar na pilha...) com basena utilização das operações aritméticas que nos permitirão simular operaçõessobre os booleanos.

code(true) = li $a0 1code(false) = li $a0 0code(B1 and B2) = code(B1) | pushr $a0

| code(B2) | popr $t0 | mul $a0, $t0, $a0code(not B1) = code(B1) | neg $a0 | add $a0, $a0, 1code(B1 or B2) = exercício....

S. Melo de Sousa (DIUBI) Geração de Código 33 / 92

Esquema de compilação com condicionais

É comum compilar as expressões booleanas da seguinte forma:I B1 and B2 �! if B1 then B2 else falseI B1 or B2 �! if B1 then true else B2I not B1 �! if B1 then false else true

No entanto, numa linguagem com efeitos laterais, o facto de calcular ou nãoas sub-expressões (aqui B1 ou B2) pode levar a comportamento globaisbem diferentes.

S. Melo de Sousa (DIUBI) Geração de Código 34 / 92

Esquema de compilação com saltos

As expressões booleanas servem frequentemente como operação de controlo.

Sejam E-true e E-false dois labels que usaremos neste contexto. Aexecução de E resulta em pc = E-true quando o valor de E é true ouresulta em pc = E-false no caso contrário.

Definimos assim uma função code-bool que toma em entrada umaexpressão booleana, os dois labels e que devolve o código de controlo

S. Melo de Sousa (DIUBI) Geração de Código 35 / 92

Código gerado

code-bool(true,lab-t,lab-f) = j lab-tcode-bool(false,lab-t,lab-f) = j lab-fcode-bool(not B1,lab-t,lab-f) = code-bool(B1,lab-f,lab-t)code-bool(B1 or B2,lab-t,lab-f) =

code-bool(B1,lab-t,new-l) | new-l: code-bool(B2,lab-t,lab-f)code-bool(B1 and B2,lab-t,lab-f) =

code-bool(B1,new-l,lab-f) | new-l: code-bool(B2,lab-t,lab-f)code-bool(E1 relop E2,lab-t,lab-f) =

code(E1) | pushr $a0 | code(E2) | popr $t0 |code(relop) $a0, $t0, $a0 | beqz $a0 lab-f | j lab-t

new-l: novo label.code(relop) = instrução de comparação para o operador relop (sle, slt, . . .)

S. Melo de Sousa (DIUBI) Geração de Código 36 / 92

Compilação do teste

Evitemos o empilhamento de valores intermédios:

code(if E then I1 else I2) =code-bool(E,new-true,new-false)

| new-true: code(I1) | j new-next| new-false: code(I2) | new-next:

S. Melo de Sousa (DIUBI) Geração de Código 37 / 92

Plano

1 Introdução

2 Geração de código na presença de uma pilha

3 Dados CompostosTipos produtosVectores

4 Compilação de chamadas a funções

S. Melo de Sousa (DIUBI) Geração de Código 38 / 92

Tipos estruturados

Vamos aqui admitir que os vectores suportados são vectores à la Pascal. Ou sejaque estes são declarados com a informação do primeiro índice e do último índice.Relembremos que em C, OCaml ou Java (etc.) um vector é declarado com o seutamanho n

type typ = Tbool | Tint | ... | Tprod of typ*typ| Tarr of int * int * typ

A cada tipo está associada o tamanho dos dados correspondente

let rec size = functionTbool | Tint -> 1

| Tprod (t1,t2) -> size t1 + size t2| Tarr (f,l,t) -> max 0 (l-f+1) * size t

S. Melo de Sousa (DIUBI) Geração de Código 39 / 92

Pares representados por valores em pilha

Juntemos na linguagem de expressões a possibilidade em expressar pares deexpressões, aceder a cada uma das duas componentes do par

E ::= (E1,E2) | fst E | snd E

O valor de uma expressão já não cabe por inteiro num registo

Podemos então arquiva-la na pilha

O valor de um objecto composto (e1,e2) pode serI um bloco formado pelo valor de e1 seguido do valor de e2;I o endereço do local em memória onde começa o arquivo dos dois

valores (endereço que cabe num registo)I é preciso escolher como posicionar as componentes e os endereços

relativamente às componentes elas próprias.

S. Melo de Sousa (DIUBI) Geração de Código 40 / 92

Pares representados por valores em pilha

(1,(2,3))

.datapar:.word 1.word 2.word 3

S. Melo de Sousa (DIUBI) Geração de Código 41 / 92

Cálculo dos valores na pilha

Esquema escolhido:

Reservar espaço num bloco, na pilha

Quando queremos manipular um bloco estruturado, precisamos conhecer:I o endereço da parte inferior do bloco no qual o valor é arquivadoI o tamanho dos dados (conhecida estaticamente ou dinamicamente)I o offset relativo às componentes (no caso das estruturas com vários

campos).

S. Melo de Sousa (DIUBI) Geração de Código 42 / 92

Código para os produtosUma função codep coloca na pilha o valor do resultado

Uma variável fica associada ao seu endereço e ao seu tamanho

codep(E) = code(E) | pushr $a0 (sendo E uma expressãoaritmética)

codep((E1,E2)) = codep(E2) | codep(E1)

codep(snd_(p1,p2)(E)) = codep(E) | add sp,sp,(4*p1)

codep(fst_(p1,p2)(E)) = (copiar os valores certos nosítio certo)

codep(id_n) = la $a1 adr(id) | lw $a0 (4 * (n - 1))($a1)| pushr $a0 | ... | lw $a0 0($a1) | pushr $a0

codep(id_n:= E) = codep(E) | la $a1 adr(id)| popr $a0 | sw $a0 0($a1) | ...| popr $a0 | sw $a0 (4 * (n-1))($a1)

S. Melo de Sousa (DIUBI) Geração de Código 43 / 92

Vectores

O espaço necessário para arquivar um vector depende do tipo dos dadosarquivados neste (inteiros, reais, vectores, estruturas) e da estratégia derepresentação dos dados.

Vamos aqui supor que o tamanho dos dados é sabido em tempo decompilação.

Os vectores são representado por sequências de células adjacentes.

vectores multidimensionais são linearizados (as linhas são concatenadasumas atrás das outras)

Os acessos a vectores: offset calculados na execução via aritmética sobendereços

O endereço por resolver depende de um endereço de base a e de um offset ncalculado em tempo de execução

S. Melo de Sousa (DIUBI) Geração de Código 44 / 92

Código : Expressões sob vectores

Juntamos então os vectores (unidimencionais) a nossa linguagem.

E ::= id[E]I ::= id[E1] := E2

Caso simples: Vector com índices a partir de 0 e de tamanho n de objectos detamanho 1.adr(id) : endereço de base do vector

code(id[E]) = code(E) | la $a1 adr(id)| add $a1, $a1, $a0 | lw $a0, 0($a1)

code(id[E1]:=E2) = code(E1) | la $a1 adr(id) | add $a1, $a1, $a0| pushr $a1 | code(E2) | popr $a1 | sw $a0, 0($a1)

S. Melo de Sousa (DIUBI) Geração de Código 45 / 92

Vectores - Cálculos de índice

Se o vector t tem índices entendidos entre m e M, e começa no endereço ae contém elementos de tamanho k, então este ocupa (M �m + 1)⇥ k

Para calcular o endereço que corresponde a uma expressão t[E], é necessáriocalcular o valor n resultante da expressão E e em seguida aceder ao endereço(a+ (n �m)⇥ k).

Se conhecemos o valor de m em tempo de compilação então podemosparcialmente avaliar esta expressão calculando antecipadamente a�m ⇥ k eguardando este valor na tabela de símbolos base(t).

Bastará assim efectuar a operação base(t) + n ⇥ k

S. Melo de Sousa (DIUBI) Geração de Código 46 / 92

Código - Expressões sob vectores - caso geral

Valores pre-calculados (vector de índice entre m e M)

adr(id) endereço de base do vector

k = size(id) tamanho de um elemento do vector

base(id) = adr(id)�m ⇥ size(id)

O código gerado é

codep(id[E]) = code(E) | mul $a0. $a0, k| la $a1 base(id) | add $a1, $a1, $a0| lw $a0, (k-1)($a1) | pushr $a0 | ...

... | lw $a0, 0($a1) | pushr $a0codep(id[E1] := E2) = code(E1) | mul $a0, $a0, k |

| la $a1 base(id) | add $a1, $a1, $a0| pushr $a1 | codep(E2)| lw $a1, (4 * size(E2))$sp| popr $a0 | sw $a0, 0($a1) ...

... | popr $a0 | sw $a0, (k-1)($a1)| addi $sp, $sp, 4

S. Melo de Sousa (DIUBI) Geração de Código 47 / 92

Em resumo

Manipular valores estruturados obriga à reserva atempada de espaçomemória.

Pode ser feita com base na pilha quando se controla o tempo de vida destesdados (por cause do cálculo de endereço por realizar).

Uma solução mais uniforme pode passar por manipular objectos estruturadoscomo apondadores para a heap.

Para uma programação mais segura, pode juntar-se ao código gerado testesde segurança, como por exemplo a verificação de que os índices estão noâmbito do vector em causa.

S. Melo de Sousa (DIUBI) Geração de Código 48 / 92

Plano

1 Introdução

2 Geração de código na presença de uma pilha

3 Dados Compostos

4 Compilação de chamadas a funçõesExemplo de uma função recursivaParâmetros de funções e procedimentosTabela de activaçãoChamadas de funções e assemblyPassagem por referênciaFunções Recursivas

S. Melo de Sousa (DIUBI) Geração de Código 49 / 92

Introdução

Compilação modular: código para a função utilizando parâmetros formais,código para a chamada à função que instância estes parâmetros (passagemde parâmetros, dos efectivos aos formais).

Várias semânticas possíveis para o modo de ligação/instanciação dosparâmetros (por valor, referência etc.)

Alocação dinâmica de novas variáveis (parâmetro, variáveis locais)

S. Melo de Sousa (DIUBI) Geração de Código 50 / 92

Exemplo

Alocação memória para os parâmetros de uma função recursiva no contexto dechamadas por valor

int j;void p(int i; int j) {if (i+j) {j=j-1; p(i-2,j); p(j,i);}}main () {read(j); p(j,j);}

O número de execução de p depende dos valores passados em entrada.

À cada entrada no procedimento p duas novas variáveis são alocadas.

À saída do procedimento, a memoria alocada é liberta.

S. Melo de Sousa (DIUBI) Geração de Código 51 / 92

Exemplo - execução

S. Melo de Sousa (DIUBI) Geração de Código 52 / 92

Acesso memória e valores

Certas expressões em programas representam endereços memóriaI variáveis introduzidas no programa;I células de um vector;I componentes de uma estrutura;I apontadores.

Nestas localizações em memória são arquivados valores

Certas expressões em programas podem designar estas localizações ou entãoos valores que aí estão arquivados

I o valor esquerdo (ou left-value) de uma expressão designa o local emmemória a partir do qual é arquivado o objecto (utilizado nasatribuições)

I o valor direito (ou right-value) de uma expressão designa o valor doobjecto arquivado em memória no dito local.

A passagem de parâmetro nos procedimentos pode fazer-se transmitindo osvalores esquerdos ou valores direitos.

S. Melo de Sousa (DIUBI) Geração de Código 53 / 92

Variáveis: ambiente e estado

um ambiente liga nomes à endereços memória: é uma função parcial queassocia um endereço a um identificador.

um estado que liga endereços a valores: uma função parcial que associa umvalor a um endereço.

Aquando de uma atribuição de um valor a uma variável, só o estado muda

Aquando da chamada de um procedimento (ou função) com parâmetros ouvariáveis locais, o ambiente muda.A variável introduzida corresponde a uma nova ligação entre nome eendereço memória

S. Melo de Sousa (DIUBI) Geração de Código 54 / 92

Procedimentos e Funções

Um procedimento tem um nome, parâmetros, declarações de variáveis ouprocedimentos locais e um corpo

uma função é um procedimento que devolve um valor

Os parâmetros formais são variáveis locais ao procedimento que serãoinstanciados aquando da chamada ao procedimento pelos parâmetrosefectivos.

O procedimento pode declarar variáveis locais que serão inicializadas nocorpo do procedimento.

S. Melo de Sousa (DIUBI) Geração de Código 55 / 92

Passagem de parâmetro

Os parâmetros formais do procedimento são variáveis que são inicializadosaquando da chamada ao procedimento.

Existem várias formas de realizar tal inicialização.

Admitindo o procedimento p com um parâmetro formal x que é invocadocom o parâmetro efectivo e.

Vamos examinar os diferentes modelos de passagem de parâmetros.

S. Melo de Sousa (DIUBI) Geração de Código 56 / 92

Passagem por valor

Na passagem por valor, x é uma nova variável alocada localmente peloprocedimento e cujo valor é o resultado da avaliação de e.

Após o fim do procedimento, o espaço memória alocado à variável x édevolvido.

as modificações que a variável x sofreu deixam de serem visíveis.

Na ausência de apontadores, as únicas variáveis alteradas são as variáveisnão locais ao procedimento explicitamente referenciado nas instruções doprograma.

É necessário reservar um espaço proporcional ao tamanho do parâmetro, oque pode ter custos elevados, no caso de vectores por exemplo.

S. Melo de Sousa (DIUBI) Geração de Código 57 / 92

passagem por referência ou por endereço

Calcula-se o valor esquerdo de uma expressão e. (se e não tem valor esquerdo,então cria-se uma variável que é inicializada com o valor direito de e e utiliza-se ovalor esquerdo da variável criada)

O procedimento aloca uma variável x que é inicializada pelo valor esquerdode e.Qualquer referência a x no corpo do procedimento é interpretado como umaoperação sobre um objecto cujo endereço está arquivado em x .Este modo de passagem de parâmetro ocupa um espaço memóriaindependente do tamanho do parâmetro (um endereço)

Algumas notas:

Em C, a passagem por referência é explicitamente programada pelapassagem por valor de um apontador (i.e. endereço memória)Em Java, a passagem dos parâmetro é por valor, mas no caso dos objectos,este valor é uma referência (ao dito objecto).em Ada e Pascal, ambas as passagens são possíveis, existam palavrasreservadas que permitam indicar que tipo de passagem se pretende (e.g.var, in ou ainda out)

S. Melo de Sousa (DIUBI) Geração de Código 58 / 92

Passagem por nome

Substituição textual no corpo do procedimento dos parâmetros formais pelosparâmetros efectivos

Este mecanismo pode gerar fenómenos (problemáticos) de captura devariáveis

Um exemplo:

swap (int x; int y) {int z; z = x; x = y; y = z;}

Se z e t são variáveis globais do programa então a chamada swap(z,t) compassagem por nome resulta em

{int z; z = z; z = t; t = z;}

S. Melo de Sousa (DIUBI) Geração de Código 59 / 92

Renomeação

Renomear as variáveis locaisswap (int x; int y) {int zz; zz = x; x = y; y = zz;}

Infelizmente, não é suficiente. Por exemplo a chamada swap(i,a[i])resulta em{int zz; zz = i; i = a[i]; a[i] = zz;}

Método no entanto útil para a compilação de procedimento de tamanhopequeno (o custo da gestão da chamada é importante)

S. Melo de Sousa (DIUBI) Geração de Código 60 / 92

Passagem por Copy-Restore

Aquando da chamada ao procedimento, o valor direito e serve parainicializar a variável x.

À saída do procedimento o valor direito de x serve para actualizar o valoresquerdo de e.

Possíveis diferenças de comportamento com a passagem por referência.

int a;p(int x) {x=2; a=0;}main () {a=1; p(a); write(a);}

Equivalente numa chamada por referência a

{a=1; a=2; a=0; write(a);}

Equivalente numa chamada por copy restore a

{a=1; {int x=a; x=2; a=0; a=x;}; write(a);}

S. Melo de Sousa (DIUBI) Geração de Código 61 / 92

Avaliação preguiçosa/ estrita

Nas linguagens funcionais, distinguimos as linguagens estritas das linguagensditas preguiçosas.

Linguagem estrita: os valores dos argumentos são calculados antes de serempassados como parâmetros a uma função (CAML, SML por exemplo)

Linguagem preguiçosa: a expressão passada em parâmetro para uma funçãof só será avaliada se f precisa deste valor (chamada por necessidade, emHaskell)

avaliação preguiçosa combina dificilmente com efeitos laterais (porque édifícil controlar quando a expressão irá ser avaliada e produzir os efeitoslaterais)

S. Melo de Sousa (DIUBI) Geração de Código 62 / 92

Avaliação preguiçosa

Imaginemos que temos f(x) = t. Pretendemos calcular f(e).

Quando a avaliação de t necessitar do valor de x então o cálculo de e serárealizado.

Mesmo se t requer x várias vezes, o calculo será feito uma so vez.

A expressão e é acompanhada do seu ambiente (os valores das variáveis quee utiliza), o que evita problemas de captura.

Mecanismo diferente do mecanismo de substituição textual (i.e. macros)

S. Melo de Sousa (DIUBI) Geração de Código 63 / 92

Compilação de funções e de procedimentos

Declaração f(x) = e

Chamada f(a) corresponde a let x = a in e

Compilar o código de e fazendo uma assumpção sobre a localização de x

Colocar a no local desejado.

Semântica de x = aI valor de aI código de aI localização em memoria de aI etc.

S. Melo de Sousa (DIUBI) Geração de Código 64 / 92

Compilação de funções e de procedimentos

Não se controla nem a quantidade de chamadas a um procedimento (oufunção), nem o momento onde estas ocorrem.

Não é possível dar estatisticamente os endereços das variáveis que apareçamno corpo do procedimento

Estes endereços poderão ser calculados relativamente ao estado da pilha nomomento da chamada ao procedimento.

O espaço memória alocado para os parâmetros e as variáveis locais édevolvido (free) aquando do fim da execução do procedimento.

S. Melo de Sousa (DIUBI) Geração de Código 65 / 92

Organização da memória

Numa chamada de um procedimento ou função, a memória envolvida éorganizada em tabelas de activação (frame em inglês).

A tabela de activação é uma porção da memória que é alocada aquandoda chamada de um procedimento devolvida no fim desta.

contém todas as informações necessárias a execução do procedimento(parâmetros, variáveis locais)

arquiva os dados que deverão ser devolvidas aquando do retorno (fim daexecução do procedimento)

Para facilitar a comunicação entre código compilado a partir de linguagens fontesdistintas (por exemplo uma função C invocar uma função assembly, etc...), éfrequente que um determinado formato para as tabelas de activação sejarecomendado para uma dada arquitectura.

S. Melo de Sousa (DIUBI) Geração de Código 66 / 92

Dados por guardar

Aquando duma chamada a procedimento

o controlo do código é modificado:Retorno normal do procedimento (sem considerar saltos para processamentode erro ou de instrução de tipo goto): a sequência de execução devecontinuar na instrução que segue a chamada.

O program counter deve assim ser salvaguardado de cada vez que éprocessada uma chamada.

Os dados locais ao procedimento organizam-se na pilha a partir de umendereço para um bloco de activação (designado em inglês de framepointer) que é determinado em tempo de execução e guardado num registoparticular (o registo fp).

Quando um novo procedimento é chamado, este valor muda, pode assim sernecessário arquivar o valor corrente que poderá ser restaurado no final dachamada.

S. Melo de Sousa (DIUBI) Geração de Código 67 / 92

Caller vs Callee

As operações por efectuar aquando de uma chamada de procedimento sãopartilhadas entre quem chama o procedimento (o caller) e que é chamado(o callee).

É preciso decidir se os parâmetros e os valores de retorno são arquivados emregistos ou na pilha

O código gerado pelo caller deve estar escrito para cada chamada enquantoo código por escrever no callee so o é uma única vez.

O caller realiza se necessário a reserva do valor de retorno (no caso de umafunção) e avalia os parâmetros efectivos, coloca-os na pilha ou nos registospensados para esse efeito.

O callee inicializa os seus dados locais e inicia a sua execução

No momento do retorno, o callee coloca, se necessário, o resultado daavaliação no lugar reservado pelo caller e restaura os registos.

O Caller e o callee devem ter uma visão concertada e coerente daorganização da memória

S. Melo de Sousa (DIUBI) Geração de Código 68 / 92

Sub-rotinas

Reutilizar em diferentes locais a mesma sequência de código I

Isola-se esta parte do código e atribuímos-lhe um label

No momento da chamada, é preciso arquivar o ponto de retorno num registodedicado (o registo $ra, a instrução jal).

No fim do código da sub-rotina efectua-se um salto para o ponto guardadono registo $ra.

Se o corpo duma sub-rotina chama outra sub-rotina, é preciso então cuidardo valor actual do registo $ra e arquivá-lo (preservá-lo para uso futuro).

S. Melo de Sousa (DIUBI) Geração de Código 69 / 92

Sub-rotina - Código

Junta-se à linguagem alvo definições e chamadas a sub-rotinasD ::= procP; begin I endI ::= call p label-p é um label único (“fresco”) associado ao

procedimento p

code (proc p; begin I end) = label-p: pushr $ra| code(I) | popr $ra | jr $ra

code (call p) = jal label-p

S. Melo de Sousa (DIUBI) Geração de Código 70 / 92

procedimentos com parâmetros

Se o procedimento tem parâmetros então esta aloca na pilha o espaço paraaí guardar as variáveis

um registo fp pode então ser posto a apontar para os valores locais, noinício da chamada ao procedimento.

À saída do procedimento o espaço é devolvido.

S. Melo de Sousa (DIUBI) Geração de Código 71 / 92

Convenção para as chamadas (MIPS)

os 4 primeiros argumentos podem ser guardados nos registos $a0, $a1, $a2e $a3. Os restantes ficam na pilha.

os registos $v0 e $v1 são utilizados para o retorno da função

o registo $ra é utilizado para passar o endereço de retorno da função.

Os registos $ti e $si podem ser utilizados para os cálculos temporários.

Um número qualquer de chamadas a funções pode estar activo emsimultâneo: os valores dos registos devem então estar guardados parautilizações futuras. É sempre necessário guardar $ra (endereço de retorno dafunção) e $fp (endereço da tabela de activação) caso este seja utilizado.

Certos registos ($ti ) são, por convenção, guardados pelo caller(caller-save), outros pelo callee (callee-save). Os registos $si assim como$ra e $fp são salvaguardados pelo callee.

S. Melo de Sousa (DIUBI) Geração de Código 72 / 92

Organização da tabela de activação

A tabela de activação contém os parâmetros da função (excepto,eventualmente, os 4 primeiros que podem ser colocados nos registos $a0,$a1, $a2 e $a3), as variáveis locais,. Or registos $ra e $fp.

A instrução jal label realiza um salto para o código situado no endereçolabel e preserva (arquiva) o endereço de retorno no registo $ra.

A instrução jr $ra permite voltar para a instrução que segue a chamada,após o restauro dos registos necessários.

O registo frame pointer ($fp) fica posicionado para um local fixo dentro databela. Este permite aceder aos doados via o offset fixo eindependentemente do estado da pilha.

S. Melo de Sousa (DIUBI) Geração de Código 73 / 92

Tabela de activação

Parâmetros da função: e1 . . . enVariáveis locais ou registos por salvaguardar: v1 . . . vm

S. Melo de Sousa (DIUBI) Geração de Código 74 / 92

Protocolo de chamadas

callerSalvaguarda os registos dos quais tem responsabilidade e de que precisará aseguir à chamada

avalia os elementos e1 . . . en nos registos e/ou na pilha.

Salta para a instrução correspondente à etiqueta label da função semesquecer, antes, de arquivar o ponto de retorno dentro de $ra (instrução jallabel).

Restaura os registos salvaguardados

pop dos argumentos previamente empilhados.

S. Melo de Sousa (DIUBI) Geração de Código 75 / 92

Protocolo de chamadas

calleeReserva o espaço em pilha necessário para o procedimento: os valores de v

1

, . . . ,vm são utilizados para os registos por salvaguardar ou para variáveis locais.

Salvaguardar o valor do registo $fp do caller.

Salvaguardar o seu próprio valor de retorno (porque o registo $ra pode ficar

alterado por uma chamada interna).

Posiciona o registo $fp na tabela de activação.

Salvaguardar eventuais registos adicionais do qual o callee é responsável.

Executar as instruções do corpo da função/procedimento. Colocar o valor de

retorno no registo $v0 ou no local previsto na pilha

Restaura o valor de $ra e os outros registos do qual é responsável.

Restaura o registo $fp do caller.

pop de todo o espaço alocado para a tabela de activação.

Salta para a instrução cujo endereço está em $ra com a ajuda da instrução jr.

S. Melo de Sousa (DIUBI) Geração de Código 76 / 92

Exemplo

Bem definir a tabela de activação; seguir escrupulosamente o protocoloexposto...

let rec fact n = if n <= 0 then 1 else n * fact (n - 1)

Compilação, de forma informal:

O valor de $a0 deve ficar salvaguardado e restituído.S. Melo de Sousa (DIUBI) Geração de Código 77 / 92

Tabela de activação de fact

O argumento é passado para o registo $a0 e o valor de retorno em $v0.

S. Melo de Sousa (DIUBI) Geração de Código 78 / 92

Código MIPS associado

$a0 contém n (salvaguardado).O valor de retorno está em $v0

S. Melo de Sousa (DIUBI) Geração de Código 79 / 92

Sintaxe da linguagem com função

À la C:

Vs ::= Vs V; | ✏V ::= T idD ::= id(Vs) {Vs l}

| T id (Vs) {Vs I return E; }

S. Melo de Sousa (DIUBI) Geração de Código 80 / 92

Organização de uma tabela de activação

Para cada função ou procedimento f, podemos calcular estaticamente:

nreturn(f) : tamanho do valor de retorno

nparams(f) : tamanho dos parâmetros

nvars(f): tamanho das variáveis locais

Para cada variável x, arquivamos:

offset(x): inteiro representando a posição relativa $fp onde é arquivada avariável:

I os parâmetros são endereços maiores do que $fp (offset positivo);I as variáveis locais são endereços menores do que $fp (offset negativo).

size(x): se as variáveis podem ter um tamanho maior do que 1.

Modo de passagem de x se existe também a possibilidade de uma passagempor referência

S. Melo de Sousa (DIUBI) Geração de Código 81 / 92

Esquema geral de uma chamada de função (por valor)

code(f (e1 , . . . , en )) =code(e_1) [ | pushr $a0]| code(e_2) | [pushr $a0 ou move $a_1,$a_0]...| code(e_n) | pushr $a0

... salvaguarda os registos caller-saved| jal f

... restituí os registos caller-saved| addiu $sp,$sp,4*nparams(f)

S. Melo de Sousa (DIUBI) Geração de Código 82 / 92

Declaração de uma função

code(T f (T1 x1 ; . . . Tn xn ){U1 z1 ; . . . Up zp I return E}) =pushr $fp |pushr $ra |move $fp,$sp |addiu $sp,$sp,-4 * nvars(f) |

salvaguarda os registos callee-savedcode(I) |code(E) |move $v 0,$a0 |

restaura os registos callee-savedaddiu $sp,$sp,4 * nvars(f) |popr $ra |popr $fp |jr $ra

S. Melo de Sousa (DIUBI) Geração de Código 83 / 92

Passagem por referência

No caso onde uma variável é passada por referência, é o seu endereço que éarquivado na tabela de activação (ou nos registos).

As funções de acesso e de actualização deverão tratar sempre da indirecçãosubjacente

S. Melo de Sousa (DIUBI) Geração de Código 84 / 92

Exemplo

f (ref int x; int y;) {y:=x+y; x:=x*y; }int u=3;main(){f(u,u);print(u)}

Organização da memória:

Resultado (se tiver)

Função f:

a variável x pode ser passada para o registo $a0 e y para o registo $a1.

Os registos $fp e $ra não serão apagados

Função main:I O registo $ra deve ser salvaguardado (chamada de f).

S. Melo de Sousa (DIUBI) Geração de Código 85 / 92

Exemplo MIPS

f (ref int x; int y;) {y:=x+y; x:=x*y; }main(){f(u,u);print(u)}

.datau: .word 3

.textf: lw $a2,0($a0)

add $a1,$a2,$a1mul $a2,$a2,$a1sw $a2, 0($a0)jr $ra

main:add $sp,$sp,-4sw $ra,0($sp)la $a0,ulw $a1,ujal flw $a0,uli $v0,1syscalllw $ra,0($sp)add $sp,$sp,4jr $ra

S. Melo de Sousa (DIUBI) Geração de Código 86 / 92

Cálculo do valor esquerdo de uma expressão

O valor esquerdo: endereço onde é arquivada a expressão.

Só algumas expressões podem ser valores esquerdos: aqui, variáveis evectores.

codeg($r ,e) coloca o valor esquerdo de e no registo $r.

codeg($r , x) = la $r , adr(x) if not islocal(x)codeg($r , x) = add $r , $fp, offset(x) if islocal(x)codeg($r , x[E]) = code(E) | pushr $a0

| codeg($r , x) | popr $a0 | add $r , $r , $a0

Se uma variável deve ser passada por referência, é necessário alocar na pilha enão em registo

S. Melo de Sousa (DIUBI) Geração de Código 87 / 92

Código para as expressões

code(x) = lw $a0, adr(x) se not islocal(x)code(x) = lw $a0, decal(x)($fp) se islocal(x), por valorcode(x) = lw $a0, decal(x)($fp) se islocal(x), por referência

| lw $a0, 0($a0)

Código para o caller f(e1, . . . , en)Substituir code(ei) por codeg(ei) se o i-ésimo argumento é passado porreferência.

S. Melo de Sousa (DIUBI) Geração de Código 88 / 92

Funções Recursivas

Cada chamada de função cria novas variáveis

No caso das funções (mutuamente) recursivas,I Os registos são insuficientes para arquivar as variáveisI A tabela de activação deve ser alocado na pilhaI varias tabelas de activação da mesma função co-existem em simultâneo

O número de tabelas de activação na pilha depende dos valores dosparâmetros e logo não é conhecido em tempo de compilação.

A recursão arquiva implicitamente valores intermédios e pode simplificar aprogramação (por exemplo backtracking).

A recursão, dita terminal (tail recursive), é um caso particular que podeser compilada de forma eficaz.

S. Melo de Sousa (DIUBI) Geração de Código 89 / 92

Exemplo

let rec hanoi i j k n =

if n > 0 then begin

hanoi i k j (n-1);

Printf.printf "%d->%d \n" i k;

hanoi j i k (n-1)

end

Limites:

let rec fact n = if n <= 0 then 1 else n * fact (n-1)

# let _ = fact 1000000;;

Stack overflow during evaluation (looping recursion?).

Versão recursiva terminal

let rec factt k n =

if n <= 0 then k else factt (n * k) (n - 1)

let fact = factt 1

S. Melo de Sousa (DIUBI) Geração de Código 90 / 92

Recursão terminal

Supomos que a função f(x,y) faz uma chamada a f(t,u)

A chamada é terminal se não há calculo em espera na altura da chamadarecursiva:

os valores de x e y não serão reutilizados após o cálculo de f(t,u).

A tabela de activação e os registos da chamada de f(x,y).

É preciso ter um cuidado particular no momento da atribuição (x,y) (t,u), salvaguardar o valor de x se necessário.

A chamada recursiva no corpo transforma-se assim num simples salto

Uma função recursiva terminal é assim compilada tão eficazmente quantoum ciclo.

S. Melo de Sousa (DIUBI) Geração de Código 91 / 92

Exemplo - factt

O argumento k está no registo $a0, e n no registo $a1.

O valor de retorno está no registo $v0

O registo $ra não precisa de ser salvaguardado internamente (já que não háchamadas internas).

fact:blez $a1 outmul $a0,$a0,$a1addi $a1,$a1,-1j fact

out: move $v0,$a0jr $ra

S. Melo de Sousa (DIUBI) Geração de Código 92 / 92