Pergunta

Trabalho há algum tempo em Bytecode (Java), porém, nunca me ocorreu perguntar por que algumas instruções são digitadas?Entendo que em uma operação ADD, precisamos distinguir entre uma adição inteira e uma adição FP (é por isso que temos IADD e FADD).No entanto, por que precisamos distinguir entre ISTORE e FSTORE?Ambos envolvem exatamente a mesma operação, que é mover 32 bits da pilha para uma posição de variável local?

A única resposta que consigo pensar é na segurança de tipo, para evitar isso:(ILOAD, ILOAD, FADD).No entanto, acredito que a segurança de tipo já é aplicada no nível da linguagem Java.OK, o formato de arquivo Class não está diretamente acoplado ao Java, então essa é uma maneira de impor segurança de tipo para linguagens que não o suportam?Alguma ideia?Obrigado.

EDITAR: para acompanhar a resposta de Reedy.Eu escrevi este programa mínimo:

public static void main(String args[])
{
    int x = 1;
}

que compilou para:

iconst_1
istore_1
return

usando um editor de bytecode, alterei a segunda instrução:

iconst_1
fstore_1
return

e retornou um java.lang.VerifyError:Esperando encontrar float na pilha.

Eu me pergunto, se na pilha não há informação sobre o tipo, apenas bits, como a instrução FSTORE sabia que estava lidando com um int e não com um float?

Observação: Não consegui encontrar um título melhor para esta pergunta.Sinta-se à vontade para melhorá-lo.

Foi útil?

Solução

Estas instruções são digitadas para garantir que o programa seja seguro.Ao carregar uma classe, a máquina virtual realiza a verificação dos bytecodes para garantir que, por exemplo, um float não seja passado como argumento para um método que espera um número inteiro.Esta verificação estática requer que o verificador possa determinar os tipos e o número de valores na pilha para qualquer caminho de execução.As instruções de carregamento e armazenamento precisam da tag type porque as variáveis ​​locais nos quadros de pilha não são digitadas (ou seja,você pode armazenar em uma variável local e depois armazenar na mesma posição).As tags de tipo nas instruções permitem ao verificador saber que tipo de valor está armazenado em cada variável local.

O verificador analisa cada opcode do método e rastreia quais tipos estarão na pilha e nas variáveis ​​locais após a execução de cada um.Você está certo ao dizer que esta é outra forma de verificação de tipo e duplica algumas das verificações feitas pelo compilador Java.A etapa de verificação evita o carregamento de qualquer código que possa fazer com que a VM execute uma instrução ilegal e garante as propriedades de segurança da plataforma Java sem incorrer na grande penalidade de tempo de execução da verificação de tipos antes de cada operação.A verificação do tipo de tempo de execução para cada opcode seria um impacto no desempenho cada vez que o método é executado, mas a verificação estática é feita apenas uma vez quando a classe é carregada.

Caso 1:

Instruction             Verification    Stack Types            Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>          OK              []                     1: none
iconst_1                OK              [int]                  1: none
istore_1                OK              []                     1: int
return                  OK              []                     1: int

Caso 2:

Instruction             Verification    Stack Types            Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>          OK              []                     1: none
iconst_1                OK              [int]                  1: none
fstore_1                Error: Expecting to find float on stack

O erro é dado porque o verificador sabe que fstore_1 espera um float na pilha, mas o resultado da execução das instruções anteriores deixa um int na pilha.

Esta verificação é feita sem executar os opcodes, mas sim observando os tipos de instrução, assim como o compilador java dá um erro quando você escreve (Integer)"abcd".O compilador não precisa executar o programa para saber que "abcd" é uma string e não pode ser convertida em Integer.

Outras dicas

Geoff Reedy explicou em sua resposta o que o verificador faz quando uma classe é carregada. Eu só quero acrescentar que você pode desativar o verificador usando um parâmetro JVM. Isso não é recomendado!

Para o seu programa de exemplo (com iconst e fstore), o resultado da execução com a verificação desativada é um erro da VM que interrompe a JVM com a seguinte mensagem:

=============== DEBUG MESSAGE: illegal bytecode sequence - method not verified ================

#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_PRIV_INSTRUCTION (0xc0000096) at pc=0x00a82571, pid=2496, tid=3408
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_15-b04 mixed mode, sharing)
# Problematic frame:
# j  BytecodeMismatch.main([Ljava/lang/String;)V+0
#
...

Para responder sua primeira pergunta com o meu melhor palpite: esses bytecodes são diferentes porque podem exigir implementações diferentes. Por exemplo, uma arquitetura específica pode manter operando inteiro na pilha principal, mas operandos de ponto flutuante nos registros de hardware.

Para responder à sua segunda pergunta, o VerifyError é jogado quando a classe é carregada, não quando é executada. O processo de verificação é descrito aqui; Nota Passe #3.

Todo o bytecode deve ser comprovadamente TypeSafe com uma análise estática de fluxo de dados, conforme mencionado acima. No entanto, isso não explica realmente por que as instruções como _ store têm tipos diferentes, pois o tipo pode ser inferido a partir do tipo de valor na pilha. De fato, existem algumas instruções como POP, DUP e SWAP que fazem exatamente isso e operam em vários tipos. Por que algumas instruções são digitadas e outras não são algo que só pode ser explicado pelos desenvolvedores originais do Java.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top