Papel de um Compilador
Produzir um Object Module
O compilador traduz o programa para instruções de máquina, dando informação para construir um programa completo através de peças soltas:
- Header: descreve os componentes de um object module;
- Text segment: tradução das instruções;
- Static data segment: dados alocados para dar vida ao programa;
- Relocation info: para componentes que dependem da localização absoluta do loaded program;
- Symbol table: definições globais e referências externas;
- Debug info: para associação com o código fonte.
Ligar Object Modules
Ao fazermos linking de object modules produzimos uma imagem executável que converge segmentos, resolve labels, que determinam o seu endereço, e corrigem a localização-dependente e referências externas. Para além disso, não podemos deixar as dependências de localização para corrigirem a mudança de carregador, contudo, com memória vistual, tal não é necessário visto que o programa pode ser carregado para uma localização absoluta no espaço de memória virtual.
Mas como é que carregamos um ficheiro no disco para a memória? Há seis passos diferentes:
- Leitura do header para determinar o tamanho dos segmentos;
- Criação do espaço de endereçamento virtual;
- Cópia do texto e inicialização dos dados para a memória:
- Ou definição das entradas da tabela de páginas para que possam ser inseridas;
- Configuração dos argumentos na pilha;
- Inicialização dos registos (incluindo
$sp
,$fp
,$gp
); - Salto para o início da rotina:
- Copia dos argumentos para
$a0
, ... e chama a função main; - Quando a função main retorna, faz um syscall do exit.
- Copia dos argumentos para
Dynamic Linking
À medida que vamos precisando, temos que ir dando link/load da nossa biblioteca. Para este processo é obrigatório que o código seja realocado de modo a evitar que as imagens inchem por causa da ligação estática a todas as bibliotecas referenciadas. Assim, novas versões de bibliotecas são automaticamente apanhadas. Também podemos ter lazy linkage: a ligação feita somente quando uma função é chamada, sabendo que só funções é que de facto podem ser ligadas.
É importante referir que no Just in time compiler é um tradutor on the fly, ou seja, pega nas instruções Java e recompila em Assembly no momento.
x86 Instruction Encoding
O tamanho das variáveis que devem ser condificadas têm bytes postfix que especificam o modo de endereçamento e operações de bytes de prefixos.
Implementação do IA-32
São conjuntos complexos de instrução que tornam a sua implementação difícil visto que o hardware traduz instruções para micro-operações mais simples, ou seja instruções simples do género 1-1, ou instruções complexas do género 1-muitos; micromotor parecido com RISC; e mercado de partilha que turna isto economicamente viável.
Equívocos Comuns
É fácil cair em conclusões precipitadas relativamente aos compiladores e processadores:
A primeira é que ter instruções mais complexas implica execuções mais rápidas. Isto nem sempre é verdade visto que apesar de serem necessárias menos instruções, estas são mais complexas e difíceis de implementar, o que pode obrigar o processador a correr a uma frequência mais baixa, "atrasando" todas as instruções, incluindo as mais simples. Para além disso, os compiladores são extremamente bons a escrever código eficiente a partir de instruções simples.
A segunda é que devemos escrever trechos de código em Assembly se queremos que sejam mais eficientes. Pelo contrário, os compiladores modernos são bem melhores que qualquer um de nós a escrever Assembly. O tempo desperdiçado a escrever 50 linhas de Assembly para uma função de 10 linhas em C é melhor utilizado a estudar para OC.