• Nenhum resultado encontrado

Uma máquina virtual para uma linguagem concorrente intermédia

N/A
N/A
Protected

Academic year: 2021

Share "Uma máquina virtual para uma linguagem concorrente intermédia"

Copied!
108
0
0

Texto

(1)

Faculdade de Ciˆencias

Departamento de Inform´atica

Uma M´aquina Virtual para uma Linguagem Concorrente

Interm´edia

Roberto Rosa da Silva

DISSERTAC

¸ ˜

AO

MESTRADO EM ENGENHARIA INFORM ´

ATICA

Especializac¸˜ao em Engenharia de Software

2013

(2)
(3)

Faculdade de Ciˆencias

Departamento de Inform´atica

Uma M´aquina Virtual para uma Linguagem Concorrente

Interm´edia

Roberto Rosa da Silva

DISSERTAC

¸ ˜

AO

MESTRADO EM ENGENHARIA INFORM ´

ATICA

Especializac¸˜ao em Engenharia de Software

Dissertac¸˜ao orientada pelo Prof. Doutor Francisco Cipriano da Cunha Martins e co-orientado pelo Prof. Doutor Vasco Manuel Thudichum de Serpa Vasconcelos

(4)
(5)

Agradec¸o, em primeiro lugar, aos meus orientadores, ao Professor Doutor Francisco Martins e ao Professor Doutor Vasco Vasconcelos, pelo apoio e dedicac¸˜ao, pela atenc¸˜ao demonstrada ao longo deste ano e pela objetividade e rigor com que orientaram este tra-balho.

Agradec¸o tamb´em `a minha fam´ılia pelo apoio incondicional, paciˆencia e incentivo nos momentos mais dif´ıceis. Sem eles nada disto seria poss´ıvel.

Agradec¸o tamb´em aos meus amigos que estiveram ao meu lado durante esta fase, pelo companheirismo, forc¸a e apoio em certos momentos dif´ıceis.

(6)
(7)

A necessidade de aumentar o poder de computac¸˜ao exigiu a produc¸˜ao de hardware com maior capacidade de processamento. Esta situac¸˜ao levou os fabricantes a abandonar o modelo tradicional de Von Neumann e a adotar sistemas com m´ultiplos processado-res/n´ucleos. Para que o software acompanhasse a evoluc¸˜ao do hardware, as linguagens de programac¸˜ao concorrente ganharam protagonismo, mas com este regressaram os pro-blemas bem conhecidos dos sistemas concorrentes e distribu´ıdos, condic¸˜oes de corrida e impasses.

O mecanismo mais popular que suporta programac¸˜ao concorrente ´e o uso de m´ultiplos fios de execuc¸˜ao, que partilham vari´aveis. Neste contexto, propomos o desenvolvimento de uma m´aquina virtual para a linguagem MIL que imponha, estaticamente, que progra-mas bem tipificados s˜ao livres de condic¸˜oes de corrida e n˜ao entrem em impasses. A pri-meira propriedade ´e conseguida atrav´es da imposic¸˜ao de uma disciplina no uso de trincos atrav´es de tipos singulares; a segunda ´e alcanc¸ada atrav´es de anotac¸˜oes polim´orficas sobre a ordem pela qual os trincos s˜ao fechados. O sistema de tipos recusa programas cujos fios de execuc¸˜ao dependam, ciclicamente, uns dos outros no fecho de trincos. No entanto, a introduc¸˜ao de anotac¸˜oes sobre trincos pode introduzir uma complexidade desnecess´aria no processo de gerac¸˜ao de c´odigo. De forma a resolver este problema propomos um algoritmo para inferir as anotac¸˜oes polim´orficas sobre trincos. A inferˆencia faz-se da re-colha de restric¸˜oes locais sobre a ordem pela qual os trincos s˜ao fechados. As restric¸˜oes s˜ao passadas a um SMT que averigua a sua consistˆencia. Implement´amos o algoritmo e experiment´amo-lo extensivamente em programas MIL, nomeadamente, em c´odigo de alguma dimens˜ao, obtido atrav´es da compilac¸˜ao de programas escritos numa linguagem de objetos concorrentes.

Para al´em disso, pretendemos potenciar a integrac¸˜ao da linguagem MIL, quer com lin-guagens de alto n´ıvel que tenham o MIL como alvo de compilac¸˜ao, quer com o desenvol-vimento e integrac¸˜ao de outras ferramentas, como, depuradores para multiprocessadores.

Palavras-chave: MIL, linguagem interm´edia tipificada, condic¸˜oes de corrida, impasses, multiprocessador.

(8)
(9)

The constant need for computing power required the production of yet more powerful hardware. This induced manufacturers to abandon the traditional Von Neumann model and to embrace systems with multiple processors/nuclei. In order to make the software able to keep the evolution of the hardware, concurrent programming languages come to the rescue, but brought together the well race condition and deadlock problems.

The most popular mechanism that supports concurrent programming is the use of multiple threads, on top shared variables. Within this frame we intend to propose the development of a virtual machine for the MIL language able to guarantee that well-typed programs are free from race conditions and avoiding deadlock situations, statically. The first property is achieved the imposing of a discipline on the use of locks (through sin-gleton types); the second is achieved through polymorphic annotations dictating the order locks are closed. Type system refuses programs whose threads depend on each other in acquiring locks cyclically. However, annotating locks can cause unnecessary complexity in the process of code generation. In order to solve this problem we propose an algorithm to infer the polymorphic annotations of locks. The inference is made by collecting of local restrictions by that gather the order locks are acquired. There restrictions are the passed to an SMT solver that ascertains its consistency. We implemented the algorithm and tested it extensively using MIL programs, particularly in big code obtained by compiling program from a concurrent object oriented language into MIL.

In addition, we intend to enhance MIL language integration, both with high-level concurrent languages targeting MIL, and with the development and integration with de-velopment tools such as, debuggers for multiprocessors.

Keywords: MIL, typed intermediate language, race condition, deadlock, multiprocessor

(10)
(11)

Lista de Figuras xii

Lista de Tabelas xiii

1 Introduc¸˜ao 1 1.1 Motivac¸˜ao . . . 1 1.2 Contribuic¸˜oes . . . 3 1.3 Estrutura do documento . . . 4 2 Trabalho relacionado 5 2.1 Prevenc¸˜ao de impasses . . . 5

2.2 Compilac¸˜ao de linguagens de alto n´ıvel para linguagens interm´edias . . . 6

3 MIL 9 3.1 Sintaxe . . . 9

3.2 Exemplo MIL . . . 11

3.3 Sistema de Tipos . . . 13

4 Inferˆencia de anotac¸˜oes para evitar impasses 19 4.1 Anotac¸˜oes para evitar impasses . . . 19

4.2 Algoritmo de inferˆencia de anotac¸˜oes . . . 21

5 Implementac¸˜ao da M´aquina Virtual 27 5.1 Tecnologias utilizadas para a construc¸˜ao da m´aquina virtual do MIL . . . 27

5.1.1 SableCC . . . 27

5.1.2 Z3 . . . 28

5.2 Arquitetura . . . 28

5.3 Fase de An´alise . . . 29

5.3.1 An´alise L´exica e Sint´atica . . . 29

5.3.2 An´alise Semˆantica . . . 29

5.4 Fase de Interpretac¸˜ao . . . 42

5.4.1 M´aquina Abstrata . . . 42

(12)

6.1 Sintaxe . . . 45

6.2 Exemplo . . . 46

7 Compilac¸˜ao de MOOL em MIL 49 7.1 Func¸˜ao de Traduc¸˜ao . . . 49

7.2 Exemplo . . . 57

8 Conclus˜ao 65 A Exemplos Mil 67 A.1 Exemplo com readOnly . . . 67

A.2 Exemplo sem readOnly . . . 70

B Sistema de Tipos 73 B.1 Regras de heap values e heaps . . . 73

B.2 Regras de boa formac¸˜ao dos tipos . . . 73

B.3 Construc¸˜ao do kinding . . . 74

B.4 Regras para valores . . . 74

B.5 Regras de subtipos . . . 74

B.6 Regras para instruc¸˜oes . . . 75

C Regras da Semˆantica Operacional 77 C.1 M´aquina Abstrata . . . 77

C.2 Regras para instruc¸˜oes . . . 77

C.3 Func¸˜ao de avaliac¸˜ao . . . 78

D Restric¸˜oes em Z3 79

E Exemplo de traduc¸˜ao de MOOL em MIL 83

Bibliografia 91

´Indice 92

(13)

3.1 Arquitetura da M´aquina Abstrata . . . 10

3.2 Gram´atica . . . 11

3.3 O jantar dos fil´osofos escrito em MIL . . . 12

3.4 Regra de boa formac¸˜ao . . . 14

3.5 Regras para instruc¸˜oes . . . 14

3.6 Regra de heap value . . . 15

3.7 Excerto do programa do apˆendice A.1 . . . 16

3.8 Novas regras de tipos (extende Fig.3.5) . . . 17

3.9 Relac¸˜ao de ordem dos trincos K ` λ ≺ λ , K ` Λ ≺ λ , K ` λ ≺ Λ , K ` Λ ≺ Λ . . . 17

4.1 O jantar dos fil´osofos com uma indirec¸˜ao . . . 20

4.2 Algoritmo para colecionar restric¸˜oes . . . 22

5.1 Arquitetura da M´aquina Virtual . . . 29

5.2 Diagrama de classes . . . 30

5.3 Algoritmo para a instruc¸˜aonew . . . 34

5.4 Algoritmo para a regra T-STORE . . . 35

5.5 Algoritmo para as regras T-VALAPPe T-VALAPPLOCK . . . 35

5.6 Representa o predicadosetLessThanLock . . . 38

5.7 Representa o predicadolockLessThanSet . . . 39

5.8 Representa o predicadosetLessThanSet. . . 40

6.1 Sintaxe de utilizador . . . 46

6.2 Fatorial escrito em MOOL . . . 47

6.3 Classe Main . . . 48

7.1 Func¸˜ao de traduc¸˜ao dos m´etodos printInt eprintBool . . . 51

7.2 Blocospin . . . 55

7.3 Blocospin . . . 61

7.4 BlococalcProd1 . . . 61

7.5 BlocoscalcProd1 continuationewhile calcProd1 . . . 62

7.6 BlococontinueWhile calcProd1. . . 63

(14)

A.2 Resultado do exemplo . . . 70

A.3 Exemplo . . . 72

A.4 Resultado do exemplo . . . 72

B.1 Regras de heap values e heaps . . . 73

B.2 Regras de boa formac¸˜ao dos tipos . . . 73

B.3 Construc¸˜ao do kinding . . . 74

B.4 Regras para valores . . . 74

B.5 Regras de subtipos . . . 74

B.6 Regras para instruc¸˜oes . . . 75

C.1 M´aquina Abstrata . . . 77

C.2 Regras para instruc¸˜oes . . . 78

C.3 Func¸˜ao de avaliac¸˜ao . . . 78

(15)

4.1 Restric¸˜oes geradas durante a primeira passagem do algoritmo . . . 23

4.2 Restric¸˜oes geradas durante a segunda passagem do algoritmo . . . 24

5.1 Constantes e vari´aveis de trincos . . . 36

5.2 O algoritmo numa bancada de trabalho . . . 41

D.1 Restric¸˜oes da primeira passagem em Z3 . . . 80

D.2 Restric¸˜oes da segunda passagem em Z3 . . . 81

(16)
(17)

Introduc¸˜ao

1.1

Motivac¸˜ao

A utilizac¸˜ao da inform´atica na resoluc¸˜ao de problemas de maior dimens˜ao e complexidade implica, naturalmente, uma necessidade permanente de hardware com maior capacidade de processamento. Esta situac¸˜ao fez com que, num passado recente, os fabricantes tenham abandonado o modelo tradicional de Von Neumann, com uma unidade central de proces-samento, e adotado sistemas com m´ultiplos processadores ou com m´ultiplos n´ucleos por processador. Hoje em dia, a maior parte dos aparelhos eletr´onicos usa processadores com m´ultiplos n´ucleos, desde computadores pessoais a telem´oveis.

No entanto, ´e fundamental que o software acompanhe a evoluc¸˜ao do hardware, de forma a tirar partido do poder de computac¸˜ao extra, oferecido pelos sistemas com pro-cessamento paralelo. Torna-se imperioso o desenvolvimento de novas linguagens de programac¸˜ao concorrentes que ajudem os programadores a lidar com as dificuldades adi-cionais, colocadas pela programac¸˜ao com m´ultiplos fios de execuc¸˜ao. O tema n˜ao ´e novo. Problemas decorrentes de condic¸˜oes de corrida e de impasse remontam aos prim´ordios da inform´atica. A novidade est´a na necessidade generalizada de recorrer `a programac¸˜ao concorrente, necessidade essa que antes se restringia a um grupo limitado de profissionais altamente competentes.

As mudanc¸as ao n´ıvel das linguagens de programac¸˜ao abrangem os v´arios n´ıveis de abstrac¸˜ao, desde as linguagens de alto n´ıvel at´e `as linguagens de baixo n´ıvel. Devido aos m´ultiplos processamentos que podem ocorrer em simultˆaneo, ´e fundamental garantir que programas escritos em linguagens de alto n´ıvel n˜ao incorram em condic¸˜oes de corrida ou em impasses, e que estas verificac¸˜oes sejam preservadas pelo processo de compilac¸˜ao.

As linguagens de programac¸˜ao de alto n´ıvel sofrem transformac¸˜oes substanciais, por exemplo, a criac¸˜ao de primitivas de concorrˆencia num n´ıvel adequado de abstrac¸˜ao. Estas transformac¸˜oes equilibram o poder adicionado pela programac¸˜ao concorrente, e dimi-nuem a complexidade nas aplicac¸˜oes. Uma poss´ıvel abordagem para expressar e verificar se os programas das linguagens de alto n´ıvel n˜ao entram em condic¸˜oes de corrida nem em

(18)

impasses, em tempo de execuc¸˜ao, passa por dotar as linguagens interm´edias de tipos, e ti-rar proveito das propriedades de seguranc¸a desses tipos, impostas pelos sistemas de tipos. A construc¸˜ao de compiladores que preservam tipos, ao traduzir uma linguagem de alto n´ıvel tipificada numa linguagem interm´edia tipificada, ajuda na obtenc¸˜ao de propriedades da linguagem fonte.

Neste sentido, para se tirar o melhor proveito de processadores com m´ultiplos n´ucleos, os programadores tˆem de passar a desenvolver software adequado a este novo paradigma, ou seja, tˆem de passar a dominar a programac¸˜ao concorrente e paralela.

O mecanismo mais popular que suporta programac¸˜ao concorrente ´e o uso de m´ultiplos fios de execuc¸˜ao [3], que permite que v´arios programas sejam executados ao mesmo tempo e que partilhem vari´aveis. Tal como referimos anteriormente, a programac¸˜ao com m´ultiplos fios de execuc¸˜ao coloca dificuldades como: condic¸˜oes de corrida (race conditi-ons) e impasses.

Uma condic¸˜ao de corrida ocorre quando dois fios de execuc¸˜ao tentam aceder `a mesma vari´avel e, pelo menos um dos fios de execuc¸˜ao escreve na vari´avel.

Uma situac¸˜ao de impasse ocorre quando um programa n˜ao progride, ou seja, quando n˜ao permite que dois ou mais fios de execuc¸˜ao continuem as suas execuc¸˜oes, ficando, por isso, bloqueadas.

A linguagem interm´edia MIL ´e apropriada para o desenvolvimento baixo n´ıvel de sis-temas concorrentes com uso de mem´oria partilhada. O seu sistema de tipos garante que programas MIL bem tipificados n˜ao possuem condic¸˜oes de corrida nem impasses [26, 27]. A primeira propriedade ´e conseguida atrav´es da imposic¸˜ao criteriosa de uma disciplina no uso de trincos, atrav´es da utilizac¸˜ao de tipos singulares [26]. Esta propriedade pode ser conseguida de forma est´atica, por meio de um sistema de tipos. Em [26], os autores ga-rantem que programas bem tipificados n˜ao incorrem em condic¸˜oes de corrida. O sistema de tipos garante que os programas est˜ao bem tipificados, evitando assim os erros mais comuns (saltos para zonas de mem´oria ilegais, operac¸˜oes sobre valores do tipo errado, etc.). O sistema de tipos do MIL faz cumprir uma pol´ıtica de utilizac¸˜ao de trincos que pro´ıbe: apanhar um trinco que esteja fechado; libertar um trinco que n˜ao est´a na posse do fio de execuc¸˜ao; e obrigar os fios de execuc¸˜ao a libertar todos os trincos no final da sua execuc¸˜ao.

A segunda propriedade, ausˆencia de impasses, ´e alcanc¸ada atrav´es de anotac¸˜oes po-lim´orficas, sobre a ordem pela qual os trincos devem ser fechados (ou tomados ou adqui-ridos) [3]. O sistema de tipos verifica as anotac¸˜oes polim´orficas e recusa programas cujos fios de execuc¸˜ao dependam ciclicamente uns dos outros, na obtenc¸˜ao de trincos. Toda-via, a anotac¸˜ao de programas pode complicar o processo de gerac¸˜ao de c´odigo, pois os conceitos de trinco e de ordem para apanhar o trinco podem n˜ao fazer parte da linguagem fonte.

(19)

quer com linguagens de alto n´ıvel que tenham o MIL como alvo de compilac¸˜ao, quer no desenvolvimento e integrac¸˜ao de outras ferramentas como, por exemplo, depuradores para multiprocessadores. Para al´em disso, pretendemos desenvolver um algoritmo para inferir as anotac¸˜oes polim´orficas referentes `a ordem pela qual os trincos devem ser fechados.

Neste contexto, vimos propor o desenvolvimento de um analisador semˆantico est´atico e de uma m´aquina virtual para a linguagem MIL, que imponha, em tempo de compilac¸˜ao, que programas bem tipificados n˜ao acedam a posic¸˜oes de mem´oria inv´alidas, sendo li-vres de condic¸˜oes de corrida e de impasses. Propomos tamb´em o desenvolvimento de um compilador de uma linguagem de objetos concorrentes (MOOL [7]) para a lingua-gem interm´edia concorrente MIL. O que ´e fundamental registar ´e o facto de ambas as linguagens serem fortemente tipificadas e da linguagem alvo possuir um sistema de ti-pos suficientemente rico para garantir que programas bem tipificados n˜ao incorram em condic¸˜oes de corrida e n˜ao possuam impasses. Ambas as linguagens, tal como os res-petivos paradigmas subjacentes, diferem substancialmente. A linguagem de alto n´ıvel ´e orientada a objetos, ´e baseada na composic¸˜ao e interac¸˜ao entre diversas unidades de software, e a sincronizac¸˜ao ´e semelhante ao mecanismo usado em Java, para evitar inter-ferˆencia entre fios de execuc¸˜ao. A linguagem alvo ´e imperativa e a sincronizac¸˜ao ´e feita atrav´es de mem´oria partilhada. O processo de traduc¸˜ao de MOOL para MIL n˜ao ´e direto nem trivial, pois os tipos da linguagem alvo s˜ao bastante ricos. A func¸˜ao de traduc¸˜ao que definimos ´e uma especificac¸˜ao formal do compilador. A traduc¸˜ao da linguagem MOOL para MIL cont´em a traduc¸˜ao de tipos, de valores, express˜oes, m´etodos, classes e operado-res booleanos. O principal objetivo ´e permitir que o compilador produza programas MIL corretamente tipificados a partir de c´odigo fonte correto.

1.2

Contribuic¸˜oes

O analisador semˆantico para a linguagem MIL evita condic¸˜oes de corrida, o acesso ilegal a tuplos e garante que um programa n˜ao atinja impasses. Para al´em disso, foi constru´ıda uma m´aquina virtual que interpreta os resultados dos programas.

Tal como referimos anteriormente, a anotac¸˜ao de programas pode complicar o pro-cesso de gerac¸˜ao de c´odigo. A fim de solucionar este problema, propomos um algoritmo para inferir as anotac¸˜oes polim´orficas referentes `a ordem pela qual os trincos devem ser fechados num programa MIL. Esta inferˆencia ´e efetuada atrav´es da recolha de restric¸˜oes locais sobre a ordem pela qual os trincos devem ser tomados em cada bloco de c´odigo. As restric¸˜oes s˜ao depois passadas a um SMT [14] que averigua a sua consistˆencia.

As contribuic¸˜oes deste trabalho s˜ao as seguintes:

• Implementac¸˜ao de um analisador semˆantico est´atico e de uma m´aquina virtual para a linguagem MIL;

(20)

• Criac¸˜ao de um algoritmo para inferir as necess´arias anotac¸˜oes polim´orficas sobre a ordem pela qual os trincos devem ser fechados num programa MIL, a fim de assegurar que os programas n˜ao atinjam impasses;

• Implementac¸˜ao em Z3 de um resolvedor das restric¸˜oes geradas pelo algoritmo; teste do algoritmo atrav´es da sua aplicac¸˜ao a um grande leque de programas MIL; • Implementac¸˜ao de um compilador de uma linguagem de objetos concorrentes para

uma linguagem interm´edia concorrente.

1.3

Estrutura do documento

Este trabalho encontra-se organizado em sete cap´ıtulos. O cap´ıtulo 2 apresenta uma s´ıntese dos trabalhos j´a realizados e que est˜ao relacionados com o que pretendemos de-senvolver.

O cap´ıtulo 3 define a linguagem MIL. A primeira secc¸˜ao (3.1) define a linguagem MIL atrav´es da gram´atica da linguagem. Na segunda secc¸˜ao (3.2) ´e apresentado um exemplo de um programa MIL. Por fim (3.3) apresenta o sistema de tipos, que define a an´alise semˆantica.

O cap´ıtulo 4 apresenta a discuss˜ao dos problemas da inferˆencia de anotac¸˜oes, em par-ticular, como ´e que a informac¸˜ao sobre as restric¸˜oes flui atrav´es do grafo de chamada dos blocos de c´odigo. Na secc¸˜ao 4.2 apresenta-se, detalhadamente, o algoritmo ilustrando-o com exemplos.

O cap´ıtulo 5 descreve o modo como foi desenvolvida a m´aquina virtual para a MIL. Na secc¸˜ao 5.1 s˜ao abordadas as tecnologias usadas para a construc¸˜ao da m´aquina virtual. A secc¸˜ao 5.2 trata da arquitetura da m´aquina virtual e a secc¸˜ao 5.3 apresenta o modo como a fase de an´alise foi implementada e os elementos necess´arios para a sua construc¸˜ao. Esta inclui um diagrama de classes que representa os tipos da linguagem MIL e apresenta a resoluc¸˜ao das restric¸˜oes em Z3. Na secc¸˜ao 5.4 ´e abordado o modo como a fase de s´ıntese foi implementada.

O cap´ıtulo 6 define a linguagem de alto n´ıvel (MOOL). No cap´ıtulo 7 ´e descrita a func¸˜ao de traduc¸˜ao que definimos, uma especificac¸˜ao formal do compilador.

O ´ultimo cap´ıtulo apresenta as conclus˜oes e o trabalho que nos propomos realizar num futuro pr´oximo.

(21)

Trabalho relacionado

Este cap´ıtulo apresenta uma discuss˜ao mais extensa de trabalhos relacionados com este tema, analisando as principais linhas de pesquisa que inspiraram diretamente esta tese.

A implementac¸˜ao do nosso analisador semˆantico e da m´aquina virtual assenta sobre um prot´otipo j´a desenvolvido para a linguagem MIL. Este prot´otipo que se encontra em http://gloss.di.fc.ul.pt/mil/, foi desenvolvido por Tiago Cogumbreiro, Vasco Vasconcelos e Francisco Martins. A especificac¸˜ao da linguagem MIL [11], implementada neste prot´otipo, ´e diferente da que se efetuou no decorrer desta tese, porque o seu objetivo ´e uniformizar a sintaxe da linguagem.

2.1

Prevenc¸˜ao de impasses

A literatura sobre sistemas de tipos para a prevenc¸˜ao de impasses em linguagens baseadas em trincos ´e vasta. Coffman et al. classificam o problema de impasses em trˆes categorias: detec¸˜ao e recuperac¸˜ao, impedimento e prevenc¸˜ao [10]. Na primeira categoria, detec¸˜ao e recuperac¸˜ao, existem alguns trabalhos que verificam se programas atingem impasses, em tempo de execuc¸˜ao. Cunningham et al. inferem trincos numa linguagem orientada a objetos, mas usam um mecanismo em tempo de execuc¸˜ao para detetar quando ´e que a aquisic¸˜ao de um trinco, por parte de um fio de execuc¸˜ao, atinge um impasse [12]. Java PathFinder [6] e Driver Verifier [2] identificam violac¸˜oes na disciplina do uso de trincos, por via de testes de software.

Na categoria de impedimento, Boudol apresenta uma semˆantica livre de impasses para uma linguagem de programac¸˜ao concorrente, funcional e imperativa, na qual os trincos s˜ao associados a ponteiros [4]. A semˆantica impede estados inconsistentes por depender de uma an´alise est´atica de programas, atrav´es de um sistema de tipos e efeitos.

O nosso trabalho assenta sobre a terceira categoria, prevenc¸˜ao. Flanagan e Abadi apre-sentam uma linguagem funcional com referˆencias mut´aveis [15]. De forma a resolver o problema de impasses, os trincos s˜ao introduzidos na verificac¸˜ao de tipos atrav´es de tipos singulares. Com esta introduc¸˜ao pode antecipar-se uma poss´ıvel presenc¸a de impasses,

(22)

bem como garantir a ausˆencia de condic¸˜oes de corrida nos programas. A prevenc¸˜ao de impasses tamb´em tem sido estudada no ˆambito de linguagens orientadas a objetos. Boya-pati et al. utilizam uma variante de tipos de prevenc¸˜ao de impasses em Java, realizando uma inferˆencia parcial de anotac¸˜oes, mas n˜ao das que s˜ao relacionadas com a ordem dos trincos [5]. Suenaga prop˜oe uma linguagem concorrente funcional idˆentica `a linguagem de Flanagan e Abadi, exceto na estrutura de blocos [24]. Esta linguagem inclui primitivas separadas para a obtenc¸˜ao e libertac¸˜ao de trincos, tal como no nosso caso.

O nosso algoritmo de inferˆencia tem como base o sistema de tipos de [27]. O sistema de tipos garante que programas MIL bem tipificados n˜ao possuam condic¸˜oes de corrida nem impasses. O algoritmo baseado nas regras do sistema de tipos recolhe as restric¸˜oes e passa-as a um SMT para verificac¸˜ao de consistˆencia.

2.2

Compilac¸˜ao de linguagens de alto n´ıvel para

lingua-gens interm´edias

De seguida, apresentamos alguns compiladores que traduzem linguagens de alto n´ıvel para linguagens interm´edias. O que distingue estes compiladores de outros, ´e que estes s˜ao certificados.

Greg Morrisett et al. apresentam um compilador para traduzir Systema F para a lin-guagem interm´edia tipificada (TAL) [23]. Uma das caracter´ısticas do TAL ´e fornecer um conjunto de abstrac¸˜oes embutidas, tais como: n´umeros inteiros, tuplos, polimor-fismo e etiquetas de c´odigo. Para cada uma destas abstrac¸˜oes s˜ao aplic´aveis apenas algumas operac¸˜oes, por exemplo, para operac¸˜oes aritm´eticas apenas ´e permitido o uso de inteiros e nas operac¸˜oes de transferˆencias de controlo apenas ´e permitido utilizar eti-quetas de c´odigo. Tal como na linguagem TAL, na linguagem MIL o objetivo do sis-tema de tipos ´e verificar se os programas est˜ao bem tipificados. Este trabalho mos-tra as etapas necess´arias para mos-transformar uma linguagem de alto n´ıvel em TAL, man-tendo as informac¸˜oes dos tipos. A primeira etapa da compilac¸˜ao converte c´odigo para continuation-passing style[21]. Na segunda etapa da compilac¸˜ao, chamada closure con-version [20], a traduc¸˜ao separa o c´odigo do programa de dados, reescrevendo, assim, func¸˜oes que esperam um argumento adicional. A terceira etapa define os valores do amontoado, que consistem em blocos de c´odigo. A quarta etapa faz alocac¸˜ao de mem´oria expl´ıcita. A ´ultima etapa da compilac¸˜ao realiza traduc¸˜oes, principalmente sint´aticas, como por exemplo, converter vari´aveis em registos. Uma diferenc¸a fundamental en-tre [23] e o nosso trabalho ´e que n˜ao h´a concorrˆencia em System F nem em TAL.

Adam Chlipala apresenta um compilador certificado do c´alculo lambda simplesmente tipificado para uma linguagem interm´edia [9]. A certificac¸˜ao de compiladores ´e um m´etodo que verifica a semˆantica do c´odigo gerado. Chlipala tenta mostrar as vantagens de fazer um compilador que preserva tipos e um compilador orientado a tipos. Os

(23)

com-piladores que preservam os tipos guardam a informac¸˜ao dada pelos tipos at´e `a ´ultima fase da compilac¸˜ao, permitindo, assim, uma compilac¸˜ao mais segura e que preserva a semˆantica. O compilador orientado a tipos (exemplo, Typed Intermediate Language for the ML language [TIL] [25]) demonstra que o uso de uma linguagem interm´edia, forte-mente tipificada, aumenta n˜ao s´o a seguranc¸a, mas tamb´em a eficiˆencia.

Por fim, Tiago Cogumbreiro et al. apresentam uma traduc¸˜ao que preserva os tipos do c´alculo π para o MIL [11]. Em c´alculo π, os processos comunicam atrav´es da passagem de mensagens, no MIL, os fios de execuc¸˜ao comunicam atrav´es de mem´oria partilhada. Para ajudar na traduc¸˜ao, foram usados monitores de Hoare, estes introduzem uma forma de sincronizar os fios de execuc¸˜ao. A traduc¸˜ao do c´alculo π para MIL suporta a traduc¸˜ao de tipos, de valores e de processos. Na nossa traduc¸˜ao, tamb´em ´e suportada a traduc¸˜ao de tipos e de valores. Por´em, como a linguagem MOOL n˜ao ´e uma linguagem de processos, n˜ao suportamos a traduc¸˜ao de processos, mas a de objetos.

(24)
(25)

MIL

Neste cap´ıtulo apresentamos a sintaxe e semˆantica da linguagem de programac¸˜ao MIL— Multithreaded Intermediate Language. A linguagem MIL destaca-se pela sua semˆantica est´atica imposta por um sistema de tipos, que garante, em tempo de compilac¸˜ao, que programas bem tipificados n˜ao acedam a posic¸˜oes de mem´oria inv´alidas e sejam livres de condic¸˜oes de corrida e de impasses.

A linguagem MIL destina-se a programar uma m´aquina abstrata com v´arios proces-sadores, cuja mem´oria principal ´e partilhada (cf. figura 3.1). Cada processador tem um conjunto de registos, uma mem´oria local, para instruc¸˜oes, e um conjunto de trincos fe-chados. A mem´oria principal ´e dividida em duas partes: um amontoado e uma piscina para os fios de execuc¸˜ao suspensos. O amontoado armazena blocos de dados e blocos de c´odigo: os primeiros s˜ao representados por tuplos e s˜ao partilhados entre os v´arios proces-sadores; os segundos s˜ao compostos por uma assinatura e por um conjunto de instruc¸˜oes. A assinatura cont´em a descric¸˜ao dos tipos dos registos utilizados e os trincos fechados necess´arios. A piscina de fios de execuc¸˜ao cont´em os fios de execuc¸˜ao que est˜ao `a espera de um processador livre.

3.1

Sintaxe

A figura 3.2 apresenta a gram´atica da linguagem MIL. Esta gram´atica est´a escrita segundo a notac¸˜ao Backus-Naur Form (BNF), notac¸˜ao muito utilizada para expressar gram´aticas de linguagens de programac¸˜ao [22].

Os programas MIL est˜ao organizados em blocos, que s˜ao identificados por etiquetas, representadas por l. Os programas podem tamb´em conter abreviaturas de tipos, o que permite definir identificadores de tipos globais, com o objetivo de atribuir um tipo a um identificador, permitindo a existˆencia de tipos recursivos. As etiquetas identificam trˆes tipos de blocos: os blocos de dados, os valores de trincos e os blocos de c´odigo.

Os valores da linguagem incluem os registos, os n´umeros inteiros, as etiquetas do programa, os valores de trinco, os tipos n˜ao inicializados (valor usado apenas na criac¸˜ao

(26)

CPU núcleo 1

CPU núcleo N

registos instruções

registos instruções

piscina para fios de execução amontoado

Figura 3.1: Arquitetura da M´aquina Abstrata

de um novo tuplo) e a aplicac¸˜ao de valores (permite instanciar os tipos polim´orficos). Os valores de trinco s˜ao 0 e 1; 0 representa um trinco aberto e 1 um trinco fechado. O modo de acesso permite definir o tipo de protec¸˜ao de um tuplo. O tuplo pode ser protegido por λ ou porreadOnly, e a ´unica forma de criar tuplos protegidos porreadOnly

´e usando a instruc¸˜aonewRO.

Uma sequˆencia de instruc¸˜oes termina ou com a instruc¸˜ao jump ou com a instruc¸˜ao

done. A instruc¸˜ao jump continua a execuc¸˜ao no bloco indicado na instruc¸˜ao de salto; a instruc¸˜ao done termina o fio de execuc¸˜ao, ficando o processador dispon´ıvel para executar outro fio de execuc¸˜ao.

Relativamente aos tipos, a linguagem cont´em inteiros, strings, vari´aveis de tipo (λ), lock(lock λ), tuplos, bloco de c´odigo (ΓrequiresΛ), o tipo n˜ao inicializado (?τ ), tipos po-lim´orficos e tipos existenciais. O tipo inteiro representa os valores inteiros. O tipo string representa sequencias de caracteres. A vari´avel de tipo ´e usada para descrever o tipo sin-gular do valor que est´a num trinco. O tipo lock descreve um trinco do programa. O tipo de tuplos (h~τ iπ) descreve um tuplo protegido por π. O tipo Γ requires Λ representa um bloco

de c´odigo; Γ indica quais os tipos esperados para os registos e Λ representa as permiss˜oes necess´arias para poder aceder `a etiqueta em quest˜ao. O tipo n˜ao inicializado descreve valores de um dado tipo, que n˜ao estejam inicializados. Este simboliza o tipo futuro de uma determinada posic¸˜ao de um tuplo, por exemplo, h?intiλ, indica que o tipo da posic¸˜ao 1 do tuplo n˜ao est´a inicializado, mas que ser´a um inteiro. Os tipos polim´orficos permitem abstrair tipos, e possibilitam o tratamento de v´arios tipos de uma forma homog´enea. Por fim, os tipos existˆencias tˆem a func¸˜ao de esconder a vari´avel de tipos, impedindo que os tipos fiquem dependentes desta.

(27)

registos r ::= r1 | . . . | rR

n´umeros inteiros n ::= . . . | -1 | 0 | 1 | . . .

strings s ::= ”...”

valores de trincos b ::= 0λ | 1λ

valores v ::= r | n | s | l | b | ?τ | v[λ] | pack τ, v as τ modo de acesso π ::= λ | readOnly

instruc¸˜oes ι ::=

dados/controlo de fluxo r := v | r := v + v | if r = 0λjump r | mem´oria r :=new τ | r := r[n] | r[n] := r |

r :=newRO h~vi

unpack ω, r :=unpack v |

lock r :=getSetLock r | unlock r | λ :: Lock |

concorrˆencia fork r

sequˆencia de instruc¸˜oes I ::= ι; I | jump r | done

tipos τ ::= int | string | λ | lock λ | h~τ iπ | Γ requires Λ | ?τ | ∃ω.τ | ∀α.τ | τ [τ ] | id

tipos de registos Γ ::= {r1: τ1, . . . , rn: τn}

permiss˜oes Λ ::= {λ1, . . . , λn}

amontoado de valores h ::= h~vi | {I} | b

entradas do amontoado e ::= type id = τ | l :: τ | l = h | λ :: Lock programas ou amontoados H ::= {e1, . . . , en}

Figura 3.2: Gram´atica

3.2

Exemplo MIL

A figura 3.3 ilustra uma implementac¸˜ao do famoso problema do jantar de fil´osofos [19] escrito em MIL. O programa ´e composto por quatro blocos de c´odigo, identificados pelas etiquetasmain,levantarGarfoEsquerdo, levantarGarfoDireito ecomerEFilosofar.

Comec¸amos por apresentar as assinaturas dos quatro blocos de c´odigo do programa. A primeira linha cont´em a assinatura do blocomain, que n˜ao indica restric¸˜oes, nem ao n´ıvel do tipo dos registos, nem dos trincos requeridos, o que se justifica por ser o ponto de entrada da execuc¸˜ao do programa. Por sua vez, a linha 11 cont´em a assinatura do blocolevantarGarfoEsquerdo. Neste caso, ´e requerido que aquando de um salto para, ou do lanc¸amento de um fio de execuc¸˜ao comlevantarGarfoEsquerdo, os registos r1 e r2 de-ver˜ao conter dois trincos que, neste caso, se encontram abstra´ıdos universalmente. J´a as definic¸˜oes de levantarGarfoDireito (linha 17) e decomerEFilosofar(linha 23) exigem, adici-onalmente, a posse do trincoe, na primeira, e dos trincoseed, na segunda.

(28)

main :: {} 2 main = {

g1::Lock r3 := new lock g1 unlock r3 −− primeiro garfo

4 g2::Lock r4 := new lock g2 unlock r4 −− segundo grafo

g3::Lock r5 := new lock g3 unlock r5 −− terceiro grafo

6 r1 := r3 r2 := r4 fork levantarGarfoEsquerdo[g1][g2] −− primeiro filosofo

r1 := r4 r2 := r5 fork levantarGarfoEsquerdo[g2][g3] −− segundo filosofo

8 r1 := r5 r2 := r3 fork levantarGarfoEsquerdo[g3][g1] −− terceiro filosofo done

10 }

levantarGarfoEsquerdo :: ∀ e ::Lock.∀ d::Lock.{r1: lock e, r2: lock d}

12 levantarGarfoEsquerdo = { r3 := getSetLock r1

14 if r3 == 0 jump levantarGarfoDireito[e][d] jump levantarGarfoEsquerdo[e][d]

16 }

levantarGarfoDireito :: ∀ e ::Lock.∀ d::Lock.{r1: lock e, r2: lock d} requires {e}

18 levantarGarfoDireito = { r3 := getSetLock r2

20 if r3 == 0 jump comerEFilosofar[e][d] jump levantarGarfoDireito[e][d]

22 }

comerEFilosofar :: ∀ e ::Lock.∀ d::Lock.{r1: lock e, r2: lock d} requires {e,d}

24 comerEFilosofar = { −− comer

26 unlock r1 −− pousa o garfo esquerdo unlock r2 −− pousa o garfo direito

28 −− pensar

jump levantarGarfoEsquerdo[e][d]

30 }

Figura 3.3: O jantar dos fil´osofos escrito em MIL

que representam os trˆes garfos que os fil´osofos partilham (linhas 3–5). Analisando a li-nha 3 em detalhe, o trincog1 ´e declarado (g1::Lock), de seguida ´e reservada mem´oria no amontoado para o guardar, ficando a referˆencia para a mem´oria guardada no registo r3

(r3 := new lock g1). Por ´ultimo, o trinco ´e aberto (unlock r3), pois o fio de execuc¸˜ao que cria um trinco fica com a sua posse e neste exemplo podemos liberta-lo de imediato. As linhas 6–8 lanc¸am os fios de execuc¸˜ao referentes aos trˆes fil´osofos. Por exemplo, no caso do fil´osofo 1, s˜ao carregados nos registos r1 e r2, as referˆencias respeitantes aos trincos g1 e g2 e, de seguida, a instruc¸˜ao fork lanc¸a um novo fio de execuc¸˜ao que ir´a executar o blocolevantarGarfoEsquerdo. Note-se que os parˆametros de tipo eeds˜ao ins-tanciados com os trincos g1 e g2, respetivamente. No instante do lanc¸amento de cada fio de execuc¸˜ao, os tipos dos registos em main coincidem com os tipos esperados pelo bloco levantarGarfoEsquerdo. Para tal, verifique-se que aquando do lanc¸amento do fio de execuc¸˜ao do terceiro fil´osofo (linha 8) o registo r1 refere o trincog3, enquanto o re-gistor2 refere o trinco g1, o que est´a de acordo com a instanciac¸˜ao dos argumentos de tipolevantarGarfoEsquerdo[g3][g1].

(29)

A instruc¸˜ao done (linha 9) termina o fio de execuc¸˜ao, deixando o processador dis-pon´ıvel para executar outro fio de execuc¸˜ao, que esteja a aguardar na piscina de fios de execuc¸˜ao.

Os blocos de c´odigolevantarGarfoEsquerdo e levantarGarfoDireito fecham os trincos e

ed, que representam os garfos esquerdo e direito do fil´osofo. O blocolevantarGarfoEsquerd otenta fechar o trincoe, utilizando a instruc¸˜aogetSetLock(linha 13). Esta instruc¸˜ao, de forma indivis´ıvel, obt´em o valor do trinco indicado pelo registo r1 e fecha-o. O teste na linha 14 verifica se o trinco estava aberto anteriormente. Se tal se verificar, signi-fica que o fio de execuc¸˜ao fechou o trinco e, nesse caso, o controle passa para o bloco

levantarGarfoEsquerdo. Caso contr´ario, volta a tentar fechar o trinco, utilizando o m´etodo da espera ativa (linha 15). O blocolevantarGarfoDireito ´e semelhante aolevantarGarfoEsquer do, excetuando o facto de o blocolevantarGarfoEsquerdo requerer que o trincoej´a tenha sido fechado, para al´em disso, tenta fechar o trincode, quando tal acontecer, salta para o bloco de c´odigocomerEFilosofar.

Por ´ultimo, no blococomerEFilosofar, o fil´osofo come e pousa os garfos, libertando, para tal, os trincos referidos por r1e r2 (linhas 26 e 27), pensa e inicia uma nova ronda (linha 29).

Para consultar, com maior detalhe, a semˆantica operacional, leia-se o apˆendice C.

3.3

Sistema de Tipos

Um sistema de tipos para uma linguagem de programac¸˜ao ´e composto por um conjunto de regras (de tipos) [8]. O prop´osito de um sistema de tipos ´e evitar a ocorrˆencia de erros de execuc¸˜ao no decorrer dos programas [8].

A descric¸˜ao de um sistema de tipos ´e representada por regras de inferˆencia (regras de tipos), compostas por sentenc¸as. A sentenc¸a mais importante ´e o sequente. Segue-se um exemplo de uma destas regras, em que K ´e o conjunto de permiss˜oes, Ψ ´e uma tabela que tem etiquetas como chave e, como valor, os tipos das etiquetas e em que Γ ´e uma tabela que tem registos como chave e, como valor, os tipos dos registos. A regra afirma que a etiqueta l tem o tipo τ em Ψ.

K; (Ψ, l : τ ); Γ ` l : τ

As regras de tipo afirmam a validade de determinadas sentenc¸as, com base noutras j´a conhecidas. Podemos tomar como exemplo:

K ` τ K; Ψ; Γ `?τ : ?τ

Cada regra de tipo ´e representada por premissas, acima da linha horizontal. A con-clus˜ao encontra-se abaixo dessa linha. Quando o n´umero de premissas ´e 0, a regra diz-se um axioma. Quando todas as premissas s˜ao satisfeitas, ent˜ao a conclus˜ao ´e verdadeira.

(30)

K ` ∀l.τ K ` τ0

K ` ∀l.τ [τ0]

Figura 3.4: Regra de boa formac¸˜ao

K ` h~τiλ K; Ψ; Γ{r : h ~?τ iλ}; Λ ` I

K; Ψ; Γ; Λ ` r := new h~τiλ; I (T-NEWTUPLE)

K; Ψ; Γ{r : lock λ}; Λ, λ ` I

K; Ψ; Γ; Λ ` r := new lock λ; I (T-NEWLOCK) K; Ψ; Γ ` r0: h..τ

n..iπ K; Ψ; Γ{r : τn}; Λ ` I τn 6=? π ∈ Λ

K; Ψ; Γ; Λ ` r := r0[n]; I (T-LOAD)

K; Ψ; Γ ` ~v : ~τ K; Ψ; Γ{r : h~τireadOnly}; Λ ` I

K; Ψ; Γ; Λ ` r := newRO h~viI (T-NEWRO) K, λ :: Lock; Ψ; Γ; Λ ` I

K; Ψ; Γ; Λ ` λ :: Lock; I (T-LOCK)

Figura 3.5: Regras para instruc¸˜oes

O sistema de tipos proposto para a linguagem MIL segue a tradic¸˜ao das linguagens interm´edias tipificadas, como por exemplo o TAL [23]. As verificac¸˜oes do sistema de tipos s˜ao efetuadas estaticamente, ou seja, determinadas em tempo de compilac¸˜ao.

Nesta secc¸˜ao, apresentamos novas regras que surgiram com a alterac¸˜ao da sintaxe da linguagem e as regras do sistema de tipos com anotac¸˜oes, de forma a mostrar como ´e efetuada a prevenc¸˜ao de impasses em programas anotados, apresentada em [27]. As restantes regras encontram-se no apˆendice B.

A regra da figura 3.4 apresenta uma regra de boa formac¸˜ao de tipos. O sequente (K ` ∀l.τ [τ0]) destas regras, tem de conter o conjunto de trincos, porque a verificac¸˜ao da boa formac¸˜ao dos tipos ´e baseada neste conjunto. Esta regra certifica que a aplicac¸˜ao de tipos est´a bem formada. Para tal, ´e necess´ario que os tipos τ e τ0 estejam bem formados. Para al´em disso o tipo ao qual ´e efetuada a aplicac¸˜ao tem de ser um tipo universal (∀l.τ ). Esta regra ´e utilizada para verificar a inicializac¸˜ao de tipos definidos, de forma a poder us´a-los ao longo dos programas.

As regras da figura 3.5 apresentam as regras de tipos para instruc¸˜oes. No sequente (K; Ψ; Γ; Λ `) destas regras, necessitamos do conjunto de trincos, do conjunto de per-miss˜oes e das tabelas Ψ e Γ porque, para a verificac¸˜ao das instruc¸˜oes, estas estruturas s˜ao ´uteis. A regra T-NEWTUPLE assegura que o tuplo de tipos tem de estar bem formado. Neste caso, ´e atribu´ıdo ao registo r o tuplo de tipos, em que os tipos do tuplo ficam como tipos n˜ao inicializados.

(31)

K ` τ K; Ψ{id : τ }; Γ; Λ

K ` type id = τ ; Ψ; Γ; Λ (T-TYPE)

Figura 3.6: Regra de heap value

lock. Para al´em disso, λ tamb´em ´e adicionado ao conjunto de permiss˜oes, porque o trinco λ ´e fechado neste ponto do programa.

Na regra T-LOADverifica-se se o registo r’ ´e um tuplo de tipos e se o π que protege o

tuplo de tipos, ´e um λ. No caso de ser λ, este tem que pertencer ao conjunto de permiss˜oes pois, caso contr´ario, o tuplo ´e protegido porreadOnly. O tipo que se encontra no ´ındice n do tuplo tem de estar inicializado, e ´e adicionado a Γ o registo r, com o tipo que est´a no ´ındice n. Pois no caso de o tipo n˜ao estar inicializado, ´e indicador de que n˜ao foi atribu´ıdo nenhum valor `a posic¸˜ao n do tuplo.

Na regra T-LOCK, o trinco λ ´e adicionado ao conjunto de trincos, porque o trinco ´e declarado neste ponto do programa.

O intuito da regra T-NEWRO ´e verificar se o tuplo de valores h~vi est´a bem formado. Ao registo r ´e atribu´ıdo um tuplo de tipos h~τ i, com os tipos dos valores do tuplo h~vi, que ´e protegido porreadOnly. Esta regra ´e eficaz quando um tuplo ´e partilhado por v´arios processadores e todos necessitam de ler o conte´udo do tuplo. Ao existir um tuplo prote-gido porreadOnly, n˜ao ´e necess´ario obter nenhum trinco para se aceder ao conte´udo do tuplo. Caso esta regra n˜ao existisse, seria necess´ario obter o trinco do tuplo partilhado por v´arios processadores, assim, todos os processadores teriam de esperar que o processador que tivesse o trinco fechado o libertasse. Por exemplo, nas figuras A.1 e A.3 apresenta-mos um exemplo que usufrui da instruc¸˜aonewRo(figura A.1) e outro que, em vez utilizar a instruc¸˜aonewRo, utiliza um trinco global (figura A.3). O objetivo de ambos os exem-plos ´e o mesmo: imprimir, usando dois processadores diferentes, os n´umeros de 50 at´e 60 e de 10 at´e 0. Com estes exemplos, pretendemos demonstrar a vantagem de usar a instruc¸˜aonewRo. Com a instruc¸˜aonewRo, os n´umeros n˜ao s˜ao apresentados na consola de forma sequencial, ou seja, os n´umeros de 50 at´e 60 est˜ao intercalados com os n´umero de 10 at´e 0, indicando que os processadores conseguem, em simultˆaneo, aceder `a estrutura definida no programa (linha 1). Por fim, sem a instruc¸˜aonewRo, os n´umeros s˜ao apresen-tados, na consola, de forma sequencial, ou seja, primeiro s˜ao mostrados os n´umeros de 50 at´e 60 e depois os n´umero de 10 at´e 0, ou vice-versa. Isto acontece porque um dos processadores fica `a espera que o outro processador liberte o trincolockReadque protege o tuplo<<int> guarded by b, lock b>.

A figura 3.6 representa o modo como os identificadores de tipos globais devem ser utilizados. ´E adicionado ao Ψ o idcom o tipo τ , e o tipo τ tem de estar bem formado. O objetivo desta regra ´e permitir definir os nossos pr´oprios tipos. Para al´em disso, possibilita a introduc¸˜ao de tipos recursivos na nossa linguagem.

(32)

printInt :: ∀ l2 :: Lock. {r1: lock l2 , r2: <int> guarded by l2, r3 : int , r4:∀ l3 :: Lock.{

r1:lock l3 , r2: <int> guarded by l3} requires {l3}} requires {l2}

2 printInt = { r32 := printLock 4 r33 := getSetLock r32 if r33 == 0 jump printIntCritical [ l2 ] 6 jump printInt [ l2 ] } 8

printIntCritical :: ∀ l2 :: Lock. {r1: lock l2 , r2: <int> guarded by l2, r3 : int , r4:∀ l3 :: Lock.{r1: lock l3 , r2: <int> guarded by l3} requires {l3}} requires {consoleLock, l2}

10 printIntCritical = {

r5 := console r5 [1] := r3 r3 := printLock

12 unlock r3 jump r4[l2]

14 }

Figura 3.7: Excerto do programa do apˆendice A.1

O exemplo da figura 3.7 ilustra o modo como se apresentam os resultados na consola. A apresentac¸˜ao de resultados na consola, neste caso, ´e conseguida atrav´es das etiquetas

printInt e printIntCritical da figura.

Para se poder visualizar o resultado de programas MIL, foram adicionadas duas en-tradas especiais:

• console :: <int, string>ˆconsoleLock

• printLock :: lock consoleLock

printLock = 0

Para se visualizar o resultado, ´e necess´ario guardar o valor a visualizar no tuplo que existe por defeito, o console. Como a linguagem MIL s´o tem, como tipos primitivos, inteiros e strings, ent˜ao o tuplo s´o tem o tipo int e string. No caso de vir a surgir, por exemplo, o tipoboolean, basta adicionar esse tipo ao tuplo, ficando:

• console: <int, string, boolean>ˆconsoleLock.

Assim, para se poder visualizar o resultado na consola seria necess´ario:

1. atribuir o lock consoleLocka um registo, para se poder fazer um getSetLock sobre esse registo e obter o valor do trinco;

2. fazer um salto condicional para a etiqueta que requer olock consoleLock;

3. atribuir ao registo r a etiqueta console que cont´em o tuplo especial que permite apresentar os resultados na consola;

(33)

kindings K ::= ∅ | K, λ :: Lock(Λ, Λ) τ <: τ0 ∀λ :: Lock(Λ1, Λ2).τ <: ∀λ ::Lock(Λ1, Λ2).τ0 (subtyping) K ` Λ1, Λ2 λ 6∈ Λ1, Λ2 K 6` Λ2 ≺ Λ1 K ` λ :: Lock(Λ1, Λ2) K ` λ :: Lock(Λ1, Λ2) K, λ ::Lock(Λ1, Λ2) ` τ K ` ∀λ :: Lock(Λ1, Λ2).τ

(well formed types) K ` λ K; Ψ; Γ ` v : ∀λ0::Lock(Λ1, Λ2).τ K ` Λ1 ≺ λ ≺ Λ2 K; Ψ; Γ ` v[λ] : τ [λ/λ0] (T-VALAPPLOCK) K; Ψ; Γ ` r : λ K; Ψ; Γ ` r0: Γrequires (Λ ] {λ}) K; Ψ; Γ; Λ ` I K ` Λ ≺ λ K; Ψ; Γ; Λ ` if r = 0λjump r0; I (T-CRITICAL) K ` λ :: Lock(Λ1, Λ2); Ψ; Γ ` v : ∃ω.τ K, λ :: Lock(Λ1, Λ2); Ψ; Γ{r : τ }; Λ ` I λ 6∈ K K; Ψ; Γ; Λ ` λ, r := unpack v; I (T-UNPACK) K ` λ :: Lock(Λ1, Λ2) K, λ :: Lock(Λ1, Λ2); Ψ; Γ; Λ ` I K; Ψ; Γ; Λ ` λ :: Lock(Λ1, Λ2); I (T-LOCK) K ` ∀~λ :: Lock(~Λ1, ~Λ2).Γrequires Λ K, ~λ :: Lock(~Λ1, ~Λ2); Ψ; Γ; Λ ` I

K; Ψ ` ∀~λ :: Lock(~Λ1, ~Λ2).Γrequires Λ {I} : ∅

(heap values)

Figura 3.8: Novas regras de tipos (extende Fig.3.5)

K(λ) = (Λ1, ) λ1 ∈ Λ1 K ` λ1 ≺ λ K(λ) = ( , Λ2) λ2 ∈ Λ2 K ` λ ≺ λ2 K ` λ1≺ λ2 K ` λ2 ≺ λ3 K ` λ1 ≺ λ3 K ` λi ≺ λ (1 ≤ i ≤ n) K ` {λ1, . . . , λn} ≺ λ K ` λ ≺ λi (1 ≤ i ≤ n) K ` λ ≺ {λ1, . . . , λn} K ` λ1 ≺ Λ (1 ≤ i ≤ n) K ` {λ1, . . . , λn} ≺ Λ

Figura 3.9: Relac¸˜ao de ordem dos trincos K ` λ ≺ λ , K ` Λ ≺ λ , K ` λ ≺ Λ , K ` Λ ≺ Λ

4. apresentar os resultados armazenando-os na posic¸˜ao 1 do registo r, no caso de in-teiros, e na posic¸˜ao 2, no caso de strings.

A figura 3.8 apresenta uma extens˜ao ao sistema de tipos base da linguagem MIL. Que tem como objetivo evitar impasses em programas MIL [27]. Os impasses, geralmente, s˜ao impedidos pela imposic¸˜ao de uma ordem parcial rigorosa de trincos, obrigando os programas a respeitar essa ordem quando adquiram trincos [3, 10, 15]. Como se pode verificar atrav´es das regras da figura 3.8, os trincos s˜ao decorados com Λ1 e Λ2. Λ1 representa o conjunto de trincos menores do que o trinco λ e Λ2 representa o conjunto de trincos maiores do que o trinco λ. Para fazer a verificac¸˜ao, ´e necess´ario ter uma relac¸˜ao (≺) de ordem entre os trincos, expressa na figura 3.9.

(34)

Na regra T-CRITICAL, obtemos uma restric¸˜ao em que o conjunto de permiss˜oes do tipo do registo r’ tem de ser menor que o trinco obtido pela regra T-CRITICAL, ou seja,

a restric¸˜ao ´e Λ ≺ λ. Na regra T-VALAPP, as restric¸˜oes obtidas s˜ao: λ ´e menor que o conjunto de trincos maiores de λ0e o conjunto menor de λ0´e menor do que λ. Para al´em de adicionar estas restric¸˜oes, a substituic¸˜ao das vari´aveis tamb´em ´e feita nos conjuntos de λ0. Por fim, as regras T-LOCK, T-UNPACK eheap values recorrem `a regra da boa formac¸˜ao dos tipos, para esta verificar se os conjuntos Λ1 e Λ2 est˜ao bem formados, ou seja, se os trincos que estes contˆem est˜ao no dom´ınio dos conjuntos de trincos, e adiciona duas restric¸˜oes: λ 6∈ Λ1, Λ2 e Λ1 ≺ Λ2.

A figura 3.9 apresenta a relac¸˜ao de ordem entre os trincos. O trinco λ1 ´e menor que o

trinco λ se o trinco λ1pertencer ao conjunto de trincos menores (Λ1) de λ ou se o trinco

λ pertencer ao conjunto de trincos maiores de λ1. O conjunto Λ1 ´e menor que o trinco λ,

se todos os trincos do conjunto Λ1 forem menores que o trinco λ. No caso, λ ≺ Λ2, λ ´e menor que o conjunto Λ2, se λ for menor que todos os trincos de Λ2. Tamb´em ´e feita a relac¸˜ao entre dois conjuntos, ou seja, o conjunto Λ1 ´e menor que o conjunto Λ2 se todos os trincos de Λ1 forem menores que cada trinco do conjunto Λ2.

(35)

Inferˆencia de anotac¸˜oes para evitar

impasses

4.1

Anotac¸˜oes para evitar impasses

Um impasse ocorre quando um programa n˜ao consegue progredir porque os seus fios de execuc¸˜ao necessitam de recursos que est˜ao detidos por outros fios de execuc¸˜ao e vice-versa. A figura 3.3 apresenta um caso simples de um programa que, quando executado, poder´a eventualmente atingir um impasse. Na verdade, h´a um escalonamento que per-mite que cada fil´osofo possa apanhar o garfo `a sua esquerda (levantarGarfoEsquerdo) e ficar indefinidamente `a espera de apanhar o garfo `a sua direita (ciclo de espera ativa em

levantarGarfoDireito). Esta situac¸˜ao ´e facilmente evitada substituindo a instruc¸˜aofork

levan-tarGarfoEsquerdo[g3][g1]da linha 8 porfork levantarGarfoEsquerdo[g1][g3], ou seja, trocando as voltas (ou antes, os brac¸os) ao terceiro fil´osofo.

Mas como determinar, sem executar o programa da figura 3.3, que este tem um esca-lonamento que pode levar a um impasse, e que a variante que propomos nunca atinge um impasse? O sistema de tipos apresentado na secc¸˜ao 3.3 consiste em decorar as vari´aveis de trinco com anotac¸˜oes (locais e polim´orficas) de ordem, conduzindo a uma ordem (par-cial) global para o fecho dos trincos. Por sua vez, o sistema de tipos verifica que de facto o programa fecha os trincos por esta ordem. A tarefa de anotar um programa pode com-plicar a gerac¸˜ao de c´odigo, porque em alguns casos a informac¸˜ao sobre a ordem de fecho de trincos est´a ausente do processo de compilac¸˜ao (isto foi-nos dado observar na escrita de um compilador de uma linguagem de objetos concorrentes em MIL, cap´ıtulo 7).

A figura 4.1 apresenta uma generalizac¸˜ao do blocolevantarGarfoEsquerdo (cf. figura 3.3), al´em de tornar expl´ıcitas algumas anotac¸˜oes polim´orficas sobre os trincos. O obje-tivo ´e ilustrar a dificuldade em inferir estas anotac¸˜oes. Note-se que este simples programa de 35 linhas cont´em 16 anotac¸˜oes relacionadas com ordens de trincos (linhas 4-5, 14-15, 22 e 29). Nesta vers˜ao, o blocolevantarGarfoEsquerdorecebe no registor6 o enderec¸o da sua continuac¸˜ao, usado na instruc¸˜aojump(linha 18) em lugar do nome expl´ıcito do bloco

levantarGarfoDireito, tal como acontece com o c´odigo na figura 3.3 (linha 14).

(36)

2 main = {

g1::Lock r3 := new lock g1 unlock r3 −− primeiro garfo

4 g2::Lock({g1},{}) r4 := new lock g2 unlock r4 −− segundo grafo g3::Lock({g1,g2},{}) r5 := new lock g3 unlock r5 −− terceiro grafo

6 r1 := r3 r2 := r4 r6 := levantarGarfoDireito [g1]

fork levantarGarfoEsquerdo[g1][g2] −− primeiro filosofo

8 r1 := r4 r2 := r5 r6 := levantarGarfoDireito [g2]

fork levantarGarfoEsquerdo[g2][g3] −− segundo filosofo

10 r1 := r5 r2 := r3 r6 := levantarGarfoDireito [g3]

fork levantarGarfoEsquerdo[g3][g1] −− terceiro filosofo

12 done

}

14 levantarGarfoEsquerdo::∀ e::Lock({},{}).∀ d::Lock.{r1:lock e,r2:lock d,

r6:∀ l ::Lock({e},{}).{r1:lock e,r2:lock l } requires {e}}

16 levantarGarfoEsquerdo = { r3 := getSetLock r1 18 if r3 == 0 jump r6[d] jump levantarGarfoEsquerdo[e][d] 20 } levantarGarfoDireito ::

22 ∀ e1::Lock({},{}).∀ d1::Lock({e1},{}).{r1:lock e1,r2:lock d1} requires {e1} levantarGarfoDireito = { 24 r3 := getSetLock r2 if r3 == 0 jump comerEFilosofar[e1][d1] 26 jump levantarGarfoDireito[e1][d1] } 28 comerEFilosofar::

∀ e2::Lock({},{}).∀ d2::Lock({},{}).{r1:lock e2,r2:lock d2} requires {e2,d2} 30 comerEFilosofar = {

...

32 r6 := levantarGarfoDireito [e2]

jump levantarGarfoEsquerdo[e2][d2]

34 }

Figura 4.1: O jantar dos fil´osofos com uma indirec¸˜ao

Voltemos a nossa atenc¸˜ao para o bloco main. Cada declarac¸˜ao de trinco (linhas 4-5) ´e decorada com dois conjuntos, por exemplo g2::Lock({g1},{}), que contˆem os trincos que devem ser fechados antes—{g1}—e depois—{}—de g2. Assim, ´e poss´ıvel estabe-lecer uma relac¸˜ao de ordem parcial entreg2e os demais trincos vis´ıveis neste ponto do programa. As declarac¸˜oes polim´orficas de blocos de c´odigo s˜ao tamb´em anotadas com conjuntos com igual semˆantica (vide linhas 14–15, 22 e 29). Na ausˆencia de uma dada anotac¸˜ao, o algoritmo que propomos na secc¸˜ao seguinte, anota a vari´avel de trinco, n˜ao com conjuntos concretos, mas com vari´aveis—L0, L1,. . . —sobre conjuntos de trincos. Ser´a tarefa do algoritmo concretizar estas vari´aveis de modo a que o programa passe no sistema de tipos.

A ordem pela qual os trincos s˜ao fechados ´e determinada quando um fio de execuc¸˜ao, tendo na sua posse alguns trincos fechados, tenta fechar o pr´oximo trinco. Neste ponto do programa ficamos com a indicac¸˜ao de que os trincos j´a fechados tˆem de ser menores do que o trinco que se est´a a tentar fechar. Pretendemos recolher esta informac¸˜ao (na forma

(37)

de restric¸˜oes) e depois verificar se a informac¸˜ao ´e consistente, ou seja, se as restric¸˜oes recolhidas n˜ao levam a concluir que para fechar um trinco ´e necess´ario ter j´a este mesmo trinco fechado.

Tomando o exemplo da figura 4.1, note-se que, na linha 25, se pretende fechar o trincod1 tendo j´a o trinco e1 fechado (veja-se a menc¸˜ao requires{e1} na assinatura do bloco de c´odigo, na linha 22). Mas repare-se que e1 e d1 n˜ao s˜ao trincos concretos do programa; antes, correspondem a parˆametros de tipo que s˜ao substitu´ıdos em tempo de execuc¸˜ao pelos trincos g1, g2 e g3. Esta informac¸˜ao ´e obtida atrav´es da an´alise do corpo dos blocos de c´odigo e ´e expressa em termos da informac¸˜ao local, na maior parte das vezes referente a parˆametros polim´orficos. A relac¸˜ao dos trincos concretos com os trincos polim´orficos ´e conseguida atrav´es das restric¸˜oes obtidas no algoritmo apresen-tado na secc¸˜ao seguinte. Tal como no exemplo da figura 3.3 h´a um escalonamento que permite que cada fil´osofo possa apanhar o garfo `a sua esquerda (levantarGarfoEsquerdo) e ficar indefinidamente `a espera de apanhar o garfo `a sua direita (ciclo de espera ativa em levantarGarfoDireito). O problema ´e resolvido, como no exemplo da figura 3.3, subs-tituindo a instruc¸˜aofork levantarGarfoEsquerdo[g3][g1]da linha 11 porfork levantarGarfoEs

querdo[g1][g3].

4.2

Algoritmo de inferˆencia de anotac¸˜oes

A figura 4.2 apresenta o pseudo-c´odigo do nosso algoritmo. Este efetua duas passagens sobre o c´odigo fonte. Na primeira passagem associa duas novas vari´aveisLi1eLi2a cada trinco li declarado e gera restric¸˜oes que relacionam Li1, Li2 e li (1.1). Estas vari´aveis s˜ao definidas sobre conjuntos de trincos e denotam os trincos a apanhar antes (Li1) e de-pois (Li2) do trinco em quest˜ao. As restric¸˜oes geradas capturam estes factos: o trinco li

´e apanhado ap´os todos os trincos em Li1 e antes de qualquer trinco em Li2. Estas trˆes restric¸˜oes,Li1 < li, li < Li2 eLi1 < Li2, s˜ao escritas abreviadamente comoLi1 < li < Li2. Adicionalmente, requeremos que os trincos deLi1e deLi2correspondam a trincos que es-tejam no ˆambito do bloco ou da instruc¸˜ao em causa. Caso contr´ario, a soluc¸˜ao encontrada pode ser inv´alida por mencionar trincos declarados noutro ponto do programa. No caso de um trinco li conter anotac¸˜oes (1.2), essas anotac¸˜oes s˜ao colecionadas nesta fase do algoritmo atrav´es de restric¸˜oes. Neste caso, as restric¸˜oes adicionadas registam o valor que as vari´aveis de conjuntos de li devem assumir, ou seja,Li1 = MieLi2 = Ni, em queLi1eLi2

s˜ao as vari´aveis associadas ao trinco li, eMie Nicorrespondem aos conjuntos definidos na anotac¸˜ao do trinco li.

A primeira passagem no exemplo da figura 4.1, enumera os conjuntos sequencial-mente (comec¸ando emL0), resulta que na linha 3 s˜ao associadas ag1as vari´aveisL0eL1

e as restric¸˜oesL0 < g1 < L1,L0⊆ ∅ eL1⊆ ∅. Na linha 14, por exemplo, s˜ao associadas ao

(38)

// Primeira passagem

1. Analisar cada bloco do programa:

1.1 para cada trinco li definido na assinatura de um bloco de c´odigo (da forma ∀li ::Lock.t), no corpo de um bloco (da forma li ::Lock), ou numa instruc¸˜ao unpack (da forma li , r:=unpack v), associar-lhe duas novas vari´aveis sobre conjuntos de trincos Li1 e Li2 e incluir as seguintes restric¸˜oes: Li1 < li < Li2, Li1 ⊆ Trincos,

Li2 ⊆ Trincos, em que Trincos representa o conjunto de trincos conhecidos no ˆambito do bloco ou da instruc¸˜ao em causa.

1.2 Se existirem anotac¸˜oes em trincos (da forma li ::Lock(Mi1,Mi2), Mi1

e Mi2 s˜ao conjuntos concretos que contˆem trincos do programa, podendo estes serem vazios), ent˜ao gerar as restric¸˜oes: Li1 = Mi1

e Li2 = Mi2, em que Li1 e Li2 est˜ao associados ao trinco li. // Segunda passagem

2. Analisar cada bloco do programa:

2.1 Se a instruc¸˜ao ´e um salto condicional sobre o trinco li (da forma

if r==0 jump v) com v do tipo {...} requires G, ent˜ao gerar a restric¸˜ao: G < li.

2.2 Se a instruc¸˜ao inclui a aplicac¸˜ao de um trinco li a um valor v

(da forma v[ li ]), em que o tipo de v ´e ∀m::Lock.t, e m est´a associado aos conjuntos Li1 e Li2, ent˜ao gerar as restric¸˜oes:

Li1 < li < Li2.

2.3 Se a instruc¸˜ao ´e um salto (da forma jump v) ou o lanc¸amento de um fio de execuc¸˜ao (da forma fork v), com v do tipo

{r1: t1 ,..., rn: tn} requires G, comparar os tipos ti que s˜ao da forma ∀li ::Lock.ui, com os tipos dos registos correspondentes conhecidos at´e ao momento (da forma ∀mi ::Lock.si). Se li est´a associado aos conjuntos Li1 e Li2 e mi aos conjuntos Mi1 e Mi2, ent˜ao gerar as restric¸˜oes: Li1 = Mi1, Li2 = Mi2.

3. Resolver as restric¸˜oes recolhidas usando um SMT

Figura 4.2: Algoritmo para colecionar restric¸˜oes

pois o trinco e encontra-se em ˆambito. Estes dois exemplos s˜ao referentes `a al´ınea 1.1 do algoritmo. Como o exemplo da figura 4.1 est´a parcialmente anotado, a regra 1.2 pode ser aplicada diversas vezes no mesmo. Por exemplo, na linha 5 (g3::Lock({g1,g2},{})) s˜ao adicionadas as restric¸˜oesL4 = {g1, g2}eL5 = {}, em queL4eL5s˜ao as vari´aveis associa-das ao trincog3. A tabela 4.1 apresenta todas as restric¸˜oes geradas na primeira passagem do algoritmo.

Na segunda passagem o algoritmo processa cada bloco de c´odigo e comporta-se de trˆes formas distintas consoante se trate de um teste (2.1), da instanciac¸˜ao de um valor polim´orfico (2.2) ou de uma instruc¸˜ao de salto ou de lanc¸amento de um fio de execuc¸˜ao (2.3). Em relac¸˜ao a (2.1), a recolha de restric¸˜oes ocorre na instruc¸˜ao if. Neste ponto do programa ficamos com a indicac¸˜ao de que os trincos j´a fechados tˆem de ser menores do que o trinco que est´a a tentar fechar-se. Esta regra pode ser aplicada em duas situac¸˜oes no

(39)

Linha Restric¸˜oes Al´ınea 3 L0 < g1 < L1,L0⊆ ∅ eL1⊆ ∅ 1.1 4 L2 < g2 < L3,L2⊆ {g1} eL3⊆ {g1} 1.1 L2 = {g1}eL3 = {} 1.2 5 L4 < g3 < L5,L4⊆ {g1, g2} eL5⊆ {g1, g2} 1.1 L4 = {g1, g2}eL5 = {} 1.2 14, 15 L6 < e < L7,L6⊆ ∅,L7⊆ ∅,L8 < d < L9,L8⊆ {e},L9⊆ {e}, 1.1 L10 < l < L11,L10⊆ {e, d} eL11⊆ {e, d} L6 = {},L7 = {},L10 = {e}eL11 = {} 1.2 22 L12 < e1 < L13,L12⊆ ∅,L13⊆ ∅,L14 < d1 < L15,L14⊆ {e1}, e 1.1 L15⊆ {e1} L12 = {},L13 = {},L14 = {e1}eL15 = {} 1.2 29 L16 < e2 < L17,L16⊆ ∅,L17⊆ ∅,L18 < d2 < L19,L18⊆ {e2}, e 1.1 L19⊆ {e2} L16 = {},L17 = {},L18 = {}eL19 = {} 1.2

Tabela 4.1: Restric¸˜oes geradas durante a primeira passagem do algoritmo

exemplo da figura 4.1. Na linha 18 pretende-se obter o trincoesem que tenha sido fechado qualquer outro trinco anteriormente. Na linha 25 pretende-se obter o trinco d1tendo j´a o trincoe1fechado (cf. requires {e1}na assinatura do bloco de c´odigo na linha 22). As restric¸˜oes adicionadas s˜ao, respetivamente,{} < ee{e1} < d1.

Em relac¸˜ao `a aplicac¸˜ao de valores (2.2) a restric¸˜ao adicionada regista o facto do trinco li respeitar a ordem do fecho dos trincos associado am, ou seja, ser fechado depois dos trincos denotados porLi1e antes dos deLi2. Esta regra pode ser aplicada por diversas vezes no exemplo que apresentamos. Por exemplo, na linha 6 (r6:=levantarGarfoDireito [g1]) s˜ao adicionadas as restric¸˜oesL12 < g1 < L13, em queL12eL13s˜ao as vari´aveis associadas ao trinco polim´orficoe1declarado na linha 21. A tabela 4.2 cont´em todas as restric¸˜oes geradas de acordo com esta regra, assinaladas por (2.2).

O tratamento das instruc¸˜oes jumpefork (2.3) ´e mais complicado porque h´a que de-terminar o tipo dos registos antes da execuc¸˜ao destas instruc¸˜oes, que ´e calculado pelo sistema de tipos. Vamos ilustrar a aplicac¸˜ao desta regra `a linha 7. O tipo dos registos relevantes para o lanc¸amento do fio de execuc¸˜ao s˜ao:

• r1:lock g1; • r2:lock g2;

• r6:∀ d1::Lock.{r1:lock g1, r2: lock d1} requires {g1}.

Tanto o registo r1 como o registo r2 n˜ao s˜ao do tipo universal, ou seja, n˜ao contˆem declarac¸˜oes de trincos polim´orficos. Neste caso, o que ´e relevante nas instruc¸˜oes fork

ejump ´e se os tipos dos registos, antes da execuc¸˜ao destas instruc¸˜oes, s˜ao subtipos dos tipos dos registos para onde ´e efetuado o salto ou o lanc¸amento do novo fio de execuc¸˜ao.

(40)

Linha Restric¸˜oes Al´ınea 6, 8, 10 L12 < g1 < L13,L12 < g2 < L13eL12 < g3 < L13 2.2 7 L6 < g1 < L7,L8[g1/e] < g2 < L9[g1/e], 2.2 L10[g1/e][g2/d] = L14[g1/e1] e L11[g1/e][g2/d] = L15[g1/e1] 2.3 9 L6 < g2 < L7,L8[g2/e] < g3 < L9[g2/e], 2.2 L10[g2/e][g3/d] = L14[g2/e1] e L11[g2/e][g3/d] = L15[g2/e1] 2.3 11 L6 < g3 < L7,L8[g3/e] < g1 < L9[g3/e], 2.2 L10[g3/e][g1/d] = L14[g3/e1] e L11[g3/e][g1/d] = L15[g3/e1] 2.3 18 L10 < d < L11e{} < e 2.2, 2.1 19 L6 < e < L7,L8[e/e] < d < L9[e/e], 2.2

L10[e/e][d/d] = L10[e/e]eL11[e/e][d/d] = L11[e/e] 2.3 25 L16 < e1 < L17, L18[e1/e2] < d1 < L19[e1/e2] e {e1} < d1 2.2, 2.1 26 L12 < e1 < L13eL14[e1/e1] < d1 < L15[e1/e1] 2.2 32 L12 < e2 < L13 2.2 33 L6 < e2 < L7,L8[e2/e] < d2 < L9[e2/e], 2.2 L10[e2/e][d2/d] = L14[e2/e1] e L11[e2/e][d2/d] = L15[e2/e1] 2.3

Tabela 4.2: Restric¸˜oes geradas durante a segunda passagem do algoritmo

Todavia, o tipo que considera as vari´aveis de conjuntos ´e o tipo universal. Um tipo univer-sal (∀ l1 :: Lock(L0, L1).t) ´e subtipo de outro (∀ m1::Lock(M0, M1).t) quando as vari´aveis de conjuntos menor (L0) e maior (L1) do trinco (l1) s˜ao iguais `as vari´aveis de conjuntos me-nor (M0) e maior (M1) do trinco (m1). Neste caso, o ´unico registo que ´e do tipo universal ´e or6. Com isto, o tipo do registor6delevantarGarfoEsquerdo, ap´os a instanciac¸˜ao porg1e

g2´er6:∀ l ::Lock.{r1:lock g1, r2: lock l } requires {g1}h´a que gerar as restric¸˜oesL10[g1/e] [g2/d]=L14[g1/e1]eL11[g1/e][g2/d]=L15[g1/e1], em queL10eL11s˜ao as vari´aveis associadas ao trinco polim´orfico l do blocolevantarGarfoEsquerdo e L14e L15 s˜ao as associadas ao trinco polim´orficod1do bloco levantarGarfoDireito.

Por ´ultimo, (3) as restric¸˜oes recolhidas s˜ao passadas a um SMT que afere da sua consistˆencia. Caso sejam consistentes, o SMT indica um modelo que instancia cada um dos conjuntos associados aos trincos, obtendo deste modo uma anotac¸˜ao v´alida; caso contr´ario, o SMT responde negativamente e o programa n˜ao tem qualquer anotac¸˜ao poss´ıvel. No caso de programas completamente anotados, o SMT n˜ao vai procurar obter conjuntos para as vari´aveis de conjuntos mas, no caso de programas em que n˜ao exis-tam anotac¸˜oes, o SMT vai procurar obter conjuntos para as vari´aveis de conjuntos. Ou-tra situac¸˜ao que pode ocorrer ´e ter um programa parcialmente anotado (exemplo da fi-gura 4.1) e, neste caso, o SMT vai procurar obter conjuntos para as vari´aveis de conjuntos cujo os trincos n˜ao tenham anotac¸˜oes. No caso de os trincos estarem anotados, o SMT

(41)

n˜ao vai procurar obter conjuntos para as vari´aveis de conjuntos desses trincos.

Um resolvedor SMT consegue determinar os conjuntos (Li), porque os programas MIL declaram um conjunto finito de trincos que pode ser determinado em tempo de compilac¸˜ao. Portanto, os conjuntos (Li) que pretendemos determinar s˜ao finitos e defi-nidos sobre um conjunto de trincos tamb´em finito.

(42)
(43)

Implementac¸˜ao da M´aquina Virtual

Neste cap´ıtulo damos conta das tecnologias sobre as quais o analisador semˆantico est´atico e a m´aquina virtual do MIL s˜ao constru´ıdos (secc¸˜ao 5.1), apresentamos a sua arqui-tetura geral e funcionamento (secc¸˜ao 5.2), distinguindo entre as fases de an´alise e de interpretac¸˜ao (secc¸˜ao 5.3 e secc¸˜ao 5.4).

5.1

Tecnologias utilizadas para a construc¸˜ao da m´aquina

virtual do MIL

A m´aquina virtual MIL aceita como entrada um ficheiro . mil com o c´odigo fonte. O conte´udo do ficheiro ´e analisado de acordo com a especificac¸˜ao da gram´atica MIL, ´e feita a an´alise semˆantica est´atica e ap´os a an´alise, a m´aquina virtual interpreta o fi-cheiro. A m´aquina virtual MIL ´e implementada em SableCC e Java. O SableCC ´e usado para gerar as classes Java que procedem `a verificac¸˜ao l´exica e sint´atica, enquanto que a an´alise semˆantica e interpretac¸˜ao ´e programada em Java. O resolvedor SMT usado para a verificac¸˜ao das restric¸˜oes ´e o Z3.

5.1.1

SableCC

O SableCC [16] ´e uma ferramenta que gera um analisador l´exico e sint´atico em Java a partir de um ficheiro que cont´em a especificac¸˜ao da gram´atica, simplificando a escrita de compiladores e interpretadores, pois gera um conjunto de classes Java, as quais contˆem os analisadores l´exicos e sint´aticos. O resultado da an´alise sint´atica ´e uma ´arvore sint´atica abstrata (AST) que representa o programa.

O SableCC oferece as seguintes funcionalidades: an´alise l´exica e sint´atica do pro-grama de entrada; construc¸˜ao autom´atica de uma AST do propro-grama fonte (a ser com-pilado) e criac¸˜ao de visitantes para percorrer a AST, de acordo com o padr˜ao de dese-nho visitante apresentado em GoF [18]. Este padr˜ao separa as estruturas de dados da implementac¸˜ao, ou seja, permite fazer operac¸˜oes sobre a estrutura. Atrav´es do uso deste

(44)

padr˜ao ´e poss´ıvel adicionar visitantes, para implementar as v´arias fases do analisador semˆantico e do interpretador.

5.1.2

Z3

O Z3 [13] ´e um resolvedor SMT (Satisfiability Modulo Theories) eficiente, sendo o estado de arte dos provadores de teoremas, desenvolvido pela Microsoft Research. Este resolve-dor permite verificar a consistˆencia de f´ormulas l´ogicas sobre uma ou v´arias teorias. As teorias que suporta s˜ao as seguintes: aritm´etica de n´umeros inteiros e reais, vetores de bits de tamanho fixo, matrizes, func¸˜oes n˜ao interpretadas e quantificadores.

O Z3 aceita como entrada um ficheiro que cont´em uma sequˆencia de comandos. ´E atrav´es destes comandos que o Z3 determina se as f´ormulas l´ogicas introduzidas s˜ao sa-tisfaz´ıveis. Internamente, o Z3 mant´em uma pilha que armazena as f´ormulas e declarac¸˜oes de cada programa.

Para verificar a consistˆencia das f´ormulas s˜ao usados dois comandos: o assert e o

check−sat. O primeiro acrescenta uma f´ormula `a pilha interna do Z3. O segundo deter-mina se as f´ormulas que se encontram na pilha s˜ao consistentes. Em caso afirmativo o Z3, devolve sat; caso contr´ario, devolve unsat. Um conjunto de f´ormulas ´e consistente se existe uma interpretac¸˜ao que faz com que todas as f´ormulas sejam verdadeiras [17]. No caso de um conjunto de f´ormulas ser consistente, o Z3 tem a capacidade de produzir o modelo que satisfaz as f´ormulas.

A teoria usada para inferir as anotac¸˜oes definidas no cap´ıtulo 4 foi a de vetores de bits. Atrav´es dos vetores de bits representamos os trincos e as vari´aveis de conjuntos. Os trincos s˜ao conjuntos singulares e, ´e atribu´ıdo um bit diferente para representar cada trinco do programa. As vari´aveis de conjuntos s˜ao expressas como conjuntos de trincos, isto ´e, como cada trinco ´e representado por um bit diferente, facilmente determinamos quais os trincos que o conjunto cont´em. Assim, a partir desta teoria, ´e poss´ıvel encontrar conjuntos de bits que satisfac¸am as restric¸˜oes passadas ao Z3.

5.2

Arquitetura

A arquitetura da m´aquina virtual do MIL est´a dividida em duas fases: fase de an´alise e fase de interpretac¸˜ao. A fase de an´alise verifica o c´odigo fonte, onde a estrutura e significado do programa s˜ao reconhecidos. Esta fase est´a dividida em trˆes etapas: l´exica, sint´atica e semˆantica est´atica. A fase de interpretac¸˜ao efetua a execuc¸˜ao do programa MIL.

A figura 5.1 apresenta o modo como a m´aquina virtual do MIL est´a organizada. As conex˜oes entre componentes (setas tracejadas) representam o fluxo de controlo.

O componenteLexer ´e o respons´avel pela an´alise l´exica. ´E executado a partir do com-ponenteParser(componente respons´avel pela an´alise sint´atica), quando este necessita de obter o pr´oximo s´ımbolo. Finda a an´alise l´exica e sint´atica sem erros o controlo ´e passado

Referências

Documentos relacionados

O termo extrusão do núcleo pulposo aguda e não compressiva (Enpanc) é usado aqui, pois descreve as principais características da doença e ajuda a

A apixaba- na reduziu o risco de AVE e embolismo sistêmico em mais de 50%: houve 51 eventos entre os pacientes do grupo apixabana versus 113 no grupo do AAS

Considera-se que a interdisciplinaridade contribui para uma visão mais ampla do fenômeno a ser pesquisado. Esse diálogo entre diferentes áreas do conhecimento sobre

• Quando o navegador não tem suporte ao Javascript, para que conteúdo não seja exibido na forma textual, o script deve vir entre as tags de comentário do HTML. &lt;script Language

num ritmo aproximado de uma flexão em cada 3 segundos, partindo da posição facial, mantendo o corpo em extensão, atingindo ou ultrapassando o nível de prestação definido (

Nos tempos atuais, ao nos referirmos à profissão docente, ao ser professor, o que pensamos Uma profissão indesejada por muitos, social e economicamente desvalorizada Podemos dizer que

Various agroindustrial by-products have been used as elephant grass silage additives, such as orange pulp, that promoted better fermentation and raised the nutritive value of the

dois gestores, pelo fato deles serem os mais indicados para avaliarem administrativamente a articulação entre o ensino médio e a educação profissional, bem como a estruturação