Você está na página 1de 4

Capı́tulo 2

Compilação de L0

A maioria dos compiladores traduz o programa fonte primeiro para alguma forma de representação intermediária
entre a linguagem do programa fonte e a linguagem executável por uma máquina. Essa representação inter-
mediário pode ser traduzida para outras representações intermediárias de mais baixo nı́vel até se chegar em
código de máquina.
Otimizações de código em geral são feitas sobre essas representações intermediárias. Existem diversas
representações intermediárias em uso. Algumas são consideradas de alto nı́vel pois preservam muitas das
caracterı́sticas da linguagem de programação e outras são linguagens intermediárias de mais baixo nı́vel com
caracterı́sticas que se aproximam de código de máquinas reais. Programas em linguagens intermediárias
também podem ser interpretados ao invés de serem traduzidas para outra linguagem. Programa em Java,
por exemplo, são compilados para a linguagem intermediária conhecida como bytecode Java. Java bytecode
é então interpretado pela máquina virtual do Java.
Para ilustrar a descrição formal de uma tradução (compilação), vamos apresentar uma máquina abstrata
e a compilação de programas escritos na linguagem L0 para programas na linguagem intermediária dessa
máquina abstrata.

2.1 Máquina abstrata SSM0


Vamos descrever uma máquina abstrata denominada SSM0 (Simple Stack Machine) e a sua linguagem
denominada SSM0-IR. Um programa SSM0 é uma sequência de instruções que operam sobre uma pilha. O
estado de uma máquina é determinada pelas instruções a serem executadas e pelo conteúdo da pilha.

Definição 13. O estado da máquina SSM0 (configuração) consiste de um par (code, stack) onde (i) code é
uma lista de instruções SSM0, e (ii) stack é uma lista de números inteiros.

Sintaxe de SSM0-IR:

n ∈ N
z ∈ Z

i ∈ Inst
i ::= PUSH z | POP | COPY | INC |
DEC | JUMP n | JMPIFZERO n (com n ≥ 0)

17
Abaixo segue uma descrição informal das instruções da máquina SSM0:

• PUSH z : empilha inteiro z


• POP : remove topo da pilha
• COPY : empilha uma nova cópia do número no topo da pilha
• INC : soma 1 ao número no topo da pilha
• DEC : subtrai um do número no topo da pilha
• JUMP n : pula n instruções da lista de código
• JMPIFZERO n : testa o topo da pilha, removendo-o. Se o topo da pilha for 0, pula n instruções da lista
de código. Se o topo da pilha for diferente de 0, não faz nada.

A semântica da linguagem SSM0-IR também pode ser definida formalmente usando o estilo de semântica
operacional small-setp. Para isso vamos precisar de notação para lidar com listas.

Notação para listas:


• lista vazia : []
• prefixação: 1 :: []
• notação simplificada: [1; 2; 3] = 1 :: 2 :: 3 :: [], 5 :: [] = [5]
• concatenação: [1; 2]++[3; 4] = [1; 2; 3; 4]
• tamanho: length([3; 4]) = 2

Obs.: tanto code quanto stack serão operados como pilhas, onde o elemento mais à esquerda representa
o topo. Exemplo: a stack [3; 4; 2; 1; 5] possui 3 no topo.

Definição 14. A relação de redução B ⊆ (SSM0-IR × Stack) × (SSM0-IR × Stack) é a menor relação tal que

(S-Push)
(PUSH z :: code, stack) B (code, z :: stack)

(S-Dec)
(DEC :: code, z :: stack) B (code, (z − 1) :: stack)

(S-Copy)
(COPY :: code, z :: stack) B (code, z :: z :: stack)

(S-Jmp)
(JUMP n :: i1 :: . . . :: in :: code, stack) B (code, stack)

(S-Pop)
(POP :: code, z :: stack) B (code, stack)

(S-Inc)
(INC :: code, z :: stack) B (code, (z + 1) :: stack)

(S-JZeroZero)
(JMPIFZERO n :: i1 :: . . . :: in :: code, 0 :: stack) B (code, stack)

z 6= 0
(S-JZero)
(JMPIFZERO n :: code, z :: stack) B (code, stack)

Definição 15. B∗ é o fecho transitivo e reflexivo de B.

18
As instruções são executadas sequencialmente. A redução de configurações de SSM0, iniciando em
configuração (code, stack), permite os seguintes comportamentos (observar que que divergência não é possı́vel
na máquina SSM0.)

• Avaliação termina sem erro: (code, stack) B∗ ([], stack 0 )

• Avaliação termina em erro: (code, stack) B∗ (code0 , stack 0 ) 6B e code0 6= []

Com a exceção de JMP, todas as demais instruções de SSM0 operam com a pilha. E das instruções que
operam com a pilha, apenas PUSH pode operar, sem erro, com pilha vazia. Um erro também ocorre se a
instrução JMP n é executada com n maior do que o número de instruções que seguem essa instrução. O
mesmo acontece com a execução de JUMPIFZERO n (se o topo da pilha contém 0).
Com base nessa análise podemos detalhar as configurações com erro:

• (i :: code, []) 6B, se i ∈ { POP z, COPY, INC, DEC, JMPIFZERO n }

• (JMP n :: code, stack) 6B, se n > length(code)

• (JMPIFZERO n :: code, 0 :: stack) 6B, se n > length(code)

Note também que uma instrução JMP 0 tem o mesmo efeito de uma instrução NOP (no operation), e que
uma instrução JMPIFZERO 0 tem o mesmo efeito de uma instrução POP.

2.2 Compilação de L0 para SSM0


Vamos definir uma função de compilação

C : L0 → SSM0-IR

que associa para cada AST de L0 uma sequência de instruções de SSM0-IR.

Definição 16. C : L0 → SSM0-IR (função de compilação)

C(true) = [PUSH 1]
C(false) = [PUSH 0]
C(0) = [PUSH 0]
C(succ e1 ) = C(e1 ) ++ [INC]
C(if(e1 , e2 , e3 )) = let
n2 = length(C(e2 ))
n3 = length(C(e3 ))
in
C(e1 ) ++ [JMPIFZERO (n2 +1)] ++
C(e2 ) ++ [JUMP n3 ] ++ C(e3 )
C(iszero e1 ) = C(e1 ) ++ [JMPIFZERO 2; PUSH 0; JUMP 1; PUSH 1]
C(pred e1 ) = C(e1 ) ++ [COPY; JUMPIFZERO 1; DEC]

19
Observe que todas as instruções, com exceção de JMP, operam com o topo da pila. A compilação C(succ e1 )
por exemplo, gera a seguinte lista de instruções

C(e1 ) ++ [INC]

As intruções geradas por C(e1 ), quando executadas, deixam um valor no topo da pilha. Esse valor será
incrementado pela execução da instrução INC.
A função de compilação deve, evidentemente, preservar a semântica do programa fonte na linguagem L0.
A execução de todo programa SSM0 que resulta da função de compilação, quando iniciada com uma pilha
vazia, termina com a pilha contendo apenas um valor. O seguinte diagrama captura a noção de correção da
função de compilação:
−→∗ / v
e

C ρ

 B∗

(C(e), []) / ([], [ρ(v)])

O teorema a seguir expressa a preservação do comportamento de programas L0 pela função de compilação.

Teorema 10. Se e : T e se e −→∗ v então (C(e), []) B∗ ([], [ρ(v)])

onde
ρ(true) = 1
ρ(false) = 0
ρ(0) = 0
ρ(succ nv) = 1 + ρ(nv)

Prova. Da hipótese e −→∗ v obtém-se e ⇓ v (equivalência entre small e big step para L0.)

Precisamos fortalecer a hipótese indutiva, considerando a propriedade abaixo:

∀c ∈ Code, ∀s ∈ Stack, (C(e)++c, s) B∗ (c, ρ(v) :: s)

Prova-se a propriedade acima por indução em e ⇓ v.

O teorema desejado é um corolário da propriedade acima. 

Exercı́cio 15. Implemente em OCaml a função de compilação C que mapeia ASTs de L0 para SSM0.

Exercı́cio 16. Implemente em OCaml um interpretador para programas SSM0.

Exercı́cio 17. Aumente a linguagem L0 com operações binárias and(e1 , e2 ) e or(e1 , e2 ) e com operação
unária not(e). Defina regras da semântica operacional small e big-step; defina regras de tipos, e estenda a
função de compilação para essas novas construções da linguagem.

20

Você também pode gostar