Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes TEMA 4: INTERPRETACIÓN, COMPILACIÓN OBJETIVO Dar respuesta a las siguientes preguntas: ¿Qué es la interpretación de un lenguaje? ¿Qué es la compilación de un lenguaje? ¿Cómo se diseña y construye un intérprete? ¿Cómo se diseña y construye un compilador? El reconocimiento del lenguaje ya no es el problema. Se supone que el análisis léxico-sintáctico y el análisis semántico han resuelto el problema del reconocimiento. El problema ahora es ¿qué hacer con las sentencias del lenguaje? ¿QUÉ ES LA INTERPRETACIÓN DE UN LENGUAJE? Interpretar un lenguaje significa ejecutar directamente sus sentencias. Supongamos el lenguaje LF definido en el Tema 1, Teoría. Para interpretar LF tenemos que definir qué es la ejecución de un programa LF. LF es un lenguaje de programación secuencial. Las instrucciones se ejecutan una a una desde el comienzo del programa hasta el fin. LF tiene la capacidad de expresar variables enteras y dos tipos de instrucciones: (a) definición de variables con expresión entera (DEF) y (b) evaluación de variables (EVAL). No se acepta el uso de variables sin declarar. La declaración de variables asocia valor 0 por defecto a éstas. Ejecutar una instrucción DEF significa vincular una expresión a una variable para una eventual evaluación. Toda definición de una variable solapará sus definiciones anteriores en el programa. Toda variable definida sobre sí misma se le asocia un valor indefinido. Ejecutar una instrucción EVAL x significa evaluar el valor de la expresión asociada a x mostrando por pantalla el resultado. A continuación mostramos un programa LF y su interpretación. Ejemplo 1: Programa LF VARIABLES x, y, z, a, b; INSTRUCCIONES 1 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes a DEF -1; b DEF (a+1); EVAL b; a DEF 2; EVAL b; b DEF b + 1; z DEF 2*y; EVAL z; EVAL b; Ejemplo 2: Interpretación Programa LF b b z b --> --> --> --> 0 3 0 INDEF ¿QUÉ ES LA COMPILACIÓN DE UN LENGUAJE? Compilar un lenguaje significa ser capaz de compilar cada sentencia del lenguaje. Compilar una sentencia es traducirla a otro lenguaje preservando en lo posible el significado de la sentencia. A diferencia de la interpretación, la compilación involucra dos lenguajes: el lenguaje fuente y el lenguaje destino. El lenguaje fuente suele ser un lenguaje expresivo para el que no se dispone de intérprete. El lenguaje destino suele ser un lenguaje menos expresivo pero tiene intérprete. Un ejemplo de lenguaje compilado es Java. El lenguaje destino es un lenguaje ensamblador interpretable (después del ensamblado) en la llamada máquina virtual de Java. La construcción de un compilador obliga a especificar correspondencias entre los recursos expresivos del lenguaje fuente y los recursos expresivos del lenguaje destino. Las principales correspondencias en nuestro problema son: (1) el programa LF se corresponde con una clase Java con main, (2) la instrucción EVAL se corresponde con la definición de un método (sin parámetros) y la correspondiente llamada a dicho método en el main para presentar por pantalla el resultado, (3) la instrucción DEF se corresponde con la definición de un método (sin parámetros); Ejemplo 3: Compilación en Java de programa LF en Ejemplo 1 import java.io.*; public class _Programa { private static int a1(){ return -1; } private static int b1(){ return a1()+1; 2 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes } private static int a2(){ return 2; } private static int b2(){ return a2()+1; } private static int y3(){ return 0; } private static int z3(){ return 2*y3(); } private static int b4(){ return b4()+1; } public static void main(String[] args) { System.out.println(b1()); System.out.println(b2()); System.out.println(z3()); System.out.println(b4()); } } ¿CÓMO SE DISEÑA Y CONSTRUYE UN INTÉRPRETE? No hay una respuesta general a esta pregunta. La construcción del intérprete es fuertemente dependiente de la semántica del lenguaje. El paso clave del diseño es clarificar la semántica del lenguaje. Una forma de clarificar esta semántica es poner sentencias de ejemplos y decir qué significan (ver Ejemplos 1 y 2). Por ejemplo, la ejecución mostrada en el Ejemplo 2 facilita la comprensión de LF. Las dos primeras evaluaciones de b producen resultados diferentes sin cambiar su definición. Esto se debe a que las expresiones se asocian a las variables justo en el momento de su evaluación no en el momento de su definición (late binding). En nuestro programa de ejemplo (ver Ejemplo 1), la variable a modifica su definición a lo largo del programa y la definición de b depende de la definición de a. Esto hace que la evaluación de b pueda ser distinta sin modificar su definición. Una vez comprendida la semántica de LF, diseñamos un intérprete. Para evaluar una variable es necesario almacenar su definición. Dado que la evaluación no se produce hasta alcanzar una instrucción EVAL, estas definiciones se almacenan en una tabla para recuperarlas en el momento necesario (atributo heredado y sintetizado). A continuación se muestra un intérprete para LF en forma de tree-parser. Ejemplo 3: Intérprete para el lenguaje LF header{ import java.util.*; import antlr.*; } 3 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes class Anasint2 extends TreeParser; options { importVocab = Anasint; } tokens{ INDEF; } { Hashtable<String, AST> variables = new Hashtable<String, AST>(); ASTFactory factory = new ASTFactory(); public void imprimir(){ System.out.println(variables); } public void declarar(String var){ AST n = nodo("0",NUMERO); variables.put(var,n); } public AST nodo(String texto, int tipo){ AST resultado = new CommonAST(); resultado.setType(tipo); resultado.setText(texto); return resultado; } public int convertir(AST numero){ return (new Integer(numero.getText())).intValue(); } public void almacenar(String var, AST expr){ variables.put(var,expr); } public boolean ocurrencia(String var, AST expr){ switch(expr.getType()){ case NUMERO: return false; case VAR: return expr.getText().equals(var); case MAS: case POR: return ocurrencia(var, expr.getFirstChild()) || ocurrencia(var, expr.getFirstChild().getNextSibling()); case MENOS: if (expr.getFirstChild().getNextSibling()==null) return ocurrencia(var, expr.getFirstChild()); else return ocurrencia(var, expr.getFirstChild()) || ocurrencia(var, expr.getFirstChild().getNextSibling()); } return false; } public AST evaluar(AST expr){ AST resultado = null; 4 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes AST a,b; switch(expr.getType()){ case NUMERO: resultado = factory.dupTree(expr); break; case VAR: resultado = evaluar(variables.get(expr.getText())); break; case MAS: a = evaluar(expr.getFirstChild()); b = evaluar(expr.getFirstChild().getNextSibling()); if (a.getType()==INDEF || b.getType()==INDEF) resultado = nodo("INDEF",INDEF); else resultado = nodo((new Integer(convertir(a) + convertir(b))).toString(),NUMERO); break; case POR: a = evaluar(expr.getFirstChild()); b = evaluar(expr.getFirstChild().getNextSibling()); if (a.getType()==INDEF || b.getType()==INDEF) resultado = nodo("INDEF",INDEF); else resultado = nodo((new Integer(convertir(a) * convertir(b))).toString(),NUMERO); break; case MENOS: if (expr.getFirstChild().getNextSibling()==null){ a = evaluar(expr.getFirstChild()); if (a.getType()==INDEF) resultado = nodo("INDEF",INDEF); else resultado = nodo((new Integer(-1*convertir(a))).toString(),NUMERO); } else{ a = evaluar(expr.getFirstChild()); b = evaluar(expr.getFirstChild().getNextSibling()); if (a.getType()==INDEF || b.getType()==INDEF) resultado = nodo("INDEF",INDEF); else resultado = nodo((new Integer(convertir(a) - convertir(b))).toString(),NUMERO); } break; case INDEF: resultado = factory.dupTree(expr); break; } return resultado; } } programa : #(PROGRAMA variables instrucciones ) ; variables : #(VARIABLES (v:VAR {declarar(#v.getText());})*) ; instrucciones : #(INSTRUCCIONES (definicion|evaluacion)*) ; definicion : #(DEF v:VAR e:expr) {if (!ocurrencia(#v.getText(),#e)) almacenar(#v.getText(),#e); else{ AST n = new CommonAST(); n.setType(INDEF); n.setText("INDEF"); 5 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes almacenar(#v.getText(),n);} } ; evaluacion : #(EVAL v:VAR) {System.out.println(#v.getText()+" --> "+evaluar(#v).toStringTree());} ; expr : #(MAS expr expr) | #(POR expr expr) | NUMERO | VAR | (#(MENOS expr expr)) => #(MENOS expr expr) | #(MENOS expr) ; ¿CÓMO SE DISEÑA Y CONSTRUYE UN COMPILADOR? Tampoco hay respuesta general a esta pregunta. La construcción del compilador es fuertemente dependiente de los lenguajes involucrados (lenguaje fuente y lenguaje destino). Los pasos claves del diseño son (1) clarificar las semánticas del lenguaje fuente y del lenguaje destino y (2) clarificar las correspondencias entre ambos lenguajes. Tal y como ya hemos visto, el enlace dinámico de expresiones a variables constituye el principal problema a la hora de traducir LF a Java (late binding). Las variables Java son imperativas: el enlace del valor a la variable se produce al ejecutar una asignación y se mantiene hasta que otra asignación la solape. Por tanto, no podemos usar asignaciones imperativas para implementar definiciones LF. Hemos visto que las principales correspondencias entre LF y Java son: (1) el programa LF se corresponde con una clase Java con main, (2) la instrucción EVAL se corresponde con la definición de un método (sin parámetros) y la correspondiente llamada a dicho método en el main para presentar por pantalla el resultado, (3) la instrucción DEF se corresponde con la definición de un método (sin parámetros); Una forma de resolver el problema del enlace variable/expresión es codificar cada evaluación con la declaración de un método. El cuerpo del método implementará el enlace dinámico. Por ejemplo, los métodos b1() y b2() en el Ejemplo 3 implementan las dos primeras evaluaciones de b en el programa LF del Ejemplo 2. Cada evaluación además generará una llamada al método correspondiente en el main. El siguiente ejemplo muestra el compilador de LF a Java. Ejemplo 4: Compilador de LF header{ import java.util.*; import antlr.*; import java.io.*; } 6 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes class Anasint3 extends TreeParser; //Compilador options { importVocab = Anasint; } { FileWriter fichero; private void open_file(){ try{ fichero = new FileWriter("_Programa.java"); }catch(IOException e) {System.out.println("open_file (exception): "+e.toString());} } private void close_file(){ try{ fichero.close(); }catch(IOException e) {System.out.println("close_file (exception): "+e.toString());} } ////////////////// int espacios = 0; private void gencode_espacios(){ try{ for (int i = 1; i<=espacios;i++) fichero.write(" "); }catch(IOException e) {System.out.println("gencode_espacios (exception): "+e.toString());} } ///////////////// ASTFactory factory = new ASTFactory(); Hashtable<String, AST> vars = new Hashtable<String, AST>(); Hashtable<String, AST> vars2 = new Hashtable<String, AST>(); List<String> evals_pend = new LinkedList<String>(); Integer num_evals = new Integer(0); public void declarar_variable(String var){ AST nodo = new CommonAST(); nodo.setType(NUMERO); nodo.setText("0"); vars.put(var,nodo); } public void definir_variable(String var, AST expr){ vars.put(var,expr); } 7 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes public void evaluar_variable(String var){ Integer i; String aux = new String(var); num_evals++; Enumeration<String> v = vars.keys(); while (v.hasMoreElements()){ String key = v.nextElement(); if (cierre(var).contains(key)){ //Se calcula el cierre de var. // se calcula una variable indexada para var. aux = key+num_evals.toString(); // se sustituye en la expresión de la definición cada // variable original por una nueva variable indexada // por el número de evaluaciones vars2.put(aux,sustitucion_expr(vars.get(key))); } } try{ // A cada variable presente en la definición de la variable // evaluada (las incluidas en vars2) se genera un método. v = vars2.keys(); while (v.hasMoreElements()){ String key = v.nextElement(); gencode_espacios(); fichero.write("private static int "+key+"(){\n"); espacios++; gencode_espacios(); fichero.write("return "+gencode_exp(vars2.get(key))+";\n"); espacios--; gencode_espacios(); fichero.write("}\n"); } }catch(IOException e){} vars2.clear(); evals_pend.add(var+num_evals.toString()); //Queda pendiente generar // llamada en el main. } public Set<String> cierre(String var){ Set<String> resultado = new HashSet<String>(); int size_ant = 0; int size_act = 0; resultado.add(var); size_act = resultado.size(); while (size_ant!=size_act){ resultado.addAll(calcular_variables(vars.get(var))); size_ant = size_act; size_act = resultado.size(); } return resultado; } public Set<String> calcular_variables(AST expr){ Set<String> aux = new HashSet<String>(); switch(expr.getType()){ case NUMERO: break; case VAR: aux.add(expr.getText()); break; 8 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes case MAS: case POR: aux.addAll(calcular_variables(expr.getFirstChild())); aux.addAll(calcular_variables(expr.getFirstChild().getNextSibling())); break; case MENOS: aux.addAll(calcular_variables(expr.getFirstChild())); if (expr.getFirstChild().getNextSibling()!=null) aux.addAll(calcular_variables(expr.getFirstChild().getNextSibling())); break; default: return null; } return aux; } public AST sustitucion_expr(AST expr){ AST nodo; String aux; switch(expr.getType()){ case NUMERO: return factory.dupTree(expr); case VAR: aux = expr.getText(); nodo = new CommonAST(); aux = aux + num_evals.toString(); nodo.setType(VAR); nodo.setText(aux); return nodo; case MAS: nodo = new CommonAST(); nodo.setType(MAS); nodo.setText("+"); nodo.setFirstChild(sustitucion_expr(expr.getFirstChild())); nodo.getFirstChild().setNextSibling( sustitucion_expr(expr.getFirstChild().getNextSibling())); return nodo; case POR: nodo = new CommonAST(); nodo.setType(POR); nodo.setText("*"); nodo.setFirstChild(sustitucion_expr(expr.getFirstChild())); nodo.getFirstChild().setNextSibling( sustitucion_expr(expr.getFirstChild().getNextSibling())); return nodo; case MENOS: nodo = new CommonAST(); nodo.setType(MENOS); nodo.setText("-"); nodo.setFirstChild( sustitucion_expr(expr.getFirstChild())); if (expr.getFirstChild().getNextSibling()!=null) nodo.getFirstChild().setNextSibling( sustitucion_expr(expr.getFirstChild().getNextSibling())); return nodo; default: return null; } } private void gencode_begin_class(){ try{ gencode_espacios(); fichero.write("import java.io.*;\n"); gencode_espacios(); 9 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes fichero.write("public class _Programa"+"\n"); gencode_espacios(); fichero.write("{\n"); espacios++; }catch(IOException e){} } private void gencode_main(){ try{ gencode_espacios(); fichero.write("public static void main(String[] args) {\n"); espacios++; Iterator<String> it = evals_pend.listIterator(); while (it.hasNext()){ gencode_espacios(); fichero.write("System.out.println("+it.next()+"());\n"); } espacios--; gencode_espacios(); fichero.write("}\n"); }catch(IOException e){} } private void gencode_end_class(){ try{ espacios--; gencode_espacios(); fichero.write("}"); }catch(IOException e){} } public String gencode_exp(AST expr){ switch(expr.getType()){ case NUMERO: return expr.getText(); case VAR: return expr.getText()+"()"; case MAS: return gencode_exp(expr.getFirstChild())+"+" +gencode_exp(expr.getFirstChild().getNextSibling()); case POR: return gencode_exp(expr.getFirstChild())+"*" +gencode_exp(expr.getFirstChild().getNextSibling()); case MENOS: if (expr.getFirstChild().getNextSibling()!=null) return gencode_exp(expr.getFirstChild())+"-" +gencode_exp(expr.getFirstChild().getNextSibling()); else return "-"+gencode_exp(expr.getFirstChild()); default: return null; } } } programa : {open_file(); gencode_begin_class();} #(PROGRAMA variables instrucciones) {gencode_main(); gencode_end_class(); close_file();} ; 10 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes variables : #(VARIABLES (v:VAR {declarar_variable(v.getText());})*) ; instrucciones : #(INSTRUCCIONES (definicion|evaluacion)*) ; definicion : #(DEF v:VAR e:expr) {definir_variable(v.getText(),e);} ; evaluacion : #(EVAL v:VAR) {evaluar_variable(v.getText());} ; expr : #(MAS expr expr) | #(POR expr expr) | NUMERO | VAR | (#(MENOS expr expr)) => #(MENOS expr expr) | #(MENOS expr) ; 11 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes GENERACIÓN DE CÓDIGO PARA LA MÁQUINA VIRTUAL DE JAVA LA MÁQUINA VIRTUAL DE JAVA El formato de ficheros “class”. La máquina virtual Java (JVM) ejecuta un código particular que es independiente de arquitecturas concretas y sistemas operativos. Los ficheros ensamblados para dicha máquina tienen extensión “class”. Tipos de Datos en JVM. JVM opera sobre dos clases de tipos: los tipos predefinidos y el tipo referencia. Los tipos primitivos son: byte (1 byte), short (2 bytes), int (4 bytes), long (8 bytes), char (2 bytes), float (4 bytes), double (8 bytes), boolean (valores representados como valores tipo int). dirección de retorno (tipo interno de la máquina, no programable. Sus valores son apuntadores a código JVM). Los tipos reference (referencia) son: tipo clase (referencias a instancias de clase creadas dinámicamente) tipo array (referencias a instancias de arrays creadas dinámicamente) tipo interfaz (referencias a instancias de interfaces creadas dinámicamente) Existe un valor especial null (para cualquiera de los 3 tipos) para referirnos a la ausencia de objeto. Runtime JVM. JVM es una máquina que simula concurrencia real a través de un conjunto de tareas o hilos de control. Cada tarea representa una secuencia de instrucciones en ejecución. JVM cuenta con un contador de programa y una pila para cada tarea y con un montón y una zona de código común a todas las tareas. En tiempo de ejecución, la pila puede contener múltiples frames. Sólo el frame situado en la parte superior marcará el método activo en cada instante de ejecución de la tarea. Cada frame contiene (a) una referencia al objeto actual (sólo si es instanciable la clase), (b) parámetros, (c) array de variables locales del método y una pila de operandos. 12 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes El array de variables locales está destinado a almacenar la dirección del objeto, los parámetros del método y las variables locales del método. Cada variable local puede almacenar un valor de tipo boolean, byte, short, int, float o referencia. Los valores de tipo long o double necesitan dos variables locales. JVM no requiere alineado de ningún tipo. La pila de operandos está destinada a almacenar resultados parciales y preparación de llamadas a métodos y obtención de resultados. Por ejemplo, para sumar dos enteros es necesario cargarlos previamente en la pila y luego ejecutar la instrucción suma. La operación consumirá los operandos y los sustituirá por el resultado. JVM Tarea k Frame k.1 Variables locales Pila operandos El mónton es una zona de memoria destinada a almacenar instancias de clases, arrays e interfaces. La zona de código almacena constantes, estructuras de clases (atributos y métodos) y código de métodos. Juego de Instrucciones para JVM En esta sección presentaremos un resumen extendido del juego de instrucciones existentes para la máquina virtual de Java. Es importante señalar que JVM es una máquina basada en el uso de pila y las descripciones de las instrucciones usarán la notación (estado antes ==> estado después) haciendo referencia al estado de la pila antes y después de la ejecución de la instrucción. Las definiciones harán uso de dos vocablos con un significado preciso: cargar, para mover datos desde las variables a la pila, y almacenar, para mover los datos desde la pila hacia las variables. JVM define un juego de instrucciones para cada tipo predefinido. Las instrucciones sobre enteros presentan código de operación prefijado con i, l para long, s para short, b para byte, c para char, f para float, d para double y a para reference. Según la funcionalidad, las instrucciones JVM se clasifican en: Carga y almacenamiento desde variables locales a pila de operandos. Instrucciones aritméticas Instrucciones de conversión de tipos Instrucción de gestión de la pila de operandos Instrucciones de transferencia de control 13 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Instrucciones de llamada a método y retorno Instrucciones dedicadas a elevar excepción y monitorización A continuación mostramos un resumen de la mayoría de estas instrucciones: Tipo Referencia Carga una referencia desde un array de referencias. aaload (..., referencia_array, indice => ..., valor) Almacenar una referencia en un array de referencias. aastore (..., referencia_array, indice, valor => ...) Cargar referencia nula. aconst_null (...,=> ..., null) Cargar referencia desde variable local indexada por indice. aload indice (...,=> ..., referencia) Crea en el montón un nuevo array de referencias. indice1 + indice2 constituye un índice a una referencia simbólica array, clase o interfaz en la tabla de constantes. anewarray indice1 indice2 (...,numero de componentes => ..., referencia_array). Devuelve referencia desde método. areturn (..., referencia =>) Almacenar referencia en variable local. astore indice (..., referencia => ...) Comparación de dos referencias. indice1+indice2 representa el offset al que se salta si la comparación es cierta. Si la comparación falla, el programa sigue por la instrucción siguiente a la que indica el contador de programa. if_acmpeq indice1 indice2 if_acmpne indice1 indice2 if_acmpgt indice1 indice2 if_acmplt indice1 indice2 if_acmpge indice1 indice2 if_acmple indice1 indice2 (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) 14 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Tipo Byte Carga un byte o boolean desde un array. baload (..., referencia, indice => ..., valor) Almacena un byte o boolean a un array. bstore (..., referencia, indice, valor => ...) Carga byte. bipush byte (...,=> ..., valor) Tipo Char Carga char desde array caload (...,referencia, indice => ..., valor) Almacenar en una array de caracteres. castore (..., referencia, indice, valor => ...) Tipo Double Convierte double a float d2f (..., valor => ..., resultado) Convierte double a int d2i (..., valor => ..., resultado) Convierte double a long d2l (..., valor => ..., resultado) Suma para el tipo double dadd (..., valor1, valor2 => ..., resultado) Carga double desde array daload (...,referencia, indice => ..., valor) Almacenar en una array de tipo double. dastore (..., referencia, indice, valor => ...) Comparación tipo double. El resultado es 1/0/-1 si valor1 es mayor/igual/menor que valor2. (ver nota aclaratoria sobre el uso de NaN) dcmpg (..., valor1, valor2 => ..., resultado) dcmpl (..., valor1, valor2 => ..., resultado) Divide para el tipo double ddiv (..., valor1, valor2 => ..., resultado) 15 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Carga double desde variable local dload indice (..., => ..., valor) Multiplica para el tipo double dmul (..., valor1, valor2 => ..., resultado) Cambio signo para el tipo double dneg (..., valor => ..., resultado) Devuelve double desde un método. dreturn (..., valor =>) Almacenar double en una variable local. dstore indice (...,valor => ...) Tipo Float Similar a las instrucciones del tipo double. Tipo int Convierte int a byte i2b (..., valor => ..., resultado) Convierte int a char i2c (..., valor => ..., resultado) Convierte int a double i2d (..., valor => ..., resultado) Convierte int a float i2f (..., valor => ..., resultado) Convierte int a long i2l (..., valor => ..., resultado) Convierte int a short i2s (..., valor => ..., resultado) Suma para el tipo int iadd (..., valor1, valor2 => ..., resultado) Carga int desde array iaload (...,referencia, indice => ..., valor) Almacenar en una array de tipo int. iastore (..., referencia, indice, valor => ...) 16 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Almacenar una constante tipo int desde el slot n de constantes. iconst_<n> (..., => ...,<n>) Devuelve int desde un método. ireturn (..., referencia, indice, valor => ...) Comparación de dos enteros. indice1+indice2 representa el offset al que se salta si la comparación es cierta. Si la comparación falla, el programa sigue por la instrucción siguiente a la que indica el contador de programa. if_icmpeq indice1 indice2 if_icmpne indice1 indice2 if_icmpgt indice1 indice2 if_icmplt indice1 indice2 if_icmpge indice1 indice2 if_icmple indice1 indice2 (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) (..., valor1, valor2 => ...) Incrementa la variable local referenciada ubicada en el slot indice con el valor de la constante constante iinc indice constante (...=> ...) Carga int desde variable local iload indice (..., => ..., valor) Almacenar int en una variable local. istore indice (...,valor => ...) Tipo Long Similar a las instrucciones para el tipo int. Manipulación de la pila Duplica la cima de la pila. dup (...,valor => ..., valor, valor) Instrucciones asociadas a objetos: Carga campo desde un objeto. indice1+ indice2 identifica una referencia simbólica al atributo en la tabla de constantes. getfield indice1, indice2 (...,referencia => ..., valor) Establece el valor de un campo de objeto. indice1+ indice2 identifica una referencia simbólica al atributo en la tabla de constantes. putfield indice1, indice2 (...,referencia, valor => ...) 17 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Invoca un método desde un objeto. indice1+ indice2 identifica una referencia simbólica al método en la tabla de constantes. invokevirtual indice1, indice2 (...,referencia, arg1, ..., argN => ...) Instrucciones asociadas a clases: Carga campo de clase (estático). indice1+ indice2 identifica una referencia simbólica a la clase en la tabla de constantes. getstatic indice1, indice2 (..., => ..., valor) Establece el valor de un campo de clase (estático). indice1+ indice2 identifica una referencia simbólica a la clase en la tabla de constantes. putstatic indice1, indice2 (..., valor => ...) Invoca un método de clase (estático). indice1+ indice2 identifica una referencia simbólica al método de clase en la tabla de constantes. invokestatic indice1, indice2 (..., arg1, ...argN => ..., valor) Salto incondicional: Salto incondicional. indice1+ indice2 representa un offset para el contador de programa. goto indice1, indice2 (..., => ..., valor) Creación de objetos: Crea un objeto. indice1+ indice2 representa un índice para referenciar la clase dentro de la zona de código. new indice1, indice2 (..., => ..., referencia) Jamaica. Ensamblador para JVM Jamaica es un ensamblador para JVM con la capacidad programar clases al estilo Java, de usar etiquetas simbólicas y de nombrar variables y atributos en vez de utilizar índices. Además, Jamaica cuenta con un conjunto de macros para simplificar la programación. Para más información, consultar http://judoscript.org/articles/jamaica.html A continuación se muestran dos ejemplos de código Jamaica, uno sin macros y otro con macros: 18 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes public class C { int contador; public C( ){ iconst_0 putfield count int return } public void inc(int c) { aload_0 getfield contador int iload c iadd putfield contador int return } public void imprimir( ) throws IOException { getstatic System.out PrintStream aload this invokevirtual PrintStream.println(Object) void return } } public class C { int contador; public C( ){ %set contador = 0 } public void inc(int c) { %load contador %load c iadd putfield contador int } public void imprimir( ) throws IOException { %println this } } Reglas léxicas en Jamaica Los ficheros Jamaica aceptan comentarios de línea y comentarios multilínea al estilo Java. Los identificadores en Jamaica siguen las mismas reglas léxicas que los identificadores Java. Los mnemónicos de las operaciones son palabras reservadas. Los tipos primitivos, nombres de clases e interfaces y nombres de arrays son los mismos que los nombres de tipos en Java. Todas las macros comienzan con % 19 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Los bytecodes y macros no terminan con ‘;’ Clases e Interfaces en Jamaica Las clases e interfaces Jamaica se declaran con la misma sintaxis que Java. Jamaica define la importación por defecto de los paquetes java.lang.*, java.io.*, java.util.* Ejemplo: package A; public class Elemento { private int e; public Elemento() { aload this invokespecial super()void return } public int consultar() { aload this getfield e int ireturn } public void modificar(int i) { aload this iload i putfield e int return } } Juego de Instrucciones de Jamaica Carga de constantes Las constantes pueden ser de tipo int, long, float, string y null. La instrucción aconst_null carga la constante null. Las instrucciones bipush numero y sipush numero cargan valores enteros de 1 o 2 bytes respectivamente. Las instrucciones iconst_m1, iconst_0, ..., iconst_5 carga las constantes int –1,0,..,5 respectivamente. 20 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Las instrucciones lconst_0 y lconst_1 carga las constantes long 0 y 1 respectivamente. Las instrucciones fconst_0, ..., fconst_2 carga las constantes float 0.,..,2. respectivamente. Las instrucciones dconst_0 y dconst_1 carga las constantes double 0 y 1 respectivamente. Sin embargo Jamaica dispone de lcd como instrucción general de carga de constantes: ldc 129834 ldc “pepe” ldc 5.5 Variables La instrucción <tipo>load carga un valor de una variable a la pila donde tipo = i, l, f , d, a (int, long, float, double y reference respectvamente) La instrucción <tipo>store carga un valor desde la pila a una variable donde tipo = i, l, f , d, a (int, long, float, double y reference respectvamente) . Sinónimos: aload_0 y aload this Ejemplo : intercambio de valores entre dos variables enteras. iload x istore tmp iload y istore x iload tmp istore y Formaciones La instrucción <tipo>aload carga un valor de una componente de un formación a la pila donde tipo = i, l, f , d, a (int, long, float, double y reference respectvamente) La instrucción <tipo>astore carga un valor desde la pila a una componente de una formación donde tipo = i, l, f , d, a (int, long, float, double y reference respectvamente) . Ejemplo : almacenar el valor de una variable x en la componente 1 de un vector llamado formacion. aload formacion iconst_1 iload x iastore 21 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Atributos La instrucción getfield [nombre_clase.]nombre_atributo tipo carga en la pila el valor del atributo atributo desde una instancia de clase. La instrucción getstatic [nombre_clase.]nombre_atributo tipo carga en la pila el valor del atributo atributo desde una clase no instanciable. La instrucción putfield [nombre_clase.]nombre_atributo tipo carga el valor de la cima de la pila en el atributo atributo desde una instancia de clase. La instrucción putstatic [nombre_clase.]nombre_atributo tipo carga el valor de la cima de la pila en el atributo atributo desde una clase no instanciable. Método La instrucción invokevirtual [nombre_clase.]nombre_metodo signatura llamada a un método desde una instancia de clase donde signatura = ( [Tipo (, Tipo)*]) Tipo. La instrucción invokestatic [nombre_clase.]nombre_metodo signatura llamada a un método desde una clase no instanciable donde signatura = ( [Tipo (, Tipo)*]) Tipo. La instrucción invokespecial [nombre_clase.]nombre_metodo signatura llamada a un método constructor donde signatura = ( [Tipo (, Tipo)*]) Tipo. Creación de objetos La instrucción new nombre_clase crea una instancia de la clase nombrada en la cima de la pila. Instrucciones aritméticas y lógicas Las instrucciones <tipo>add <tipo>sub <tipo>mul <tipo>div suman, restan, mutiplican y dividen dos operados de la pila dejando el resultado en la pila. Las instrucciones <tipo>and <tipo>or <tipo>xor realizan un and, or xor de dos operados de la pila dejando el resultado en la pila. Aunque JVM contemplan la existencia del tipo boolean, sus valores se representan con enteros (0 para el valor falso y 1 para el valor cierto). La instrucción iinc variable incremento incrementa una variable sin usar la pila. 22 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Manipulaciones de la pila La instrucción pop se utiliza para desapilar y la instrucción dup para duplicar la cima. Saltos incondicionales La instrucción goto etiqueta se utiliza para pasar el control a la instrucción referenciada por etiqueta. La instrucción return se utiliza para finalizar procedimientos. La instrucción <tipo>return se utiliza para finalizar funciones. Saltos condicionales La instrucción if_icmp<op> etiqueta se utiliza para comparar dos enteros en la pila y saltar a etiqueta en consecuencia donde op = eq (igual), ne (distinto), lt (menor), le (menor o igual), gt (mayor) o ge (mayor o igual). La instrucción if_acmpeq etiqueta se utiliza para saber si son iguales dos referencias en la pila y saltar a etiqueta en consecuencia. La instrucción if_acmpne etiqueta se utiliza para saber si son iguales dos referencias en la pila y saltar a etiqueta en consecuencia. La instrucción if_null etiqueta se utiliza para saber es nula la referencia en la pila. La instrucción lcmp compara dos long en la pila y deja el resultado en la pila. Las instrucciones fcmpl/fcmpg compara si un float es menor/mayor que otro en la pila y deja el resultado en la pila.Las instrucciones dcmpl/dcmpg compara si un double es menor/mayor que otro en la pila y deja el resultado en la pila. Entonces se utiliza la instrucción if<op> etiqueta para saltar a etiqueta en consecuencia donde op = eq, ne, lt, le, gt y ge. Macros Las siguientes instrucciones son propias de Jamaica y no existen en el juego original de instrucciones JVM: 23 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes (%println | %print | %flush) [parametro (, parametro)*] donde parametro = constante|nombre([parametro])* Impresión en la salida estándar (out). Estas macros pueden tener un número variable de argumentos como en Java. %load parametro Cargar constante, variable, atributo o componente de formación en la pila. %set parametro = parametro Almacenamiento de constante, variable, atributo o componente de formación en un atributo, variable o componente de formación. %object nombre_clase Crear objeto. Compilando para la JVM en Ensamblador Jamaica Los siguientes ejemplos muestran la compilación en Jamaica de distintos fragmentos de un programa en un lenguaje de programación de alto nivel. Constantes, variables y estructuras de control Fragmento programa en lenguaje de alto nivel entero i; //X i=1; // A mientras (i<=10) hacer // B si (i<5) entonces // C escribir(i); // D finsi i:=i+1; // E finmientras Traducción Jamaica (bajo nivel) int i; //X bipush 1 // A istore i // A goto bucle_1_test // B bucle_1_cuerpo: iload i // C bipush 5 // C if_icmplt si_2_cuerpo // C goto fin_si_2_cuerpo // C si_2_cuerpo: // D %println i // D goto fin_si_2_cuerpo // D fin_si_2_cuerpo: // D iinc i 1 // E bucle_1_test: // B iload i // B bipush 10 // B if_icmple bucle_1_cuerpo// B 24 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Asignaciones y expresiones aritméticas Fragmento programa en lenguaje de alto nivel entero x; entero y; x = 2 * x + y; Traducción Jamaica (bajo nivel) int x; int y; bipush 2 iload x imul iload y iadd istore x Fragmento programa en lenguaje de alto nivel real x; real y; x = 2 * x + y; //Parte //Parte //Parte //Parte //Parte //Parte izq. izq. izq. izq. izq. der. asignación asignación asignación asignación asignación asignación Traducción Jamaica (bajo nivel) float x; float y; ldc 2.0 fload x fmul fload y fadd fstore x 25 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Clases instanciables, objetos, atributos y métodos Fragmento programa en lenguaje de alto nivel inst clase Elemento { oculto entero e; Traducción Jamaica (bajo nivel) public class Elemento { private int e; consultar() dev entero { dev objeto.e; } modificar(entero i) { objeto.e:=i; } public Elemento() { aload this invokespecial super()void return } public int consultar() { aload this getfield e int ireturn } public void modificar(int i) { aload this iload i putfield e int return } } } 26 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Fragmento programa en lenguaje de alto nivel clase Programa2 { inicio(){ Elemento e; Pila p; entero i; entero tmp; Traducción Jamaica (bajo nivel) public class Programa2 { public static void main(String[] args) { Elemento e; Pila p; int i; int tmp; e := crear(Elemento) i := 1 mientras (i<=10) hacer e.modificar(i); p.apilar(e); tmp := e.consultar(); escribir(tmp); i:=i+1; finmientras } %object Elemento astore e bipush 1 istore i goto bucle_1_test bucle_1_cuerpo: aload e iload i invokevirtual Elemento.modificar(int)void aload e invokevirtual Elemento.consultar()int istore tmp %println tmp iinc i 1 bucle_1_test: iload i bipush 10 if_icmple bucle_1_cuerpo } } } 27 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Clases no instanciables Fragmento programa en lenguaje Traducción Jamaica (bajo nivel) de alto nivel clase Elemento public class Elemento { { oculto entero e; private static int e; consultar() dev entero { dev e; } modificar(entero i) { e:=i; } } clase Programa { inicio(){ entero i; entero tmp; public static int consultar(){ getstatic e int ireturn } public static void modificar(int i){ iload i putstatic e int return } } public class Programa { public static void main(String[] args) { i := 1 mientras (i<=10) hacer Elemento.modificar(i); tmp := Elemento.consultar(); escribir(tmp); i:=i+1; finmientras } int i; int tmp; bipush 1 istore i goto bucle_1_test bucle_1_cuerpo: iload i invokestatic Elemento.modificar(int)void invokestatic Elemento.consultar()int istore tmp %println tmp iinc i 1 bucle_1_test: iload i bipush 10 if_icmple bucle_1_cuerpo } } } 28 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Formaciones Fragmento programa en lenguaje Traducción Jamaica (bajo nivel) de alto nivel inst clase Pila2 public class Pila2{ { private Elemento almacen[]; oculto formacion 100 private int cima; Elemento almacen; oculto entero cima; public Pila2() { aload this iniciar(){ invokespecial super()void cima := -1; aload this } bipush 100 anewarray Elemento apilar(Elemento e){ putfield almacen Elemento[] cima := cima + 1; return componente(almacen, } cima):= e; } public void iniciar(){ aload this desapilar() dev Elemento{ iconst_m1 Elemento tmp; putfield cima int tmp := return componente(almacen,cima); } cima := cima - 1; public Elemento desapilar(){ dev tmp Elemento tmp; } aload this } getfield almacen Elemento[] aload this getfield cima int aaload astore tmp aload this aload this getfield cima int ldc 1 isub putfield cima int aload tmp areturn } public void apilar(Elemento e){ aload this aload this getfield cima int ldc 1 iadd putfield cima int aload this getfield almacen Elemento[] aload this getfield cima int aload e aastore return } } 29 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Módulos (I) Fragmento programa en lenguaje de alto nivel modulo B Traducción Jamaica (bajo nivel) importacion: public class Elemento { private int e; exportacion: package B; inst clase Elemento { oculto entero e; %default_constructor <public> public int consultar(){ aload this getfield e int ireturn } public void modificar(int i){ aload this iload i putfield e int return } consultar() dev entero { dev objeto.e; } modificar(entero i) { objeto.e:=i; } } } implementacion: 30 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Fragmento programa en lenguaje de alto nivel modulo A Traducción Jamaica (bajo nivel) importacion: B import B.Elemento; exportacion: public class Programa4{ package A; clase Programa4 { inicio(){ Elemento e; Pila p; entero i; entero tmp; public static void main(String[] args) { Elemento e; Pila p; int i; int tmp; %object Elemento astore e e := crear(Elemento) i := 1 mientras (i<=10) hacer e.modificar(i); p.apilar(e); tmp := e.consultar(); escribir(tmp); i:=i+1; finmientras } bipush 1 istore i goto bucle_1_test bucle_1_cuerpo: aload e iload i invokevirtual Elemento.modificar(int)void aload e invokevirtual Elemento.consultar()int istore tmp %println tmp iinc i 1 bucle_1_test: iload i bipush 10 if_icmple bucle_1_cuerpo } } implementacion: } 31 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Módulos (II) Fragmento programa en lenguaje de alto nivel modulo A Traducción Jamaica (bajo nivel) importacion: public class Programa5{ package A; exportacion: public static void main(String[] args) { A.Elemento e; Pila p; int i; int tmp; clase Programa5 { inicio(){ Elemento e; Pila p; entero i; entero tmp; %object A.Elemento astore e e := crear(Elemento) bipush 1 istore i goto bucle_1_test bucle_1_cuerpo: aload e iload i invokevirtual A.Elemento.modificar(int)void aload e invokevirtual A.Elemento.consultar()int istore tmp %println tmp iinc i 1 bucle_1_test: iload i bipush 10 if_icmple bucle_1_cuerpo } i := 1 mientras (i<=10) hacer e.modificar(i); p.apilar(e); tmp := e.consultar(); escribir(tmp); i:=i+1; finmientras } } implementacion: inst clase Elemento { oculto entero e; consultar() dev entero { dev objeto.e; } modificar(entero i) { objeto.e:=i; } } } 32 Departamento de Lenguajes y Sistemas Informáticos Procesadores de Lenguajes Fragmento programa en lenguaje de alto nivel Traducción Jamaica (bajo nivel) package A; class Elemento { private int e; %default_constructor <public> private int consultar(){ aload this getfield e int ireturn } private void modificar(int i){ aload this iload i putfield e int return } } 33