Aula 02: Introdução aos Compiladores
Objetivos
- Definir formalmente o conceito de compilador e seus componentes.
- Diferenciar modelos de execução: Compilação, Interpretação e Híbridos.
- Compreender a estrutura básica de T-Diagrams (Diagramas de Lápide).
- Visualizar o ciclo de vida da tradução de código.
Conteúdo
1. O que é um Compilador?
Em sua essência, um compilador é um tradutor. Formalmente, definimos um compilador \(C\) como uma função que mapeia um programa \(P_S\) escrito em uma linguagem fonte \(L_S\) para um programa semanticamente equivalente \(P_T\) em uma linguagem alvo \(L_T\).
\[ C: L_S \to L_T \] \[ \forall input \in Inputs: Exec(P_S, input) \equiv Exec(P_T, input) \]
A propriedade fundamental é a preservação da semântica. O programa compilado deve se comportar exatamente como o original, produzindo os mesmos resultados e efeitos colaterais.
Por que compilar?
Por que não escrevemos diretamente em linguagem de máquina? 1. Produtividade: Linguagens de alto nível (Java, Python, C++) são mais expressivas e concisas. 2. Portabilidade: O mesmo código fonte pode ser compilado para x86, ARM, RISC-V, etc. 3. Otimização: Compiladores modernos aplicam transformações matemáticas complexas que humanos dificilmente fariam manualmente.
2. Modelos de Execução
A) Compiladores Puros (Ahead-of-Time - AOT)
O código é totalmente traduzido antes da execução. - Exemplos: C, C++, Rust, Go, Haskell. - Fluxo: Fonte (.c) -> Compilador -> Objeto (.o) -> Linker -> Executável (.exe). - Prós: Máxima performance (otimizações agressivas offline), detecção de erros antes de rodar. - Contras: Ciclo de desenvolvimento mais lento (compilar leva tempo), executável preso à plataforma.
B) Intérpretes Puros
Não existe tradução para código de máquina. Uma Máquina Virtual lê o código fonte (ou uma AST) e executa as ações em tempo real. - Exemplos: Bash, versões antigas de PHP/Ruby, Python (conceitualmente). - Fluxo: Fonte (.py) -> Intérprete (Lê e Executa loop). - Prós: Flexibilidade extrema ( eval("print(1+1)") ), portabilidade fácil (só precisa do intérprete), ciclo rápido. - Contras: Performance ruim (overhead de interpretação pode ser 10x-100x mais lento).
C) Híbridos (Bytecode e JIT)
O modelo dominante hoje. O código fonte é compilado para uma Linguagem Intermediária (IL) padrão, que é interpretada por uma VM eficiente. - Exemplos: Java (JVM), C# (.NET CLR), JavaScript (V8/SpiderMonkey). - Fluxo: 1. Fonte (.java) -> Compilador (javac) -> Bytecode (.class). 2. Bytecode -> JVM -> Interpretação + JIT -> Código de Máquina. - Just-In-Time (JIT) Compiler: A VM monitora o código rodando. Se uma função é executada muitas vezes (“hot code”), o JIT a compila para código de máquina nativo em tempo de execução.
3. Diagramas de Lápide (T-Diagrams)
Uma ferramenta visual para representar compiladores e processos de cross-compilation. Um T-Diagram tem 3 partes: - Topo Esquerdo: Linguagem Fonte (S). - Topo Direito: Linguagem Alvo (T). - Base: Linguagem de Implementação (I).
Imagine um “T” onde: - S -> T está na barra horizontal. - Escrito em I está na base.
Exemplo: Um compilador de C para Assembly escrito em C. C -> ASM (base: C).
Isso ajuda a entender Bootstrapping: Como compilamos o primeiro compilador de C? (Geralmente em Assembly). Depois, reescrevemos o compilador em C e o usamos para compilar a si mesmo.
4. A Anatomia Simplificada
Para fins de estudo, dividimos o compilador em duas grandes fases (Modelo Análise-Síntese):
Front-End (Análise)
Entende o que o programador escreveu e verifica se está correto. 1. Léxico: “As palavras existem?” (if, while, 42). 2. Sintático: “A frase faz sentido gramatical?” (if (x > 10) ...). 3. Semântico: “O significado é válido?” (Não somar int com string). Saída: Árvore Sintática Abstrata (AST) decorada.
Back-End (Síntese)
Gera o código para a máquina alvo. 1. Geração de Código Intermediário: Cria uma versão genérica do programa. 2. Otimização: Torna o código mais rápido ou menor. 3. Geração de Código Alvo: Emite Assembly ou Binário específico (x86, ARM).
Referências
- Aho, A. V., Lam, M. S., Sethi, R., & Ullman, J. D. (2006). Compilers: Principles, Techniques, and Tools. (Caps 1.1, 1.2)
- Cooper, K., & Torczon, L. (2011). Engineering a Compiler. (Cap 1)