A apresentação está carregando. Por favor, espere

A apresentação está carregando. Por favor, espere

1.3 – Interpretadores 1.3.1 – Compiladores versus Interpretadores Execução de um programa gerado por compilador: Execução de um programa gerado por compilador:

Apresentações semelhantes


Apresentação em tema: "1.3 – Interpretadores 1.3.1 – Compiladores versus Interpretadores Execução de um programa gerado por compilador: Execução de um programa gerado por compilador:"— Transcrição da apresentação:

1 1.3 – Interpretadores – Compiladores versus Interpretadores Execução de um programa gerado por compilador: Execução de um programa gerado por compilador: Programa fonte Programa em linguagem de máquina Dados Resultados Compilador + Montador Em tempo de compilação Em tempo de execução

2 Execução de um programa interpretado: Execução de um programa interpretado: O interpretador faz análise léxica, sintática e semântica do programa-fonte, armazenando-o numa estrutura interna O interpretador faz análise léxica, sintática e semântica do programa-fonte, armazenando-o numa estrutura interna Em seguida, ele percorre a estrutura interna, executando as operações ali especificadas, consumindo os dados por elas pedidos Em seguida, ele percorre a estrutura interna, executando as operações ali especificadas, consumindo os dados por elas pedidos Programa fonte Programa numa estrutura interna Dados Resultados Módulo de análise e armazena- mento Módulo de execução Em Tempo de execução Interpretador

3 Execução de um programa interpretado: Execução de um programa interpretado: Não há criação de um programa em linguagem de máquina equivalente ao programa-fonte Não há criação de um programa em linguagem de máquina equivalente ao programa-fonte O único programa executado é o interpretador O único programa executado é o interpretador Programa fonte Programa numa estrutura interna Dados Resultados Módulo de análise e armazena- mento Módulo de execução Em Tempo de execução Interpretador

4 Exemplo: seja o comando de atribuição v1 = v2 + v3 * v4; Um interpretador pode construir-lhe uma árvore de execução (estrutura interna): Um interpretador pode construir-lhe uma árvore de execução (estrutura interna): = Depois ele caminha pela árvore, executando as operações

5 Na raiz, ele faz uma atribuição da expressão do lado direito à variável do lado esquerdo Na raiz, ele faz uma atribuição da expressão do lado direito à variável do lado esquerdo No cálculo dessa expressão, ele soma a variável do lado esquerdo com a expressão do lado direito (chamada recursiva do cálculo de expressão) No cálculo dessa expressão, ele soma a variável do lado esquerdo com a expressão do lado direito (chamada recursiva do cálculo de expressão) =

6 Há interpretadores de código fonte e de código intermediário Há interpretadores de código fonte e de código intermediário Interpretador de código fonte: Deve haver uma forma de armazenamento do programa fonte Interpretador de código fonte: Deve haver uma forma de armazenamento do programa fonte Interpretador de código intermediário: o código intermediário pode ser gerado pelo mesmo processo usado nos compiladores Interpretador de código intermediário: o código intermediário pode ser gerado pelo mesmo processo usado nos compiladores

7 1.3.2 – Interpretadores de código-fonte Durante a análise sintática, o programa-fonte é armazenado numa estrutura denominada árvore do programa Durante a análise sintática, o programa-fonte é armazenado numa estrutura denominada árvore do programa Depois, essa estrutura é percorrida, para que os comandos do programa sejam executados Depois, essa estrutura é percorrida, para que os comandos do programa sejam executados Exemplo: programa para o Bubble-Sort Exemplo: programa para o Bubble-Sort

8 void main () { int n, i, p, aux, vetor[50]; char trocou; read (n); for (i=0; i=0 && trocou == 1; p--) { trocou = 0; for (i = 0; i<=p; i++) if (vetor[i] > vetor[i+1]) { aux = vetor[i]; vetor[i] = vetor[i+1]; vetor[i+1] = aux; trocou = 1; }} for (i=0; i

9 As declarações podem ser eliminadas

10 Cada nó retangular pode ser mais detalhado

11 Ver detalhes

12 Cada nome de variável deve ser substituído por um ponteiro para sua posição na tabela de símbolos Lá deve haver um campo para guardar seu valor

13 Interpretação de código fonte é muito ineficiente Os programas costumam ter muitos aninhamentos Cada tipo de comando é executado por um módulo específico

14 O módulo de um comando repetitivo irá chamar os módulos dos comandos de seu escopo Se um deles for um comando condicional, ocorrerá com ele o mesmo Num dado momento, poderão estar ativas várias versões de diversos módulos

15 Há consumo de muita memória e sobrecarga de trabalho para gerenciá-la Há consumo de muita memória e sobrecarga de trabalho para gerenciá-la Os interpretadores de código intermediário são os preferidos Os interpretadores de código intermediário são os preferidos No entanto, o conceito de árvore de programa é muito útil em softwares que fazem análise do programa-fonte No entanto, o conceito de árvore de programa é muito útil em softwares que fazem análise do programa-fonte Por exemplo, em compiladores paralelos, a detecção de paralelismo exige análise de dependências que só pode ser feita se o programa-fonte estiver apropriadamente armazenado Por exemplo, em compiladores paralelos, a detecção de paralelismo exige análise de dependências que só pode ser feita se o programa-fonte estiver apropriadamente armazenado

16 1.3.3 – Interpretadores de código intermediário Podem ter os mesmos componentes da frente de um compilador convencional: Podem ter os mesmos componentes da frente de um compilador convencional: Analisadores léxico, sintático e semântico Analisadores léxico, sintático e semântico Gerador de código intermediário Gerador de código intermediário Otimizador de código intermediário Otimizador de código intermediário Além desses, devem ter o componente que vai fazer a interpretação propriamente dita do código intermediário Além desses, devem ter o componente que vai fazer a interpretação propriamente dita do código intermediário Esse componente percorre o código, executando as operações ali especificadas e processando os dados por elas solicitados Esse componente percorre o código, executando as operações ali especificadas e processando os dados por elas solicitados

17 Exemplo: laço do programa anterior: for (i = 0; i<=p; i++) if (vetor[i] > vetor[i+1]) { aux = vetor[i]; vetor[i] = vetor[i+1]; vetor[i+1] = aux; trocou = 1; } Suas quádruplas com alguma otimização: Suas quádruplas com alguma otimização:

18 A propósito, quádruplas para indexação: Sejam as seguintes declarações e comandos: Sejam as seguintes declarações e comandos: int i, j, k, A[6][5]; i = 4; j = 3; k = A[i][j-2]; A[10-i][2*j+3] = i + j * k; Acesso a um elemento genérico A[i][j]: Acesso a um elemento genérico A[i][j]: Uma vez conhecido o endereço inicial da matriz A, é necessário localizar o elemento A[i][j] Uma vez conhecido o endereço inicial da matriz A, é necessário localizar o elemento A[i][j] Seja a seguir o mapa de A[6][5] na memória: Seja a seguir o mapa de A[6][5] na memória:

19 Seja m o número de linhas e n o número de colunas da matriz A Seja m o número de linhas e n o número de colunas da matriz A O endereço do elemento A[i][j] é dado pela fórmula: O endereço do elemento A[i][j] é dado pela fórmula: Ender (A) + i * n + j Para m = 6, n = 5, o endereço de A[4][3] é Para m = 6, n = 5, o endereço de A[4][3] é Ender (A) + 23

20 No programa, cada índice pode ser uma expressão inteira No programa, cada índice pode ser uma expressão inteira Calcula-se o valor de cada índice, empilhando-o numa pilha de índices Calcula-se o valor de cada índice, empilhando-o numa pilha de índices Isso pode ser feito pela execução de uma quádrupla de operador IND: Isso pode ser feito pela execução de uma quádrupla de operador IND: IND, i, ----, ---- IND, i, ----, ---- IND, j, ----, ---- IND, j, ----, ----

21 Calcula-se o endereço de A[i][j], usando uma quádrupla de operador INDEX: Calcula-se o endereço de A[i][j], usando uma quádrupla de operador INDEX: INDEX, A, 2, temp1 Sua execução consiste em: Sua execução consiste em: Pegar as dimensões e o endereço de A na tabela de símbolos Pegar as dimensões e o endereço de A na tabela de símbolos Desempilhar dois índices Desempilhar dois índices Calcular o endereço, colocando-o na variável temp1 Calcular o endereço, colocando-o na variável temp1 A variável temp1 é portanto um ponteiro A variável temp1 é portanto um ponteiro

22 Formação do código intermediário: Formação do código intermediário: int i, j, k, A[6][5]; i = 4; j = 3; k = A[i][j-2] + 5; A[10-i][2*j+3] = i+j*k; :=, 4,..., i :=, 3,..., j IND, i,...,... -, j, 2, temp1 IND, temp1,...,... INDEX, A, 2, temp2,..., temp3 +, temp3, 5, k temp2 tem o endereço do elemento A[i][j-2] É necessário saber o valor guardado nesse elemento A temp2,..., temp3 atribui a temp3 o valor do local apontado por temp2

23 Formação do código intermediário: Formação do código intermediário: int i, j, k, A[6][5]; i = 4; j = 3; k = A[i][j-2] + 5; A[10-i][2*j+3] = i+j*k; :=, 4,..., i :=, 3,..., j IND, i,...,... -, j, 2, temp1 IND, temp1,...,... INDEX, A, 2, temp2,..., temp3 +, 5, temp3, k -, 10, i, temp4 IND, temp4,...,... *, 2, j, temp5 +, temp5, 3, temp6 IND, temp6,...,... INDEX, A, 2, temp7 *, j, k, temp8 +, i, temp8, temp9 #, temp9,..., temp7 temp7 tem o endereço do elemento A[10-i][2*j+3] O valor de temp9 deve ser atribuído ao local apontado por temp7 A quádrupla #, temp9,..., temp7 atribui o valor de temp9 ao local apontado por temp7

24 Voltando ao laço do programa anterior: for (i = 0; i<=p; i++) if (vetor[i] > vetor[i+1]) { aux = vetor[i]; vetor[i] = vetor[i+1]; vetor[i+1] = aux; trocou = 1; } Suas quádruplas: Suas quádruplas:

25 Estrutura de dados São omitidos vários ponteiros As quádruplas poderiam ser guardadas numa lista encadeada ao invés de num vetor

26 Desvantagens da interpretação: A execução de programas compilados é muito mais rápida que a de programas interpretados A execução de programas compilados é muito mais rápida que a de programas interpretados Apesar do código intermediário ser aperfeiçoado antes da interpretação propriamente dita, o código objeto ainda pode ser otimizado Apesar do código intermediário ser aperfeiçoado antes da interpretação propriamente dita, o código objeto ainda pode ser otimizado Isso não ocorre em programas interpretados Isso não ocorre em programas interpretados Um interpretador faz por software a interpretação do código da operação; num programa compilado isso ocorre por hardware, o que é muito mais rápido Um interpretador faz por software a interpretação do código da operação; num programa compilado isso ocorre por hardware, o que é muito mais rápido

27 Vantagens da interpretação: O código intermediário é independente de máquina, o que lhe confere portabilidade O código intermediário é independente de máquina, o que lhe confere portabilidade Um programa compilado só pode rodar em máquinas compatíveis com sua máquina alvo Um programa compilado só pode rodar em máquinas compatíveis com sua máquina alvo Interpretadores se mostram adequados para as redes de computadores heterogêneos, que é o caso da Internet Interpretadores se mostram adequados para as redes de computadores heterogêneos, que é o caso da Internet A elaboração de um módulo interpretador propriamente dito é muito mais simples que a de uma retaguarda de compilador A elaboração de um módulo interpretador propriamente dito é muito mais simples que a de uma retaguarda de compilador

28 Os detalhes da arquitetura da máquina-alvo complicam muito o projeto da retaguarda Os detalhes da arquitetura da máquina-alvo complicam muito o projeto da retaguarda Para casos em que a rapidez de execução não é fundamental, os interpretadores são preferidos, em comparação com os compiladores Para casos em que a rapidez de execução não é fundamental, os interpretadores são preferidos, em comparação com os compiladores A Linguagem Java: Seu compilador gera código denominado bytecode, independente de máquina Seu compilador gera código denominado bytecode, independente de máquina Bytecode é traduzido para linguagem de máquina por uma máquina virtual residente em qualquer ambiente Java Bytecode é traduzido para linguagem de máquina por uma máquina virtual residente em qualquer ambiente Java Bytecode é portável, porém, para ser executado, deve ser traduzido, o que torna o processo mais lento Bytecode é portável, porém, para ser executado, deve ser traduzido, o que torna o processo mais lento

29 1.4 – Automação da Construção de Compiladores – Bootstrapping e compiladores cruzados O compilador para a primeira linguagem de programação só poderia ter sido escrito em Assembly O compilador para a primeira linguagem de programação só poderia ter sido escrito em Assembly Assim foi com Fortran e Cobol Assim foi com Fortran e Cobol Seus projetos demandaram esforço de programação descomunal Seus projetos demandaram esforço de programação descomunal

30 No princípio: Assembler Máquina M1

31 Primeiro compilador Fortran, escrito em Assembly: Assembler Máquina M1 Fortran em Assembly Fortran em M1

32 Compilador Algol, escrito em Fortran: Assembler Máquina M1 Fortran em Assembly Fortran em M1 Algol em Fortran Algol em M1

33 Compilador Pascal, escrito em Algol: Assembler Máquina M1 Fortran em Assembly Fortran em M1 Algol em Fortran Algol em M1 Pascal em Algol Pascal em M1

34 Compilador C, escrito em Pascal: Assembler Máquina M1 Fortran em Assembly Fortran em M1 Algol em Fortran Algol em M1 Pascal em Algol Pascal em M1 C em Pascal C em M1

35 Em UNIX, compiladores L1, L2,..., escritos em C: Assembler Máquina M1 Fortran em Assembly Fortran em M1 Algol em Fortran Algol em M1 Pascal em Algol Pascal em M1 C em Pascal C em M1 L1 em C L2 em C L1 em M1 L2 em M1

36 Em UNIX, compiladores C eram escritos em C: Assembler Máquina M1 Fortran em Assembly Fortran em M1 Algol em Fortran Algol em M1 Pascal em Algol Pascal em M1 C em Pascal C em M1 C 1 em C C 2 em C C 1 em M1 C 2 em M1

37 Bootstrapping: propriedade de uma linguagem compilar a si mesma Bootstrapping: propriedade de uma linguagem compilar a si mesma Compilador cruzado: compilador que roda numa máquina e gera código para outra Compilador cruzado: compilador que roda numa máquina e gera código para outra Para uma máquina M2, seria necessário escrever um programa em Assembly para um primeiro compilador? Para uma máquina M2, seria necessário escrever um programa em Assembly para um primeiro compilador? Com bootstrapping e compiladores cruzados pode-se evitar isso (visto a seguir) Com bootstrapping e compiladores cruzados pode-se evitar isso (visto a seguir)

38 Um compilador é caracterizado por 3 linguagens: Um compilador é caracterizado por 3 linguagens: A linguagem-fonte (F) que ele compila A linguagem-fonte (F) que ele compila A linguagem-objeto (O) para a qual ele gera código A linguagem-objeto (O) para a qual ele gera código A linguagem de implementação (I) na qual ele está escrito A linguagem de implementação (I) na qual ele está escrito Simbolicamente, F I O Simbolicamente, F I O Ou, usando diagrama T: Ou, usando diagrama T: F O I

39 Seja M usado para denotar a linguagem de máquina de um computador ou máquina M Seja M usado para denotar a linguagem de máquina de um computador ou máquina M Para um compilador rodar na máquina M, sua linguagem de implementação deve ser M Para um compilador rodar na máquina M, sua linguagem de implementação deve ser M F O M

40 Seja um compilador L1 M M residindo na máquina M Seja um compilador L1 M M residindo na máquina M Deseja-se em M um compilador para uma nova linguagem L2, ou seja, L2 M M Deseja-se em M um compilador para uma nova linguagem L2, ou seja, L2 M M Primeiramente escreve-se um programa L2 L1 M Primeiramente escreve-se um programa L2 L1 M Depois roda-se na máquina M: Depois roda-se na máquina M: L2 M L1 L1 M M L2 M M Entrada Programa em execução Saída O compilador de L2 está pronto para rodar em M

41 Esquema para se obter um compilador cruzado: Esquema para se obter um compilador cruzado: Agora em M1, um compilador de L2 para a máquina M2 Agora em M1, um compilador de L2 para a máquina M2 Os programas escritos em L2 são compilados em M1 e o código objeto é transportado para M2 Os programas escritos em L2 são compilados em M1 e o código objeto é transportado para M2 L2 M2 L1 L1 M1 M1 L2 M2 M1 Entrada Compilador residindo em M1, em execução Saída

42 Seja então L a primeira linguagem a ser instalada na máquina M2, mas já instalada na máquina M1 (L M1 M1) Seja então L a primeira linguagem a ser instalada na máquina M2, mas já instalada na máquina M1 (L M1 M1) Primeiramente, bootstrapping: L L M2 Primeiramente, bootstrapping: L L M2 Depois, produz-se em M1 um compilador cruzado Depois, produz-se em M1 um compilador cruzado Finalmente, usando em M1 o compilador cruzado produzido Finalmente, usando em M1 o compilador cruzado produzido É só transportar L M2 M2 para a máquina M2 É só transportar L M2 M2 para a máquina M2 L M2 L L M1 M1 L M2 M1 Entrada Programa em execução Saída Programa em execução Entrada L M2 M2 Saída Compilador desejado

43 1.4.2 – Compiladores de compiladores As ferramentas de automação, logo que começaram a surgir, receberam alguns nomes um tanto ambiciosos: As ferramentas de automação, logo que começaram a surgir, receberam alguns nomes um tanto ambiciosos: Geradores de compiladores Geradores de compiladores Compiladores de compiladores Compiladores de compiladores Sistemas de construção de tradutores Sistemas de construção de tradutores

44 1.4.2 – Compiladores de compiladores Eram ferramentas de uso limitado, pois eram orientadas em torno de modelos particulares de linguagens Eram ferramentas de uso limitado, pois eram orientadas em torno de modelos particulares de linguagens Devido à grande heterogeneidade das linguagens e das arquiteturas, é muito difícil a elaboração de um gerador de propósitos gerais eficiente Devido à grande heterogeneidade das linguagens e das arquiteturas, é muito difícil a elaboração de um gerador de propósitos gerais eficiente O que existe hoje são ferramentas automáticas para o projeto de componentes específicos, relacionados a seguir O que existe hoje são ferramentas automáticas para o projeto de componentes específicos, relacionados a seguir

45 1.4.3 – Ferramentas para cada componente Utilizam linguagens especializadas para a especificação e implementação do componente e algoritmos bem sofisticados Geradores de analisadores léxicos: Têm como entrada expressões regulares e implementam um autômato finito reconhecedor e classificador dos átomos dos programas a serem compilados Têm como entrada expressões regulares e implementam um autômato finito reconhecedor e classificador dos átomos dos programas a serem compilados A mais conhecida entre elas é o Lex do sistema Unix, que possui também versões para o sistema DOS A mais conhecida entre elas é o Lex do sistema Unix, que possui também versões para o sistema DOS O programa gerado é escrito em Linguagem C O programa gerado é escrito em Linguagem C

46 1.4.3 – Ferramentas para cada componente Geradores de analisadores sintáticos: Têm como entrada a gramática livre de contexto da linguagem-fonte do compilador Têm como entrada a gramática livre de contexto da linguagem-fonte do compilador Nos compiladores primitivos, a análise sintática consumia grande fração do tempo de compilação e do esforço intelectual para escrever um compilador Nos compiladores primitivos, a análise sintática consumia grande fração do tempo de compilação e do esforço intelectual para escrever um compilador Hoje é considerada uma das fases mais fáceis de serem implementadas Hoje é considerada uma das fases mais fáceis de serem implementadas

47 1.4.3 – Ferramentas para cada componente Geradores de analisadores sintáticos: Utilizam algoritmos de analise muito eficientes, porém muito complexos para serem implementados à mão Utilizam algoritmos de analise muito eficientes, porém muito complexos para serem implementados à mão A mais conhecida: Yacc (Yet Another Compiler-Compiler) do sistema UNIX que também possui diversas versões para o sistema DOS A mais conhecida: Yacc (Yet Another Compiler-Compiler) do sistema UNIX que também possui diversas versões para o sistema DOS O programa gerado também é escrito em Linguagem C O programa gerado também é escrito em Linguagem C

48 1.4.3 – Ferramentas para cada componente Geradores de código intermediário: Produzem uma coleção de rotinas que, ao caminhar pela árvore sintática do programa, já com atributos calculados pelo analisador semântico, produzem o código intermediário Produzem uma coleção de rotinas que, ao caminhar pela árvore sintática do programa, já com atributos calculados pelo analisador semântico, produzem o código intermediário Analisadores de fluxo de dados: Importante ferramenta para a otimização do código intermediário Importante ferramenta para a otimização do código intermediário

49 1.4.3 – Ferramentas para cada componente Geradores de código objeto: Recebem como entrada uma coleção de regras que definem a tradução de cada tipo de comando do código intermediário em código de máquina ou Assembly Recebem como entrada uma coleção de regras que definem a tradução de cada tipo de comando do código intermediário em código de máquina ou Assembly Essas regras devem incluir detalhes suficientes para se escolher os locais adequados para alocação de variáveis (registradores, memória, pilha, etc) Essas regras devem incluir detalhes suficientes para se escolher os locais adequados para alocação de variáveis (registradores, memória, pilha, etc)


Carregar ppt "1.3 – Interpretadores 1.3.1 – Compiladores versus Interpretadores Execução de um programa gerado por compilador: Execução de um programa gerado por compilador:"

Apresentações semelhantes


Anúncios Google