Outro dos aspectos essenciais para um implementa¸c˜ao segura dos objectos remotos (e tamb´em da adop¸c˜ao de esquemas de sincronismo com menor conten¸c˜ao), assenta na necessidade de o sistema de compila¸c˜ao detectar – sem falhas – quais os servi¸cos que n˜ao tˆem efeitos colaterais para o estado vis´ıvel do programa5. A invoca¸c˜ao de servi¸cos
remotos s´o ser´a estaticamente permitida nesses casos.
Nesta perspectiva declarativa, n˜ao faz sentido permitir a invoca¸c˜ao de procedimen- tos em entidades remotas (j´a que estes, por defini¸c˜ao, s˜ao comandos, e como tal podem mudar o estado do programa). Existem duas poss´ıveis excep¸c˜oes a esta regra, ambas a serem estudadas mais profundamente no futuro. A primeira ´e o caso dos servi¸cos de execu¸c˜ao ´unica (principalmente as fun¸c˜oes), j´a que, mesmo que tenham efeitos colate- rais, estes podem n˜ao ser considerados como resultado da invoca¸c˜ao remota, mas t˜ao s´o da pr´opria semˆantica desses servi¸cos. O resultado do programa ´e o mesmo, indepen- dentemente do processador em particular respons´avel pela primeira invoca¸c˜ao desses servi¸cos. O segundo caso tem a ver com possibilidade de virem a existir atributos locais a cada processador (sec¸c˜ao 5.21). Servi¸cos que utilizem esta variedade de atributos n˜ao tˆem, pelo menos nesse aspecto, efeitos colaterais para a execu¸c˜ao dos restantes pro- cessadores pelo que podem ser considerados puros no que a esse aspecto diz respeito. Aparte destas duas poss´ıveis excep¸c˜oes, resta a possibilidade de invoca¸c˜oes a atributos ou a fun¸c˜oes.
O primeiro caso, n˜ao levanta problemas de maior, j´a que, novamente por defini¸c˜ao, a observa¸c˜ao (segura) do estado de atributos n˜ao produz efeitos colaterais,
No caso das fun¸c˜oes, ´e necess´ario que o sistema de compila¸c˜ao analise apropriada- mente o respectivo algoritmo, assim como o algoritmo de todos os servi¸cos utilizados, sejam do pr´oprio objecto ou de outros,
A simplicidade da linguagem Eiffel, ou n˜ao permitir a atribui¸c˜ao de valor a argu- mentos formais de fun¸c˜oes (que s˜ao s´o de leitura), e ao deixar apenas que se atribua o valor de atributos dentro da respectiva classe, facilita tremendamente este problema. Assim as ´unicas instru¸c˜oes imperativas elementares que s˜ao respons´aveis pela mudan¸ca de estado dos objectos s˜ao as instru¸c˜oes de atribui¸c˜ao de valor. E mesmo essas s´o ser˜ao importantes caso n˜ao se apliquem a vari´aveis locais (j´a que estas, por si s´o, n˜ao afectam o estado vis´ıvel de nenhum objecto)
Uma vez que uma fun¸c˜ao pode invocar outras fun¸c˜oes (incluindo ela pr´opria), s´o pode haver a certeza de que uma fun¸c˜ao ´e pura, se o seu algoritmo n˜ao contiver atri- bui¸c˜oes de valor a atributos, e se n˜ao invocar nenhuma outra fun¸c˜ao que n˜ao seja tamb´em pura.
Em linguagens orientadas por objectos ´e necess´ario ter tamb´em em considera¸c˜ao a poss´ıvel existˆencia de polimorfismo subtipo e encaminhamento dinˆamico (sec¸c˜ao 3.8).
5O estado vis´ıvel de um programa numa linguagem orientada por objectos pura, ´e aquele dado pelo conjunto
Assim, nas invoca¸c˜oes qualificadas a rotinas tomamos a aproxima¸c˜ao conservativa de verificar se todas as rotinas que podem ser executadas como resultado desses mecanis- mos s˜ao tamb´em puras. As rotinas recursivas (quer invocadas directamente na rotina ou por interm´edio de outras rotinas), n˜ao colocam problemas de maior j´a que o sistema de compila¸c˜ao mant´em o registo das rotinas para as quais j´a verificou e s˜ao puras. B.3.1 Invoca¸c˜oes polim´orficas
Com o que j´a foi apresentado, ´e poss´ıvel anotar todas as fun¸c˜oes com tendo, ou n˜ao, efeitos colaterais. Falta no entanto, ter em considera¸c˜ao uma das caracter´ısticas essenciais das linguagens orientadas por objectos: o polimorfismo e o encaminhamento dinˆamico. Com efeito, sempre que ´e invocado um servi¸co, h´a que ter em considera¸c˜ao que podem, em tempo de execu¸c˜ao, ser invocados servi¸cos diferentes (mas com o mesmo contrato) de diferentes classes. Assim, ´e necess´ario ter em considera¸c˜ao todas as classes que sejam descendentes do tipo relativamente ao qual o servi¸co ´e invocado. Basta um dos servi¸cos de uma dessas classes n˜ao ser pura para que o servi¸co onde a invoca¸c˜ao ´e feita tamb´em n˜ao o ser.
B.3.2 Grafo de invoca¸c˜ao de servi¸cos
Torna-se assim necess´ario que o sistema de compila¸c˜ao crie um grafo (dirigido), cujos n´os ser˜ao todos os servi¸cos6 de todas as classes do programa, e cujas liga¸c˜oes
entre os n´os sejam todas as invoca¸c˜oes poss´ıveis (incluindo, ´e claro, todas as invoca¸c˜oes polim´orficas). Este grafo de invoca¸c˜ao de servi¸cos – tal como no caso do grafo de dependˆencias entre entidades – depende proporcionalmente da dimens˜ao do programa, pelo que a sua complexidade ´e trat´avel.
B.4
Processadores
Neste prot´otipo da linguagem MP-Eiffel restringiu-se o mapeamento dos proces- sadores a threads dentro de um mesmo processo num ´unico computador. Muito embora a realiza¸c˜ao de outros mapeamentos de processadores – como por exemplo, processos no mesmo computador ou em computadores fisicamente separados – pudesse levantar problemas e condi¸c˜oes de experimenta¸c˜ao muito interessantes e relevantes, optou-se por dar prioridade a outros aspectos dos mecanismos. Espera-se futuramente ter condi¸c˜oes para extender o sistema de compila¸c˜ao tamb´em nesse sentido.
No prot´otipo actual, os processadores s˜ao implementados como classes descendentes de uma classe n˜ao instanci´avel chamada PROCESSOR7. O sistema de compila¸c˜ao, sempre
que h´a a possibilidade da cria¸c˜ao de uma entidade remota (ou seja, criar um novo processador), gera uma nova classe descendente quer da classe PROCESSOR (o construtor utilizado ser´a implementado como a redefini¸c˜ao do programa do processador), quer da classe associada `a entidade.
A figura B.2 exemplifica esta situa¸c˜ao.
6Bastam os “vivos”. Ou seja, aqueles que podem ser utilizados em tempo de execu¸c˜ao pelo programa. 7O c´odigo fonte pode ser consultado no apˆendice E.1.
class CLASS X creation
make ...
end -- CLASS X
class CLASS X PROCESSOR inherit CLASS X; PROCESSOR rename main as make end
end -- CLASS X PROCESSOR
Figura B.2: Realiza¸c˜ao de processadores.
B.4.1 Detec¸c˜ao do fim do programa
Um programa em MP-Eiffel estar´a terminado quando nenhum dos seus proces- sadores estiver em execu¸c˜ao (ou seja, ou j´a terminou, ou est´a num estado de espera por triggers.
Este comportamento foi implementado na pr´opria classe associada aos processa- dores. O fim do programa ´e detectado verificando a ocorrˆencia de duas condi¸c˜oes simultˆaneas:
• se o n´umero de processadores em espera ´e igual ao n´umero de processadores existentes;
• e se todas as filas de mensagens de triggers associadas a cada processador est˜ao vazias.
Nenhuma das duas condi¸c˜oes separadamente ´e suficiente para garantir a total inac- tividade de todos os processadores do programa. Pode acontecer que o n´umero de processadores seja temporariamente igual ao n´umero de processadores em espera, ha- vendo ainda triggers para executar (j´a que ´e o pr´oprio processador que incrementa o contador de processadores em espera, e nesse intervalo podem-lhe enviar um novo trigger ). E tamb´em, evidentemente, a n˜ao existˆencia num determinado instante de triggers n˜ao invalida a possibilidade de existirem processadores a executarem os res- pectivos programas.