Download transparencias

Document related concepts
no text concepts found
Transcript
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Compiladores: Generación de
código
Francisco J Ballesteros
LSUB, URJC
http://127.0.0.1:3999/s11.gen.slide#1
Page 1 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Generación de código
Una vez tenemos un AST decorado con todo lo necesario
podemos generar código
O generamos un código intermedio
y luego lo volvemos a traducir para una máquina
O generamos código objeto (o ensamblador) directamente
O traducimos a otro lenguaje
que podemos compilar on otro compilador
o que es interpretable por otro programa
http://127.0.0.1:3999/s11.gen.slide#1
Page 2 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Objetivo
Necesitamos saber cómo es la máquina target
Generar código para ella
es en realidad imprimir texto
con instrucciones para esa máquina
pensando en un entorno de ejecución en ella (run-time)
http://127.0.0.1:3999/s11.gen.slide#1
Page 3 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM
Picky Abstract Machine
es la máquina (virtual) que ejecuta binarios de Picky
Tiene
algunos registros
memoria para el código
memoria para la pila
memoria dinámica (heap)
descriptores de procedimiento, tipo, variable
tabla de contadores de programa a fichero-línea
http://127.0.0.1:3999/s11.gen.slide#1
Page 4 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Registros
PC Contador de programa. Apunta a instrucciones.
FP Frame Pointer. Apunta a una posición en la pila.
para localizar el record que activa una llamada a procedimiento
SP Stack Pointer. Apunta a una posición en la pila.
VP (Local) variable Pointer. Localiza variables locales
apunta a la zona de variables locales dentro del frame
AP Argument Pointer. Localiza direcciones de argumentos en el frame
PID Procedure Identifier. Apunta a un descriptor de procedimiento o función.
http://127.0.0.1:3999/s11.gen.slide#1
Page 5 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Memoria de Texto
Contiene el programa.
Cada instrucción es un código de byte almacenado en una palabra
Las que tienen argumentos usan una palabra extra por argumento
El PC apunta a una de estas palabras (no bytes).
La primera dirección es 0
http://127.0.0.1:3999/s11.gen.slide#1
Page 6 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Pila
Direccionada byte a byte
En la parte baja de la pila están las variables globales
Luego hay registros de activación para cada llamada a procedimiento
SP, FP, VP y AP apuntan a la pila
http://127.0.0.1:3999/s11.gen.slide#1
Page 7 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Heap
Contiene las variables dinámicas
Cada una identificada por un puntero o descriptor
http://127.0.0.1:3999/s11.gen.slide#1
Page 8 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Descriptores
De procedimiento:
indican los metadatos de cada procedimiento o función
Ej. cuántos argumentos y dónde está su código
De tipo:
información de depuración y metadatos para los tipos
De variable
idem
http://127.0.0.1:3999/s11.gen.slide#1
Page 9 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Instrucciones (1)
ICnop = 0,
ICle,
ICge,
ICpow,
IClt,
ICgt,
ICmul,
ICdiv,
ICmod,
ICadd,
ICsub,
ICminus,
ICnot,
ICor,
ICand,
ICeq,
ICne,
ICptr,
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
nop */
le|r -sp -sp +sp */
ge|r -sp -sp +sp */
pow -sp -sp +sp */
lt|r -sp -sp +sp */
gt|r -sp -sp +sp */
mul|r -sp -sp +sp */
div|r -sp -sp +sp */
mod|r -sp -sp +sp */
add|r -sp -sp +sp */
sub|r -sp -sp +sp */
minus|r -sp +sp */
not -sp +sp */
or -sp -sp +sp */
and -sp -sp +sp */
eq|r|a -sp -sp +sp */
ne|r|a -sp -sp +sp */
ptr -sp +sp */
obtain address for ptr in stack */
http://127.0.0.1:3999/s11.gen.slide#1
Page 10 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Instrucciones (2)
ICpush=ICargs,
ICindir,
ICjmp,
ICjmpt,
ICjmpf,
ICidx,
ICfld,
ICdaddr,
ICdata,
ICeqm,
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
push|r n +sp */
push n in the stack */
indir|a n -sp +sp */
replace address with referenced bytes */
jmp addr */
jmpt addr */
jmpf addr */
idx tid -sp -sp +sp */
replace address[index] with elem. addr. */
fld n -sp +sp */
replace obj addr with field (at n) addr. */
daddr n +sp */
push address for data at n */
data n +sp */
push n bytes of data following instruction */
eqm n -sp -sp +sp */
compare data pointed to by addresses */
http://127.0.0.1:3999/s11.gen.slide#1
Page 11 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Instrucciones (3)
ICnem,
ICcall,
ICret,
ICarg,
IClvar,
ICstom,
ICsto,
ICcast,
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
nem n -sp -sp +sp */
compare data pointed to by addresses */
call pid */
ret pid */
arg n +sp */
push address for arg object at n */
lvar n +sp*/
push address for lvar object at n */
stom tid -sp -sp */
cp tid's sz bytes from address to address */
sto tid -sp -sp */
cp tid's sz bytes to address from stack */
cast|r tid -sp +sp */
convert int (or real |r) to type tid */
http://127.0.0.1:3999/s11.gen.slide#1
Page 12 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
PAM: Binarios
Para
src/lang/f.p (src/lang/f.p)
El ensamblador generado podría ser
src/lang/f.pam (src/lang/f.pam)
Ver la descripción de PAM en
The Picky Programming Language (http://lsub.org/ls/export/picky.pdf)
http://127.0.0.1:3999/s11.gen.slide#1
Page 13 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Generación de código
Hay que recorrerse los AST de las declaraciones del programa
Y generar el trozo correspondiente del objeto para cada una
Pero sólo si no hay errores
ni sintácticos
ni semánticos
http://127.0.0.1:3999/s11.gen.slide#1
Page 14 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Generación de código
func main() {
os.Args[0] = "pick"
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) != 1 {
usage()
}
l, err := newFileLex(args[0])
if err != nil {
Fatal("%s: %s", args[0], err)
}
yyParse(l)
if nerrors == 0 {
gen(os.Stdout)
}
os.Exit(nerrors)
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 15 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Generación de código
Hay que recorrer los entornos y generar código para todos
los símbolos definidos
Luego emitir el código generado en el orden apropiado
func gen(w io.Writer) {
forall(func (s *Sym) {
gtypes = append(gtypes, s)
}, Stype)
forall(func (s *Sym) {
gvars = append(gvars, s)
}, Sconst, Svar)
forall(func (s *Sym) {
gprocs = append(gprocs, s)
genproc(s)
}, Sproc, Sfunc)
emitentry(w)
emittypes(w)
emitvars(w)
emitprocs(w)
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 16 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Generación de código
type Gen func(sym *Sym);
func forall(g Gen, kinds ...Skind) {
for _, e := range envs {
for _, s := range e.order {
for _, k := range kinds {
if s.kind == k {
if debugGen {
fmt.Printf("gen %s\n", s)
}
g(s)
break
}
}
}
}
}
Con una función que imprime el nombre del símbolo
tenemos esta salida (src/lang/f.outa)
http://127.0.0.1:3999/s11.gen.slide#1
Page 17 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Generación de código
Declaramos globales para lo que tenemos que emitir
var gtypes, gvars, gprocs []*Sym
var entry int
Y lo usaremos luego como en
func emitentry(w io.Writer) {
fmt.Fprintf(w, "#!/bin/pam\n")
fmt.Fprintf(w, "entry %d\n", entry)
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 18 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Atributos para generación de código
En cada símbolo vamos a guardar un buffer para
el trozo de código a emitir, y luego lo emitiremos.
type Icode int
type Inst struct {
comment string
op Icode
arg int
}
type Sym struct {
....
code []Inst
pc int
...
}
Y conforme veamos que necesitamos más información de
un símbolo, creamos nuevos atributos si no los tenemos.
http://127.0.0.1:3999/s11.gen.slide#1
Page 19 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Atributos para generación de código
Usaremos esto para generar instrucciones y comentarios
func genComment(prog []Inst, s string) []Inst {
i := Inst{comment: s}
return append(prog, i)
}
func genInst(pc int, prog []Inst, ic Icode, arg int) (int, []Inst) {
i := Inst{op: ic, arg: arg}
pc++
if ic.hasArg() {
pc++
}
return pc, append(prog, i)
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 20 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Datos
Para generar constantes y variables globales
hay que asignar una dirección a cada una
func gen(w io.Writer) {
...
forall(func (s *Sym) {
genvar(s)
gvars = append(gvars, s)
}, Sconst, Svar)
...
emitvars(w)
}
var daddr int
func genvar(s *Sym) {
if s.val == nil {
return
}
s.addr = daddr
daddr += s.val.typ.Len()
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 21 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Datos
Luego incluimos addr como nuevo atributo en Sym
Ojo, hay expresiones que requiren inventar `variables'
Por ejemplo el Nd para "hola"
var tmpid int
func genTemp(nd *Nd) {
name := fmt.Sprintf("tmp%d", tmpid)
tmpid++
s := &Sym{name: name, kind: Sconst, id: ID, typ: nd.typ, val: nd}
gvars = append(gvars, s)
nd.sym = s
genvar(s)
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 22 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Código
PAM require asignar un id a cada procedimiento o función
Basta generar código para la sentencia BLOCK del cuerpo
var procid int
func genproc(s *Sym) {
if s.val == nil {
fmt.Printf("no body for %s\n", s)
return
}
bdy := s.val.args[len(s.val.args)-1]
if bdy.op != BLOCK {
panic("genproc: bug")
}
s.procid = procid
s.code = genComment(nil, s.String())
s.pc, s.code = genstmt(pc, s.code, bdy)
pc = s.pc
procid++
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 23 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Sentencias
Para generar un bloque, generamos sentencia tras sentencia
func genstmt(pc int, prog []Inst, nd *Nd) (int, []Inst) {
prog = genComment(prog, nd.String())
switch nd.op {
case NOP:
case BLOCK:
for i := range nd.args {
pc, prog = genstmt(pc, prog, nd.args[i])
}
...
}
return pc, prog
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 24 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Sentencias
Para generar un return
generamos el código que calcula el valor retornado (en la pila)
generamos el código de la instrucción de retorno
func genstmt(pc int, prog []Inst, nd *Nd) (int, []Inst) {
prog = genComment(prog, nd.String())
switch nd.op {
...
case RETURN:
pc, prog = genExpr(pc, prog, nd.args[0])
pc, prog = genInst(pc, prog, ICret, procid)
...
}
return pc, prog
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 25 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Expresiones
Para generar el código para evaluar una expresión
Generamos instrucciones que usan la pila para evaluarla
Por ej., para constantes básicas:
func genExpr(pc int, prog []Inst, nd *Nd) (int, []Inst) {
prog = genComment(prog, nd.String())
switch nd.op {
case INT:
pc, prog = genInst(pc, prog, ICpush, nd.ival)
case FLOAT:
i := math.Float32bits(float32(nd.rval))
pc, prog = genInst(pc, prog, ICpush, int(i))
case CHAR:
pc, prog = genInst(pc, prog, ICpush, int(nd.cval))
...
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 26 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Parámetros y variables locales
La forma de direccionar parámetros y variables locales
depende de la arquitectura objeto
normalmente utiliza el frame pointer para localizarlos
require asignar una dirección (relativa en el frame) a cada uno
Pero en PAM hay instrucciones para direccionar
el parámetro n
la variable local n
Tan sólo tenemos que darle un índice único a cada una
Los parámetros por referencia requiren una indirección
http://127.0.0.1:3999/s11.gen.slide#1
Page 27 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Parámetros y variables locales
Luego necesitamos definir isref, isparm y islvar
func defparms(proc *Sym, parms []*Nd) {
addr := 0
for i := range parms {
if parms[i].op != PARM {
panic("defparms: bug: not a parm")
}
defvar(parms[i].sym, parms[i].sym.tnd)
parms[i].sym.addr = addr
if parms[i].bval {
parms[i].sym.isref = true
} else {
parms[i].sym.isparm = true
}
addr++
proc.parms = append(proc.parms, parms[i].sym)
}
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 28 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Expresiones
Podemos entonces
func genExpr(pc int, prog []Inst, nd *Nd) (int, []Inst) {
switch nd.op {
case ID:
if nd.sym.isref {
pc, prog = genInst(pc, prog, ICarg, nd.sym.addr)
pc, prog = genInst(pc, prog, ICindir, 8)
break
}
if nd.sym.isparm {
pc, prog = genInst(pc, prog, ICarg, nd.sym.addr)
break
}
if nd.sym.islvar {
pc, prog = genInst(pc, prog, IClvar, nd.sym.addr)
break
}
pc, prog = genInst(pc, prog, ICdaddr, nd.sym.addr)
...
http://127.0.0.1:3999/s11.gen.slide#1
Page 29 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Expresiones
Para operadores evaluamos los argumentos
Y luego la instrucción del operador
func genExpr(pc int, prog []Inst, nd *Nd) (int, []Inst) {
switch nd.op {
case '-':
pc, prog = genExpr(pc, prog, nd.args[0])
if len(nd.args) == 1 {
pc, prog = genInst(pc, prog, ICminus, nd.sym.addr)
return pc, prog
}
pc, prog = genExpr(pc, prog, nd.args[1])
pc, prog = genInst(pc, prog, ICsub, nd.sym.addr)
...
Ojo a operadores unarios y binarios
http://127.0.0.1:3999/s11.gen.slide#1
Page 30 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Estructuras de control
Para
while(expr) {
stmt
}
Queremos
loop:
eval expr
jump if false to end
stmt
jump to loop
end:
http://127.0.0.1:3999/s11.gen.slide#1
Page 31 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Estructuras de control
Problema
loop:
eval expr
jump if false to end
stmt
jump to loop
end:
No sabemos que PC corresponde a end antes de generar el código
http://127.0.0.1:3999/s11.gen.slide#1
Page 32 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Back-patching
No sabemos que PC corresponde a end antes de generar el código
Solución:
utilizamos una variable libre para la dirección del salto
nos la inventamos cuando la necesitamos
ya le daremos un valor (antes de escribir el código!)
Dicho de otro modo:
usamos un puntero a un entero en lugar de un entero para la etiqueta
y más adelante le daremos el valor
http://127.0.0.1:3999/s11.gen.slide#1
Page 33 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Back-patching
No sabemos que PC corresponde a end antes de generar el código
Otra solución:
Creamos una etiqueta
que apunta a la instrucción del salto
y más adelante actualizamos la instrucción del salto
cuando le demos un valor a la etiqueta.
http://127.0.0.1:3999/s11.gen.slide#1
Page 34 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Estructuras de control
type Label struct{pc, usedat int}
func genstmt(pc int, prog []Inst, nd *Nd) (int, []Inst) {
...
case WHILE:
loop := Label{pc: pc}
end := Label{}
pc, prog = genExpr(pc, prog, nd.args[0])
end.usedat = pc
pc, prog = genInst(pc, prog, ICjmpf, 0)
genstmt(pc, prog, nd.args[1])
pc, prog = genInst(pc, prog, ICjmp, loop.pc)
end.pc = pc
prog[end.usedat].arg = end.pc
...
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 35 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Generación de código
Una vez hemos generado el código, lo emitimos
func emitprocs(w io.Writer) {
for _, prg := range gprocs {
emitProg(w, prg.code, prg.pc)
}
}
func emitProg(w io.Writer, prg []Inst, pc int) {
for i := range prg {
if prg[i].comment != "" {
fmt.Fprintf(w, "# %s\n", prg[i].comment)
} else {
fmt.Fprintf(w, "%05d\t%s\n", pc, prg[i])
pc++
if prg[i].op.hasArg() {pc++}
}
}
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 36 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Generación de código
func (i Inst) String() string {
if i.op.hasArg() {
return fmt.Sprintf("%s\t%#x", i.op, i.arg)
}
return fmt.Sprintf("%s\t%s", i.op)
}
http://127.0.0.1:3999/s11.gen.slide#1
Page 37 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
¿Ahora qué?
Tenemos un prototipo del compilador
Hay que usarlo con cuantos más programas mejor
tanto correctos
como erróneos
Los usuarios escriben programas que nunca imaginarías
Seguramente hay bugs por
falta de comprobaciones semánticas
y otras meteduras de pata
http://127.0.0.1:3999/s11.gen.slide#1
Page 38 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
¿Ahora qué?
Si quieres ver cómo quedaría un compilador para Picky
Mira el que hay en archivo (tar comprimido, .tgz) que hay en
la página de picky (http://lsub.org/fdp/picky.html)
Ese está hecho en C
pero ahora debería ser fácil de entender
y cientos de estudiantes (miles?) lo han usado
por lo que debería tener no demasiados bugs
Mira también otros compiladores para otros lenguajes
Y programa alguno tú mismo
http://127.0.0.1:3999/s11.gen.slide#1
Page 39 of 40
Compiladores: Generación de código - (c)2014 LSUB
1/25/16, 2:54 PM
Questions?
Francisco J Ballesteros
LSUB, URJC
http://lsub.org (http://lsub.org)
http://127.0.0.1:3999/s11.gen.slide#1
Page 40 of 40