scieee AI-readable full text Open interactive document viewer

Repositorio Institucional de Documentos

Abstract

Los dispositivos con requisitos de tiempo real son cada vez más utilizados, por ejemplo en automóviles (e.g. ABS), aeronáutica, electrodomésticos, etc. Para poder planificar los requisitos temporales de cualquier tarea, el primer paso es conocer (una cota superior de) su tiempo de ejecución en el peor caso (worst case execution time o WCET). Este cálculo depende de factores hardware y software, como por ejemplo de las memorias cache y del compilador utilizado, y debe conocerse previamente a su ejecución. Además, requiere información que maneja internamente el compilador pero no queda explícita en el ejecutable final, con lo que recuperarla es muy complejo. Cuanto más ajustada sea la cota superior obtenida, mejor se aprovecharán los recursos del sistema, aumentando así la planificabilidad del mismo. Por todo lo anterior, se ha realizado este proyecto de fin de carrera, cuyo objetivo principal ha sido la implementación de una serie de pasos (fases en la terminología usual de compiladores) que obtengan la información necesaria directamente en el proceso de compilación: reúsos de bloques de memoria y número máximo de iteraciones en bucles. Para ello se ha utilizado la infraestructura de compilación Low Level Virtual Machine (LLVM). Se han creado dos bibliotecas para ayudar al cálculo de la cota superior de los procesos. Estas bibliotecas van a sacar a relucir los accesos a memoria que existen, pudiendo así saber el reúso de variables y constantes, tanto temporal como espacial, y el número de veces que se ejecuta cada bucle de instrucciones como máximo. En particular: La biblioteca libmarcarLoadsStores localiza en el código intermedio de LLVM los accesos a memoria que existen (loads y stores) añadiéndoles los metadatas de depuración para su posterior reconocimiento con sus correspondientes instrucciones en el fichero que contiene el código ensamblador ARM. La biblioteca libbuclesReusos analiza en profundidad el código LLVM Intermediate Repesentation (IR) en busca de iteraciones y subiteraciones, indicando en el fichero ARM el máximo número de veces que se puede ejecutar un bloque básico de instrucciones, siempre que se sepa este dato en tiempo de compilación. Además, recoge los datos de los accesos a memoria, para poder identificar el reúso espacial y temporal, la variable o constante a la cual se refiere, y el desplazamiento que existe. López Ara, Marta; Segarra Flor, Juan

Full text

Proyecto Fin de Carrera de Ingenier´ıa en Inform´atica DESARROLLO DE FASES DE COMPILACI ´ ON PARA DESCUBRIR EL TIEMPO DE EJECUCI ´ ON DE PEOR CASO Marta L´opez Ara Director: Juan Segarra Flor ´ Area de Arquitectura y Tecnolog´ıa de Computadores Departamento de Inform´atica e Ingenier´ıa de Sistemas Escuela de Ingenier´ıa y Arquitectura Universidad de Zaragoza Septiembre 2011 Curso 2010 / 2011 Agradecimientos Una vez llegado al final de este proyecto, me gustar´ıa expresar mi agradecimiento a todas aquellas personas que de una manera u otra han contribuido a su realizaci´on. En primer lugar a mi director de proyecto, Juan Segarra Flor, por conseguir que llegara hasta aqu´ı gracias a sus continuas explicaciones, su apoyo y su confianza depositada en m´ı. A Ra´ul por su inestimable ayuda, paciencia, tranquilidad, por estar ah´ı cuando las fuerzas empiezan a flaquear y por infundir el color necesario para seguir trabajando cuando se ve todo de negro. A mi padre y mi madre, por el apoyo, cari˜no y comprensi´on que me han dado durante todos estos a˜nos. A mis abuelos, por su amor incondicional. A todos mis amigos de la universidad, por todos estos a˜nos compartidos juntos que nunca olvidar´e. A mis amigos: compa˜neros de la escuela de idiomas, postgrado, monitores de tiempo libre y alguno m´as que me puedo dejar en el tintero, porque gracias a su amistad me han aportado algo m´as que buenos momentos. ¡¡Muchas gracias a todos!! Desarrollo de fases de compilaci´on para descubrir el tiempo de ejecuci´on de peor caso RESUMEN Los dispositivos con requisitos de tiempo real son cada vez m´as utilizados, por ejemplo en autom´oviles (e.g. ABS), aeron´autica, electrodom´esticos, etc. Para poder planificar los requisitos temporales de cualquier tarea, el primer paso es conocer (una cota superior de) su tiempo de ejecuci´on en el peor caso (worst case execution time o WCET). Este c´alculo depende de factores hardware y software, como por ejemplo de las memorias cache y del compilador utilizado, y debe conocerse previamente a su ejecuci´on. Adem´as, requiere informaci´on que maneja internamente el compilador pero no queda expl´ıcita en el ejecutable final, con lo que recuperarla es muy complejo. Cuanto m´as ajustada sea la cota superior obtenida, mejor se aprovechar´an los recursos del sistema, aumentando as´ı la planificabilidad del mismo. Por todo lo anterior, se ha realizado este proyecto de fin de carrera, cuyo objetivo principal ha sido la implementaci´on de una serie de pasos (fases en la terminolog´ıa usual de compiladores) que obtengan la informaci´on necesaria directamente en el proceso de compilaci´on: re´usos de bloques de memoria y n´umero m´aximo de iteraciones en bucles. Para ello se ha utilizado la infraestructura de compilaci´on Low Level Virtual Machine (LLVM). Se han creado dos bibliotecas para ayudar al c´alculo de la cota superior de los procesos. Estas bibliotecas van a sacar a relucir los accesos a memoria que existen, pudiendo as´ı saber el re´uso de variables y constantes, tanto temporal como espacial, y el n´umero de veces que se ejecuta cada bucle de instrucciones como m´aximo. En particular: La biblioteca libmarcarLoadsStores localiza en el c´odigo intermedio de LLVM los accesos a memoria que existen (loads y stores) a˜nadi´endoles los metadatas de depuraci´on para su posterior reconocimiento con sus correspondientes instrucciones en el fichero que contiene el c´odigo ensamblador ARM. La biblioteca libbuclesReusos analiza en profundidad el c´odigo LLVM Intermediate Repesentation (IR) en busca de iteraciones y subiteraciones, indicando en fichero ARM el m´aximo n´umero de veces que se puede ejecutar un bloque b´asico de instrucciones, siempre que se sepa este dato en tiempo de compilaci´on. Adem´as, recoge los datos de los accesos a memoria, para poder identificar el re´uso espacial y temporal, la variable o constante a la cual se refiere, y el desplazamiento que existe. ´ Indice general 1. Introducci´on 1 1.1. Motivaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2. Contexto de realizaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.3. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.4. Herramientas utilizadas . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.5. Fases del trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.6. Estructura de la memoria . . . . . . . . . . . . . . . . . . . . . . . . 4 2. Planificaci´on 6 2.1. Ciclo de vida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2. Planificaci´on del trabajo . . . . . . . . . . . . . . . . . . . . . . . . . 7 3. Conceptos 8 3.1. Funcionamiento del compilador LLVM . . . . . . . . . . . . . . . . . 8 3.2. Bucles y re´usos en c´odigo ARM . . . . . . . . . . . . . . . . . . . . . 10 4. Desarrollo 13 4.1. An´alisis y dise˜no . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 4.1.1. Alcance del proyecto . . . . . . . . . . . . . . . . . . . . . . . 13 4.1.2. An´alisis del proyecto . . . . . . . . . . . . . . . . . . . . . . . 14 4.1.3. Dise˜no del proyecto . . . . . . . . . . . . . . . . . . . . . . . . 14 4.2. Implementaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.2.1. Cuenta de iteraciones . . . . . . . . . . . . . . . . . . . . . . . 15 4.2.2. Marcaci´on de loads y stores . . . . . . . . . . . . . . . . . . . 16 4.2.3. Localizaci´on de re´usos espaciales . . . . . . . . . . . . . . . . . 17 4.2.4. Localizaci´on de re´usos temporales . . . . . . . . . . . . . . . . 20 4.3. Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 5. Conclusiones 22 5.1. Dificultades encontradas . . . . . . . . . . . . . . . . . . . . . . . . . 22 5.2. Trabajo futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 5.3. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 5.4. Valoraci´on personal . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 ´ INDICE GENERAL iv A. LLVM Intermediate Representation 26 A.1. Visi´on general del juego de instrucciones . . . . . . . . . . . . . . . . 26 A.2. Tipos primarios y derivados . . . . . . . . . . . . . . . . . . . . . . . 27 A.3. SSA form (PHINODE) . . . . . . . . . . . . . . . . . . . . . . . . . . 28 A.4. Acceso a direcciones de memoria . . . . . . . . . . . . . . . . . . . . . 29 A.5. Lectura y escritura en memoria . . . . . . . . . . . . . . . . . . . . . 30 A.6. Ejemplo completo comentado . . . . . . . . . . . . . . . . . . . . . . 31 B. Gu´ıa de comandos LLVM 32 C. Pruebas 35 C.1. Cuenta de iteraciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 C.2. Marcaci´on de loads y stores . . . . . . . . . . . . . . . . . . . . . . . 47 C.3. Localizaci´on de re´usos temporales y espaciales . . . . . . . . . . . . . 51 D. Manual de Uso 69 D.1. Introducci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 D.2. Requerimientos Software . . . . . . . . . . . . . . . . . . . . . . . . . 69 D.3. Compilaci´on de las bibliotecas . . . . . . . . . . . . . . . . . . . . . . 70 D.4. Compilaci´on de los ficheros a analizar . . . . . . . . . . . . . . . . . . 71 D.5. Ficheros Resultado . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Bibliograf´ıa 76 ´ Indice de figuras 2.1. Ciclo de vida incremental. . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2. Diagrama de Gantt correspondiente a las fases de desarrollo . . . . . 7 3.1. Arquitectura y m´odulos LLVM . . . . . . . . . . . . . . . . . . . . . 9 3.2. C´odigo C de stores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.3. C´odigo LLVM de stores . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.4. C´odigo ARM de stores . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.5. C´odigo ejemplo en C . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.6. C´odigo ejemplo en ARM . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.7. C´odigo ejemplo final ARM . . . . . . . . . . . . . . . . . . . . . . . . 12 4.1. Optimizaci´on indvars ........................... 16 4.2. Ejemplo de cuenta de iteraciones . . . . . . . . . . . . . . . . . . . . 16 4.3. Ejemplo de debug en loads y stores . . . . . . . . . . . . . . . . . . . 17 4.4. Ejemplo de metadatas . . . . . . . . . . . . . . . . . . . . . . . . . . 17 4.5. Ejemplo de puntero con varios par´ametros . . . . . . . . . . . . . . . 18 4.6. Ejemplo de load con m´ultiples variables . . . . . . . . . . . . . . . . . 18 4.7. Ejemplo de store con una variable . . . . . . . . . . . . . . . . . . . . 18 4.8. Ejemplo de incremento constante . . . . . . . . . . . . . . . . . . . . 19 4.9. Ejemplo de incremento variable . . . . . . . . . . . . . . . . . . . . . 19 4.10. Ejemplo de re´uso temporal . . . . . . . . . . . . . . . . . . . . . . . . 20 4.11. Ejemplo de store de pila . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.12. Ejemplo de load de constante . . . . . . . . . . . . . . . . . . . . . . 20 A.1. C´odigo ejemplo en C . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 A.2. C´odigo ejemplo comentado en LLVM IR . . . . . . . . . . . . . . . . 31 C.1. C´odigo del fichero bucle.c . . . . . . . . . . . . . . . . . . . . . . . . 36 C.2. Bucle anidado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 C.3. Bucles con incremento constante . . . . . . . . . . . . . . . . . . . . . 37 C.4. Bucle con incremento variable . . . . . . . . . . . . . . . . . . . . . . 37 C.5. Bucles con decremento constante . . . . . . . . . . . . . . . . . . . . 37 C.6. Fichero bucle.ll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 C.7. Salida por pantalla al compilar bucle.c . . . . . . . . . . . . . . . . . 41 C.8. Parte del fichero bucle.arm.opt.s que nos muestra la salida para los bucles anidados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 ´ INDICE DE FIGURAS vi C.9. Parte del fichero bucle.arm.opt.s que nos muestra la salida para distintos bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 C.10.Parte del fichero bucle2.c . . . . . . . . . . . . . . . . . . . . . . . . . 44 C.11.Parte del fichero bucle2.arm.opt.s . . . . . . . . . . . . . . . . . . . . 44 C.12.Parte del fichero bucle3.c . . . . . . . . . . . . . . . . . . . . . . . . . 45 C.13.Parte del fichero bucle3.arm.opt.s . . . . . . . . . . . . . . . . . . . . 45 C.14.Ejemplo de bucle con instrucci´on do . . . . . . . . . . . . . . . . . . . 45 C.15.Tranformaci´on de bucle con instrucci´on do en lenguaje ARM . . . . . 46 C.16.Ejemplo de prueba para marcaci´on de loads y stores . . . . . . . . . . 47 C.17.Salida por pantalla al compilar el fichero stores.c . . . . . . . . . . . . 48 C.18.Fichero storesMarcado.ll . . . . . . . . . . . . . . . . . . . . . . . . . 49 C.19.Fichero stores.ll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 C.20.Parte del fichero stores.arm.opt.s . . . . . . . . . . . . . . . . . . . . 51 C.21.Fichero pruebaStores.c . . . . . . . . . . . . . . . . . . . . . . . . . . 52 C.22.Fichero pruebaStores.arm.opt.s . . . . . . . . . . . . . . . . . . . . . 55 C.23.Fichero jfdctint.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 C.24.Fichero jfdctint.arm.opt.s . . . . . . . . . . . . . . . . . . . . . . . . . 68 D.1. Ejemplo de compilaci´on de bibliotecas . . . . . . . . . . . . . . . . . 70 D.2. Comandos para compilar las bibliotecas . . . . . . . . . . . . . . . . . 70 D.3. Contenido de la carpeta . . . . . . . . . . . . . . . . . . . . . . . . . 71 D.4. C´odigo ejemplo de Makefile . . . . . . . . . . . . . . . . . . . . . . . 73 D.5. Makefile de ”Prueba1” . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Cap´ıtulo 1 Introducci´on En este cap´ıtulo se comentar´an los aspectos m´as generales del presente Proyecto Fin de Carrera, entre los que se encuentran: la motivaci´on para la consecuci´on del mismo, su contexto de realizaci´on, los objetivos definidos en la propuesta, las herramientas que se han utilizado durante su elaboraci´on, las fases en las que se ha dividido el trabajo y, por ´ultimo, una breve explicaci´on de la estructura del documento. 1.1. Motivaci´on A la hora de elegir un proyecto, fueron varios los motivos que me hicieron decantarme por ´este. Para empezar, el hecho de contribuir a la utilizaci´on de herramientas de software libre, en particular el proyecto Low Level Virtual Machine (LLVM). Otro aspecto interesante fue la posibilidad de ampliar mis conocimientos de ingenier´ıa del software, as´ı como de los lenguajes ensamblador y C++. Tambi´en consider´e interesante para completar mi formaci´on el hecho de incorporarme a un proyecto de investigaci´on y tener que adaptarme a ´el y a las necesidades del mismo, puesto que en mi futura vida laboral es una situaci´on m´as que probable. Y, por supuesto, el poner en pr´actica los conocimientos adquiridos durante la carrera, que considero debe ser el principal objetivo de cualquier PFC. 1.2. Contexto de realizaci´on El presente proyecto de fin de carrera se enmarca en temas de investigaci´on llevados a cabo dentro del grupo de Arquitectura de Computadores (gaZ) de la Universidad de Zaragoza. Parte de dicha investigaci´on consiste en desarrollar metodologias para analizar el tiempo de ejecuci´on en el peor caso (WCET) y proponer componentes hardware alternativos a los existentes cuyo an´alisis sea factible. Para analizar el WCET en un sistema de tiempo real son necesarios par´ametros como el m´aximo n´umero de iteraciones de cada bucle. En caso de que el sistema 1.3 Objetivos 2 final disponga de cache de datos, tambi´en resulta imprescindible conocer el re´uso de datos del programa a analizar. En este PFC se obtienen dichos par´ametros desde dentro del compilador, lo cual facilita el posterior an´alisis y proporciona mayor independencia respecto al repertorio de instrucciones final. 1.3. Objetivos Los objetivos principales del proyecto son los siguientes: 1. Estudio del funcionamiento interno del compilador LLVM [1] 2. Localizaci´on de bucles y variables de iteraci´on en c´odigo LLVM 3. Backtracking de variables de iteraci´on para obtener el m´aximo n´umero de iteraciones en bucles cuando sea posible 4. Identificaci´on de re´uso espacial y temporal b´asico en accesos a memoria en c´odigo LLVM 5. Generaci´on de ensamblador ARM etiquetado con la informaci´on anterior (m´aximo n´umero de iteraciones en bucles e informaci´on de re´uso en accesos a memoria) 1.4. Herramientas utilizadas Para realizar el desarrollo software habr´ıa bastado simplemente con un editor de texto y el entorno de desarrollo implementado por LLVM. Sin embargo, ha habido muchas otras herramientas que han sido muy ´utiles durante todo el desarrollo. ´ Estas han sido las herramientas utilizadas: Gedit 2.30.4 [2]:´ Este fue el editor elegido para codificar y manejar los diferentes tipos de archivos. Destaca por su simpleza y rapidez, y por sus m´ultiples funciones y plugins que ofrece a la hora de desarrollar. Y, sobre todo, por poder manejar a la vez m´ultiples tipos de archivos (C, C++, ensamblador ARM, LLVM IR) con facilidad. GCC 4.5 [3]: Compilador de C/C++ del proyecto GNU/Linux. Usado para compilar las bibliotecas. LLVM 2.8 [1]: Infraestructura de compilaci´on, junto con todas sus herramientas llvm-gcc, llvm-dis, llc, opt. Estas herramientas permiten compilar programas, pasar de un c´odigo a otro y hacer pasadas de an´alisis y compilaci´on sobre el c´odigo. 3.1 Funcionamiento del compilador LLVM 9 Figura 3.1: Arquitectura y m´odulos LLVM En este proyecto nos vamos a basar principalmente en este lenguaje para analizar los diferentes tipos de instrucciones, para calcular el n´umero de iteraciones en los bucles y los re´usos tanto temporales como espaciales. Una vez calculados y localizados se escribir´an en el c´odigo ARM correspondiente. Para entenderlo mejor, se va a mostrar el mismo c´odigo de un bucle con dos stores, tanto en lenguaje C como su transformaci´on en lenguaje LLVM IR. En la Figura 3.2 vemos c´omo ser´ıa el c´odigo en lenguaje C, en la Figura 3.3 vemos c´omo ser´ıa el mismo c´odigo en lenguaje LLVM IR, y para terminar en la Figura 3.4 vemos su transformaci´on en lenguaje ensamblador ARM. for (varBucle=0;varBucle<Tam;varBucle++) { Vector1[varBucle]=varBucle*5; Vector2[varBucle]=varBucle*5; } Figura 3.2: C´odigo C de stores En el c´odigo representado en la Figura 3.3, bb indica la etiqueta de un bloque b´asico que contiene todas las intrucciones siguientes del ejemplo. La instrucci´on getelementptr devuelve un puntero con la direcci´on de memoria. Las instrucciones add ymul son la suma y la multiplicaci´on en c´odigo LLVM, mientras que br es el salto. La instrucci´on store es la instrucci´on str en c´odigo ensamblador ARM. 3.2 Bucles y re´ usos en c´ odigo ARM 10 bb: ; preds = %bb, %bb.nph9 %varBucle.08 = phi i32 [ 0, %bb.nph9 ], [ %tmp1, %bb ] %scevgep12 = getelementptr [5000 x i32]* %Vector1, i32 0, i32 %varBucle.08 %scevgep13 = getelementptr [5000 x i32]* %Vector2, i32 0, i32 %varBucle.08 %tmp = mul i32 %varBucle.08, 5 store i32 %tmp, i32* %scevgep12, align 4 store i32 %tmp, i32* %scevgep13, align 4 %tmp1 = add nsw i32 %varBucle.08, 1 %exitcond11 = icmp eq i32 %tmp1, 5000 br i1 %exitcond11, label %bb3, label %bb Figura 3.3: C´odigo LLVM de stores .LBB0_1: @ %bb @ =>This Inner Loop Header: Depth=1 str r0, [r2], #4 str r0, [r1], #4 add r0, r0, #5 cmp r0, r3 bne .LBB0_1 @ BB#2: @ %bb.bb3_crit_edge add lr, sp, #1, 18 @ 16384 mov r4, #226, 30 @ 904 orr r4, r4, #1, 20 @ 4096 mov r5, sp add r6, lr, #226, 28 @ 3616 ldr r7, .LCPI0_0 Figura 3.4: C´odigo ARM de stores 3.2. Bucles y re´usos en c´odigo ARM El c´odigo ARM es el lenguaje ensamblador propio de la arquitectura ARM. El c´odigo escrito en lenguaje ensamblador es complejo ya que es una reprensentaci´on del lenguaje m´aquina, con instrucciones, registros y posiciones de memoria del procesador. Al tratarse de un nivel tan bajo y tratar con registros de la m´aquina es muy dif´ıcil poder ver con claridad las iteraciones de los bucles y re´usos mirando directamente el c´odigo ARM. El siguiente sencillo ejemplo en lenguaje C, Figura 3.5, nos permitir´a explicar a que nos referimos, mostrando bucles y re´usos espaciales. En el programa de ejemplo podemos ver que hay dos vectores y un bucle que se va incrementando de uno en uno. Dentro del bucle vemos que se produce una escritura en cada vector en la posici´on “i” que es la variable de iteraci´on. Tambi´en vemos que el bucle se repetir´a cinco mil veces. Sabemos, por tanto, que se van a producir re´usos espaciales en los dos vectores ya que se accede a una posici´on del vector que se va incrementando de manera constante a lo largo del bucle. 3.2 Bucles y re´ usos en c´ odigo ARM 11 #include <stdio.h> #define Tam 5000 int main() { int i=0; int A[Tam]; int B[Tam]; for (i=0;i<Tam;i++) { A[i]=i*5; B[i]=i*5; } } Figura 3.5: C´odigo ejemplo en C Sin embargo al compilarlo a c´odigo ARM, esa informaci´on es muy dif´ıcil de ver, como se puede observar en la Figura 3.6. @ BB#0: @ %bb.nph9 stmdb sp!, {r4, r5, r6, r7, r8, lr} sub sp, sp, #113, 26 @ 7232 sub sp, sp, #2, 18 @ 32768 add lr, sp, #1, 18 @ 16384 mov r0, #0 mov r1, sp add r2, lr, #226, 28 @ 3616 mov r3, #106, 30 @ 424 orr r3, r3, #6, 20 @ 24576 .LBB0_1: @ %bb @ =>This Inner Loop Header: Depth=1 str r0, [r2], #4 str r0, [r1], #4 add r0, r0, #5 cmp r0, r3 bne .LBB0_1 @ BB#2: @ %bb.bb3_crit_edge add lr, sp, #1, 18 @ 16384 mov r4, #226, 30 @ 904 orr r4, r4, #1, 20 @ 4096 mov r5, sp add r6, lr, #226, 28 @ 3616 ldr r7, .LCPI0_0 Figura 3.6: C´odigo ejemplo en ARM 3.2 Bucles y re´ usos en c´ odigo ARM 12 Podemos ver que hay dos stores (instrucciones str) dentro de un bloque que se repite (.LBB0 1), con un salto (bne) con comparaci´on (cmp). El bloque pertenece a un bucle, pero no podemos conocer f´acilmente los re´usos de esos stores ni las repeticiones que har´a dicho bloque. El objetivo del proyecto es precisamente mostrar claramente en el c´odigo ARM este tipo de informaci´on de forma clara y transparente. La siguiente figura muestra el resultado final en c´odigo ARM despu´es de pasar las bibliotecas. .LBB0_1: @ %bb @ Numero de vueltas=5000 @ =>This Inner Loop Header: Depth=1 str r0, [r2], #4 @ Store var "A". Reuso espacial. Var iteracion "i". Desplazamiento con "stride" 1 str r0, [r1], #4 @ Store var "B". Reuso espacial. Var iteracion "i". Desplazamiento con "stride" 1 add r0, r0, #5 cmp r0, r3 bne .LBB0_1 @ BB#2: @ %bb.bb3_crit_edge add lr, sp, #1, 18 @ 16384 mov r4, #226, 30 @ 904 orr r4, r4, #1, 20 @ 4096 mov r5, sp add r6, lr, #226, 28 @ 3616 ldr r7, .LCPI0_0 @ Load Constante .LCPI0_0 Figura 3.7: C´odigo ejemplo final ARM Cap´ıtulo 4 Desarrollo Este cap´ıtulo describe c´omo se llev´o a cabo el desarrollo del proyecto y las decisiones m´as importantes tomadas durante el mismo. 4.1. An´alisis y dise˜no Durante toda la fase de an´alisis y dise˜no, se trabaj´o estrechamente con el profesor Juan Segarra Flor para definir bien los siguientes puntos: Alcance del Proyecto: Se defini´o, describi´o y prepar´o el escenario de implementaci´on. An´alisis del Proyecto: Se especificaron las necesidades actuales del proyecto, y el encaminamiento para el futuro de la aplicaci´on. Dise˜no del Proyecto: Se describi´o su soluci´on y las actividades de implementaci´on y testeo que se iban a hacer. El objetivo de esta fase era poner en firme cu´ales eran exactamente las especificaciones del proyecto, la entrega y la preparaci´on de la implementaci´on. 4.1.1. Alcance del proyecto Se empez´o a trabajar para determinar el alcance de la implementaci´on del proyecto. En este paso, se determin´o lo que cubrir´ıa el proyecto, y c´omo se gestionar´ıa el tiempo para ello. El resultado final de esta fase, fue la generaci´on de dos documentos: La propuesta del proyecto, que contiene el alcance descrito para el mismo. Una primera planificaci´on de la distribuci´on del proyecto. 4.1 An´ alisis y dise˜ no 14 4.1.2. An´alisis del proyecto En la fase del an´alisis se recogieron los requisitos necesarios. Para esto, se qued´o varios d´ıas con el profesor Juan Segarra Flor, y as´ı se delimitaron las necesidades halladas hasta el momento, las cuales cambiar´ıan muy poco en todo el proceso. Los requerimientos m´as importantes fueron: Localizar los bucles dentro de un c´odigo fuente. Calcular el n´umero de iteraciones m´aximo, en tiempo de compilaci´on, de cada bloque de instrucciones. Hallar el re´uso espacial. Encontrar el re´uso temporal. Mostrar todo lo anterior en el fichero ensamblador de ARM. 4.1.3. Dise˜no del proyecto Posteriormente, una vez establecidos los requisitos y hecha la propuesta, se dise˜n´o la biblioteca y sus dependencias. Se llev´o a cabo un mapeo de las tecnolog´ıas disponibles para su ejecuci´on y un profundo estudio de la herramienta LLVM. Se siguieron haciendo reuniones para asegurar si el camino llevado hasta el momento era el correcto y para resolver dudas. Se fij´o que hab´ıa que realizar una biblioteca que leyera el c´odigo intermedio LLVM IR y que us´andola mediante una pasada de an´alisis sobre un programa pudi´eramos leer los datos de sus instrucciones. As´ı se tendr´ıa acceso a la secuencia de instrucciones y podr´ıamos analizarlas en profundidad. Se estableci´o como hab´ıa que realizar dicha biblioteca y las opciones del makefile. Se describieron los archivos makefile, tanto de la biblioteca como de los programas a analizar. Se establecieron tambi´en los comandos para poder realizar una pasada de an´alisis-optimizaci´on usando la biblioteca sobre el c´odigo LLVM IR. Se estudiaron todos los par´ametros de optimizaci´on de LLVM y los diferentes niveles de optimizaci´on a la hora de compilar. Se realizaron varios ejemplos b´asicos de bibliotecas que realizaban un recorrido b´asico sobre las instrucciones LLVM IR. Se comprob´o as´ı que ten´ıamos acceso a dichas instrucciones y sus par´ametros. Se fijaron tambi´en los diferentes tipos de programas-pruebas en C que habr´ıa que realizar para probar nuestra biblioteca: diferentes tipos de bucles (while, for, etc), acceso a vectores sobre la variable de iteraci´on en los bucles y varios programas completos de prueba. 4.2 Implementaci´ on 15 Es gracias a esta fase y a la de an´alisis, que la fase de implementaci´on ha sido m´as corta de lo que se esperaba, teniendo una duraci´on final esta ´ultima de dos meses a tiempo parcial, como se puede ver en la Secci´on 2.2. 4.2. Implementaci´on Fue requisito del proyecto que el lenguaje de desarrollo fuese C++, ya que es el lenguaje del c´odigo fuente de las bibliotecas de LLVM. Para poder entender bien la implementaci´on, se va a dividir esta secci´on en tres partes. En la primera, se va a explicar c´omo se construy´o la biblioteca para la cuenta de iteraciones de bloques. En la segunda, se mostrar´a c´omo se cre´o la biblioteca libMarcarLoadsStores. Para finalizar, se detallar´a c´omo se pasaron los re´usos temporales y espaciales marcados en el c´odigo LLVM IR al fichero ensamblador ARM, y varios detalles adicionales que se hicieron para complementar el trabajo realizado. 4.2.1. Cuenta de iteraciones Despu´es de estudiar el funcionamiento de LLVM y su API se decidi´o realizar primero el conteo de iteraci´on de los bucles. Para ello, hubo que realizar una biblioteca, ya que se ´esta se puede cargar en una pasada de an´alisis. Por consiguiente, se cre´o la biblioteca libcuentaBucles, que despu´es se pasar´ıa a llamar libbuclesReusos. Para poder utilizarla, hubo que usar la herramienta “opt” que realiza pasadas de an´alisis y optimizaci´on sobre c´odigo LLVM IR, y a la que se le pueden a˜nadir muchos par´ametros seg´un la necesidad que se tenga. Para hallar el n´umero de veces que se pasa por un bloque de instrucciones, fue de gran ayuda la clase “LoopInfo” de la API. Gracias a ella, se pod´ıa acceder a la informaci´on de cada bucle. Dentro de esa informaci´on, destacan los siguientes datos: Etiqueta del bloque de instrucciones N´umero de vueltas Variable de iteraci´on Adem´as de cada una de las instrucciones incluidas en cada bloque. De gran importancia, fue la optimizaci´on “indvars”. Gracias a esto se pudieron convertir todos los bucles en bucles naturales con variable de iteraci´on can´onica, es decir, que todos empezaran en cero y fueran increment´andose de uno en uno, como se muestra en la siguiente figura. 4.2 Implementaci´ on 16 for (i = 10; i < 5000; i+=2) => for (i = 0; i < 2495; i++) for (i = 7; i*i < 1000; i++) => for (i = 0; i != 25; i++) Figura 4.1: Optimizaci´on indvars Con esto, ya se pod´ıa saber las iteraciones que se producir´ıan en cada bucle y su etiqueta. El nombre de las etiquetas en c´odigo LLVM IR y en c´odigo ARM son id´enticos, por lo cual, se pod´ıa escribir en el fichero ARM el n´umero de vueltas calculado. En la siguiente figura podemos ver un ejemplo de bucles anidados: .LBB0_1: @ %bb2.preheader @ Numero de vueltas=5000 @ =>This Loop Header: Depth=1 @ Child Loop BB0_2 Depth 2 mov r7, #0 .LBB0_2: @ %bb1 @ Numero de vueltas=3000 @ Parent Loop BB0_1 Depth=1 @ => This Inner Loop Header: Depth=2 mov r1, r7 mov r0, r5 add r7, r7, #1 bl printf cmp r7, r6 bne .LBB0_2 Figura 4.2: Ejemplo de cuenta de iteraciones 4.2.2. Marcaci´on de loads y stores El siguiente paso fue localizar los re´usos, tanto temporales como espaciales, en el c´odigo LLVM IR. Gracias a la biblioteca creada se pod´ıa acceder a las instrucciones y comprobar si ´estas eran loads o stores. Tambi´en, se pod´ıa saber cu´al era la variable que se cargaba en cada momento, siempre hablando en tiempo de compilaci´on, y si ´esta se incrementaba con la variable de iteraci´on. Pero como el objetivo del proyecto era localizarlos en el ARM aqu´ı estuvo el primer gran problema. El c´odigo LLVM IR usa nombres para variables mientras que el c´odigo ARM s´olo usa los registros (r1, r2, r3, etc). Por este motivo, no se pod´ıa saber qu´e instrucci´on ARM correspond´ıa con cada load o store localizado en el c´odigo LLVM IR. Primero se pens´o en a˜nadir anotaciones o comentarios en las instrucciones del c´odigo LLVM para que al compilarlo a c´odigo ARM estuvieran all´ı en las 4.2 Implementaci´ on 17 instrucciones correspondientes, pero los comentarios se eliminaban en el proceso de traducci´on. Despu´es de mucho investigar, se comprob´o que la informaci´on del tipo “metadata” de LLVM (Figura 4.3) dise˜nada para DEBUG, y disponible desde la versi´on 2.7 de LLVM, se transformaba en comentarios en el c´odigo ARM (Figura 4.4) a la hora de compilar. Escribiendo un metadata en una instrucci´on LLVM se pod´ıa saber con qu´e instrucci´on o instrucciones se correspond´ıa en el c´odigo ARM. %tmp2 = load i32* %scevgep, align 4, !dbg !7 %tmp4 = load i32* %scevgep10, align 4, !dbg !8 Figura 4.3: Ejemplo de debug en loads y stores declare i32 @printf(i8* nocapture, ...) nounwind !1 = metadata !{i32 524329, metadata !"load", null, null} !2 = metadata !{i32 524329, metadata !"store", null, null} !3 = metadata !{i32 524299, null, i32 6, i32 0, metadata !1, i32 0} !4 = metadata !{i32 524299, null, i32 6, i32 0, metadata !2, i32 0} !5= metadata !{i32 0, i32 0, metadata !4, null} !6= metadata !{i32 1, i32 0, metadata !4, null} !7= metadata !{i32 0, i32 0, metadata !3, null} !8= metadata !{i32 1, i32 0, metadata !3, null} Figura 4.4: Ejemplo de metadatas As´ı pues, hab´ıa que escribir los metadatas en los loads y stores del c´odigo LLVM IR y despu´es compilarlo para acceder a dicha informaci´on en el ARM. Como hab´ıa que marcar los loads y stores, y despu´es, compilar para obtener el ARM con la informaci´on necesaria para poder relacionar las instrucciones, se opt´o por crear dos bibliotecas en vez de una. Por este motivo, se desarroll´o una nueva biblioteca llamada libmarcarLoadsStores que se encargar´ıa de escribir los metadatas correspondientes en los loads y stores del c´odigo LLVM. As´ı, cuando se compilara el c´odigo pas´andole esta biblioteca, ya tendr´ıamos el c´odigo LLVM con metadatas y faltar´ıa pasarlo a c´odigo ARM. Es entonces, cuando se decidi´o que la biblioteca llamada libcuentaBucles, mencionada anteriormente, tambi´en fuera la encargada de realizar todo el proceso de comprobar los diferentes re´usos de los loads y stores (temporal y espacial), y pas´o a llamarse libbuclesReusos. 4.2.3. Localizaci´on de re´usos espaciales El re´uso espacial que se ha calculado en este proyecto se centra en el acceso a diferentes posiciones de vectores, matrices o estructuras, en especial, cuando estas posiciones dependen directa o indirectamente de la variable de iteraci´on de los bucles. 4.2 Implementaci´ on 18 Ya que se hab´ıa calculado el n´umero de iteraciones en los bucles y se ten´ıa la variable de iteraci´on, se opt´o por continuar con la localizaci´on de re´usos espaciales. Para continuar, lo primero que hab´ıa que entender era lo que significaba cada par´ametro de las instrucciones, en particular las de loads y stores. Estas instrucciones tienen dos par´ametros. Uno es la variable o el valor de lectura, o de escritura, dependiendo de si es un load o un store. El otro par´ametro es, en el caso de los re´usos espaciales, un puntero a una posici´on de un vector. Dicho puntero es el que nos interesaba analizar con profundidad. Los punteros tambi´en tienen varios par´ametros (ver Figura 4.5). El primero es el vector al que se quiere acceder, y los dem´as par´ametros son variables, instrucciones o valores que nos indican los ´ındices de desplazamiento sobre ´este. Nos interesaba saber el nombre de la variable del vector y los par´ametros de desplazamiento. El principal objetivo aqu´ı era saber cu´antos par´ametros ten´ıa cada puntero de los loads y stores. Si ten´ıa m´as de un par´ametro, est´abamos ante un vector multidimensional o una estructura. %scevgep = getelementptr [100 x [100 x i32]]* @A, i32 0, i32 %i.119, i32 %k.016 %scevgep25 = getelementptr [100 x [100 x i32]]* @B, i32 0, i32 %k.016, i32 %j.117 Figura 4.5: Ejemplo de puntero con varios par´ametros Aunque no fue un requisito inicial, se prefiri´o dar una explicaci´on m´as detallada de la instrucci´on a analizar. Por lo cual, adem´as de avisar que hab´ıa re´uso espacial, a partir de ahora, tambi´en se indicar´ıa si era de una o m´ultiples variables, y el nombre de las mismas o el valor, seg´un el caso, como se muestra en la figura Figura 4.6. ldr r6, [r4, -r12] @ Load var "A". Reuso espacial. Multiples variables "i" "k" ldr r7, [r2], #400 @ Load var "B". Reuso espacial. Multiples variables "k" "j" Figura 4.6: Ejemplo de load con m´ultiples variables En el caso de que fuera una sola variable la del desplazamiento sobre el puntero, se acord´o que se mostrar´ıa el “stride”, es decir, la separaci´on entre una posici´on y la siguiente a la que se accediera, siempre expresado en elementos del tipo declarado en el vector. str r0, [r2], #4 @ Store var "A". Reuso espacial. Var iteracion "i". Desplazamiento con "stride" 1 str r0, [r1], #4 @ Store var "B". Reuso espacial. Var iteracion "i". Desplazamiento con "stride" 1 Figura 4.7: Ejemplo de store con una variable