Compiladores Tópicos de JVM Rui Gustavo Crespo IST/DEEC@2005 Compiladores JVM : 1/40 Máquinas virtuais (1) • Um programa, codificado numa linguagem de alto nível, só pode ser executado na máquina para a qual o compilador gera código final – Vantagens: maior eficiência no desempenho (memória e tempo de execução) – Inconvenientes: execução noutra máquina exige • Nova compilação • Duplicação de espaço em disco (ficheiros executáveis para cada máquina) • Selecção entre as diversas configurações do programa executável. Compiladores JVM : 2/40 Máquinas virtuais (2) • A compilação para máquina virtual, permite haver um único programa executável para máquinas distintas. • A execução é feita por um interpretador específico, que transcreve instruções da máquina virtual para o processador+sistema operativo. Programa fonte Compilador Executável Compiladores Máquina virtual Processador + SO Saída JVM : 3/40 Máquinas virtuais (3) • A máquina virtual de eleição é a pilha – Vantagens: • Transcrição muito fácil • Máquinas reais disponibilizam instruções para manipulação de pilhas – Inconvenientes: processamento muito lento! • Exemplos de máquinas virtuais baseadas em pilhas – Compilador UCSD Pascal gera código para máquina baseada numa pilha,o P-code. – JVM-”Java Virtual Machine”, da Sun, para Java – CLI-”Common Language Infrastructure”, da Microsoft, para C# e VisualBasic – Linguagem Postscript processada por ferramentas (ex: Gview) com 4 pilhas: operando, dicionário, execução e gráfico Compiladores JVM : 4/40 Máquinas virtuais (4) • O JVM foi implementado em diversas plataformas – SPARC, de sistema operativo Sun-OS – Intel 86, de sistemas operativos Windows e Linux – powerPC, de sistema operativo MacOS • JVM executa “threads” – “thread”, ou “lighweight process”, é uma unidade de processamento paralelo dentro do mesmo programa. – ao contrário dos processos, todas as “threads” são executadas dentro do mesmo ambiente) Compiladores JVM : 5/40 JVM – arquitectura (1) A. Mecanismos do JVM: – Carregamento de classes (“class loader”): mecanismo que instala classes e interfaces. O mecanismo inclui verificações de segurança (ex: o ficheiro .class está bem definido). – Engenho de execução (“execution engine”): mecanismo responsável pela execução dos métodos das classes carregadas. A informação necessária à execução é dividida em 4 espaços conceptuais: Compiladores JVM : 6/40 JVM – arquitectura (2) Engenho de execução do JVM optop … opval_2 opval_1 pc Bytecodes Código Activação Pilha operandos Ambiente execução lvar_n Dados da classe Armazém constantes Área de método Compiladores … Variáveis locais lvar_1 lvar_0 Pilha Java JVM : 7/40 JVM – arquitectura (3) B. Espaços conceptuais da máquina de execução 1. Área do método (“method area”), partilhado por todas as “threads” onde reside: • • • 2. código de cada método da classe dados da classe armazém de constantes (“constant pool”) para literais de tipos primitivos e instâncias da classe de caracteres Activações (“frames”), uma para cada evocação do método – – Compiladores Acervo (“heap”), de onde são é obtida área para os dados das instâncias Registo contador de programa (“program counter”), um por cada “thread”, com o endereço da instrução a executar. JVM : 8/40 JVM – arquitectura (4) C. Componentes de activação de método: 1. Pilha privada para – – 2. Avaliação de expressões Controlo do fluxo de execução do programa Tabela de 64K para variáveis locais, referenciadas por inteiros – – – 0 referencia objecto que executa o método (this) Parâmetros da evocação referenciados a partir de 1 Variáveis locais ocupam restantes posições Nota: nas classes static, parâmetros são referenciados a partir de 0. Nota: O JVM tem uma pilha, onde são inseridas as plihas privadas de cada método evocado. A activação no topo da pilha JVM designada por pilha activa de activação (a única activação que pode ser usada). Compiladores JVM : 9/40 Instruções - formato • • • Instruções designadas por Bytecodes, por o código ocupar apenas 1 Byte (200 ao todo) Cada instrução pode ter 0, ou mais, operandos. As instruções são carregadas na área de método do JVM, e executadas quando o método (que as gera) é evocado. Compiladores JVM : 10/40 Instruções – tipos (1) O tipo de dados envolvido na instrução é determinado por uma letra. Letra Tipo Letra Tipo b Byte L Instância de classe s Short [ Tabela 1 dimensão c Caractere V “void” i Inteiro a Referência z Booleano l long d Double Compiladores Lnome; Ex: Ljava/lang/String; é de tipo cadeia de caracteres JVM : 11/40 Instruções – tipos (2) • • • JVM segue representação “big-endien” (Byte mais significativo no endereço mais baixo) Os tipos primitivos b,s,c,i e f ocupam uma entrada na pilha. Os tipos primitivos l e d ocupam duas entradas na pilha. Número de Bytes dos literais de cada tipo usados na instrução: Compiladores Tipo Espaço (bytes) Tipo Espaço (bytes) b 1 l 8 (2 entradas na pilha) s 2 f 4 c 2 d 8 (2 entradas na pilha) i 4 JVM : 12/40 Instruções –tipos (3) – i2[bcsfld] converte inteiro no topo da pilha Ex: i2f, converto inteiro para float – f2[ild], d2[ifl] Nota: não é “cast”, pode haver perda de informação! Compiladores JVM : 13/40 Instruções – “load”/ “store” (1) – Tload n carrega na pilha valor T na posição n Pilha Tabela variáveis x 0 1 2 3 a b c d Pilha Tabela variáveis iload 2 ⇒ c x 0 1 2 3 a b c d – Tload_[0-3]carrega na pilha valor T nas posições entre 0 e 3 (instrução sem operandos) Ex: iload_2, carrega inteiro da posição 2 mas ocupa apenas 1 Byte Compiladores JVM : 14/40 Instruções – “load”/ “store” (2) – bipush val – sipush val – iconst_m1 – aconst_null – Tconst_vT Tipo vT Tipo vT i 0..5 f 0..2 l 0..1 carrega na pilha o byte val carrega na pilha o short val carrega na pilha inteiro –1 (instrução sem operandos) carrega na pilha null carrega na pilha literal (instrução sem operandos) d 0..1 Pilha Tabela variáveis x Compiladores 0 1 2 3 iconst_2 ⇒ a b c d Pilha Tabela variáveis 2 x 0 1 2 3 a b c d JVM : 15/40 Instruções – “load”/ “store” (3) – ldc #idx – ldc_w #idx – ldc2_w #idx – Tstore n carrega constante de índice idx (8 bits), do armazém de constantes carrega constante de índice idx,idx+1 (16 bits) do armazém de constantes carrega constante l/d de índice idx,idx+1 do armazém de constantes salva da pilha valor T na posição n Pilha Tabela variáveis y x 0 1 2 3 istore 2 ⇒ a b c d Pilha Tabela variáveis x 0 1 2 3 a b y d – Tstore_[0-3] Compiladores JVM : 16/40 Instruções – “load”/ “store” (4) – getstatic – getfield carrega referência a variável de classe carrega referência a variável de instância Exemplo: getstatic java/lang/System.in Ljava/io/InputStream; campo Compiladores JVM : 17/40 Instruções – aritméticas (1) – Tadd retira 2 operandos da pilha, insere soma na pilha Pilha Tabela variáveis y x 0 1 2 3 a b c d Pilha Tabela variáveis iadd ⇒ y+x 0 1 2 3 a b c d – Tsub, Tmul, Tdiv, Tneg, Trem T é de tipo i,l, f, d resto negação – iinc pos val adiciona val na posição pos (pode ser negativo) Compiladores JVM : 18/40 Instruções – aritméticas (2) – Tshl,Tshr – Tand – Tor – Txor Compiladores desloca operando no 2º lugar, pelos bits mais baixos no oper do topo. T=i (5 bits), ou T=l (6 bits) And bit a bit Or bit a bit Xor bit a bit JVM : 19/40 Instruções – pilha operandos (1) retira topo da pilha – pop Pilha Tabela variáveis y x 0 1 2 3 a b c d – pop2 – swap Compiladores pop ⇒ x 0 1 2 3 a b c d retira duas posições da pilha troca dois operandos Pilha Tabela variáveis y x Pilha Tabela variáveis 0 1 2 3 a b c d Pilha Tabela variáveis swap ⇒ x y 0 1 2 3 a b c d JVM : 20/40 Instruções – pilha operandos (2) – dup duplica topo da pilha Pilha Tabela variáveis Pilha Tabela variáveis x 0 1 2 3 a b c d dup ⇒ x x 0 1 2 3 a b c d Nota: tipicamente, a geração de um objecto feita por uma sequência de instruções new e evocação do <init> new java/lang/StringBuilder dup invokespecial java/lang/StringBuilder/<init>()V Compiladores JVM : 21/40 Instruções - comparações – Tcmpg retira 2 operandos da pilha (T:d ou f), compara-os e insere inteiro na pilha » 0 : iguais » 1 : topo<2º, ou um dos valores igual a NaN » -1 : topo>2º – Tcmpl semelhante a Tcmpg, excepto resultado –1 se um dos valores for NaN. Compiladores JVM : 22/40 Instruções – controlo execução (1) salto incondicional (16 bits) salto incondicional (32 bits) retira topo da pilha, compara-o XX com zero, salta se resultado for verdadeiro – if_icmpXX <lbl> retira dois elementos, compara-os XX, salta se resultado for verdadeiro – jmp <lbl> – jmp_w <lbl> – ifXX <lbl> As etiquetas são da forma id: inst Compiladores XX Condição XX Condição eq val == 0 gt val > 0 lt val < 0 ge val >= 0 le val <= 0 null val == null ne val!= 0 nonnull val!= null JVM : 23/40 Instruções – controlo execução (2) – lookupswitch <chave1>: <lbl1> <chave2>: <lbl1> … default: <def-lbl> Compiladores Retira topo da pilha e salta para etiqueta indicada pelo valor JVM : 24/40 Instruções – objectos/métodos (1) – invokeMode <method-spec> Existem 4 modos de invocação: • interface (método declarado numa interface) • special (métodos que requerem manipuladores especiais: <init> ou <super>) • static (método de classe estática) • virtual (método normal) Nota: nas versões anteriores ao JDK1.02, instrução invokespecial denominada invokenonvirtual Compiladores JVM : 25/40 Instruções – objectos/métodos (2) • <method-spec> possui formato topo nome-classe/método/(argumentos)tipo-retorno Pilha antes da chamada: Obj-ref arg1 arg2 … argN Pilha no retorno: [res] • Os argumentos são sequência de tipos, sem separadores. Ex: private static void quicksort(char[] buffer, int lower, int upper) transcrito por .method public static quicksort([CII)V Nota: na invocação é obrigatório indicar os tipos de parâmetros, devido ao polimorfismo do JVM Compiladores JVM : 26/40 Instruções – objectos/métodos (3) – Treturn retorna valor no topo da pilha (T=“”, se método retornar void) cria instância de classe e insere referência no topo da pilha Instrução new deve ser seguida pela evocação da inicialização, que consome da pilha a referência. Exemplo: new Integer(20); transcrito por – new new java/lang/Integer ; cria objecto dup ; duplica topo pilha bipush 20 ; insere valor invokespecial java/lang/Integer/<init>(i)V Compiladores parâmetro de init JVM : 27/40 Instruções – tabelas (1) São objectos, mas tratados com instruções apropriadas para tornar código mais eficiente – newarray Tipo cria tabela Ex: newarray int (tabela de inteiros) – multianewarray <desc-array> <num-dim> Nota: 1<num-dim<255 Ex: new int[6][3][] transcrita por bipush 6 bipush 3 multianewarray [[[I 2 ;aloca 2 das 3 dimensões Compiladores JVM : 28/40 Instruções – tabelas (2) insere na pilha componente da tabela, de tipo T. – Tastore retira da pilha topo, de tipo T e modifica componente da tabela. – arraylength obtém dimensão da tabela – Taload Compiladores JVM : 29/40 Instruções – tabelas (3) ; x = new int[6][3][]; bipush 6 bipush 3 multianewarray [[[I 2 astore_1 ; guarda referência em x ; x[0][1] = new int[50]; aload_1 ; insere na pilha referência da tabela iconst_0 ; insere na pilha x[0] aaload ; iconst_1 ; aloca tabela de 50 inteiros bipush 50 ; newarray int ; aastore ; guarda referência em x[0][1] Compiladores JVM : 30/40 Instruções – excepções (1) • As excepções são lançadas pela instrução athrow. O topo da pilha deve conter a referência ao objecto instância de uma excepção. new java/io/IOException dup invokespecial java/io/IOException/<init>()V athrow Compiladores JVM : 31/40 Instruções – excepções (2) • O corpo try{…}catch{…} é determinado no jasmin pela directiva .catch java/lang/Exception from lb11 to lbl2 using Handler lbl1 e lbl2 são as etiquetas das instruções delimitadoras do try{…}, e Handler é a etiqueta da primeira instrução do catch{…}. Compiladores JVM : 32/40 Directivas (1) • A classe pode conter as directivas – .source id # opcional – .class Qualif id Qualif é o qualificador da classe (public,…) – .super id – .implements id Exemplo .source HelloWorld.j .class public HelloWorld .super java/lang/Object Compiladores JVM : 33/40 Directivas (1) • O método deve ser delimitado pelas directivas – .method Qualif id(Pars)Tipo Qualif é o qualificador do método (public,…) – .end method • Em cada método devem ser indicadas as seguintes directivas – .limits locals nn – .limits stack nn Compiladores espaço de variáveis locais (this ocupa 1ª pos. nos métodos não estáticos) profundidade máxima da pilha de operandos JVM : 34/40 Armazém de constantes • Constantes armazenadas no armazém “constant pool”, indexadas a partir de #1. void useManyNumeric() { int i = 100; int j = 1000000; long l1 = 1; long l2 = 0xffffffff; double d = 2.2; ... } Compiladores Method void useManyNumeric() 0 bipush 100 // Push a small int with bipush 2 istore_1 3 ldc #1 // Push int constant 1000000; a larger int // value uses ldc 5 istore_2 6 lconst_1 // A tiny long value uses short, fast lconst_1 7 lstore_3 8 ldc2_w #6 // Push long 0xffffffff (that is, an int -1); any // long constant value can be pushed using ldc2_w 11 lstore 5 13 ldc2_w #8 // Push double constant 2.200000; uncommon // double values are also pushed using ldc2_w 16 dstore 7 JVM : 35/40 Exemplo (1) Ex: O classico Hello world! class HelloWorld { public static void main(String args[]) { System.out.println(“Hello World!"); } } .source HelloWorld.j .class public HelloWorld .super java/lang/Object .method public <init>()V ; construtor simples aload_0 invokenonvirtual java/lang/Object/<init>()V return .end method Compiladores JVM : 36/40 Exemplo (2) .method public static main([Ljava/lang/String;)V .limit stack 2 getstatic java/lang/System/out:Ljava/io/PrintStream; ldc "Hello World!" invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V return .end method Nota: o ficheiro .class ocupa apenas 377 B Compiladores JVM : 37/40 Ferramentas (1) • javap – c lista código JVM de ficheiro class Exemplo public class Test { public static void main(String args[]) { int i; i = 2; i = i + 7; } } Compiladores JVM : 38/40 Ferramentas (2) C:\ > javap -c Test Compiled from Test.java public class Test extends java.lang.Object { public Test(); // a default constructor created public static void main(java.lang.String[]); } Method Test() 0 aload_0 1 invokespecial #1 4 return Method void main(java.lang.String[]) 0 iconst_2 // Put integer 2 on stack 1 istore_1 // Store the top stack value at location 1 2 iload_1 // Put the value at location 1 on stack 3 bipush 7 // Put the value 7 on the stack 5 iadd // Add two top stack values together 6 istore_1 // The sum, on top of stack, stored at location 1 7 return // Finished processing Compiladores JVM : 39/40 Ferramentas (3) • jasmin – gera ficheiro .class Pode ser obtido na página http://jasmin.sourceforge.net $ java -jar jasmin.jar <filenames> Compiladores JVM : 40/40