BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos A JSR-292, invokedynamic e uma JVM mais poliglota

A JSR-292, invokedynamic e uma JVM mais poliglota

Tanto a Microsoft, com o .Net 4 e a DLR, quanto a Oracle, através do projeto Da Vinci Machine e do Java 7, estão procurando aprimorar o suporte a linguagens alternativas que têm como destino suas máquinas virtuais. Este movimento é reflexo de uma tendência crescente entre os desenvolvedores e implementadores de linguagens, que estão utilizando cada vez mais ambientes de execução pré-existentes para executar suas linguagens, dado que desenvolver a partir do zero um novo ambiente de execução representa um grande investimento. Os pontos fortes da plataforma Java, tais como um garbage collector (coletor de lixo) eficiente, um modelo robusto de segurança e a disponibilidade ampla do runtime do Java (JRE), juntamente com uma grande quantidade de bibliotecas e ferramentas, fizeram a plataforma ser amplamente adotada para esta finalidade, havendo hoje mais de 240 diferentes linguagens que executam sobre a máquina virtual Java (JVM).

No entanto, mesmo existindo muitas razões para a JVM ser uma plataforma popular entre os desenvolvedores de linguagens, o bytecode do Java foi projetado para atender às necessidades de uma única linguagem: a própria linguagem Java. Isso cria um desafio considerável para desenvolvedores de implementações de linguagens dinâmicas ou funcionais. Um problema comum surge em torno das chamadas de métodos. A JVM impõe uma série de restrições sobre a forma como métodos são interligados, favorecendo a referência estática ou a execução por meio de uma classe ou interface. O tipo receptor deve estar de acordo com o tipo resolvido para a invocação, e a invocação deve vincular o tipo, o que significa que o método resolvido será sempre pré-existente. As invocações são estaticamente tipadas e esperam tipos Java. Estas restrições atendem bem à linguagem Java, mas outras linguagens possuem regras de invocações diferentes, normalmente menos restritivas.

Como Java é uma linguagem de propósito geral, é possível utilizar uma API baseada em objetos para simular opções adicionais ou até "relaxar" regras de invocação de métodos. Em um artigo detalhado (pdf) sobre InvokeDynamic, John Rose refere-se a esta abordagem como um simulador de método. A API de Reflection do Java inclui um exemplo bem conhecido, java.lang.reflect.Method.invoke, e várias outras linguagens incluem equivalentes, como clojure.lang.IFn.invoke em Clojure ou DynamicMethod.call em JRuby. Usando esta abordagem, a equipe do JRuby, por exemplo, foi capaz de criar uma implementação em Ruby com cerca do dobro do desempenho da versão C padrão. Esta também é essencialmente a abordagem que a Microsoft está tomando em relação à sua DLR para .Net. Embora funcione, isso introduz uma sobrecarga de execução e de complexidade considerável. Assim a Sun/Oracle decidiu ir um passo além. Ao invés de adicionar um simulador de método padrão através de uma biblioteca, como na DLR, o projeto Da Vinci Machine propõe alterações no nível da JVM. A JSR-292, que está prevista para o Java 7, será o primeiro resultado do projeto a ser padronizado.

A JSR-292 concentra-se principalmente nas necessidades de linguagens dinâmicas. Essa especificação introduz dois novos conceitos-chave – uma nova instrução para invocação de métodos e referências de métodos, com uma fábrica (factory) de transformação de tipos correspondentes. Isso também amplia a gramática para identificadores Java cuja grafia pode ser qualquer sequência de caracteres (os chamados "identificadores exóticos").

InvokeDynamic e referências de métodos

Na especificação do bytecode do Java 6, há quatro instruções de invocação de métodos, as quais correspondem diretamente a chamadas de métodos Java:

  1. invokestatic, utilizada para invocar métodos de classe;
  2. invokespecial, utilizada para invocar métodos de inicialização de instância, ao chamar métodos em uma superclasse e para métodos privados;
  3. invokevirtual, o método padrão para invocação de métodos de instância;
  4. invokeinterface, para métodos de interface.

Destas, as chamadas virtuais e de interface podem servir aos propósitos de uma linguagem dinâmica, mas elas não são flexíveis o suficiente para que, em tempo de execução, linguagens dinâmicas possam vincular a chamada de métodos ao destino, nos casos onde a chamada não sabe de antemão a interface de destino a qual deverá se vincular. A JSR-292, no entanto, acrescenta uma quinta instrução para invocação de métodos, invokedynamic, que funciona como um marcador para indicar que uma chamada específica de uma linguagem dinâmica ocorreu naquele momento. No nível da JVM, uma instrução invokedynamic é utilizada para chamada de métodos tendo suas semânticas de vínculo (linkage) e despacho definidas por uma linguagem diferente de Java.

Assim como as outras quatro instruções de invocação de métodos, invokedynamic também é estaticamente tipada, porém uma instrução invokedynamic é vinculada dinamicamente ao controle do programa utilizando uma referência de método (method handle). Uma referência de método não é um conceito novo, sendo semelhante ao perform em SmallTalk ou mesmo o performSelector no Objective-C. No seu blog, Rose oferece uma descrição sucinta de como funciona:

Dado qualquer método M que possa ser chamado, a JVM me oferece uma forma de produzir uma referência de método H(M). É possível então utilizar esta referência posteriormente (mesmo se for esquecido o nome de M) para chamar M quantas vezes desejar. Além disso, se a referência for fornecida a outras chamadas, estas também poderão invocar M através da referência, a inda que não tenham direito de acesso para chamar M pelo nome. Se o método não for estático, a referência de método sempre recebe o receptor como seu primeiro argumento. Se o método for virtual ou de interface, a referência de método executa a chamada sobre o receptor. Uma referência de método disponibiliza seu tipo de forma reflexiva, como uma série de valores Class, através da operação de tipo.

Linguagens dinâmicas não só vinculam chamadas de métodos tardiamente, mas também fazem uma grande quantidade de conversões automáticas de tipos em tempo de execução – converter um String "12345" para um Integer num determinado método, por exemplo. Desenvolvedores de uma linguagem dinâmica podem até criar uma classe de transformação com um método adaptador apropriado, para chamada através de uma referência de método, mas criá-lo para cada assinatura e destino possível seria impraticável. A JSR-292 introduz, assim, uma fábrica para a geração destas classes adaptadoras. Charles Nutter, líder do JRuby, nos disse:

A API de MethodHandles (referências de métodos) descrita na JSR-292 fornece os elementos básicos para a escrita da "cola" simples entre o método de origem (o "chamador") e o de destino. A InvokeDynamic contacta a sua linguagem ou biblioteca quando uma chamada dinâmica é feita, e você responde fornecendo uma referência de método (ou uma cadeia de referências). A referência então conecta a origem ao destino da maneira apropriada. Por exemplo, se ao fazer uma chamada dinâmica, for preciso reordenar os argumentos, pode-se inserir uma referência de "permutação de argumentos" na da cadeia de referências. Se for necessário tratar exceções no destino final, é possível também inserir uma referência para tratamento de exceções. Existem referências de métodos para acessar campos, para chamar outros métodos Java, para unir/desdobrar argumentos (de e para arrays "varargs"), para controlar o fluxo de condições e muito mais.

Se as referências disponíveies não forem suficientes, pode-se também estender JavaMethodHandle e implementar a interface você mesmo, em Java. Isso permite codificar uma lógica mais complexa, sem precisar decompor tudo em pedaços pequenos demais. Pode-se pensar nos MethodHandles como "ponteiros de uma função para a JVM", com a vantagem de permitirem representar sequências de chamadas mais complexas de forma que a JVM possa entender. Na minha opinião, esta é a parte mais interessante da JSR-292.

Sintaxe em Java

Os três principais recursos relacionados à JVM na JSR-292 – InvokeDynamic, referências de métodos e identificadores exóticos – têm suas sintaxes de código definidas através da proposta do projeto Coin. A mudança vai criar um mecanismo para que a linguagem Java possa interoperar com novas linguagens executando sobre a JVM e que dependem da instrução de bytecode InvokeDynamic. Espera-se também reduzir a dependência de técnicas de manipulação de bytecode ao utilizar o javac e a JVM como plataforma para o desenvolvimento de novas linguagens e ambientes de execução.

Invocações dinâmicas são suportadas pela classe com membros estáticos java.dyn.InvokeDynamic:

Object x = InvokeDynamic.getMeSomething();

Enquanto referências de métodos para uma chamada virtual podem ser recuperadas utilizando a classe java.dyn.MethodHandle:

MethodHandle mh = ...;
mh.invoke();


Destaque-se que tanto InvokeDynamic quanto MethodHandle expõem uma potencial lacuna para as exceções verificadas. Embora InvokeDynamic ou uma chamada virtual possam gerar uma exceção verificada, não há maneira de se inferir estaticamente quais exceções podem ser lançadas a partir de uma chamada de método em tempo de compilação. Isso enfraquece ainda mais o modelo de exceções verificada na linguagem Java, mas a wiki da proposta argumenta que isso não representa um problema novo:

Como programas Java já são desenvolvidos para lidar com exceções não verificadas e erros, a possibilidade da ausência de tratamento de exceções verificadas não é considerada um risco. Na verdade, as linguagens dinâmicas não podem ser suportadas sem algum tipo de relaxamento na verificação estática da exceções.

Identificadores exóticos

A proposta do Project Coin também oferece suporte direto a identificadores exóticos. Estes são iniciados por # e colocados entre aspas:

int #"strange variable name" = 42;
System.out.println(#"strange variable name"); // saída: 42


Um identificador exótico não pode ser vazio, dessa forma seria ilegal int #"";. Certos caracteres "perigosos", mais especificamente / . ; < > [ ], são também ilegais para um identificador exótico, a menos que sejam precedidos por um caractere de barra invertida. Isso não é verdade, no entanto, para strings e caracteres literais. Se um caractere perigoso for precedido por uma barra invertida, a barra invertida é removida e o caractere é coletado. O compilador rejeita um programa que contenha um identificador exótico, se ele formar um nome de bytecode proibido pela especificação da JVM.

Conclusão

A JSR-292 permite à JVM combinar bytecode, que ainda é a representação mais eficiente para a computação estática, com cadeias de referências de métodos, que são uma maneira mais eficiente de organizar a computação dinâmica. Dessa forma, os novos recursos da JSR facilitam uma mesclagem entre linguagens estáticas e dinâmicas na JVM, tornando mais fácil aos desenvolvedores escolher a linguagem mais apropriada para cada projeto.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT