2.2.2 Código P.
2.2.2 Código P.
Generación de Código Intermedió
*Después de los análisis sintácticos y semánticos,
algunos compiladores generan una representación intermedia explicita del
programa fuente. Se puede considerar esta representación intermedia como un
programa para una maquina abstracta. Esta representación intermedia debe tener
dos propiedades importantes, debe ser fácil de producir y fácil de traducir al
programa objeto.
*El código intermedió es particularmente utilizado
cuando el objetivo de compilador es producir código muy eficiente, ya que para
hacerlo así se requiere una cantidad importante del análisis de las propiedades
del código objetivo, y esto se facilita mediante el uso del código intermedio.
*El código intermedio también puede ser útil al
hacer que un compilador sea mas fácilmente re dirigible: si el código
intermedio es hasta cierto punto independiente de la maquina objetivo, entonces
genera código para una maquina objetivo diferente solo requiere volver a
escribir el traductor de código intermedio a código objetivo, y por lo regular
esto es mas fácil que volver a escribir todo un generador de código.
*La representación intermedia puede tener diversas
formas, se trata una forma intermedia llamada "CÓDIGO DE TRES
DIRRECCIONES" y el "CÓDIGO P"
*El código de tres direcciones consiste en una
secuencia de instrucciones, cada una de las cuales tiene como máximo tres
operadores.
temp1 := entareal(60)
temp2 := id3 * temp1
temp3 := id2 + temp2
id1 := temp3
Esta representación intermedia tiene varias
propiedades.
- primera, cada instrucción de tres direcciones
tiene a lo sumo un operador, además de la asignación. Por tanto, cuando genera
esas instrucciones, el compilador tienes de decidir el orden en que deben
efectuarse las operaciones; la multiplicación precede a la adicción en el
programa fuente.
- segunda, el compilador debe generar un nombre
temporal para guardar los valores calculados por cada instrucción.
- tercera, algunas instrucciones de "tres
direcciones" tiene menos de tres operadores, por ejemplo, la primera y la
ultima instrucción.
EL CÓDIGO P
*El código P comenzó como un código ensamblador
objetivo estándar producido por varios compiladores Pascal en la década de 1970
y principios de la de 1980. Fue diseñado para código real para una maquina de
pila hipotética la idea era hacer que los compiladores de Pascal se
transportaran fácilmente requiriendo solo que se volviera a escribir el
interprete de la maquina P para una plataforma, el código P también a probado
ser útil como código intermedio y sean utilizado varias extensiones y
modificaciones del mismo en diverso compiladores de código nativo,
La mayor parte para lenguaje tipo Pascal.
*Como el código P fue diseñado para ser
directamente ejecutable, contiene una descripción implícita de un ambiente de
ejecución particular que incluye tamaños de datos, además de mucha información
especifica para la maquina P, que debe conocer si se desea que un programa de
código P se comprensible. La maquina P esta compuesta por una memoria de código,
una memoria de datos no específica para variables nombre das y una pila para
datos temporales, junto como cualquiera registro que sea necesario para
mantener la pila y apoyar la ejecución.
COMPARACIÓN
*El código P en muchos aspectos está más código de
maquina real que al código de tres direcciones. Las instrucciones en código P
también requiere menos de tres direcciones: tosas las instrucciones que hemos
visto son instrucciones de "una dirección" o "cero
direcciones". Por otra parte, el código P es menos compacto que el código
de tres direcciones en términos de números de instrucciones, y el código P no
esta "auto contenido" en el sentido que las instrucciones funciones
implícitas en una pila (y las localidades de pila implícitas son de hecho las
direcciones "perdidas"). La ventaja respecto a la pila es que
contiene los valores temporales necesarios en cada punto del código, y el
compilador no necesita asignar nombre a ninguno de ellos, como el código de
tres direcciones.
Ejemplo
·
—
Se puede considerar esta una representación intermedia como un programa para una
maquina abstracta.
·
—
Traducción
de una proposición
CÓDIGO
DE TRES DIRECCIONES
Las reglas semánticas para generar código de tres
direcciones a partir de construcciones de lenguaje de programación
comunes son similares a las reglas para construir arboles sintácticos o para
notación posfija.
El código de tres direcciones es una secuencia de
preposiciones de la forma general
x := y op z
Donde 'x', 'y' y 'z' son nombres, constates o
variables temporales generadas por el compilador, op representa
cualquier valor, como un operador aritmético de punto fijo o flotante, o un
operador lógico sobre datos con valores booleanos. Obsérvese que no se permite
ninguna expresión aritmética compuesta, pues solo hay un operador en el lado
derecho de una proposición. Por tanto, una expresión del lenguaje fuente
como x+y*z se puede traducir en una secuencia
t1 := y * z
t2 := x + t1
Donde t1 y t2 son nombres temporales generados por
el compilador.
El código de tres direcciones es una
reprehensión linealizada de un árbol sintáctico o un GDA en la que lo nombres
explícitos corresponde a los nodos interiores del grafo.
t1 := -c
t1 := -c
t2 := b *
t1
t2 := b*t1
t3 :=
-c
t5 := t2 + t2
t4 := b *
t3
a := t5
t5 := t2 +t4
a := t5
(a) Código para el árbol
sintáctico
(b) Código para el GDA
Las proposiciones de tres direcciones son análogas
al código ensamblador. Las proposiciones pueden tener etiquetas simbólicas y
existen proposiciones para el flujo del control. Una etiqueta simbólica
representa el índice de una proposición de tres direcciones en la matriz
que contiene código intermedio. Los índices reales se pueden sustituir por las
etiquetas ya sea realizado una pasada independiente o mediante "relleno en
retroceso".
La notación gen(x ':=' y '+' z) en
la figura (1) para representar la proposición de tres direcciones x :=
y + z
Se puede añadir proposiciones de flujo del control
al lenguaje de asignaciones de la figura (1) mediante producciones y reglas
semánticas como las de las proposiciones while de la figura
(2).
1.) Definición dirigida por la sintaxis para
producir código de tres direcciones para las asignaciones.
Producción
|
Reglas semántica
|
S à id :=
E
|
S. Código := E. código || gen (id. lugar ':=' E. lugar)
|
E à E1 +E2
|
E. lugar := tempnuevo;
E. código := E1. código || E2. código || gen(E. lugar ':=' E1. lugar
'+' E2.lugar)
|
E à E1 * E2
|
E. lugar := tempnuevo;
E. código := E1. código || E2. código || gen(E. lugar ':=' E1. lugar
'*' E2.lugar)
|
E à -E1
|
E. lugar := tempnuevo;
E. código := E1. código || gen('E. lugar ':=' 'menosu' E1.
lugar)
|
E à (E1)
|
E. lugar := E1. lugar;
E. código := E1. código
|
E à id
|
E. lugar := id. lugar;
E. código := ' '
|
2.) Reglas semánticas que genera código para una proposición while:
IMPLEMENTACIÓN
DE CÓDIGO DE TRES DIRECCIONES
·
—
Cuádruplas.
·
—
Estructura con 4 campos: operador, operando1, operando2, resultado.
·
—
Triplas.
·
—
Estructura con 3 campos: operador, operando1, operando2. Los resultados
temporales se referecian por la posición en que fueron calculados.
EL
CÓDIGO P
El código P comenzó como un código ensamblador
objetivo estándar producido por varios compiladores Pascal en la década de 1970
y principios de la de 1980. la descripción de diversas versiones de código P.
La máquina P está compuesta por una memoria de
código, una memoria de datos no especificada para variables nombradas y
una pila para datos temporales, junto cualquier registro que sea necesario para
mantener la pila y apoyar la ejecución.
Como primer ejemplo se considera la expresión:
·
—
La versión
de código
P para esta expresión
es la que se muestra en seguida:
ldc 2 ; carga la constante 2
lod a ; carga el valor de la variable a
mpi ; multiplicación entera
lod b ; carga el valor de la variable b
ldc 3 ; carga la constante 3
sbi ; sustracción o
resta entera
adi ; adiciona de enteros
Esta instrucción se ven como si representaran las siguientes operaciones
en una maquina P.
En primer lugar, ldc 2 inserta el
valor 2 en la pila temporal. Luego, lod a inserta el valor de
la variable a en la pila. Las instrucción mpi extrae
estos dos valores de la pila, los multiplica (en orden inverso) e inserta el
resultado en la pial. Las siguientes dos instrucciones (lod b y ldc 3)
inserta valor de b y la constante 3 en la
pila (ahora tenemos tres valores en la pila). Posteriormente, la instrucción sbi extrae
los dos valores superiores de la pila, resta el primero del segundo, e inserta
el resultado. Finalmente, la instrucción adi extrae los dos
valores restantes de la pila, los suma e inserta el resultado. El código final
con un solo valor en la pila, que representa el resultado del cálculo.
IMPLEMENTACIÓN
DEL CÓDIGO P
Históricamente, el código P ha sido en
su mayor parte generado como un archivo de texto, pero las
descripciones anteriores de las implementaciones de estructura de datos internas
para el código de tres direcciones (cuádruples y triples) también
funcionara como una modificación propia para el código P.
GENERACION
DEL CODIGO PARTIENDO DE LI. ALGORITMOS:
Se sabe que entre el parse y el lenguaje
objeto resultado de la compilación, habrá una de estas dos cosas o ambas.
Un lenguaje intermedio LI.
Una generación de código.
Se supone la existencia de ambos
componentes, nos hace falta ahora el regresar el código objeto para la maquina
objeto deseada, que en el caso normal de no tratarse de un compilador cruzado,
es el mismo código con el que está escrito el compilador.
Como ejemplo de las posibilidades
existentes se mencionan las de la figura 5 para el lenguaje pascal.
Se emplea in código P para un maquina
hipotética que opera con una pila. Este compilador es muy portable de una
maquina a otra. Es el método usado en el USCD PASCAL que al estar todo escrito
en el código de la maquina ficticia P, sólo se precisa tener un pequeño
interprete distinto para cada maquina objeto dada.
Pero la hipotética maquina a pila, ya
nos es tan hipotética puesto que ahora existe ya como microprocesador, con lo
que el código P se ejecuta directamente.
También es como el primer caso, pero en
vez de emplear un interprete, se traduce con un ensamblador para la maquina
objeto dada.
El compilador puede dar directamente un
módulo cargable para la maquina objeto en cuestión.
El compilador suministra un objeto
responsable que se puede montar con otros objetos.
Para dar concreción vamos a definir el
conjunto de instrucciones de un ordenador sencillo que tenga sólo un acumulador
A y un registro índice X. Una instrucción simbólica con un ensamblador para
esta maquina tendría hasta 4 partes separadas entre si por blancos:
-Una parte opcional, la etiqueta.
-El código de operación.
-El operando.
-Comentario opcional.
La forma de direccionar la memoria son
las siguientes:
DIRECTA: Se
toma como operando el contenido en memoria del campo de dirección.
INDIRECTA: Como
antes pero, el contenido de memoria se considera a su vez como dirección del
dato.
INMEDIATA: El
valor de la dirección lo tomo inmediatamente como operando.
Y salvo en el caso del direccionamiento
inmediato, las direcciones pueden ser: Absolutas o reales, Relativas a
la instrucción actual, Indexadas con un registro.
TÉCNICAS
BÁSICAS DE GENERACIÓN DE CÓDIGO
La generación de código intermedio(o generación de
código objetivo directa sin código intermedio), en realidad, si el código
generado se ve como un atributo de cadena, entonces este código se convierte en
un atributo sintetizado.
Para ver como el código de tres direcciones, o
bien, el código P se puede definir como un atributo sintetizado, considere la
siguiente gramática que representa un pequeño subconjunto de expresiones en C:
exp à id =
exp | aexp
aexp à aexp + factor
| factor
factorà (exp)| num | id
El código P considerando el caso de la generación
de código P en primer lugar, ya que la gramática con atributos es más simple
debido a que no es necesario generar nombres para elementos temporales.
Por ejemplo, la expresión (x=x+3)+4 tiene
ese atributo
lda x
lod x
ldc 3
adi
stn
ldc 4
adi
Gramática
con atributos para código de tres direcciones de cadena sintetizada
Regla Gramatical
|
Reglas Semánticas
|
exp à id =
exp2
|
exp1.pcode ="lda" ||id.strval++
exp2.pcode++"stn"
|
exp à aexp
|
exp. pcode =aexp.pcode
|
aexp1à aexp2 + factor
|
aexp1. pcode = aexp2.pcode ++ factor. pcode ++ "adi"
|
aexp à factor
|
aexp. pcode =factor. pcode
|
factor à (exp)
|
factor. pcode =exp. pcode
|
factor à num
|
factor. pcode = "idc"||num.stval
|
factor à id
|
factor. pcode = "lod"||id.strval
|
Un parse es un procesador de
cualquier lenguaje, por ejemplo, los navegadores llevan internamente
procesadores de varios lenguajes, html, xhtml, xml, css, javascript, etc.
Los compiladores tienen un procesador de comandos que revisan la sintaxis antes de realizar la compilación.
Así que en general, los parsers o procesadores, son los motores de procesamiento del lenguaje (el que aplique en cada momento).
Los compiladores tienen un procesador de comandos que revisan la sintaxis antes de realizar la compilación.
Así que en general, los parsers o procesadores, son los motores de procesamiento del lenguaje (el que aplique en cada momento).
Comentarios
Publicar un comentario