• Nenhum resultado encontrado

Compiladores. Tipos. Regras semânticas. Expressões de tipos. Propriedades de Sistemas Tipados. Análise Semântica e checagem de tipos

N/A
N/A
Protected

Academic year: 2021

Share "Compiladores. Tipos. Regras semânticas. Expressões de tipos. Propriedades de Sistemas Tipados. Análise Semântica e checagem de tipos"

Copied!
8
0
0

Texto

(1)

Compiladores

Verificação de tipos –

Suporte ao runtime

Análise Semântica e

checagem de tipos

• Introdução: o que são tipos, para que servem...? – Representação de tipos

• Exemplo de verificação de tipos: – Uma linguagem simples

• Declarações, expressões, instruções, funções...

– Uso de regras semânticas para verificar a declaração de tipos. – Uso de regras semânticas para verificar o tipo de expressões – Uso de regras semânticas para verificar o tipo de comandos – Uso de regras semânticas para verificar o tipo de funções

Tipos

Definições:

– “Um tipo é uma coleção de valores computáveis que compartilham alguma propriedade estrutural” (Mitchell)

– “Uma coleção de valores que um fragmento de programa pode assumir durante execução” (Cardelli)

Porque ter tipos?

1. Se voce possui tipos e um sistema tipado (conjunto de regras), é possível checar em

pré-processamento para evitar erros de execução 2. Estrutura do programa & documentação 3. Manutenção & engenharia de software 4. Otimizações

Propriedades de Sistemas Tipados

• Uma linguagem tipada irá retornar um erro caso haja

inconsistência entre os tipos dos dados.

– Programador deve saber porque o programa não compila ou produz erro

• Verificação é não trivial: – Linguagens fortemente tipadas

• Nenhum erro de tipo passa despercebido • Exemplo: ML, ADA, Pascal (maior parte), Java

– Linguagens fracamente tipadas

• Erros de tipos podem ocorrer:

• C/C++: usando conversão, aritmética envolvendo ponteiros, passando final de um array, etc

• Balanço:

– Linguagens fracamente checadas precisam de alguma forma de “garbage collection” (podem impactar o tempo de execução) – Mas são mais fáceis de escrever programas (vantagem sobre

aspecto de tempo de desenvolvimento)

Regras semânticas

Exemplos:

– Se ambos operandos de operações aritméticas são inteiros, então o resultado é inteiro

– O resultado de um operador unário & é um ponteiro para o objeto descrito pelo operando

Denotar o tipo de uma construção de

linguagem:

– Expressão de Tipo: •tipo básico •tipo estruturado:

– formado ou formado através da aplicação de um operador de

Expressões de tipos

1. Um tipo básico é um expressão de tipo.

 Ex: boolean, char, integer, real, etc. Um tipo especial, type_error sinaliza um erro durante checagem

2. Se tipos de expressões podem ter nomes, um nome de tipo é uma expressão de tipo.

 Ex. Registros/struct

3. Construtores de tipos aplicados a tipos de expressões são expressões de tipos

 Array, ponteiro

4. Expressões de Tipos podem conter variáveis cujos valores são expressões de tipos

(2)

Construtores de tipos

1. Arranjos:

 Se T é um expressão de tipo, então array (I, T) é uma expressão de tipo.

 Ex: var A: array[1..10] of integer;

2. Produtos:

 Se T1 e T2 são expressões de tipos, então o produto cartesiano T1 x T2 é uma expressão de tipo.

3. Registros:

 Um registro é formado por campos com nomes

4. Ponteiros:

 Se T é uma expressão de tipo, então ponteiro(T) representa o tipo relativo ao ponteiro de um objeto de tipo T

5. Funções:

 Mapeamento de um tipo de domínio D em um intervalo de domínio R.

Ex: function f(a, b: char) : pointer to integer char x char -> pointer(integer)

Representação Gráfica

x char char pointer integer →

function f(a, b: char) : pointer to integer

Representação de tipos estruturados

struct s1 {

int large1;

short int small1;

};

struct s2 {

int large2 : 18;

short int small2 : 10;

};

32 16 16 18 10 4

large1 small1 large2 small2

Checagem compilação vs. execução

Checagem dinâmica x estática

Estática: em tempo de compilação

– Antes da execução (“para todas as entradas”)

– Compilador maior, restringe flexibilidade,

menos expressiva

Dinâmica: em tempo de execução

– Durante a execução (“com uma dada

entrada”)

– Pode ser muito tarde!

– Cara, mas mais flexível

Regras semânticas - declaração

P -> D ; E

D -> D ; D

D -> id : T

{addtype(id.entry, T.type) }

T -> char

{ T.type := char }

T -> integer

{ T.type := integer }

T ->

T

1

{T.type := pointer(T

1

.type) }

T

array [num] of T

1

Exemplo de checagem de Tipos

Uma linguagem de Programação simples:

– Variáveis são definidas antes de serem usadas – Semelhante a Pascal

N :integer; N mod 1999

P -> D ; E

D -> D ; D | id : T

T -> char | integer | array [ num ] of T |

T

E -> literal | num | id | E mod E | E [E] | E

(3)

Regras semânticas - declaração

P -> D ; E

D -> D ; D

D -> id : T

{addtype(id.entry, T.type) }

T -> char

{ T.type := char }

T -> integer

{ T.type := integer }

T ->

T

1

{T.type := pointer(T

1

.type) }

T

array [num] of T

1

{ T.type :=

array(1..num.val,

T

1

.tipo)}

Verificação de Tipos de Expressões

E -> literal

{ E.type := char }

E -> num

{ E.type := integer }

E -> id

{ E.type := lookup(id.entry) }

E -> E1 mod E2

{ E.type :=

if E1.type == integer and E2.type = integer then integer else type_error}

E -> E1 [E2]

{ E.type := if E2.type == integer and E1.type == array(s, t) then t else type_error }

E -> E

{E.type := if E1.type == pointer(t) then t

else type_error }

Conversões Implícitas

E.tipo := IF E1.tipo = inteiro e E2.tipo = inteiro

THEN inteiro

ELSE IF E1.tipo = real e E2.tipo = real

THEN real

ELSE IF E1.tipo = real e E2.tipo = inteiro

THEN real

ELSE IF E1.tipo = inteiro e E2.tipo = real

THEN real E →E1op E2 E.tipo := procurar(id.entrada) E →id E.tipo := real E →num.num E.tipo := inteiro E →num

REGRA SEMÂNTICA

PRODUÇÃO

OBS: Resolvidas em tempo de compilação

Conversões Implícitas e Desempenho

Pode melhorar tempo de execução

– Observação de Bentley [1982] em Pascal

– Com X sendo um array de reais:

•for i := 1 to n do X[i] := 1 – Leva 48,4 x n micro-secundos

•for i := 1 to n do X[i] := 1.0 – Leva 5,4 x n micro-secundos

– Compiladores inteligentes convertem 1 para

1.0 em tempo de compilação

Compiladores

Ambiente de suporte à

execução

Suporte à Execução

O Compilador gera código executável.

Mas nem tudo está conhecido antes que o

programa seja executado!

– Valores de parâmetros e funções, – Memória dinamicamente alocada,

– Dependendo do número de chamadas, qual endereço usar para achar o início de cada execução de um procedimento?

– Etc...

É preciso de um conjunto de rotinas (run-time

support package) carregado junto com o código

objeto gerado

(4)

Procedimentos

Os comandos são organizados em

procedimentos:

– declaração com um nome associado que

realiza uma dada tarefa

– definição:

•nome

•variáveis

•corpo

Procedimentos em ação (ativação)

Fluxo de controle:

– execução seqüencial (seqüência de passos)

– começa no início do corpo

– termina no final do corpo

– tempo de vida:

•seqüência de passos executados

– chamada de procedimentos:

•desvio de execução

•retorna o controle para o ponto imediatamente após o ponto de chamada

Exemplo: o Quicksort

program sort(input, output);

var a : array [0..10] of integer; procedure readarray;

var i: integer; begin

for i:=1 to 9 do read(a[i]); end;

function partition(y, z: integer): integer; var i, j, x, v: integer; begin ... end; procedure quicksort(m, n: integer);

var i: integer; begin if ( n > m) then begin i:= partition(m, n); quicksort(m, i-1); quicksort(i+1, n); end; end; begin

a[0] := -9999; a[10]:= 9999; readarray; quicksort(1, 9);

end.

Árvores de Ativação

Cada nó representa uma ativação de um

procedimento

A raiz representa a ativação do programa

principal

O nó de ‘a’ é pai de ‘b’ se e somente se o fluxo

de controle muda de ‘a’ para ‘b’

O nó de ‘a’ está à esquerda de ‘b’ se e somente

se a vida de ‘a’ ocorre antes de ‘b’

Árvores de ativação

execution begins s

Árvores de ativação

execution begins enter readarray s r

(5)

Árvores de ativação

execution begins enter readarray leave readarray s r

Árvores de ativação

execution begins enter readarray leave readarray enter quicksort(1, 9) s r q(1,9)

Árvores de ativação

execution begins enter readarray leave readarray enter quicksort(1, 9) enter partition(1, 9) s r q(1,9) p(1,9)

Árvores de ativação

execution begins enter readarray leave readarray enter quicksort(1, 9) enter partition(1, 9) leave partition(1,9) s r q(1,9) p(1,9)

Árvores de ativação

execution begins enter readarray leave readarray enter quicksort(1, 9) enter partition(1, 9) leave partition(1,9) enter quicksort(1,3) s r q(1,9) q(1,3) p(1,9)

Árvores de ativação

execution begins enter readarray leave readarray enter quicksort(1, 9) enter partition(1, 9) leave partition(1,9) enter quicksort(1,3) ... leave quicksort(5, 9) s r q(1,9) q(1,3) p(1,3)q(1,0) q(2,3) p(2,3)q(2,1) q(3,3) p(7,9)q(7,7) q(9,9) p(5,9)q(5,5) q(7,9) q(5,9) p(1,9)

(6)

Árvores de ativação

execution begins enter readarray leave readarray enter quicksort(1, 9) enter partition(1, 9) leave partition(1,9) enter quicksort(1,3) ... leave quicksort(5, 9) leave quicksort(1,9) s r q(1,9) q(1,3) p(1,3)q(1,0) q(2,3) p(2,3)q(2,1) q(3,3) p(7,9)q(7,7) q(9,9) p(5,9)q(5,5) q(7,9) q(5,9) p(1,9)

Árvores de ativação

execution begins enter readarray leave readarray enter quicksort(1, 9) enter partition(1, 9) leave partition(1,9) enter quicksort(1,3) ... leave quicksort(5, 9) leave quicksort(1,9) execution terminated s r q(1,9) q(1,3) p(1,3)q(1,0) q(2,3) p(2,3)q(2,1) q(3,3) p(7,9)q(7,7) q(9,9) p(5,9)q(5,5) q(7,9) q(5,9) p(1,9)

Pilhas de controle

O fluxo de controle

corresponde a uma

busca em

profundidade

na

árvore de ativação

Usa-se uma pilha

para controlar as

ativações de

procedimentos

ativos

s r q(1,9) q(1,3) p(1,3)q(1,0) q(2,3) p(1,9)

Organização de Memória

Como a memória do programa é armazenada ?

– código objeto gerado; – espaço para variáveis globais

•área estática

– pilha para ativação de procedimentos – espaço para memória dinâmica (heap).

Núcleo

Texto

Pilha

HEAP

Alocação de memória

Alocação estática

:

– reserva de memória é feita durante a compilação, de forma estática.

– Tipo (ou comprimento) do dado é conhecido em tempo de compilação

– Comprimento não é modificado durante a execução

do programa Área dados (SisOp)

Pilha

HEAP

Alocação de memória

Alocação dinâmica (HEAP)

:

– estruturas de dados referenciadas através de ponteiros, as áreas também são reservadas dinamicamente.

– áreas são alocadas e liberadas, sob o controle do programa

– alocadas na área de "heap", que cresce no sentido contrário ao da pilha

.

Área dados (SisOp)

Texto

Pilha

(7)

Alocação de memória

Alocação em pilha (STACK):

– Áreas para dados locais de procedimentos (subrotinas ou funções): devem ser alocadas dinamicamente.

– A alocação de espaço de memória somente pode ser realizada em tempo de execução, porque a ordem de chamadas é determinada pela execução do programa.

– Áreas são alocadas numa estrutura em pilha de ativação de procedimentos

Na pilha entram (e saem) registros de ativação.

Registro de Ativação

aponta para dados não-locais, armazenados em outros registros de ativação valor retornado parâmetros efetivos ponteiro de controle ponteiro de acesso estado da CPU variáveis locais temporários

podem ser passados ou retornados em registradores (para maior eficiência)

"program counter"e registradores aponta para o registro de ativação da rotina chamadora

Obs: pode-se usar registradores para alguns dos campos!

Ponteiros de Acesso

parâmetros e valores retornados apontadores de controle e estado da máquina variáveis locais e temporários

avaliados pela rotina chamadora

algumas informações são preenchidas pela rotina chamadora

área usada pela rotina chamada registro de ativação da rotina chamadora sp fp 2 ponteiros:

•frame_pointer (fp): aponta para o registro de ativação corrente • stack_pointer (sp): topo da pilha de registros de ativação

Código de Chamada

rotina chamadora

:

– avalia os parâmetros efetivos e os coloca na pilha – Registradores em uso pelo chamador são salvos em

memória

– armazena o endereço de retorno e o valor antigo do frame_pointer no registro de ativação da rotina chamada e atualiza o valor do frame_pointer;

rotina chamada

:

– salva valores de registradores e outras informações do estado da máquina;

– inicializa variáveis locais e começa sua execução.

Código de Retorno

rotina chamada:

– armazena o valor de retorno logo após o registro de ativação da rotina chamadora;

– restaura o apontador topo_ra e os registradores da máquina e desvia para o endereço de retorno dentro da rotina chamadora;

rotina chamadora:

– copia o valor retornado no seu próprio registro de ativação

Seqüência de chamada

valor retorno e parâmetros

Ponteiros e Estado Tmp / dados locais

Tmp / dados locais valor retorno e parâmetros

Registro Ativação chamador

Registro Ativação chamado Topo

Topo

(8)

Seqüência de chamada

valor retorno e parâmetros

Ponteiros e Estado Tmp / dados locais

Tmp / dados locais valor retorno e parâmetros

Ponteiros e Estado Topo Sob responsabilidade do chamador Sob responsabilidade do chamado

Seqüência de retorno

O chamado coloca um valor de retorno no

devido lugar...

– Ou seja, logo depois do registro do chamador!

Recupera o antigo valor de ‘topo’ no campo de

estado

– E também os outros registradores...

Desvia para o antigo PC

Importante: o chamador ainda pode acessar o

que está acima de ‘topo’

– ... Questão: o que está neste lugar?

Próxima aula...

Fim do suporte ao runtime:

– Passagem de parâmetros

– Escopo de variável

Referências

Documentos relacionados