Geração de Código

Propaganda
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" da
sebenta "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 sintaxe
abstracta para calcular informações actualizadas (porte, tipos, sobrecarga de
operadores, etc.) que são arquivadas na árvore ou fora. Novos erros são
detectados 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 pilha
I
I
I
Instruções
Casos de base
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ção
Modelo 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 no
programa onde se encontra a execução
o programa executa-se de forma sequencial. Excepto no caso explícito de
instruções de salto, as instruções do programa são executadas de forma
sequencial, 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 da
máquina.
A memória encontra-se organizada em palavra (32 ou 64 bits). É acedida
pelo 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 unidades
de memória (listas encadeadas de unidades de memória, onde se arquiva
uma 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 pelo
intermédio de variáveis.
Outros são criados pelo próprio compilador:
I
I
I
Valores intermédios num cálculo aritmético não trivial
Variáveis aquando de uma chamada a função
Valores objecto, valores funcionais...
Certos dados têm um tempo de vida/de utilização conhecido na fase de
compilaçã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ços
fixos, desde que se conheça antecipadamente o tamanho destes.
A gestão das variáveis locais, dos blocos, dos procedimentos ou ainda do
arquivo 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 de
compilação. Este é o caso quando se manipulam apontadores (que são
expressões cujo valor é um endereço). Estes dados vão ser alocados em geral
numa outra parte da memória, organizada em heap (a estrutura de dados
Heap 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 um
programa geral de recuperação de memória, comunamente designado de GC
ou Garbage Collector (Caml, Java, etc.)
S. Melo de Sousa (DIUBI)
Geração de Código
10 / 92
Esquema organizativo da memória
sp : stack pointer
gp : global pointer
pc : 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 das
operaçõ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ão
seja 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 linguagens
intermédias
O próprio assembly utiliza pseudo-instruções de mais alto nível do que
có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 pilha
Modelo de execução na presença de uma pilha
MIPS
Expressões aritméticas simples
Variáveis
Condicionais
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 de
sintaxe 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ão
code(E1 + E2 ) = . . . code(E1 ) . . . code(E2 )
especifica o valor da função code sobre a árvore de sintaxe abstracta que
corresonde à soma de duas expressões.
Se C1 e C2 representam sequências de instruções então C1 |C2 é a notação
para 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 alocados
num espaço suplementar. Quando for necessário manipular uma cadeia de
caracteres, 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 arquitectura
MIPs:
Computer Organization & Design: The Hardware/Software Interface,
Second Edition. By John Hennessy and David Patterson. Published by
Morgan Kaufmann, 1997. ISBN 1-55860-428-6.
uns acetatos completos e pedagógicos sobre o MIPS: (link1)
(link2)
SPIM: Simulador MIPS
I
I
(link1)
(link2)
S. Melo de Sousa (DIUBI)
Geração de Código
17 / 92
MIPS em resumo
li
$r0, C
lui $r0, C
move $r0, $r1
$r0 <- C
$r0 <- 2^16*C
$r0 <- $r1
add
addi
sub
div
div
mul
neg
$r0,
$r0,
$r0,
$r0,
$r1,
$r0,
$r0,
$r1,
$r1,
$r1,
$r1,
$r2
$r1,
$r1
$r2
C
$r2
$r2
slt
slti
sle
seq
sne
$r0,
$r0,
$r0,
$r0,
$r0,
$r1,
$r1,
$r1,
$r1,
$r1,
$r2
C
$r2
$r2
$r2
$r2
S. Melo de Sousa (DIUBI)
$r0
$r0
$r0
$r0
$lo
$r0
$r0
<<<<<<<-
$r1 +
$r1 +
$r1 $r1 /
$r1 /
$r1 *
-$r1
$r0
$r0
$r0
$r0
$r0
<<<<<-
1
1
1
1
1
se
se
se
se
se
Geração de Código
$r2
C
$r2
$r2
$r2, $hi <- $r1 mod $r2
$r2 (sem overflow)
$r1
$r1
$r1
$r1
$r1
<
<
<=
=
<>
$r2,
C,
$r2,
$r2,
$r2,
$r0
$r0
$r0
$r0
$r0
<<<<<-
0
0
0
0
0
senão
senão
senão
senão
senão
18 / 92
MIPS em resumo
la
$r0, adr
$r0 <- adr
lw
$r0, adr
$r0 <- mem[adr]
sw
$r0, adr
mem[adr] <- $r0
beq
beqz
bgt
bgtz
$r0,
$r0,
$r0,
$r0,
salto
salto
salto
salto
$r1, label
label
$r1, label
label
se
se
se
se
$r0
$r0
$r0
$r0
=
=
>
>
$r1
0
$r1
0
beqzal $r0, label
bgtzal $r0, label
salto se $r0 = 0, $ra <- $pc + 1
salto se $r0 > 0, $ra <- $pc + 1
j
jal
jr
jalr
salto
salto
salto
salto
label
label
$r0
$r0
S. Melo de Sousa (DIUBI)
Geração de Código
para
para
para
para
label
label, $ra <- $pc + 1
$r0
$r0, $ra <- $pc + 1
19 / 92
MIPS em resumo
Registers Calling Convention
Name
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 expression
evaluation
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
print_int
print_float
print_double
print_string
read_int
Código
em $v0
1
2
3
4
5
read_float
read_double
6
7
read_string
8
sbrk/malloc
exit
9
10
S. Melo de Sousa (DIUBI)
Argumentos
Resultados
$a0 = o inteiro por imprimir
$f12 = o float por imprimir
$f12 = o double por imprimir
$a0 = endereço da string por imprimir
$v0 = o inteiro devolvido
$f0 = o float devolvido
$f0 = o double devolvido
$a0 = endereço da string por ler $a1 =
comprimento da string
$a0 = quantidade de memória por alocar
$v0 = o código devolvido
Geração de Código
endereço em $v0
21 / 92
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
.text
fib:
sub $sp,$sp,12
# save registers on stack
sw $a0,0($sp)
sw $s0,4($sp)
sw $ra,8($sp)
bgt $a0,1,notOne
move $v0,$a0
b fret
S. Melo de Sousa (DIUBI)
# fib(0)=0, fib(1)=1
# if n<=1
Geração de Código
23 / 92
MIPS - um exemplo : Fib
notOne: sub $a0,$a0,1
jal fib
move $s0,$v0
fret:
# param = n-1
# compute fib(n-1)
# save fib(n-1)
sub $a0,$a0,1
jal fib
add $v0,$v0,$s0
# set param to n-2
# and make recursive call
# add fib(n-2)
lw $a0,0($sp)
lw $s0,4($sp)
lw $ra,8($sp)
add $sp,$sp,12
jr $ra
# restore registers
# 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 da
pilha
O endereçamento faz-se com base em bytes: uma palavra de 32 bits cabe
em 4 bytes.
Funções de arquivo e de carregamento na pilha:
l e t p u s h r r = sub $sp , $sp , 4
sw r , 0 ( $ s p )
|
l e t p o p r r = lw r , 0 ( $ s p )
|
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 pilha
O código correspondente é:
code ( i n t e i r o )
c o d e ( E1 + E2 )
=
=
l i $a0 i n t e i r o
c o d e ( E1 ) | p u s h r $a0 |
c o d e ( E2 ) | p o p r $ t 0 |
add $a0 $ t 0 $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 registos
disponíveis como parâmetro) ou pela utilização de analises estáticas muito
mais 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 e
um registo.
sw: atribuir um valor calculado para um registo reservado para as variáveis
globais;
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 é uma
constante 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 ::= ident
Uma variável x poderá ser arquivada na zona de dados associada a um label
lab(x)
.data
lab_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 4
vezes 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 forma
id:=E
code(epsilon)
= []
code(I1 id:=E;) = code(I1) | code(id:=E)
Code(id:=E)
= code(E) | sw $a0 lab(id)
code(E) | sw $a0 k($gp)
S. Melo de Sousa (DIUBI)
Geração de Código
se id global
se k=adr(id)
30 / 92
Condicionais
As expressões condicionais e os ciclos usam instruções de saltos para navegar
nas instruções
As instruções poderão ser referenciadas por endereços simbólicos inseridos
no programa.
label: introduz um endereço simbólico labelque corresponde ao endereço
real 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ões
aritméticas. Vamos manipular os valores 0 e 1 (i.e. colocar na pilha...) com base
na utilização das operações aritméticas que nos permitirão simular operações
sobre os booleanos.
code(true)
= li $a0 1
code(false)
= li $a0 0
code(B1 and B2) = code(B1) | pushr $a0
| code(B2) | popr $t0 | mul $a0, $t0, $a0
code(not B1)
= code(B1) | neg $a0 | add $a0, $a0, 1
code(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
I
I
B1 and B2 ! if B1 then B2 else false
B1 or B2 ! if B1 then true else B2
not B1 ! if B1 then false else true
No entanto, numa linguagem com efeitos laterais, o facto de calcular ou não
as sub-expressões (aqui B1 ou B2) pode levar a comportamento globais
bem 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. A
execução de E resulta em pc = E-true quando o valor de E é true ou
resulta em pc = E-false no caso contrário.
Definimos assim uma função code-bool que toma em entrada uma
expressã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-t
code-bool(false,lab-t,lab-f)
= j lab-f
code-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 Compostos
Tipos produtos
Vectores
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 seja
que 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 seu
tamanho 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 =
Tbool | Tint
| Tprod (t1,t2)
| Tarr (f,l,t)
function
-> 1
-> size t1 + size t2
-> 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 de
expressõ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 ser
I
I
I
um bloco formado pelo valor de e1 seguido do valor de e2;
o endereço do local em memória onde começa o arquivo dos dois
valores (endereço que cabe num registo)
é 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))
.data
par:
.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
I
I
o endereço da parte inferior do bloco no qual o valor é arquivado
o tamanho dos dados (conhecida estaticamente ou dinamicamente)
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 produtos
Uma 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
codep((E1,E2))
(sendo E uma expressão
aritmética)
= 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 no
sí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 dados
arquivados neste (inteiros, reais, vectores, estruturas) e da estratégia de
representação dos dados.
Vamos aqui supor que o tamanho dos dados é sabido em tempo de
compilação.
Os vectores são representado por sequências de células adjacentes.
vectores multidimensionais são linearizados (as linhas são concatenadas
umas atrás das outras)
Os acessos a vectores: offset calculados na execução via aritmética sob
endereços
O endereço por resolver depende de um endereço de base a e de um offset n
calculado 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 de
tamanho 1.
adr(id) : endereço de base do vector
code(id[E])
=
|
code(id[E1]:=E2) =
|
S. Melo de Sousa (DIUBI)
code(E) | la $a1 adr(id)
add $a1, $a1, $a0 | lw $a0, 0($a1)
code(E1) | la $a1 adr(id) | add $a1, $a1, $a0
pushr $a1 | code(E2) | popr $a1 | sw $a0, 0($a1)
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 a
e 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ário
calcular 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 podemos
parcialmente avaliar esta expressão calculando antecipadamente a m ⇥ k e
guardando 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)
O código gerado é
codep(id[E])
=
|
|
... |
codep(id[E1] := E2) =
|
|
|
|
... |
|
S. Melo de Sousa (DIUBI)
m ⇥ size(id)
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 $a0
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
Geração de Código
47 / 92
Em resumo
Manipular valores estruturados obriga à reserva atempada de espaço
memória.
Pode ser feita com base na pilha quando se controla o tempo de vida destes
dados (por cause do cálculo de endereço por realizar).
Uma solução mais uniforme pode passar por manipular objectos estruturados
como apondadores para a heap.
Para uma programação mais segura, pode juntar-se ao código gerado testes
de 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ções
Exemplo de uma função recursiva
Parâmetros de funções e procedimentos
Tabela de activação
Chamadas de funções e assembly
Passagem por referência
Funçõ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 (passagem
de parâmetros, dos efectivos aos formais).
Várias semânticas possíveis para o modo de ligação/instanciação dos
parâ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 de
chamadas 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ória
I
I
I
I
variáveis introduzidas no programa;
células de um vector;
componentes de uma estrutura;
apontadores.
Nestas localizações em memória são arquivados valores
Certas expressões em programas podem designar estas localizações ou então
os valores que aí estão arquivados
I
I
o valor esquerdo (ou left-value) de uma expressão designa o local em
memória a partir do qual é arquivado o objecto (utilizado nas
atribuições)
o valor direito (ou right-value) de uma expressão designa o valor do
objecto arquivado em memória no dito local.
A passagem de parâmetro nos procedimentos pode fazer-se transmitindo os
valores 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 que
associa um endereço a um identificador.
um estado que liga endereços a valores: uma função parcial que associa um
valor 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 ou
variáveis locais, o ambiente muda.
A variável introduzida corresponde a uma nova ligação entre nome e
endereç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 ou
procedimentos 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ão
instanciados aquando da chamada ao procedimento pelos parâmetros
efectivos.
O procedimento pode declarar variáveis locais que serão inicializadas no
corpo 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 inicializados
aquando da chamada ao procedimento.
Existem várias formas de realizar tal inicialização.
Admitindo o procedimento p com um parâmetro formal x que é invocado
com 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 pelo
procedimento 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áveis
não locais ao procedimento explicitamente referenciado nas instruções do
programa.
É necessário reservar um espaço proporcional ao tamanho do parâmetro, o
que 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 o
valor esquerdo da variável criada)
O procedimento aloca uma variável x que é inicializada pelo valor esquerdo
de e.
Qualquer referência a x no corpo do procedimento é interpretado como uma
operação sobre um objecto cujo endereço está arquivado em x.
Este modo de passagem de parâmetro ocupa um espaço memória
independente do tamanho do parâmetro (um endereço)
Algumas notas:
Em C, a passagem por referência é explicitamente programada pela
passagem 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 palavras
reservadas 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 pelos
parâmetros efectivos
Este mecanismo pode gerar fenómenos (problemáticos) de captura de
variá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) com
passagem 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 locais
swap (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 tamanho
pequeno (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 para
inicializar a variável x.
À saída do procedimento o valor direito de x serve para actualizar o valor
esquerdo 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 linguagens
ditas preguiçosas.
Linguagem estrita: os valores dos argumentos são calculados antes de serem
passados como parâmetros a uma função (CAML, SML por exemplo)
Linguagem preguiçosa: a expressão passada em parâmetro para uma função
f só será avaliada se f precisa deste valor (chamada por necessidade, em
Haskell)
avaliação preguiçosa combina dificilmente com efeitos laterais (porque é
difícil controlar quando a expressão irá ser avaliada e produzir os efeitos
laterais)
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 que
e 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 = a
I
I
I
I
valor de a
código de a
localização em memoria de a
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 (ou
função), nem o momento onde estas ocorrem.
Não é possível dar estatisticamente os endereços das variáveis que apareçam
no corpo do procedimento
Estes endereços poderão ser calculados relativamente ao estado da pilha no
momento 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 aquando
da 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 da
execução do procedimento)
Para facilitar a comunicação entre código compilado a partir de linguagens fontes
distintas (por exemplo uma função C invocar uma função assembly, etc...), é
frequente que um determinado formato para as tabelas de activação seja
recomendado 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 processamento
de erro ou de instrução de tipo goto): a sequência de execução deve
continuar 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 um
endereço para um bloco de activação (designado em inglês de frame
pointer) que é determinado em tempo de execução e guardado num registo
particular (o registo fp).
Quando um novo procedimento é chamado, este valor muda, pode assim ser
necessário arquivar o valor corrente que poderá ser restaurado no final da
chamada.
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ão
partilhadas 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 em
registos ou na pilha
O código gerado pelo caller deve estar escrito para cada chamada enquanto
o 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 uma
função) e avalia os parâmetros efectivos, coloca-os na pilha ou nos registos
pensados 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 da
avaliação no lugar reservado pelo caller e restaura os registos.
O Caller e o callee devem ter uma visão concertada e coerente da
organizaçã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 registo
dedicado (o registo $ra, a instrução jal).
No fim do código da sub-rotina efectua-se um salto para o ponto guardado
no registo $ra.
Se o corpo duma sub-rotina chama outra sub-rotina, é preciso então cuidar
do 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-rotinas
D ::= procP; begin I end
label-p é um label único (“fresco”) associado ao
I
::= call p
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 para
aí guardar as variáveis
um registo fp pode então ser posto a apontar para os valores locais, no
iní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, $a2
e $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 em
simultâneo: os valores dos registos devem então estar guardados para
utilizações futuras. É sempre necessário guardar $ra (endereço de retorno da
funçã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ço
label 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 da
tabela. Este permite aceder aos doados via o offset fixo e
independentemente 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 . . . en
Variáveis locais ou registos por salvaguardar: v1 . . . vm
S. Melo de Sousa (DIUBI)
Geração de Código
74 / 92
Protocolo de chamadas
caller
Salvaguarda os registos dos quais tem responsabilidade e de que precisará a
seguir à chamada
avalia os elementos e1 . . . en nos registos e/ou na pilha.
Salta para a instrução correspondente à etiqueta label da função sem
esquecer, antes, de arquivar o ponto de retorno dentro de $ra (instrução jal
label).
Restaura os registos salvaguardados
pop dos argumentos previamente empilhados.
S. Melo de Sousa (DIUBI)
Geração de Código
75 / 92
Protocolo de chamadas
callee
Reserva o espaço em pilha necessário para o procedimento: os valores de v1 , . . . ,
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 protocolo
exposto...
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
V
D
::=
::=
::=
|
Vs V; | ✏
T id
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 a
variável:
I
I
os parâmetros são endereços maiores do que $fp (offset positivo);
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 passagem
por 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-saved
code(I) |
code(E) |
move $v 0,$a0 |
restaura os registos callee-saved
addiu $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ção
subjacente
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)}
.data
u: .word 3
.text
f: lw $a2,0($a0)
add $a1,$a2,$a1
mul $a2,$a2,$a1
sw $a2, 0($a0)
jr $ra
S. Melo de Sousa (DIUBI)
main:
add $sp,$sp,-4
sw $ra,0($sp)
la $a0,u
lw $a1,u
jal f
lw $a0,u
li $v0,1
syscall
lw $ra,0($sp)
add $sp,$sp,4
jr $ra
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 e
vectores.
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 e
nã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,
code(x) = lw $a0,
code(x) = lw $a0,
| lw $a0,
adr(x)
decal(x)($fp)
decal(x)($fp)
0($a0)
se not islocal(x)
se islocal(x), por valor
se islocal(x), por referência
Código para o caller f(e1 , . . . , en )
Substituir code(ei ) por codeg(ei ) se o i-ésimo argumento é passado por
referê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
I
I
Os registos são insuficientes para arquivar as variáveis
A tabela de activação deve ser alocado na pilha
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 dos
parâmetros e logo não é conhecido em tempo de compilação.
A recursão arquiva implicitamente valores intermédios e pode simplificar a
programação (por exemplo backtracking).
A recursão, dita terminal (tail recursive), é um caso particular que pode
ser 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 chamada
recursiva:
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 quanto
um 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 out
mul $a0,$a0,$a1
addi $a1,$a1,-1
j fact
out: move $v0,$a0
jr $ra
S. Melo de Sousa (DIUBI)
Geração de Código
92 / 92
Download