Representaciones Intermedias CI4721 – Lenguajes de Programación II Ernesto Hernández-Novich <[email protected]> Universidad “Simón Bolívar” Copyright ©2012-2016 Hernández-Novich (USB) Representaciones Intermedias 2016 1 / 32 Generación de Código Intermedio Acceso a elementos en arreglos • Haremos las suposiciones modernas de almacenamiento para los elementos de un arreglo • Elementos almacenados en posiciones contiguas. • Disposición por filas (row major). • La anchura de cada elemento es conocido e incluye el padding sufijo cuando aplique. • El acceso a cualquier elemento requiere • Conocer la base de almacenamiento del arreglo. • Calcular el desplazamiento hasta la posición deseada. • Determinar si se quiere el r-value o el l-value según el contexto gramatical. . . . con la mínima cantidad de operaciones Hernández-Novich (USB) Representaciones Intermedias 2016 2 / 32 Generación de Código Intermedio Acceso a Arreglos Caso general de una dimensión Sea un arreglo A • Unidimensional. • Elementos de anchura w . • Indices en el rango low y high. • Dispuesto a partir de la dirección base. Entonces el i−ésimo elemento está en la dirección high i low ··· Ai ··· w base Hernández-Novich (USB) base + (i − low ) × w Representaciones Intermedias 2016 3 / 32 Generación de Código Intermedio Acceso a Arreglos Cálculo más eficiente Como base, low y w son conocidas a tiempo de compilación, podemos expandir y reordenar la fórmula para obtener i × w + (base − low × w ) • ¡La expresión c = base − low × w es constante! • Se evalúa una vez a tiempo de compilación al procesar la declaración. • Se almacena como atributo para el arreglo en la tabla de símbolos. • Para calcular la dirección de A[i] solamente es necesario generar código para la expresión i × w + c. Hernández-Novich (USB) Representaciones Intermedias 2016 4 / 32 Generación de Código Intermedio Acceso a Arreglos Agreguemos una dimensión Si A tiene dos dimensiones y está dispuesto en row major . . . • A[i, j] es equivalente a A[i][j] – arreglo de arreglos. • A[i1 , i2 ] – “desplazarse i1 filas y agregar i2 elementos”. Así que podemos deducir la fórmula base + ((i1 − low1 ) × n2 + i2 − low2 ) × w donde n2 es la cardinalidad de i2 . Como base, low1 , low2 y w son conocidas a tiempo de compilación, nuevamente reescribimos ((i1 × n2 ) × i2 ) × w + (base − ((low1 × n2 ) + low2 ) × w ) Hernández-Novich (USB) Representaciones Intermedias 2016 5 / 32 Generación de Código Intermedio Acceso a Arreglos El caso general Si A es un arreglo k−dimensional con la j−ésima dimensión variando entre lowj y highj , la posición A[i1 , i2 , . . . , ik ] tiene dirección ((· · · ((i1 × n2 + i2 ) × n3 + i3 ) · · · )nk + ik ) × w +base − (· · · ((low1 × n2 + low2 ) × n3 + low3 ) · · · )nk + lowk ) × w • ∀j : nj = highj − lowj + 1 – el segundo sumando puede calcularse a tiempo de compilación. • Si las dimensiones tienen base cero, se reduce a base. Hernández-Novich (USB) Representaciones Intermedias 2016 6 / 32 Generación de Código Intermedio Acceso a Arreglos Esquema directo con base cero – El uso de los arreglos S → L := E E→ L • L genera el nombre del arreglo y la secuencia de expresiones índice. • Asignaciones y expresiones extendidas permitiendo acceso a arreglos. Hernández-Novich (USB) Representaciones Intermedias 2016 7 / 32 Generación de Código Intermedio Acceso a Arreglos Esquema directo con base cero – El uso de los arreglos S → L := E E→ L {gen(L.array .base 0 [0 L.addr 0 ]0 0 :=0 E .addr )}) ( E .addr ← newtemp(); gen(E .addr 0 :=0 L.array .base 0 [0 L.addr 0 ]0 ) • L genera el nombre del arreglo y la secuencia de expresiones índice. • Asignaciones y expresiones extendidas permitiendo acceso a arreglos. • El acceso a cualquier posición de un arreglo sólo depende de tener la base y el offset calculados previamente. • L.array .base debe contener la base del arreglo. • L.addr debe contener el offset. Hernández-Novich (USB) Representaciones Intermedias 2016 7 / 32 Generación de Código Intermedio Acceso a Arreglos Esquema directo con base cero – Los cálculos L→ id [ E ] L→ L1 [ E ] Hernández-Novich (USB) Representaciones Intermedias 2016 8 / 32 Generación de Código Intermedio Acceso a Arreglos Esquema directo con base cero – Los cálculos L→ id [ E ] L.array ← lookup(top, id.lexema) L.type ← contents(L.array .type) L.addr ← newtemp() 0 0 gen(L.addr := E .addr 0 *0 L.type.width) L→ L1 [ E ] • L.array apunta a la tabla de símbolos – la base está en L.array .base. • contents() – aplicado sobre array (I, T ) retorna T Hernández-Novich (USB) Representaciones Intermedias 2016 8 / 32 Generación de Código Intermedio Acceso a Arreglos Esquema directo con base cero – Los cálculos L→ id [ E ] L→ L1 [ E ] L.array ← lookup(top, id.lexema) L.type ← contents(L.array .type) L.addr ← newtemp() gen(L.addr 0 :=0 E .addr 0 *0 L.type.width) L.array ← L1 .array L.type ← contents(L .type) 1 t ← newtemp() L.addr ← newtemp() gen(t 0 :=0 E .addr 0 *0 L.type.width) 0 0 0 0 gen(L.addr := L1 .addr + t) • L.array apunta a la tabla de símbolos – la base está en L.array .base. • contents() – aplicado sobre array (I, T ) retorna T Hernández-Novich (USB) Representaciones Intermedias 2016 8 / 32 Generación de Código Intermedio ¿Cómo funciona? Accediendo a un arreglo foo : array [2] of array [3] of int ; bar ,i , j : int ; bar := foo [ i ][ j ] • Supongamos que la anchura de int es 4 bytes. • El tipo de foo es array (2, array (3, integer )) – anchura es 24 bytes. • El tipo de foo[i] es array (3, integer ) – anchura es 12 bytes. Hernández-Novich (USB) Representaciones Intermedias 2016 9 / 32 Generación de Código Intermedio ¿Cómo funciona? El árbol decorado – bar := foo[i][j] S E .addr = bar := E .addr = t4 L.array =foo L.type =int L.addr =t3 bar L.array =foo L.type =array (3, int) L.addr =t1 foo.type = array (2, array (3, int)) [ E .addr = i [ E .addr = j ] ] j i Hernández-Novich (USB) Representaciones Intermedias 2016 10 / 32 Generación de Código Intermedio ¿Cómo funciona? El código generado – bar := foo[i][j] L→ id [ E ] L→ L1 [ E ] E→ L Hernández-Novich (USB) Representaciones Intermedias 2016 11 / 32 Generación de Código Intermedio ¿Cómo funciona? El código generado – bar := foo[i][j] L→ id [ E ] L.array ← lookup(top, id.lexema) L.addr ← newtemp() 0 0 L.type ← contents(L.array .type) gen(L.addr := E .addr 0 *0 L.type.width) L→ L1 [ E ] E→ L • L.type = array (3, int) con anchura 12 t1 := i * 12 L.addr = t1 , E .addr = i Hernández-Novich (USB) Representaciones Intermedias 2016 11 / 32 Generación de Código Intermedio ¿Cómo funciona? El código generado – bar := foo[i][j] L→ id [ E ] L→ L1 [ E ] L.array ← lookup(top, id.lexema) L.type ← contents(L.array .type) L.addr ← newtemp() 0 0 0 0 gen(L.addr := E .addr * L.type.width) L.array ← L1 .array L.type ← contents(L1 .type) t ← newtemp() L.addr ← newtemp() 0 *0 L.type.width) gen(t 0 :=0 E0 .addr 0 0 0 gen(L.addr := L1 .addr + t) E→ L • L.type = array (3, int) con anchura 12 L.addr = t1 , E .addr = i • L.type = int con anchura 4 t1 t2 t3 := i * 12 := j * 4 := t1 + t2 L.addr = t3 , E .addr = j Hernández-Novich (USB) Representaciones Intermedias 2016 11 / 32 Generación de Código Intermedio ¿Cómo funciona? El código generado – bar := foo[i][j] L→ id [ E ] L→ L1 [ E ] E→ L L.array ← lookup(top, id.lexema) L.type ← contents(L.array .type) L.addr ← newtemp() 0 0 0 0 gen(L.addr := E .addr * L.type.width) L.array ← L1 .array L.type ← contents(L1 .type) t ← newtemp() L.addr ← newtemp() 0 *0 L.type.width) gen(t 0 :=0 E0 .addr 0 L .addr 0 +0 t) gen(L.addr := 1 n o E .addr ← newtemp() gen(E .addr 0 :=0 L.array .base 0 [0 L.addr 0 ]0 ) • L.type = array (3, int) con anchura 12 L.addr = t1 , E .addr = i • L.type = int con anchura 4 L.addr = t3 , E .addr = j t1 t2 t3 t4 := := := := i * 12 j * 4 t1 + t2 foo [ t3 ] • L.array .base = foo, L.addr = t3 , E .addr = t4 Hernández-Novich (USB) Representaciones Intermedias 2016 11 / 32 Generación de Código Intermedio ¿Cómo funciona? El código generado – bar := foo[i][j] L→ id [ E ] L→ L1 [ E ] E→ L L.array ← lookup(top, id.lexema) L.type ← contents(L.array .type) L.addr ← newtemp() 0 0 0 0 gen(L.addr := E .addr * L.type.width) L.array ← L1 .array L.type ← contents(L1 .type) t ← newtemp() L.addr ← newtemp() 0 *0 L.type.width) gen(t 0 :=0 E0 .addr 0 L .addr 0 +0 t) gen(L.addr := 1 n o E .addr ← newtemp() gen(E .addr 0 :=0 L.array .base 0 [0 L.addr 0 ]0 ) • L.type = array (3, int) con anchura 12 L.addr = t1 , E .addr = i • L.type = int con anchura 4 L.addr = t3 , E .addr = j • L.array .base = foo, L.addr = t3 , E .addr = t4 • L.addr = t4 , E .addr = bar Hernández-Novich (USB) Representaciones Intermedias t1 t2 t3 t4 bar := := := := := i * 12 j * 4 t1 + t2 foo [ t3 ] t4 2016 11 / 32 Flujo de Control Estructuras de control • La generación de código para estructuras de control está influenciada por la generación de código para expresiones booleanas. • Vamos a suponer que el problema de generación de código para una expresión boolean B está resuelto y cumple • Los atributos B.true y B.false son etiquetas. • El código de B salta a B.true o B.false según el resultado de la expresión booleana. • El atributo heredado S.next corresponde a la etiqueta de la siguiente instrucción después del código de S. • Esto va a generar muchas etiquetas – pero son baratas. • Permite evitar saltos a saltos. • newlabel() – cada llamada retorna un nombre de etiqueta único. • label() – genera código para incorporar la etiqueta. Hernández-Novich (USB) Representaciones Intermedias 2016 12 / 32 Flujo de Control Instrucciones Simples Esquema de generación P→ S S → S1 S2 Hernández-Novich (USB) Representaciones Intermedias 2016 13 / 32 Flujo de Control Instrucciones Simples Esquema de generación P→ S S.next ←newlabel() P.code←S.code ++ label(S.next) S → S1 S2 • El programa principal es el caso base para S.next. Hernández-Novich (USB) Representaciones Intermedias 2016 13 / 32 Flujo de Control Instrucciones Simples Esquema de generación P→ S S → S1 S2 S.next ←newlabel() P.code←S.code ++ label(S.next) S1 .next←newlabel() S .next←S.next 2 S.code ←S1 .code ++ label(S1 .next) S2 .code • El programa principal es el caso base para S.next. • Al secuenciar instrucciones: • La siguiente a la secuencia es natural – piensen “FOLLOW”. • Hace falta definir la siguiente para el punto medio. Hernández-Novich (USB) Representaciones Intermedias 2016 13 / 32 Flujo de Control Selectores S → if B then S1 is false B.code is true B.true: S1 .code B.false: Hernández-Novich (USB) Representaciones Intermedias 2016 14 / 32 Flujo de Control Selectores S → if B then S1 is false B.code is true B.true: S1 .code B.false: ¡B.false corresponde a S.next! Hernández-Novich (USB) Representaciones Intermedias 2016 14 / 32 Flujo de Control Selectores Esquema para el if-then S→ if B then S1 B.true ←newlabel() B.false ←S.next S .next←S.next 1 S.code ←B.code ++ label(B.true) ++ S1 .code • Necesitamos una nueva etiqueta para el cuerpo. • Encadenamos S.next dos veces • Para el salto de salida cuando B es falso. • Como siguiente de la instrucción como un todo. Hernández-Novich (USB) Representaciones Intermedias 2016 15 / 32 Flujo de Control Selectores S → if B then S1 else S2 is false B.code is true B.true: S1 .code goto S.next B.false: S2 .code S.next: Hernández-Novich (USB) Representaciones Intermedias 2016 16 / 32 Flujo de Control Selectores S → if B then S1 else S2 is false B.code is true B.true: S1 .code goto S.next B.false: S2 .code S.next: ¡S1 .next y S2 .next deben coincidir con S.next! Hernández-Novich (USB) Representaciones Intermedias 2016 16 / 32 Flujo de Control Selectores Esquema para el if-then-else S→ if B then S1 else S2 B.true ←newlabel() B.false ←newlabel() S1 .next←S.next S2 .next←S.next S.code ←B.code ++ label(B.true) ++ S1 .code ++ 0 0 gen( goto S.next) ++ label(B.false) ++ S2 .code • Etiquetas dedicadas para los brazos del if-then-else. • Salidas de los brazos coinciden con salida del bloque – instrucciones anidadas en los brazos saltan directo a la salida. Hernández-Novich (USB) Representaciones Intermedias 2016 17 / 32 Flujo de Control Iteración S → while S1 begin: is false B.code is true B.true: S.code goto begin B.false: Hernández-Novich (USB) Representaciones Intermedias 2016 18 / 32 Flujo de Control Iteración S → while S1 begin: is false B.code is true B.true: S.code goto begin B.false: ¡B.false corresponde a S.next! Hernández-Novich (USB) Representaciones Intermedias 2016 18 / 32 Flujo de Control Iteración Esquema para el while S→ while B do S1 begin ←newlabel() B.true ←newlabel() B.false ←S.next S1 .next←S.begin S.code ←label(begin) ++ B.code ++ label(B.true) ++ S1 .code ++ 0 0 gen( goto begin) • Necesitamos una nueva etiqueta para el cuerpo. • Al ejecutar el cuerpo se regresa a evaluar la condición. • Se ejecutó todo hasta alcanzar el goto explícito. • Hay un salto en el bloque anidado que aprovecha S1 .next. Hernández-Novich (USB) Representaciones Intermedias 2016 19 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code • Las expresiones booleanas se interpretan según su contexto • Para alterar el flujo de control. • Como valor de una expresión booleana. Hernández-Novich (USB) Representaciones Intermedias 2016 20 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code • Las expresiones booleanas se interpretan según su contexto • Para alterar el flujo de control. • Como valor de una expresión booleana. • Si la definición del lenguaje obliga a evaluar todas las partes de una expresión booleana, la generación de código es similar a la de expresiones. • Codificar true y false como 1 y 0, respectivamente. • Generar código para calcular el resultado en un temporal. Hernández-Novich (USB) Representaciones Intermedias 2016 20 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code • Las expresiones booleanas se interpretan según su contexto • Para alterar el flujo de control. • Como valor de una expresión booleana. • Si la definición del lenguaje obliga a evaluar todas las partes de una expresión booleana, la generación de código es similar a la de expresiones. • Codificar true y false como 1 y 0, respectivamente. • Generar código para calcular el resultado en un temporal. • Si la definición del lenguaje permite evaluar parcialmente la expresión, se genera código de corto circuito o Jumping Code. • Los operadores booleanos se traducen a saltos. • La posición en el código representa el valor parcial o definitivo de la expresión booleana. Hernández-Novich (USB) Representaciones Intermedias 2016 20 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code – casos base B→ true B→ false B→ not B1 B→ E1 < E2 Hernández-Novich (USB) Representaciones Intermedias 2016 21 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code – casos base B→ true B→ false {B.code ← gen(0 goto0 B.true)} {B.code ← gen(0 goto0 B.false)} B→ not B1 B→ E1 < E2 • B.true y B.false contienen la etiqueta destino – Just do it! Hernández-Novich (USB) Representaciones Intermedias 2016 21 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code – casos base B→ true B→ false B→ not B1 {B.code ← gen(0 goto0 B.true)} {B.code ← gen(0 goto0 B.false)} ( ) B1 .true ←B.false B1 .false←B.true B.code ←B1 .code B→ E1 < E2 • B.true y B.false contienen la etiqueta destino – Just do it! • La negación mantiene el código, pero invierte el sentido de saltos. Hernández-Novich (USB) Representaciones Intermedias 2016 21 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code – casos base B→ true B→ false B→ not B1 B→ E1 < E2 {B.code ← gen(0 goto0 B.true)} {B.code ← gen(0 goto0 B.false)} ( ) B1 .true ←B.false B1 .false←B.true B.code ←B1 .code B.code←E1 .code ++ E2 .code ++ gen(0 if0 E1 .addr 0 <0 E2 .addr 0 goto0 B.true) ++ gen(0 goto0 B.false) • B.true y B.false contienen la etiqueta destino – Just do it! • La negación mantiene el código, pero invierte el sentido de saltos. • Cada operador relacional genera los saltos a las etiquetas correspondientes. Hernández-Novich (USB) Representaciones Intermedias 2016 21 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code – disyunción B→ B1 or B2 B1 .true ←B.true B1 .false←newlabel() B2 .true ←B.true B .false←B.false 2 B.code ←B1 .code ++ label(B .false) ++ 1 B2 .code • Si B1 es cierta, seguimos directo a B.true – cortocircuito. • Si B1 es falsa, evaluamos B2 saltando a la nueva etiqueta. • El resto de salidas para B1 y B2 se mantienen a partir de B. Hernández-Novich (USB) Representaciones Intermedias 2016 22 / 32 Expresiones Booleanas para Control de Flujo Expresiones Booleanas para Control de Flujo Jumping Code – conjunción B→ B1 and B2 B1 .true ←newlabel() B1 .false←B.false B2 .true ←B.true B .false←B.false 2 B.code ←B1 .code ++ label(B .true) ++ 1 B2 .code • Si B1 es falsa, seguimos directo a B.false – cortocircuito. • Si B1 es cierta, evaluamos B2 saltando a la nueva etiqueta. • El resto de salidas para B1 y B2 se mantienen a partir de B. Hernández-Novich (USB) Representaciones Intermedias 2016 23 / 32 Expresiones Booleanas para Control de Flujo Un ejemplo . . . que conduce a una mejora if (x < 100 or x > 200 and x != y ) x = 0; El esquema generaría código similar a if x goto L3: if x goto L4: if x goto L2: x := L1: < 100 goto L2 L3 > 200 goto L4 L1 != y goto L2 L1 0 Hernández-Novich (USB) • goto L3 es redundante • Ambos goto L1 podrían eliminarse si se contara con ifnot en el TAC. Representaciones Intermedias 2016 24 / 32 Expresiones Booleanas para Control de Flujo Mejorando el código para el if Fall Through Optimization S→ if B then S1 B.true ←fall B.false ←S.next S .next←S.next 1 S.code ←B.code ++ S1 .code • El código de S1 siempre está después del código de B. • No generar una etiqueta en medio. • La etiqueta fall indica “dejarse caer” a la siguiente instrucción. • Se aplica la misma técnica para el while. Hernández-Novich (USB) Representaciones Intermedias 2016 25 / 32 Expresiones Booleanas para Control de Flujo Mejorando el código para expresiones booleanas Fall Through Optimization para operadores relacionales B→ E1 < E2 test ← E1 .addr ++ 0 <0 ++ E2 .addr if B.true = 6 fall and B.false = 6 fall then 0 0 0 0 0 0 s ← gen( if test goto B.true) ++ gen( goto B.false) else if B.true 6= fall then s ← gen(0 if0 test 0 goto0 B.true) else if B.false 6= fall then s ← gen(0 ifnot0 test 0 goto0 B.false) else s ←00 B.code ← E1 .code ++ E2 .code ++ s Ahora genera la cantidad mínima de saltos. Hernández-Novich (USB) Representaciones Intermedias 2016 26 / 32 Expresiones Booleanas para Control de Flujo Mejorando el código para expresiones booleanas Fall Through Optimization para disyunción B→ B1 or B2 B1 .true ←if B.true 6= fall then B.true else newlabel() B .false←fall 1 B .true ←B.true 2 B .false←B.false 2 B.code ←if B.true = 6 fall then B .code ++ B2 .code 1 else B1 .code ++ B2 .code ++ label(B1 .true) Ahora genera la cantidad mínima de saltos. Hernández-Novich (USB) Representaciones Intermedias 2016 27 / 32 Expresiones Booleanas para Control de Flujo El ejemplo, después de la optimización if (x < 100 or x > 200 and x != y ) x = 0; El esquema optimizado generaría código similar a if x < 100 goto L2 ifnot x > 200 goto L1 ifnot x != y goto L1 L2: x := 0 L1: Hernández-Novich (USB) • No hay etiquetas redundantes • Menos saltos. Representaciones Intermedias 2016 28 / 32 Expresiones Booleanas por su valor Evaluando Expresiones Booleanas Jumping Code hasta el resultado • Jumping Code sigue aplicando para evaluar expresiones booleanas para obtener su valor. • Se salta por cortocircuito hasta determinar el valor final. • La instrucción final produce el valor booleano correspondiente. • El valor booleano puede ser natural o codificado según convenga al lenguaje y al TAC. • Este método es el más eficiente si la semántica del lenguaje permite evaluar con corto circuito. Hernández-Novich (USB) Representaciones Intermedias 2016 29 / 32 Expresiones Booleanas por su valor Expresiones Booleanas por su valor Cálculo del valor final S→ id := B B.true ←newlabel() B.false←newlabel() S.code←B.code ++ label(B.true) ++ 0 0 gen(id.addr := true) ++ 0 goto0 S.next) ++ gen( label(B.false) ++ 0 0 gen(id.addr := false) Hernández-Novich (USB) Representaciones Intermedias 2016 30 / 32 Consideraciones Consideraciones • La generación para instrucciones utiliza la técnica de concatenación de cadenas. Reescríbala para hacer generación progresiva. • La generación para instrucciones utiliza atributos heredados. • Intente escribirla para utilizar atributos sintetizados. • Aplique la técnica de marcadores y posible reescritura de reglas para convertir la herencia en copia a través de la pila de un reconocedor LR. • Extienda el esquema de generación por corto circuito para incluir implicación, disyunción exclusiva y equivalencia. • Complete el esquema de generación por corto circuito aplicando la técnica de fall through para la conjunción y los casos agregados anteriormente. Hernández-Novich (USB) Representaciones Intermedias 2016 31 / 32 Referencias Bibliográficas Bibliografía • [Aho] (Primera Edición) • Secciones 8.3 y 8.4 • Ejercicios 8.7 a 8.10 • [Aho] (Segunda Edición) • Secciones 6.4.3, 6.4.4 • Ejercicios 6.4.1 a 6.4.9 Hernández-Novich (USB) Representaciones Intermedias 2016 32 / 32