reificación de llamadas a métodos en mono

92
Reificación de llamadas a métodos en Mono Ricardo Federico Markiewicz Dirección Lic. Rosa Wachenchauzer Co-Dirección Lic. Alan Cyment Padrón 78.226 Tesis de Grado en Ingeniería en Informática Departamento de Computación Facultad de Ingeniería Universidad de Buenos Aires Septiembre 2012

Upload: phungthien

Post on 06-Jan-2017

230 views

Category:

Documents


5 download

TRANSCRIPT

Page 1: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos enMono

Ricardo Federico MarkiewiczDirección Lic. Rosa Wachenchauzer

Co-Dirección Lic. Alan Cyment

Padrón 78.226

Tesis de Grado en Ingeniería en InformáticaDepartamento de Computación

Facultad de IngenieríaUniversidad de Buenos Aires

Septiembre 2012

Page 2: Reificación de llamadas a métodos en Mono
Page 3: Reificación de llamadas a métodos en Mono

Índice general

1. Introducción 11.1. Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2. Alcance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.3. Limitaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.4. Organización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.5. Material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

2. Definición del problema 52.1. Entrelazado en tiempo de compilación . . . . . . . . . . . . . . . . . . . . . . 52.2. Entrelazado en tiempo de carga . . . . . . . . . . . . . . . . . . . . . . . . . 62.3. Entrelazado en tiempo de ejecución . . . . . . . . . . . . . . . . . . . . . . . 7

3. Análisis de la solución 93.1. Requerimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

4. Estado del Arte 114.1. Entrelazado de Advice en AspectJ . . . . . . . . . . . . . . . . . . . . . . . . 11

4.1.1. Transformación de código intermedio . . . . . . . . . . . . . . . . . 114.1.2. Joinpoint shadows . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134.1.3. ¿Por qué AspectJ no pone inline el código de un Advice? . . . . . . . 14

4.2. Oracle JRockit JVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144.2.1. Propuesta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154.2.2. Suscripciones y ejecución de acciones . . . . . . . . . . . . . . . . . 164.2.3. Método de acción . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174.2.4. Acción de Instancia y Acción de Clase . . . . . . . . . . . . . . . . . 184.2.5. Suscripciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194.2.6. Beneficios obtenidos . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4.3. Wool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224.3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224.3.2. JIT : Agregado de hooks . . . . . . . . . . . . . . . . . . . . . . . . 224.3.3. Aspectos en Wool . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

I

Page 4: Reificación de llamadas a métodos en Mono

4.3.4. Controlando el Entrelazador . . . . . . . . . . . . . . . . . . . . . . 264.3.5. Implementación de inserción de “hooks” . . . . . . . . . . . . . . . . 264.3.6. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

5. Mono 295.1. Qué es Mono . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.2. Componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315.3. Mono Runtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

5.3.1. Aspectos Generales . . . . . . . . . . . . . . . . . . . . . . . . . . . 325.3.2. Pasadas del JIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335.3.3. Representación Interna de Instrucciones (IR) . . . . . . . . . . . . . 345.3.4. Registros Virtuales (Vregs) . . . . . . . . . . . . . . . . . . . . . . . 365.3.5. Trampolines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405.3.6. Emisión de código nativo . . . . . . . . . . . . . . . . . . . . . . . . 435.3.7. Assembler x86 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445.3.8. Frame y Registro EBP . . . . . . . . . . . . . . . . . . . . . . . . . 46

5.4. Internal Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

6. Solución adoptada 496.1. .NET API : Definición de Joinpoints . . . . . . . . . . . . . . . . . . . . . . . 49

6.1.1. La Clase Weaver . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496.1.2. Definición de Joinpoints . . . . . . . . . . . . . . . . . . . . . . . . 506.1.3. Eliminación de Joinpoints . . . . . . . . . . . . . . . . . . . . . . . 516.1.4. Evento : “On Exit” . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

6.2. Detalles de Implementación . . . . . . . . . . . . . . . . . . . . . . . . . . . 556.2.1. Joinpoint Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . 556.2.2. Concordancia mediante patrones y firma de los métodos . . . . . . . 576.2.3. Eliminación de Joinpoints . . . . . . . . . . . . . . . . . . . . . . . 58

6.3. El Entrelazador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606.3.1. Extracción de los parámetros . . . . . . . . . . . . . . . . . . . . . . 616.3.2. Contexto de ejecución del callback . . . . . . . . . . . . . . . . . . . 636.3.3. Ejecución del callback . . . . . . . . . . . . . . . . . . . . . . . . . 63

7. Pruebas 657.1. Ejecución de un método con N Advices . . . . . . . . . . . . . . . . . . . . . 657.2. Agregar o Eliminar un Joinpoint . . . . . . . . . . . . . . . . . . . . . . . . . 667.3. Pruebas sintetizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

7.3.1. binary-trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687.3.2. pidigits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

8. Conclusión 758.1. Puntos pendientes, problemas y limitaciones . . . . . . . . . . . . . . . . . . 758.2. Trabajos futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

9. Anexos 799.1. Anexo 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 799.2. Anexo 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 809.3. Anexo 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

II

Page 5: Reificación de llamadas a métodos en Mono

Índice de figuras

2.1. Esquema del proceso de entrelazado en tiempo de compilación . . . . . . . . . 62.2. Esquema del proceso de entrelazado en tiempo de ejecución . . . . . . . . . . 8

4.1. Esquema de ejecución segun el tipo usado . . . . . . . . . . . . . . . . . . . . 234.2. Gran cantidad de cambios de contexto debido a Advices ejecutados frecuente-

mente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244.3. Uso mixto de breakpoints y Advices incrustados, dando una mejor performance. 25

5.1. Arquitectura simplificada de Mono . . . . . . . . . . . . . . . . . . . . . . . . 305.2. Esquema de un JIT en una Java VM . . . . . . . . . . . . . . . . . . . . . . . 335.3. Call Stack típico de la arquitectura x86 . . . . . . . . . . . . . . . . . . . . . . 44

7.1. Ejecución de un (1) método con N Advices. . . . . . . . . . . . . . . . . . . . 667.2. Eliminar un Joinpoint, habiendo ejecutado N métodos antes. . . . . . . . . . . 677.3. Tiempo de ejecución de BinaryTrees . . . . . . . . . . . . . . . . . . . . . . . 707.4. Tiempo de ejecución de PiDigits . . . . . . . . . . . . . . . . . . . . . . . . . 74

III

Page 6: Reificación de llamadas a métodos en Mono

IV

Page 7: Reificación de llamadas a métodos en Mono

Índice de cuadros

4.1. Tipos de Joinpoint Shadow . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134.2. Anotaciones disponibles y sus significados . . . . . . . . . . . . . . . . . . . . 18

5.1. Arquitecturas soportadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315.2. Tipos de registros definidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375.3. Posibles valores de iassign[vreg] . . . . . . . . . . . . . . . . . . . . . . . . . 395.4. Tipos de Trampolin disponibles . . . . . . . . . . . . . . . . . . . . . . . . . . 415.5. mono_arch_patch_callsite para x86 simplificada . . . . . . . . . . . . . . . . . 425.6. mono_arch_output_basic_block para x86 (simplificado) . . . . . . . . . . . . . 435.7. Estructura de un frame x86 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

V

Page 8: Reificación de llamadas a métodos en Mono

Resumen

Desde sus inicios la industria del software ha evolucionado generando técnicas y paradigmascada vez más avanzados para ayudar en el proceso de creación de sistemas informáticos. Conla creciente complejidad en los Sistemas de Información además se han definido parámetrospara caracterizarlos: la mantenibilidad, escalabilidad, extensibilidad, [UMCS] entre otros.

Los paradigmas evolucionaron desde los más simples, como la programación estructurada quebuscaba separar las funcionalidades en módulos, hasta llegar a paradigmas más complejos,como la Programación Orientada a Aspectos (AOP en adelante).

Agradecimientos

A mis viejos y hermanas por soportarme y apoyarme, a Alina por llegar en ese momento justo,donde uno necesita un empujón, a mi esposa Noelia por la paciencia, a Rosita y Alan por in-troducirme en el tema, aguantar mis baches y ayudarme a mantener el foco, a la gente que hizola innumerable cantidad de Software Libre que usé en este trabajo por simplificarme enorme-mente la vida, y a la gente que me estoy olvidando por hacer eso que me estoy olvidando quehizo.

We’re doing Mono because we careabout upgrading the developmentplatform, we care about languageindependence and it’s very nice twowork on.

Miguel de Icaza

Page 9: Reificación de llamadas a métodos en Mono

CAPÍTULO 1

Introducción

Desde sus inicios la industria del software ha evolucionado generando técnicas y paradigmascada vez más avanzados para ayudar en el proceso de creación de sistemas informáticos. Conla creciente complejidad en los Sistemas de Información además se han definido parámetrospara caracterizarlos: la mantenibilidad, escalabilidad, extensibilidad [UMCS], entre otros.

Los paradigmas evolucionaron desde los más simples, como la programación estructurada quebuscaba separar las funcionalidades en módulos, hasta llegar a paradigmas más complejos co-mo la Programación Orientada a Aspectos (AOP en su sigla en inglés, que utilizaremos a partirde ahora).

Este último paradigma centra sus esfuerzos en lograr una efectiva modularización de las in-cumbencias de un sistema, tratando de solucionar un problema que introdujo la programaciónorientada a objetos : incumbencias transversales (cross-cutting concerns, o CCC). Éstas últimaspor su naturaleza son difíciles de modularizar dentro de la programación orientada a objetos.

Como consecuencia directa de estos CCC aparecen dos fenómenos no deseados. El primerode ellos es denominado code scattering (código disperso) y se da cuando una incumbenciaestá definida a lo largo de muchos objetos que nada tienen que ver con él, resultando en unaimplementación no modular a lo largo de varios módulos.

El segundo de estos fenómenos es el llamado code tangling (código mezclado) y se da cuandouna clase tiene un código que nada tiene que ver con su esencia, lo cual disminuye su cohesión yviola uno de los principios básicos del diseño de objetos: el principio de única responsabilidad.

Para hacer frente a estos dos nuevos fenómenos, AOP busca incorporar sentencias del tipo :

Para todo programa P, ante la condición C, ejecutar la acción A.

Este simple postulado implica la posibilidad de ejecutar métodos determinados frente a condi-ciones pre-establecidas por el programador. A su vez nos permite encapsular de manera atómicafuncionalidades que antes debíamos tener entrelazadas dentro de la lógica de un módulo da-do. La clave de la separación de incumbencias es la condición del postulado, que nos permitedefinir incumbencias en lugares diferentes y luego mezclarlas.

Las condiciones “C”, que en AOP se denominan Pointcuts [PAP1], son puntos de ejecución deun programa en donde se realiza un entrelazado entre el programa y una incumbencia dada. Es-tas condiciones son declaradas a partir de una construcción más simple, denominada Joinpoint.

1

Page 10: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Antes de continuar, repasemos algunos términos utilizados en AOP [PAP1] :

Joinpoint : Evento interceptado en tiempo de ejecución (punto en el grafo de ejecución).

Shadow : Región en el código fuente asociada a un Joinpoint (determinado estática-mente).

Advice : Código adicional a ejecutar en un determinado Joinpoint. En el presente trabajotambién lo llamaremos callback, de manera indistinta.

Pointcut : Predicado que define un conjunto de Joinpoints en los que se ejecutarán uno omás Advices. A diferencia de los Jointpoints, los Pointcuts, son definidos por el progra-mador.

Un Joinpoint se define como “cualquier punto en la ejecución de un programa donde puedaexistir un Pointcut” [PAP1]. Algunos ejemplos son :

La ejecución de un método

La creación de una instancia

La inicialización de un atributo

La definición de estos puntos dentro de un programa siempre viene acompañada de algúnlenguaje de alto nivel por medio del cual el desarrollador pueda definir el conjunto de reglasque se deben aplicar.

Sin embargo, existe un componente adicional que todas las implementaciones de aspectos nece-sitan, un componente extra para la realización del entrelazado entre el código principal y losaspectos. Este componente es llamado entrelazador (Weaver en inglés).

Específicamente, el trabajo de este componente es detectar los puntos dentro del programapara los cuales hay definida una regla, y crear ahí el Joinpoint, para luego poder aplicar lascondiciones que fueron definidas para ese punto en particular de la ejecución.

Uno de los campos importantes en los que se ha estado avanzando en el estudio de la progra-mación orientada a aspectos es en mejorar la forma de realizar el entrelazado, ya que tieneincidencia directa sobre la performance o el tiempo de compilación de una aplicación.

Es por ello que a medida que el paradigma ha evolucionado han aparecido tres formas derealizar esta tarea :

Entrelazado en tiempo de compilación

Entrelazado en tiempo de carga

Entrelazado en tiempo de ejecución

En la siguiente sección analizaremos las debilidades que surgen de aplicar cada una de esastécnicas para luego presentar la solución que se espera probar en el presente trabajo.

1.1 Objetivo

El objetivo del presente trabajo es estudiar y analizar la viabilidad de la implementación de unentrelazado en tiempo de ejecución sobre la máquina virtual del proyecto Mono.

2 Capítulo 1. Introducción

Page 11: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Se estudiará el impacto en el tiempo de carga y ejecución de la implementación.

1.2 Alcance

El alcance de este trabajo se limita a los siguientes puntos:

Explorar la estructura interna del compilador JIT (Just-In-Time) de Mono.

Evaluar los puntos donde el JIT puede ser modificado para cumplir la tarea.

Analizar las posibles soluciones y las diferentes estructuras de datos.

Implementar una solución en base al análisis elaborado.

Comparar mediante la utilización de un banco de pruebas (benchmark) el impacto de lasolución actual a soluciones tradicionales.

Comparar mediante la utilización de un banco de pruebas (benchmark) el impacto de lasolución actual a soluciones AOP.

1.3 Limitaciones

En el presente trabajo no se busca obtener una implementación completa de un framework deAOP, sino más bien focaliza su esfuerzo en la implementación del entrelazado dinámico entrelas llamadas a métodos de un programa y los posibles Advices que sean definidos durante laejecución.

La implementación de muestra que se presentará será solo para plataformas Intel x86 o com-patibles, dado que implementar las más de 10 plataformas soportadas por el proyecto Monoescapan a la finalidad de este trabajo.

1.4 Organización

Este trabajo se encuentra dividido en 7 capítulos que se describen a continuación:

1. Introducción: breve descripción del problema a tratar, presentando los objetivos y al-cances del trabajo.

2. Definición del problema: análisis de los diferentes tipos de entrelazadores.

3. Estado del Arte: análisis del estado del arte en diferentes implementaciones AOP.

4. Mono: introducción a Mono y a cómo funciona su compilador JIT.

5. Solución adoptada: propone una solución a los problemas principales encontrados.

6. Pruebas: define un banco de pruebas para analizar los resultados de las modificacioneshechas.

1.2. Alcance 3

Page 12: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

7. Conclusión: describe las conclusiones alcanzadas en base a los resultados del trabajo,analizando puntos pendientes y proponiendo trabajos futuros.

1.5 Material

Todo el material generado en este trabajo, incluído el código fuente completo de Mono modifi-cado puede ser obtenido en el sitio http://www.gazer.com.ar/mono-weaver/

4 Capítulo 1. Introducción

Page 13: Reificación de llamadas a métodos en Mono

CAPÍTULO 2

Definición del problema

En esta sección analizaremos los problemas que presentan actualmente las diferentes técnicasde entrelazado a fin de poder formalizar la solución propuesta.

2.1 Entrelazado en tiempo de compilación

Una de las primeras técnicas que surgió fue el entrelazado en tiempo de compilación, resultadode una falta de soporte en las máquinas virtuales de la época para realizar cualquier técnicamás avanzada. Durante los primeros días de la AOP, la flexibilidad que ofrecían las máquinasvirtuales era poca o nula, no pudiendo interferir en el proceso de carga o ejecución de losprogramas dentro de la misma.

Este técnica de entrelazado se vale de un pre-compilador para modificar el código fuente origi-nal mezclando los aspectos que sean necesarios. En un nivel lógico es similar a cómo funcionaun preprocesador en el lenguaje C, donde el código del programa es modificado según sea es-pecificado por los aspectos. El código resultante es el que se envía al compilador para generarel programa final.

Hoy en día prácticamente es una técnica en desuso, salvo casos muy particulares en imple-mentaciones, como puede ser PHPAspect o AspectLua, dado que la evolución constante de lasmáquinas virtuales ha permitido migrar a formas de entrelazado más avanzadas.

En la actualidad se encuentra que este método de realizar el entrelazado conlleva muchos pro-blemas para resultar efectiva. En primer lugar, nos encontramos que requiere que todo el códigofuente, tanto del programa como de los aspectos, esté disponible al momento de la compilación,haciendo imposible, por ejemplo, aplicar aspectos a una biblioteca externa o a las clases de labiblioteca estándar del sistema.

Otro problema típico de este tipo de soluciones es el costo extra en el tiempo de compilación,ya que todo el proceso de entrelazado tiene diversos pasos de mezcla y validación.

También es importante notar que el entrelazador debería actualizarse al lenguaje a medida queéste evoluciona, lo cual hace arduo el trabajo de mantener al día los diferentes componentesque realizan la tarea de mezclar el código con los aspectos.

5

Page 14: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Figura 2.1: Esquema del proceso de entrelazado en tiempo de compilación.

2.2 Entrelazado en tiempo de carga

En este caso, el entrelazado se realiza cuando un nuevo módulo es cargado en la máquinavirtual, modificando su byte-code antes de que esté listo para ser usado. Entonces la tarea querealiza el entrelazador es tomar el código binario que entiende la máquina virtual del lenguaje ydetectar donde se encuentran las llamadas a funciones. Luego mezcla los aspectos (que tambiénse encuentran en forma de código binario intermedio) para pasar, por último, el código que seráel que finalmente sea ejecutado.

Esta técnica es utilizada por AspectJ, por nombrar un ejemplo popular, y fue gracias a lasnuevas características incorporadas a la máquina virtual de Java que este lenguaje AOP pudolograr tener este tipo de entrelazado en su conjunto de herramientas, ya que en sus primerasversiones el entrelazado se realizaba en tiempo de compilación.

Cabe destacar que, si bien en este caso el entrelazado es dinámico, AspectJ depende de uncompilador propio para convertir la sintaxis del lenguaje en byte-code capaz de correr en unamáquina virtual Java estandar. Para que esto se pueda dar es necesario que uno o más “en-trelazadores en tiempo de carga” esten disponibles antes en la Java VM previo a comenzar acargar el código de la aplicación. Eso puede darse explícitamente por el entorno donde se estáejecutando o bien activado por algún “agente de entrelazamiento”.

Una de las principales desventajas es que esta técnica consume mucho CPU durante el iniciode la aplicación y, en ocasiones, una cantidad considerable de memoria. Por dar un ejemplo,interceptar todas las llamadas a método toString generaría que cada clase que se carga debaser interceptada línea por línea para encontrar aquellos lugares donde este método es llamado.Nótese que muchas veces la llamada en este caso es transparente al programador, haciendo estatarea aún más compleja.

Otro problema es que para poder hacer un entrelazado completo es necesario tener metadataadicional que no siempre puede estar presente. Por ejemplo necesitamos la firma completa delos métodos, clases y nombres de espacio. Esta información ocupa memoria y la búsquedasobre la misma puede requerir tiempos considerables.

Un último inconveniente es que es imposible capturar llamadas reflexivas, haciendo que en

6 Capítulo 2. Definición del problema

Page 15: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

varios casos la aplicación de aspectos sea un problema. Tomemos por ejemplo el siguienteprograma de prueba:

using System;using System.Reflection;

public class ReflectionExample{

public ReflectionExample(){}

public void Say(string s){

System.Console.WriteLine(s);}

static public void Main(string [] args){

ReflectionExample n = ReflectionExample Normal();

object[] p = new object[] { "Hola Mundo!" };

Type thisType = n.GetType();MethodInfo theMethod = thisType.GetMethod( "Say" );theMethod.Invoke(n, p);

}}

Dado que dentro del código intermedio que se genera nunca aparecerá el patrónReflectionExample::Say, es imposible para un entrelazador en tiempo de carga atra-par esta llamada y ejecutar un Advice asociado, haciendo que la aplicación de los aspectosquede en forma parcial para casos complejos donde el programador utilice técnicas modernasde metaprogramación, por nombrar alguna.

Cabe notar que las técnicas de reflexión son utilizadas con frecuencia en las aplicaciones, ya quepermiten realizar módulos genéricos para ciertas tareas comunes, como puede ser, por ejemplo,la serialización de una instancia dada a XML o JSON.

2.3 Entrelazado en tiempo de ejecución

El último método que nos queda estudiar es el entrelazado en tiempo de ejecución, el cual solu-ciona la mayoría de los problemas expuestos en los otros métodos desarrollados anteriormente.El éxito posible de esta técnica, como veremos más adelante, es muy dependiente de la im-plementación, porque tiene incidencias directas sobre los tiempos de ejecución y consumo dememoria de un programa. Para poder realizar el entrelazado la máquina virtual debe ser capazde diferentes tareas.

El mecanismo que utiliza el entrelazador dinámico permite a un aspecto ser ligado a una claseen tiempo de ejecución del programa. Este mecanismo es similar al enlace de métodos que serealiza en la programación orientada a objetos, que es también llamado polimorfismo.

Como primer punto, es necesario que la máquina virtual sea capaz de almacenar internamente

2.3. Entrelazado en tiempo de ejecución 7

Page 16: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Figura 2.2: Esquema del proceso de entrelazado en tiempo de ejecución.

la información de los Joinpoints. Esto aumenta de por sí la cantidad de memoria utilizada alinicio de la aplicación, por lo que en principio se requeriría más R.A.M. que en otras técnicas.

Como segundo punto se debe tener un lenguaje de alto nivel, para los desarrolladores, parapoder definir dónde hay un Joinpoint.

Y por útimo pero no menos importante la máquina virtual debe ser capaz de interceptar lasllamadas a los métodos y actuar en consecuencia.

El entrelazado dinámico no es solamente un mecanismo que suena bien, pero que no tieneuso en la práctica. Es todo lo contrario. Es un mecanismo necesario, especialmente a la horade implementar incumbencias entrelazadas a lo largo de varios módulos. Con esta técnica esposible agregar o quitar estos requerimientos no funcionales de un programa en ejecución a unmínimo coste de procesamiento, dando una gran flexibilidad en sistemas grandes y de misióncrítica.

Típicamente podemos encontrar dos formas de entrelazado dinámico : a nivel de clase y a nivelde objetos. En la primera forma, el entrelazado se realiza contra una clase, haciendo que lasinstancias presentes y futuras adquieran el nuevo comportamiento agregado. Sin embargo, haycasos donde, por cuestiones de rendimiento, esto no es conveniente. Y es por ello que existela segunda forma que permite elegir un grupo de objetos (instancias) y aplicar sobre ellossolamente el entrelazado. Esto hace que el impacto sea mínimo, localizado y controlado.

Es importante destacar que existen diferentes tipos de máquinas virtuales : las intérpretes ylas que poseen un compilador JIT, cuyo objetivo es convertir el código intermedio en lenguajeensamblador de la plataforma en la que se está ejecutando.

En este trabajo se analiza el caso de una máquina virtual con JIT, específicamente la del proyec-to Mono, ya que en la actualidad, salvo que existan restricciones de la plataforma, es el acer-camiento típico en la construcción de máquinas virtuales por ofrecer un mejor rendimiento entiempo de ejecución.

En la sección Estado del Arte se profundizarán conceptos, al mismo tiempo en que se analizaránotras soluciones que implementan entrelazado en tiempo de ejecución. De esos analisis sacare-mos elementos para darle forma a la solución final a implementar.

8 Capítulo 2. Definición del problema

Page 17: Reificación de llamadas a métodos en Mono

CAPÍTULO 3

Análisis de la solución

Antes de poder entrar en detalle sobre la solución propuesta es fundamental que el lector tengaen claro qué es lo que se desea lograr.

Tanto un Pointcut como un Advice son estructuras de alto nivel utilizadas en los lenguajesorientados a aspectos, por lo que no serán el centro de análisis. Sin embargo, para obtener unaimplementación funcional y poder experimentar sobre el fin de este trabajo, se deberá crearmecanismos para poder interactuar con el JIT.

Podemos hacer una correlación con las definiciones ya vistas en el capítulo Introducción, ale-gando las siguientes similitudes :

Joinpoint : Punto de un programa que es posible interceptar. Es una definición abstracta.

Shadow : Es un punto del programa interceptable, de forma física. Por ejemplo en as-sembler podríamos decir que un Shadow está antes de llamar la instrucción call queejecuta una rutina en memoria.

Advice : Comportamiento a agregar, escrito en un lenguaje de alto nivel.

Pointcut : Reglas creadas por el programador, indicando algún evento que va a suceder yque es de interés interceptar (por ejemplo, la llamada de un cierto método o constructor).

El término “reificación” significa, según la Real Academia Española, “cosificación”, o “Reducira la condición de cosa aquello que no lo es”, cuya tarea trataremos de desarrollar. Eso quequeremos convertir en “cosa” es la llamada a un método. Esta materialización de la llamada aun método es lo que habíamos definido como Joinpoint previamente.

3.1 Requerimientos

En esta sección analizaremos cuáles son los requerimientos que debemos cumplir a fin de poderimplementar la solución del problema. Definiremos así algunos puntos de interés u objetivos alograr :

1. Detectar los puntos donde se produce una llamada a un método a nivel del JIT, a fin depoder decidir si hay Advices que ejecutar.

9

Page 18: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

2. Implementar rutinas que introduzcan una penalidad aceptable, y estimar cuál será.

3. Proveer a los desarrolladores un lenguaje familiar, simple y versátil a la hora de definirAdvices.

Para poder cumplir con el punto uno es necesario conocer cómo funciona Mono internamente :

1. Qué tareas realiza el JIT para compilar a assembler dicho método?

2. Qué opciones tenemos para modificar el assembler original y ejecutar código adicional?

3. Qué estructuras de datos deberemos manejar internamente para poder responder contiempos aceptables en la aplicación de un Advice?

Estas son las preguntas que iremos respondiendo en las secciones siguientes. Para ello, es fun-damental entrar en un análisis detallado de qué es el JIT de Mono y cómo funciona.

10 Capítulo 3. Análisis de la solución

Page 19: Reificación de llamadas a métodos en Mono

CAPÍTULO 4

Estado del Arte

Antes de entrar de lleno en el análisis del trabajo nos tomaremos unas páginas para analizaralgunas soluciones más utilizadas y estudiar cómo realizan el entrelazado entre los aspectosy el programa sobre el cual actúan. También aprovecharemos la oportunidad para estudiar lasherramientas que le brindan al programador a fin de poder utilizar algunas de esas ideas en laelaboración de la API que se presentará.

Si bien algunas de las implementaciones que veremos no corresponden a entrelazadores entiempo de ejecución, algunos conceptos y técnicas utilizadas pueden llegar a resultar útiles alos fines del trabajo presente.

4.1 Entrelazado de Advice en AspectJ

AspectJ es quizás uno de los framework AOP más populares en el mundo JAVA y es por esoque resulta de especial interés analizar cuál es su solución al momento de entrelazar.

El documento que mejor refleja el funcionamiento interno es tal vez “Advice Weaving in As-pectJ” [HH01] y del que repasaremos sus puntos más importantes en este trabajo.

Cabe destacar que al momento de escribir este trabajo AspectJ soporta solamente entrelazadoen tiempo de compilación y de carga, pero no en tiempo de ejecución. Esto es debido a que porahora mantienen compatibilidad de código intermedio con la máquina virtual Java original.

4.1.1 Transformación de código intermedio

Cada Advice en AspectJ es mapeado a un método estático dentro de una clase. Esto es realizadopor un precompilador que genera el código Java estándar y luego es pasado al compiladorjavac que comunmente se utiliza.

Por ejemplo. El siguiente Advice :

before(String s): execution(void go(*)) && args(s){

11

Page 20: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

System.out.println(s);}

es compilado y encapsulado en un método como sigue :

public void ajc$before$A$a9();0: getstatic [java/lang/System.out]3: aload_14: invokevirtual [java/io/PrintStream.println]7: return

El back-end de AspectJ funciona agregando en ciertos lugares llamadas a métodos compiladosque son representaciones de los Advices declarados por el usuario. Para poder realizar esto,tiene en cuenta aquellos lugares del código intermedio donde pueda existir un Joinpoint. Aestos puntos se los denomina static shadows (sombras estáticas).

Para cada uno de estos static shadows, Aspectj verifica, para cada Advice definido, si estestatic Shadow en particular cumple con los Poincuts definidos del Advice. Si esta verificaciónes exitosa, se agregará una llamada a la implementación del Advice que ya fue previamentecompilada.

Si consideramos el Advice del inicio, el Poincut especifica que se debe correr antes de la eje-cución de cualquier llamada al método go cuando es llamada con un solo argumento de tipostring. El entrelazador deberá entonces instrumentar el código intermedio para insertar la lla-mada al método antes de la llamada real, en caso de que la condición se haya cumplido.

Por ejemplo, si consideramos la siguiente llamada del método sin parámetros :

void go(java/lang/Object):0: return

El entrelazador puede estáticamente determinar si la ejecución de este método coincide con ladel Pointcut previamente definido y causar que se ejecute el Advice. Sin embargo, una com-probación dinámica en tiempo de ejecución debe ser insertada para chequear que el tipo delargumento sea el correcto (String en el ejemplo) y que solo se produzca la invocación cuandotodas las condiciones estén satisfechas.

Esto produciría que el entrelazador modifique la llamada quedando en definitiva :

void go(java/lang/Object):0: aload_1 # copy first argument into1: astore_2 # a temporary frame location2: aload_2 # check whether argument3: instanceof [String] # is actually a String6: ifeq 19 # if not, skip Advice9: invokestatic [A.aspectOf] # get aspect12: aload_2 # load argument again13: checkcast [String] # guaranteed to succeed16: invokevirtual [A.ajc$before$A$a3] # run Advice19: return

Con el objetivo de lograr buena performance en el código entrelazado, el entrelazador nece-sita eliminar este tipo de verificaciones dinámicas cuando le sea posible, ya que llamadas ainstanceof son realmente ineficientes. Cuando el entrelazador puede determinar estática-

12 Capítulo 4. Estado del Arte

Page 21: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

kind signature this target args Bytecode

Method-execution method ALOAD_0 or none Same as this Local vars Entire code segment of

Method-call method ALOAD_0 or none From stack From stack Invokeinterface, invokespecial (only forprivates), invokestatic, invokevirtual

Constructor-execution constructor ALOAD_0 or none Same as this Local vars Code segment of <init> after callto super

Constructor-call constructor ALOAD_0 or none None From stack Invokespecial (plus some extra pieces)

Field-get Field ALOAD_0 or none From stack None Getfield or getstatic

Field-set Field ALOAD_0 or none From stack From stack Putfield or putstatic

Advice-execution None ALOAD_0 or none Same as this Local vars Code segment of corresponding method

Initialization Corresponding constructor ALOAD_0 or none Same as this Complex Requires in-lining of all constructorsin a given class into one

Static-initialization Typename None None None Code segment of <clinit>

Pre-initialization Corresponding constructor None Local vars Local vars Code segment of <init> before call tosuper, this may require in-lining

Exception-handler Typename of exception ALOAD_0 or none From stack From stack Start is found from exception handlertable.

Exception-throws Typename of exception ALOAD_0 or none From stack From stack athrow

Synchronized-block Typename of exception ALOAD_0 or none From stack From stack Code between monitorenter/monitorexitpair

Cuadro 4.1: Tipos de Joinpoint Shadow.

mente que el parámetro es del tipo requerido, esto es posible. Así como también si el entre-lazador puede determinar que nunca se podría cumplir la condición necesaria para la ejecucióndel Advice.

Si somos capaces de eliminar todas las verificaciones dinámicas, la llamada a un Advice sereduce al siguiente código intermedio :

void go(java/lang/String):0: invokestatic [A.aspectOf]3: invokevirtual [A.ajc$before$A$a3]6: return

4.1.2 Joinpoint shadows

En el lenguaje de AspectJ, un Joinpoint es un punto en el flujo de ejecución dinámico de unprograma que puede ser modificado por un Advice. Cada Joinpoint dinámico tiene su corres-pondencia con un static Shadow en el código fuente o en el código intermedio del programa.

En AspectJ 1.1 existen once variedades de Joinpoints Shadows como se puede ver en latabla 4.1. Cada uno de estos está definido por un tipo, una firma y una región en el bytecodedonde es factible que aparezca. Los últimos dos casos están pensados para futuras versiones deAspectJ.

Cada Joinpoint, además, expone tres variables que definen un estado :

this - el objeto actual que ejecuta. En un contexto estático puede ser NULL.

target - el objetivo del Joinpoint

args - los argumentos del Joinpoint

Si miramos el código intermedio para un simple programa que imprime ‘Hello World’:

4.1. Entrelazado de Advice en AspectJ 13

Page 22: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

0: getstatic [java/lang/System.out]3: ldc [String Hello World]5: invokevirtual [java/io/PrintStream.println]8: return

Existen tres Shadows en este programa. Uno es un method-execution entre las líneas 0 y 8.Otro es un field-get que ocurre en la línea 0 del código intermedio. El tercero y último es unmethod-call en la línea 5.

Entrezalar un Advice en este shadow method-call es extremadamente simple y muy similar acomo se entrelaza el method-execution shadow que describimos anteriormente. En este caso,se inserta una llamada antes del invokevirtual en lugar de hacerlo al inicio del método. Porejemplo :

0: getstatic [java/lang/System.out]3: ldc [String hello world]

5: invokestatic [A.aspectOf]8: invokevirtual [A.ajc$before$A$15a]

11: invokevirtual [java/io/PrintStream.println]14: return

4.1.3 ¿Por qué AspectJ no pone inline el código de un Advice?

Como se puede ver de los simples ejemplos anteriores, el código del Advice nunca se compilainline, sino que se inserta en el código intermedio una llamada a un método extra.

El principal factor que impacta en la performance en AspectJ es causado por la búsqueda yejecución de un método dentro de una instancia de AspectJ. Éste podría ser eliminado si elentrelazador fuera capaz de hacer inline el código del Advice en lugar de realizar esta llamadaextra. Versiones más antiguas de AspectJ hacían esto.

Sin embargo, los esfuerzos puestos en los últimos años para mejorar la performance del JITal emitir código nativo hacen que sea menos costoso recaer en éste último para hacer las op-timizaciones al generar assembler a partir del código intermedio modificado, que especulardirectamente haciendo el inline.

El otro motivo importante para evitar hacer el inline desde AspectJ es que se deben realizardemasiados chequeos para garantizar el acceso a variables, parámetros, etc, que son muy de-mandantes de uso de procesasmiento e incrementan el tiempo de carga.

4.2 Oracle JRockit JVM

Durante 2005 la gente de JRockit, empleados de Oracle, anunciaron soporte nativo en sumáquina virtual ([JRK01], [JRK02] ) para entrelazado de aspectos en tiempo de ejecución.Si bien hasta el momento los detalles de la implementación de alguno de sus componentessiguen permaneciendo cerrados, otros han sido revelados y los analizaremos aquí.

En general hablaremos sobre la API expuesta sobre Java para acceder al entrelazados, y no tantoen sí cómo se realiza el entrelazado en tiempo de ejecución, ya que este no está públicamentedisponible.

14 Capítulo 4. Estado del Arte

Page 23: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

4.2.1 Propuesta

Según alegan, las ventajas de JRockit son :

No hay costo de inicialización ya que el código intermedio permanece sin modifica-ciones.

Soporte en tiempo de ejecución completo para agregar y eliminar Advices en cualquierlugar, en cualquier momento y a costo lineal.

Soporte implícito para Advices en llamadas reflexivas.

No hay costo extra de memoria para replicar el modelo de clases con estructuras especi-ficas de AOP

La forma de interactuar con el entrelazador es mediante una API en Java, que permite pro-gramáticamente definir los Pointcuts y Advices a fin de poder interceptar las llamadas.

Veamos el siguiente ejemplo :

public class Hello {// -- método a interceptarpublic void sayHello() {

System.out.println( "Hello World" );}

// -- JRockit JVM support for AOPstatic void weave() throws Throwable {

// coincidencia en el nombre del métodoStringFilter methodName = new StringFilter(

"sayHello" ,StringFilter.Type.EXACT

);

// coincidencia contra una clase en particularClassFilter klass = new ClassFilter(

Hello.class,false,null

);

// Advice is a regular method dispatchMethod advice = Aspect.class.getDeclaredMethod(

"advice" ,new Class[0]

);

// tomamos el JRockit weaver y suscribimos el Advice// Advice al Joinpoint definido por los filtrosWeaver w = WeaverFactory.createWeaver();

w.addSubscription(new MethodSubscription(new MethodFilter(

0,null,klass,methodName,null,

4.2. Oracle JRockit JVM 15

Page 24: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

null),MethodSubscription.InsertionType.BEFORE,advice

));}

// -- código de ejemplostatic void test() {

new Hello().sayHello();}

public static void main(String a[]) throws Throwable {weave();test();

}

// -- the sample aspectpublic static class Aspect {

public static void advice() {System.out.println( "About to say:" );

}}

}

Lo que hace el ejemplo al ser compilado y ejecutado en JRockit es primero suscribir la ejecu-ción del método estático Aspect.advice a que sea ejecutado antes de la llamada al métodosayHello de la clase Hello.

Para que el entrelazado sea efectivo, éste código debe ser incorporado como parte de la lógicade inicialización del programa o de otra forma no surge efecto alguno.

4.2.2 Suscripciones y ejecución de acciones

La API nos permite describir una suscripcion bien definida a un poincut específico, para locual es posible, además, registrar una acción que la JVM deberá ejecutar. Esta acción estarácompuesta por :

Un método Java común y corriente (que nombrearemos de ahora en más como métodode acción) que será llamado para cada Joinpoint que coincida con las condiciones de lassuscripción.

Una instancia de una clase, opcional, sobre la cual ejecutar el método de acción.

Un conjunto de parámetros opcionales, anotados, que le indicará a la máquina virtualJava qué argumentos espera el método de acción del stack de invocación. Esto servirápara poder leer los parámetros dentro de la ejecución.

La acción a ejecutar también podrá ser marcada con una bandera que le indique al entrelazadorsi debe ejecutar la acción antes de llamar al método, de retornar, de lanzar una excepción, ydemás conceptos AOP.

Para poder invocar la API, primero es necesario obtener una instancia dejrockit.ext.weaving.Weaver que actúa de manejador. Esta instancia controla

16 Capítulo 4. Estado del Arte

Page 25: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

qué operaciones están permitidas de acuerdo al contexto de la llamada. Por ejemplo, puedeque no quiera desplegar una aplicación en un servidor para crear un entrelazador que puedasuscribirse a un método en algún container de las clases del negocio o algún punto específicodel JDK que pueda comprometer la seguridad de los datos.

4.2.3 Método de acción

Un ‘método de acción’ (similar al concepto de Advice en AOP) es como un método de Javaestándar (que pertenece a una clase) que actua como aspecto. Puede ser tanto un método estáticocomo un método de instancia de forma indistinta. El tipo de dato que devuelve tiene que seguiralguna convención implícita y debe ser evitado cuando el Advice se ejecuta antes del métodointerceptado. Para el caso de las llamadas interceptadas después de la ejecución el tipo de datodeberá ser el correcto de manera que puesto en el stack nuevamente no genere conflicto de tipoen tiempo de ejecución.

JRockit no asegura que esta condición sea factible dejando del lado del programador la respon-sabilidad de esta verificación.

Un método puede tener parámetros con anotaciones Java, que serán a futuro quienes controlenel contexto de exposición como se ilustra en el siguiente ejemplo :

import java.lang.reflect.*;import jrockit.ext.weaving.*;

public class SimpleAction {

public static void simpleStaticAction() {out.println( "hello static action!" );

}

public void simpleAction() {out.println( "hello action!" );

}

public void simpleAction(@CalleeMethod WMethod calleeM,@CallerMethod WMethod callerM) {

out.println(callerM.getMethod().getName());out.println( " calling " );out.println(calleeM.getMethod().getName());

}

}

En este ejemplo se introduce la clase jrockit.ext.weaving.WMethod . Es-ta última actúa de envoltura (wrapper) para java.lang.reflect.Method ,java.lang.reflect.Constructor y para los inicializadores de clase estáticosque no están presentes en java.lang.reflect.* . Esto es similar a la abstracción quehace AspectJ con JoinPoint.StaticPart.getSignature() .

En la tabla 4.2 en la página siguiente hay una lista de los posibles anotaciones y sus significados.

Para poder soportar instead-of (lo que en AOP es ‘around’) y tener la habilidad dedecidir si proceder o no con la cadena de intercepción, la gente de JRockit vio

4.2. Oracle JRockit JVM 17

Page 26: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Annotation Exposes Notes

CalleeMethod The callee method (method,constructor, static initializer)

CallerMethod The caller method (method, constructorstatic initializer)

Callee The callee instance Filters out static members invocation. Acts asan instance-of. type filter: The callee type mustbe an instance of the annotated argument type

Caller The caller instance Filters out invocations from within static membersActs as an instance-of type filter : The callertype must be an instance of the annotated argumenttype.

Arguments The invocation arguments

Cuadro 4.2: Anotaciones disponibles y sus significados.

necesaria la introducción de una nueva clase que define el contexto de invocaciónjrockit.ext.weaving.InvocationContext como se ejemplifica a continuación :

import jrockit.ext.weaving.*;

public class InsteadOfAction {

public Object instead(InvocationContext jp,@CalleeMethod Method callee) {

return jp.proceed();}

}

4.2.4 Acción de Instancia y Acción de Clase

Como fue ilustrado en los ejemplos previos, un ‘método de acción’ puede ser tanto estáticocomo no. Si resulta no ser estático es necesario proveer una instancia (o instance action) sobrela cual la máquina virtual de Java deberá invocar este método.

Esto se hace siguiendo el estilo de sintaxis con el que un desarrollador de Java invoca un métodode manera reflexiva :

java.lang.reflect.Method.invoke(null/*static method*/, .../*args*/)

Aunque a diferencia del ejemplo anterior, cuando tenemos soporte de AOP en la JVM la lla-mada que se da no es reflexiva en ninguna forma.

Cabe destacar que esta característica es útil a fin de implementar los diferentes patrones AOPsobre instanciación de aspectos, como issingleton(), pertarget(), perthis(),percflow(), y demás, mientras que no atamos a la API con alguna semántica predefinida yestática.

Antes de registrar la suscripción a la instancia de entrelazador, a éste se le da un tipo de Advice: before, instead-of, after-returning, o after-throwing.

Para crear la suscripción se puede escribir algo como :

18 Capítulo 4. Estado del Arte

Page 27: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

// Get a Weaver instance that will act as a// container for the subscription(s) we createWeaver w = WeaverFactory.getWeaver();

// regular java.lang.reflect is used to refer// to the action method "simpleStaticAction()"Method staticActionMethod =

SimpleAction.class.getDeclaredMethod("simpleStaticAction" ,new Class[0]//no arguments

);

MethodSubscription ms = new MethodSubscription(.../* where to match*/,InsertionType.BEFORE,staticActionMethod

);

w.addSubscription(ms);

Semánticas como within() y withincode() pueden ser también implementadas utilizan-do variaciones alrededor de esta API.

4.2.5 Suscripciones

Como ya se mostró en ejemplos de la sección anterior, la API de suscripciones recae sobreel modelo de objetos de java.lang.reflect.* y algunas abstracciones simples, comojrockit.ext.weaving.WMethod para unificar Method, Constructor y los inicializado-res estáticos de clases.

El primer parámetro de new MethodSubscription(...) debe ser una instancia dejrockit.ext.weaving.Filter, que posee dentro de la API varias implementacionesconcretas a fin de evaluar concidencias con nombres de método, nombres de clase, campos yotros.

Una instancia de jrockit.ext.weaving.MethodFilter actúa de definición, sobre lacual se basa la implementación del weaver para hacer la evaluación de coincidencia sobre losJoinpoint Shadow. La clase jrockit.ext.weaving.MethodFilter permite filtrar por:

Los modificadores de miembro (definidos por java.lang.reflect.Modifier co-mo ABSTRACT, PUBLIC, STATIC, etc)

Class<? extends java.lang.annotation.Annotation> para coincidiruno (o más) de las anotaciones visibles en tiempo de ejecución.

jrockit.ext.weaving.ClassFilter para coincidencia en el tipo de la instan-cia

jrockit.ext.weaving.StringFilter para coincidencia por nombre de méto-do

jrockit.ext.weaving.ClassFilter para evaluar el tipo de retorno del método

4.2. Oracle JRockit JVM 19

Page 28: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

jrockit.ext.weaving.UserDefinedFilter para implementar cualquier otralógica

Asimismo, la clase jrockit.ext.weaving.ClassFilter expone algunos modifi-cadores :

Class<? extends java.lang.annotation.Annotation> para coincidiruno (o más) de las anotaciones visibles en tiempo de ejecución.

Un Class para verificar coincidencia con una clase en particular

Un boolean para expresar si subtipos son aceptados

En el ejemplo siguiente, se realiza una verificación de todos los métodos que empiezan con“bar” Note que hemos pasado en varios lugares null para este simple caso :

StringFilter sf =new StringFilter( "bar" , STARTSWITH);

MethodFilter mf =new MethodFilter(0, null, null, sf, null, null);

MethodSubscription ms = new MethodSubscription(mf,InsertionType.BEFORE,staticActionMethod

);

w.addSubscription(ms);

A fin de mostrar un ejemplo un poco más completo y realista, a continuación se hace unamatching sobre tres métodos de EJB :

// Prepare the Pointcut to match// @Stateless annotated classes business methodsMethodFilter ejbBizMethods = new MethodFilter(

PUBLIC_NONSTATIC,// Method annotation does not matternull,new ClassFilter(

// Declaring class, the java.lang.Class// for the EJB we are currently manipulatingejbClass,// no subtypes matchingfalse,// class annotationStateless.class

),// EJB methods matching is handled// in a UserDefinedFilter below insteadnull,// return type does not matternull,// custom Filter callbacknew UserDefinedFilter() {

public boolean match(MethodFilter methodFilter,WMember member,

20 Capítulo 4. Estado del Arte

Page 29: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

WMethod within) {return !isEjbLifeCycleMethod(member);

}}

);

4.2.6 Beneficios obtenidos

Eliminando la instrumentación del código intermedio se incrementó la escalabilidad

El código intermedio no se modifica. El proceso estándar de compilación esseguido en la máquina virtual Java. No hay necesidad de analizar las instruccionesen el código intermedio y representar esas instrucciones con alguna estructuraintermedia a fin de poder manipular e intrumentar el framework AOP.

El entrelazador se vuelve omnipresente. A pesar de que es posible registrar suscrip-ciones durante la face de inicializacion del programa, este ya no es un requerimien-to y se pueden definir en cualquier momento. Esto es importante porque reduce eltiempo de inicializacion ya que no hay necesidad de analizar el código intermedioinstrucción por instrucción en busca de puntos donde se debe aplicar un Advice.

Todo esto también provee la oportunidad de desarrollar un sistema verdaderamentedinámico, es decir, la posibilidad de desplegar o sacar aspectos en tiempo de ejecu-ción.

Eliminar el registro de tipos reduce el uso de memoria y aumenta la escalabilidadDebido a que no se realiza mas una instrumentación del bytecode, ya no hay pro-blemas asociados con el modelo de objetos y tener un doble registro de este. La APIde suscripción se apoya en la API java.lang.reflect.* que ya otorga estainformación en una manera muy familiar para el desarrollador Java.

Múltiples agentes son mantenidos de manera consistenteComo no hay cambios en el bytecode, no se corre el riesgo de que dos agentesdiferentes entren en conflicto por modificar al mismo tiempo, pero con diferenteobjetivo, el código intermedio. El orden de registración de las suscripciones actuacomo regla de precedencia. Si la clase fuera Serializable, no se le han agrega-do estucturas ocultas a fin de soportar el Advice entrelazado, por lo que el soportede serialización de clases permanecerá soportado completamente sin esfuerzo extra.Usualmente las técnicas de instrumentación de código intermedio necesitan asegu-rarse de que las modificaciones sean preservadas en la serialización, cosa que entiempo de ejecución no es necesario.

Soporte para interceptar llamadas reflexivasAl utilizar el dispatching de métodos a nivel de la JVM, todas las llamadas reflexi-vas puede ser probadas contra las condiciones cargadas, haciendo que puedan serinterceptadas por Advices al igual que los métodos regulares sin control extra en laimplementación.

4.2. Oracle JRockit JVM 21

Page 30: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

4.3 Wool

En este punto analizaremos la solución adoptada por Wool, un entrelazador de aspectos paraJava, dinámico y selectivo, producto del trabajo de Yoshiki Sato [WOOL].

Lo novedoso de esta implementación es que, además de utilizar la máquina virtual Java es-tándar, utiliza técnicas mixtas para realizar el entrelazado, dejando al programador la opciónde qué técnica utilizar según el caso en particular. Como veremos más adelante, cada formade lidiar con el problema tiene sus ventajas y desventajas por lo que la elección de la correctaqueda en manos del programador.

Una de las técnicas más típicas para implementar entrelazado dinámico es insertar piezas decódigo, a las que llamamos “hooks” en todos los Joinpoints y en tiempo de ejecución estoshooks determinan si hay que ejecutar un callback asociado a él o no. Si bien este método puedeno ser siempre la más eficiente, en general es un requerimiento poder prender y apagar loscallbacks en tiempo de ejecución por lo que no hay opción.

Wool por su lado, si bien utiliza este procedimiento, también innova con nuevas formas derealizar esta tarea. La primera forma es utilizando breakpoints del depurador de Java, y la se-gunda modificando el bytecode y recargando la clase en la máquina virtual una vez modificada.Entraremos en detalle más adelante sobre cada técnica.

4.3.1 Introducción

Wool está implementado como una biblioteca Java que provee una API para definir aspectos,un entrelazador dinámico y un systema que acepta peticiones externas al programa en ejecuciónpara poder inyectar aspectos sobre un programa en ejecución.

La flexibilidad que han implementado en este caso permite al programador aplicar aspectoslocalmente dentro del programa, desde otro hilo, corriendo en la misma máquina virtual yhasta desde otro ordenador mediante una comunicación TCP/IP, permitiendo gran flexibilidad.Esto es de gran utilidad, si por ejemplo necesitamos levantar un profiler en un servidor deproducción por un cierto tiempo para analizar un cuello de botella.

En el siguiente fragmento de código podemos ver como se realiza un entrelazado :

WlAspect azpect = WlAspect.forName( "ProfileAspect" );

Wool wool = Wool.connect( "localhost" , 5432);wool.weave(azpect);

Cabe destacar que en el caso de un entrelazado desde fuera del programa en ejecución, la cargay preparación del aspecto en realizado en otra máquina virtual. Luego el entrelazador se conectade manera remota con el subsistema dentro del programa original y envía el aspecto a cargar.

4.3.2 JIT : Agregado de hooks

Los autores de esta implementación adoptaron un esquema híbrido para insertar los hooks den-tro del programa original, por lo que el programador puede optar por una u otra considerando

22 Capítulo 4. Estado del Arte

Page 31: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Figura 4.1: Esquema de ejecución segun el tipo usado.

el costo de cada caso. Las opciones que nos dan son : ejecutar vía “breakpoints” o incrustar elAdvice dentro del código, como se ilustra en la figura 4.1

La estrategia para decidir si el Advice será ejecutado o embebido dentro del programa es muysimple. Para comenzar, todo hook es representado en primer instancia como breakpoint, ya quees mucho más rápido de definir. Cuando se ejecuta mediante un breakpoint, la tarea es realizadapor el depurador que corre en paralelo con el programa principal.

Si se detecta que una tarea va a ser interceptada una y otra vez, resultando en múltiples llamadasal hook, lo que se hace es incrustar ese hook en particular en la clase y recargarla en la máquinavirtual, para así minimizar la penalidad que se genera al utilizar el depurador. Esto es válidosi el tiempo de ejecución acumulado se estima va a ser mayor al de incrustar. Una vez que serealiza este paso, el entrelazador quita el breakpoint ya que no va a ser usado nuevamente.

A continuación se detallan los diferentes métodos utilizados para la ejecución de los hooks.

Ejecución basada en breakpointEsta técnica consiste en insertar breakpoints en el código en los Joinpoints definidospor el programador. Esto es posible hacerlo gracias a la interfaz de depuración queviene con las máquinas virtuales Java, llamada JPDA (Java Platform Debugger Ar-chitecture).

Esta plataforma le permite al programador registrar pedidos de ejecución dentro dela máquina virtual y tener control sobre esas ejecuciones.

Cuando un thread del programa llega a un breakpoint, la máquina virtual cambiainmediatamente al hilo de ejecución del depurador, dándole el control para realizarla ejecución de las rutinas definidas por el programador. La ventaja clara de estatécnica es que no requiere la modificación de la máquia virtual.

La penalidad de ejecutar un breakpoint no es un problema serio, porque el depu-rador sigue utilizando el JIT para compilar el código intermedio Java a assembler,por lo que no hay una penalidad en el tiempo de ejecuión. A pesar de que el progra-ma pasa a ser ejecutado en modo depuración, no representa una penalidad grandetampoco en las máquinas virtuales modernas.

4.3. Wool 23

Page 32: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Depurador

Figura 4.2: Gran cantidad de cambios de contexto debido a Advices ejecutados frecuente-mente..

Lo que sí puede generar un gran impacto en el rendimiento del programa es la grancantidad de cambios de contexto de ejecución que genera ir del hilo principal al hilodel depurador, como se esquematiza en la figura 4.2. Es por eso que si tenemos unagran cantidad de Joinpoints corriendo con este método, solamente el desempeño severá afectado de manera notoria.

Ejecución basada en llamada a métodoEste segundo método se basa en la traducción dinámica del código intermedio. Parareducir el impacto de los cambios de contexto que se introducen en el método an-terior, un método que es ejecutado constantemente es reemplazado por un métodonuevo que incluye al hook, quien es finalmente el encargado de evaluar si se debe ono ejecutar el callback asociado.

El reemplazo del código intermedio en tiempo de ejecución es realizado utilizandoun mecanismo de “cambio en caliente” (hotswap en inglés) que es parte del JPDA.Éste permite que una clase sea recargada en tiempo de ejecución cuando está enel contexto del depurador. El cambio no es instantáneo, debido a que este procesoes costoso. Lo que se hace, entonces, es dejar vigente el breakpoint hasta que elproceso de incrustar el Joinpoint es completado y recién en ese momento se recargay se elimina el breakpoint.

En la figuta 4.3 en la página siguiente se ilustra el caso, luego de que varios Advicesfueron incrustados y se sacaron los breakpoints.

4.3.3 Aspectos en Wool

Wool provee a los programadores una API para definir aspectos en Java, aunque no provee

24 Capítulo 4. Estado del Arte

Page 33: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Depurador

advice()

advice()

advice() advice()

advice()

advice()

advice()

advice()

advice()

advice()

advice()

Figura 4.3: Uso mixto de breakpoints y Advices incrustados, dando una mejor performance..

de una sintaxis especial para declararlos, como sí hacen otras implementaciones, por ejemplo,AspectJ.

El siguiente código representa un aspecto de profiling escrito en Java utilizando la API de Wool:

public class ProfileAspect extends WlAspect {Timer timer = new Timer();int count = 0;

public void pointcut_paintcall() {Pointcut p = Pointcut.methodCall( "*" , "Figure+" , "paint" , "*" );

associate(p, "enter" , WlAdvice.BEFORE);

associate(p, "exit" , WlAdvice.AFTER);}

public void enter() {timer.start();count++;

}

public void exit() {timer.stop();

}}

En el ejemplo anterior, el Poincut está especificando que se va a hacer profiling de la llamadaal método paint de la clase figura y sus subclases.

La forma de identificar qué métodos definen Pointcuts en Wool es llamar automáticamente acualquier método con el prefijo pointcut_, como es pointcut_paintcall. Lo malo deesto es que tiene un costo ya que se deben escanear las clases en busca de estos métodos. Si el

4.3. Wool 25

Page 34: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

aspecto tiene muchos métodos podría ser un problema, aunque en general no lo es.

Mediante el método associate, lo que define el aspecto es el nombre de los métodos aejecutar y si debe ser antes o después de la ejecución del método interceptado.

Para definir Pointcuts, Wool nos da una gran variedad de métodos en su API, haciendo mássimple y claro interceptar diferentes casos, a saber:

static Pointcut methodCall(String, String, String, String)Identifica una llamada a un método

static Pointcut methodExecute(String, String, String, String)Identifica la ejecución de un método

static Pointcut fieldGet(String, String, String)Identifica la lectura de un atributo

static Pointcut fieldSet(String, String, String)Identifica la escritura de un atributo

static Pointcut instanceCreate(String, String, String)Identifica la creación de una instancia

static Pointcut exceptionHandle(String)Identifica el manejo de una excepción

4.3.4 Controlando el Entrelazador

Este proyecto ofrece a los programadores una funcionalidad opcional que les permite controlarel comportamiento del entrelazador. Esta opera en el momento en que un aspecto efectivamentese entrelaza con un programa, es decir, cuando el efecto del aspecto aparece en el programa enejecución.

Para lograr esto, Wool delega métodos relacionados con las operaciones de entrelazado alprogramador. Simplemente realizando una sobrecarga de métodos heredados de la claseWlAspect, específicamente el método hook() e initWeave(). Estos métodos recibenun argumento que describe la operación que se está realizando. Dentro, el programador tiene lalibertad de definir como se completan las operaciones de entrelazado y crear versiones propias.

4.3.5 Implementación de inserción de “hooks”

En los puntos anteriores se describió los problemas de la inserción de los hooks mediante ladescripción de los pasos necesarios para realizar el entrelazado. En Wool, entonces, los pasospara realizar un entrelazado son :

1. Explorar las clases.

2. Insertar los hooks como breakpoints.

3. El programador selecciona el método más conveniente.

a) Se ejecuta usando el depurador.

26 Capítulo 4. Estado del Arte

Page 35: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

b) Se incrusta el hook y se hace la llamada al Advice.

Explorar las clases

Luego de que Wool sea iniciado al programa donde interactuará, los hilos de ejecución soninterrumpidos (a excepción de unos pocos, como el JIT y el recolector de basura) por unosinstantes. En este período se procede a inspeccionar todas las clases cargadas y buscar losmétodos donde se definen poincuts. El método initWeave() es llamado justo antes de re-alizar este procedimiento. Por ejemplo, si algunas clases son filtradas por este método de lasiguiente manera :

public class ProfileAspect {public void initWeave(Wool wl) throws WoolException {wl.filterClass( "^java.*|^sun.*" , false);

}}

estas clases son excluidas de ser escaneadas. Esto es muy útil si se pretende acelerar el proceso,eliminando clases donde sabemos que no hay poincuts definidas, por ejemplo, las clases de labliblioteca estándar.

Insertar los hooks como breakpoints

Los breakpoints son usados por Wool para especificar puntos donde hay joinspoints en lasclases escaneadas. Esta implementación está basada en la clase BreakpointRequest delJPDA. Con cada hook que inserta, además, se crea un enlace con el Advice correspondiente afin de poder ejecutarlo.

Una vez terminado este proceso, se procede a continuar con la ejecución de los hilos que habíansido suspendidos.

El programador selecciona el método más conveniente

Cuando un hilo del programa alcanza el primer Joinpoint, éste es interceptado por Wool, quese encarga de llamar al método hook(). El programador puede evitar que esto suceda si lodesea, sobreescribiendo el método que hereda de la clase WlAspect. Al hacerlo, recibiráinformación generada dinámicamente sobre el Joinpoint en particular. De creerlo necesario,podría decirle a Wool que prefiere incrustar este Poincut en particular y así mejorar el tiempode respuesta, o si ejecuta el Advice en ese mismo momento.

Pointcut p = Pointcut.methodCall( "public" , "FigureElement" ,

"paint" , "*" );

public void hook(Wool wl, Joinpoint $jp) throws WoolException {if (wl.countActivationFrame( "main" ) > 0 ) {

// breakpoint-based executionwl.advice($jp);

} else {// dynamic code translation

4.3. Wool 27

Page 36: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

wl.embedHook($jp, p);}

}

Este fragmento ilustra el caso mencionado anteriormente. Si no existe un frame activo de nom-bre “main”, el Advice es activado directamente, realizando la ejecución en el contexto deldepurador. Ahora bien, si la condición no se cumple, el programador eligirá que en ese caso seincruste el hook en el Joinpoint.

Se ejecuta usando el depurador

Hay solo dos casos en que el depurador ejecuta un Advice. Uno es cuando el método hook()no fue sobreescrito, que es el caso por defecto. El otro es que el método advice() es lla-mado desde un método hook() que fue sobreescrito. Simplemente al ser llamado, el métodoasociado al Advice es automáticamente ejecutado.

Se incrusta el hook y se hace la llamada al Advice

Cuando el método embedHook() es llamado, Wool carga una herramienta que permite mo-dificar el código intermedio de Java y llama a un método especial que ésta provee para redefinirclases. Durante la intercepción y reemplazo de la clase, al programa interceptado se le permitecontinuar con su ejecución utilizando la implementación basada en breakpoint.

Cuando la traducción del código se encuentra completa, Wool toma nuevamente el controlbloqueando el programa hasta completar, ahora sí, la carga de la clase modificada. Una vezterminado libera al programa objetivo y sigue con la ejecución.

4.3.6 Conclusiones

La utilización de las técnicas mixtas que el autor de Wool emplea en la implementación deWool son realmente ingeniosas. El hecho de no necesitar modificar la máquina virtual de Javapara lograr una implementación de entrelazado dinámico también es digno de ser mencionado.

El trabajo original abarca mucho más que lo expresado aquí, como ser las posibilidades dedeclarar Inter-tipos, por ejemplo.

Lo otro que no se decanta de manera obvia en el trabajo es cómo se acceden a los parámetrosoriginales al interceptar una llamada. Por la forma en que está implementado seguramente seautilizando alguna API de depurador para inspeccionar el stack y poder obtener los parámetrosen una llamada dada.

28 Capítulo 4. Estado del Arte

Page 37: Reificación de llamadas a métodos en Mono

CAPÍTULO 5

Mono

5.1 Qué es Mono

Mono es un proyecto iniciado a principios del año 2000 con la finalidad de brindar soportea la reciente plataforma liberada por Microsoft : .NET. Esta plataforma brindaba una ampliabiblioteca de clases, una máquina virtual con recoleccion de basura y la posibilidad de utilizarcualquier lenguaje de programación para desarrollar.

El proyecto GNOME venía desde hacía años utilizando CORBA para poder integrar diferenteslenguajes de programación, con relativamente poco éxito. Fue por eso que Miguel de Icazapuso un gran esfuerzo en esa época en comenzar una implementación libre de .NET : nacióMono.

Mono está compuesto por diversos componentes para desarrollar software:

Una máquina virtual con infraestructura para lenguaje común (CLI) que contiene un car-gador de clases, un compilador en tiempo de ejecución (JIT), y unas rutinas de recolec-ción de memoria (garbage collection).

Una biblioteca de clases que puede utilizarse en cualquier lenguaje que esté soportado enel CLR (Common Language Runtime). La figura 5.1 en la página siguiente muestra estaestructura simplificada.

Un compilador para el lenguaje C#, MonoBasic (la versión para Mono de Visual Basic),Java y Python.

El CLR y el Sistema de tipos común (CTS) permite que la aplicación y las bibliotecassean escritas en una amplia variedad de lenguajes diferentes que compilen todas al mismocódigo intermedio.

Esto significa que, por ejemplo, si se define una clase que realice una manipulación alge-braica en C#, ésta pueda ser reutilizada en cualquier lenguaje compatible con CLI. Puedecrear una clase en C#, una subclase en C++ e instanciar esa clase en un programa enEiffel.

Un sistema de objetos único : sistema de hilos, bibliotecas de clases y sistema recolectorde memoria pueden ser compartidos por todos estos lenguajes.

29

Page 38: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Figura 5.1: Arquitectura simplificada de Mono.

Es un proyecto independiente de la plataforma. Actualmente Mono funciona enGNU/Linux, FreeBSD, UNIX, Mac OS X, Solaris y plataformas Windows.

El proyecto Mono es mucho más que solo un runtime. Desde que la empresa Novell compraraa Ximian en el año 2003 (empresa que inicialmente llevaba a cabo el desarrollo en formaparticular) empezó un acelerado proceso por brindar una plataforma completa y compatiblecon la de Microsoft con el solo fin de captar una mayor base de desarrolladores y posibilitarel port de aplicaciones desde Windows a Linux a bajo costo, ganando así toda la comunidadde Software Libre. En el año 2011 Novell decide dejar de lado a Mono, cuyo desarrollo actualestá a cargo de la empresa Ximarin.

En los últimos años se ha invertido mucho tiempo en aspectos como la performance, la com-patibilidad de bibliotecas y hasta han aparecido proyectos anexos, como Moonlight, una imple-mentación opensource de Silverlight, y MonoTouch, una solución comercial y privativa paradesarrollar aplicaciones para iPhone.

Mono no es simplemente otro proyecto de software libre, sino que ha sido utilizado con éxitotanto por empresas como por desarrolladores independientes para migrar sus plataformas asistemas operativos Unix, utilizando el mismo software que ya poseían.

Existen muchos beneficios por los que elegir Mono al momento de desarrollar una aplicación,por ejemplo :

Popularidad : Al estar construido sobre la plataforma .NET, existen millones de desarro-lladores en el mundo que han demostrado gran habilidad en el uso de la plataforma, asícomo también se han escrito cientos de libros sobre el tema, siendo uno de los lenguajescon más documentación escrita despues de Java.

30 Capítulo 5. Mono

Page 39: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Supported Architectures Runtime Operating system

s390, s390x (32 and 64 bits) JIT Linux

SPARC (32) JIT Solaris, Linux

PowerPC JIT Linux, Mac OSX, Wii, PlayStation 3

x86 JIT Linux, BSD, Windows, Solaris, Android

x86-64: AMD64 and EM64T JIT Linux, BSD, Solaris, OS X

ARM: little and big endian JIT Linux, iPhone, Android

Alpha, MIPS, HPPA JIT Linux

Cuadro 5.1: Arquitecturas soportadas.

Programación de alto nivel: Todos los lenguajes en Mono se benefician de las carac-terísticas del runtime, como el manejo automático de la memoria, reflexión, genéricos,hilos, etc. Estas características permiten al programador centrar todo su esfuerzo en laconstrucción de la aplicación.

Base Class Library: Tener una buena biblioteca de clases ha demostrado en todos loslenguajes de programación que aumenta la productividad, dejando las tareas repetitivasya realizadas y probadas de entrada.

Multi plataforma: Mono puede correr en Linux, Windows, BSD y varios unixes, iPhone.Además puede correr en diferentes plataformas como ser x86, ia64, PowerPC, Sparc,entre otras (Cuadro 5.1).

Common Language Runtime: Permite elegir el lenguaje con el que más a gusto esté elprogramador, haciendo que no tenga que descartar todo lo aprendido. Se puede usar desdeVB.NET hasta Eifell.NET, pasando por una gran variedad de lenguajes con diferentescaracterísticas como Ruby.NET o Python.NET.

Es por todo esto que el proyecto es de especial interés.

5.2 Componentes

El desarrollo de Mono está dividido en varios componentes. Cada uno de estos tiene una fina-lidad específica y nos permite modificar diferentes aspectos según sea nuestro interés.

Los componentes que podemos encontrarnos son :

Compilador C#: El compilador C# de Mono soporta completamente las versiones C# 1.0,2.0, 3.0, and 4.0 (ECMA). Una buena descripción de las características de cada versiónpuede ser encontrada en Wikipedia.

Mono Runtime: El Runtime implementa el ECMA Common Language Infrastructure(CLI), es decir, es motor de ejecución para las aplicaciones, entre otras cosas. Este esuno de los componentes en los que centraremos futuras secciones ya que será nuestroprincipal objeto de estudio.

Base Class Library: La plataforma provee un conjunto de clases compatible con el frame-work .NET de Microsoft, de acuerdo a lo declarado en el estandar ECMA.

5.2. Componentes 31

Page 40: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Mono Class Library: Provee características extra para desarrolladores, que no se encuen-tran en el estándar. Ejemplos de estas bibliotecas son Gtk+, Zip files, LDAP, OpenGL,Cairo, POSIX, etc.

5.3 Mono Runtime

5.3.1 Aspectos Generales

Antes de empezar a analizar cada parte es importante comprender cómo funciona la máquinavirtual del proyecto Mono, ya que de ésta organización interna surgen muchas de las solucionesque luego se explicarán.

Dentro del proyecto, el componente que se encarga de ejecutar el CLI (Common IntermedianLanguage) es el Mono Runtime que provee :

1. Just-in-Time compiler (JIT) : El objeto de estudio. Genera assembler para ser ejecutadodirectamente en el microprocesador.

2. Ahead-of-Time compiler (AOT) : Utiliza técnicas de caching en disco para acelerar eltiempo de carga. No lo tendremos en cuenta en el trabajo.

3. Un interprete para plataformas no soportadas directamente por el JIT. No será contem-plado en el presente trabajo.

En suma a los ya mencionados modos de trabajo que posee, el Runtime también es el encargadode manejar :

1. Garbage collector : para el manejo automático de la memoria.

2. Library loader : se encarga de leer los archivos con el código intermedio y cargarlos enla máquina virtual para ser ejecutados.

El JIT, o como también puede ser conocido Traductor Dinámico, es un método para mejorarla performance de ejecución en los sistemas de computación, que ha ganado grandes adeptosdentro de los desarrolladores de máquinas virtuales.

La forma básica de trabajo es un híbrido entre el método interpretado y el compilado. A medidaque se va interpretando el código se produce una compilación. Esta última es guardada en uncache asociado al código que fue interpretado.

La próxima vez que el intérprete alcance un punto ya compilado, lo que se realiza es la ejecu-ción del código en el cache, ahorrándose así el tiempo de re-compilación. A la larga, todo elprograma estará compilado haciendo su ejecución similar en tiempo a si se lo hubiera compila-do antes de empezar, pero ganando en tiempo de arranque, ya que para empezar no hace faltatener compilado nada de antemano.

El JIT de Mono pasó por varias fases hasta llegar al estado actual, en el cual se utiliza unarepresentación lineal (IL) del código CIL, que luego es finalmente convertida a assembler.

La decisión de utilizar este tipo de representación interna para el JIT vino dada de varios fac-tores que el proyecto necesitaba resaltar para sus objetivos globales, por ejemplo :

Simplificar el port a una nueva arquitectura

32 Capítulo 5. Mono

Page 41: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Figura 5.2: Esquema de un JIT en una Java VM.

Incrementar la performance global

5.3.2 Pasadas del JIT

Todo el proceso de compilación está separado en varias etapas bien definidas.

Method-to-IRLa primera es Method-to-IR en donde el JIT toma el código IL y lo convierte enuna Representación Interna de Instrucciones (IR). Este es uno de los pasos máscostosos, ya que es donde se hace la mayor parte del trabajo. También se ocupa dedescomponer operadores complejos en secuencias de operaciones más simples, yporque podrían ser optimizables en el futuro.

Decomposición de operadoresComo segundo paso el JIT trata de descomponer operaciones sobre enteros largos(LONG) en plataformas de 32 bits, de manera que sean consistentes. Recordemosque en 32 bits el tamaño de un LONG en memoria suele ser de 32 bits y no de 64como se esperaría.

Optimizacion de ramificaciones (Branch)Otro paso a realizar durante la compilación es la optimizacion de ramificaciones enel código (IF, CASE, etc). Estas optimizaciones se realizan localmente al método yno de forma global.

Asignación de registrosLuego le toca el turno a los registros. Inicialmente el JIT reserva solamente registros

5.3. Mono Runtime 33

Page 42: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

locales, debido a que es un proceso costoso. En este paso el, JIT analiza registrosque se repitan a lo largo de un bloque y decide mediante heurísticas si es recomend-able promocionar dicho registro a un registro global o viceversa.

Entraremos en detalle sobre cómo se manejan los registros más adelante.

Eliminación de código muertoYa optimizado el código y acomodados los registros el siguiente paso del JIT eseliminar el código muerto, lo que salvará principalmente memoria y tiempo decompilación. Esto es, por ejemplo, eliminar todo código debajo de un return y elfinal de un bloque, ya que nunca será ejecutado.

Generación de AssemblerRecién una vez realizadas estas funciones (y algunas más omitidas por no ser devital importancia), el JIT se dispone a generar el assembler de la plataforma dondese va a ejecutar.

5.3.3 Representación Interna de Instrucciones (IR)

El primer paso del JIT es convertir el código CLI en una representación intermedia que utilizaun código estándar de tres direcciones :

OP dreg <- sreg1 sreg2

Donde dreg, sreg1 y sreg2 son registros virtuales y OP es el código de la operación a realizar.

Los operadores soportados son variados y complejos, puede ir desde lanzar una excepción acódigos definidos en el IL directamente. En una primera pasada el JIT lo que hace es eliminarlos opcodes IL nativos, para reemplazarlos por representaciones propias.

Esto es así ya que por ejemplo, para arrays hay optimizaciones específicas que tienen operado-res propios que escapan al manejo clásico de una clase.

Cada instrucción internamente es manejada por una estructura en C (MonoInst) que está defini-da en el archivo mono/mini/mini.h :

struct MonoInst {guint16 opcode;guint8 type; /* stack type */guint ssa_op : 3;guint8 flags : 5;

/* used by the register allocator */gint32 dreg, sreg1, sreg2, sreg3;

MonoInst *next, *prev;

union {union {

MonoInst *src;MonoMethodVar *var;mgreg_t const_val;

34 Capítulo 5. Mono

Page 43: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

#if(SIZEOF_REGISTER > SIZEOF_VOID_P) && (G_BYTE_ORDER == G_BIG_ENDIAN)struct {

gpointer p[SIZEOF_REGISTER/SIZEOF_VOID_P];} pdata;

#elsegpointer p;

#endifMonoMethod *method;MonoMethodSignature *signature;MonoBasicBlock **many_blocks;MonoBasicBlock *target_block;MonoInst **args;MonoType *vtype;MonoClass *klass;int *phi_args;MonoCallInst *call_inst;

} op [2];gint64 i8const;double r8const;

} data;

/* for debugging and bblock splitting */const unsigned char* cil_code;

/* used mostly by the backend to store additional infoit may need */

union {gint32 reg3;gint32 arg_info;gint32 size;/* in OP_MEMSET and OP_MEMCPY */MonoMemcpyArgs *memcpy_args;gpointer data;gint shift_amount;/* for variables in the unmanaged marshal format */gboolean is_pinvoke;/* For CEE_CASTCLASS */gboolean record_cast_details;/* for OP_ICONV_TO_R8_RAW and OP_FCONV_TO_R8_X */MonoInst *spill_var;/* OP_XCONV_R8_TO_I4 needs to know which op was used to do

proper widening */guint16 source_opcode;

} backend;

MonoClass *klass;};

Cada uno de estos campos tiene un uso específico, siendo éste el siguiente :

opcode : contiene el código único que identifica la instrucción a ser ejecutada. Siempre

5.3. Mono Runtime 35

Page 44: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

está presente.

dreg, sreg1, sreg2 : contiene los registros (vregs) de origen y destino de la instrucción. Sila instrucción no hace uso de alguno de los registros, esos campos son completados con-1.

backend : se usa con varios propósitos:

• para las estructuras MonoInst que representan tipos virtuales (vtype), indica que lavariable es no-manejada

• en algunos códigos de operación, como X86_LEA, es usado para guardar informa-ción extra.

next y prev : son usados para enlazar las instrucciones en secuencia (se comporta comouna lista doblemente enlazada de manera de poder recorrerla en ambos sentido de formaeficiente).

cil_code : apunta a las instrucciones IL que pertenecen a esta instruccion. Es usado paragenerar informacion de depuración.

ins->flags : flags varios

klass : contiene la información de tipo para el resultado de la instrucción. Estos métodosson usados durante la pasada de method_to_ir ().

Adicionalmente a los campos descriptos arriba, cada MonoInst contiene dos punteros quepueden ser usados por la instrucción para almacenar datos arbitrarios. Estos datos pueden serluego accedidos utilizando el set de macros inst_<XXX>.

Algunos lineamientos para su uso a continuación:

macros OP_<XXX>_IMM almacena el argumento en inst_imm.

macros OP_<XXX>_MEMBASE almacena el registro base en inst_basereg (sreg1), y eldesplazamiento en inst_offset.

macros OP_STORE<XXX>_MEMBASE almacena el registro base en inst_destbasereg(dreg), y el desplazamiento en inst_offset. Este tiene un motivo histórico dado que elregistro dreg nunca es modificado por la instrucción.

5.3.4 Registros Virtuales (Vregs)

Toda instrucción IR que se genere trabaja con registros virtuales (o vregs). Cada uno de estosse identifica internamente por un índice entero y un tipo, como se define en la tabla 5.2 en lapágina siguiente, aunque éste está implícito (es decir, no está definido físicamente en el código).El tipo de un registro es inferido a partir del código de operación que está usando ese registro.Esto significa que, por ejemplo, en un momento el registro 5 puede ser de un tipo, pero en otrocontexto cambiar.

El tipo de registro permite identificar posteriormente cómo se debe procesar el dato, ya quesiempre accedemos al contenido de un registro por un puntero (void *) y es necesario hacer uncast. Por ejemplo si el registro es de tipo STACK_I4 sabemos que es un integer de 4 bytes por

36 Capítulo 5. Mono

Page 45: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

typedef enum {STACK_INV,STACK_I4,STACK_I8,STACK_PTR,STACK_R8,STACK_MP,STACK_OBJ,STACK_VTYPE,STACK_MAX

} MonoStackType;

Cuadro 5.2: mini.h:

lo que podemos, en plataformas de 32bits, hacer un cast directo a int. Esto será importante másadelante cuando sea neceario leer el stack para extraer parámetros en la llamada a métodos.

Los registros también tiene concepto de localidad : pueden ser globales o locales a la operación.Esto es así para poder definir el scope de validez del dato que en ellos se guarda y además hacerun seguimiento de cuando un registro puede ser usado nuevamente dentro de otra rutina.

La diferencia entre ambos tipos de registros es bien marcada y tiene su fundamento en qué usose le da en cada contexto. Veremos a continuación una simple enumeración de sus capacidadesy limitaciones.

Registrios locales (lvreg) :

son locales a un block básico (una porción de código)

son livianos: reservar recursos para un registro local es equivalente a incrementar uncontador, y no consumen memoria adicional.

algunas optimizaciones operan solo con registros locales.

son asignados a registros ‘duros’ (hregs) por el almacenador de registros local. Estos noparticipan en el análisis de vida ni en el almacenamiento de registros globales

no tienen una dirección

no pueden ser volátiles

Registros globales :

son pesados: reservar recursos es lento y consumen memoria. Cada registro global tieneuna entrada en los arrays cfg->varinfo y cfg->vars.

son almacenados durante la inicialización de registros globales o bien durante almace-namientos en el stack.

tienen una dirección física, por lo que es posible aplicar operadores de direcciones, comoser LDADDR.

El mapeo entre el registro global y su asociación en el array cfg->varinfoes realizado por el array cfg->vreg_to_inst. Existe un macro llamadoget_vreg_to_inst () que genera el índice en el array. Un registro es global siget_vreg_to_inst (cfg, vr) retorna algo diferente de NULL.

5.3. Mono Runtime 37

Page 46: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

La motivación de este esquema de tener dos tipos de registros es principalmente que en la fasede conversión MSIL a IL se realiza una gran cantidad de reserva de registros que representanel contenido del stack. Como los registros globales son lentos para reservar, en muchos casosse utilizan registros locales que utilizan menos recursos y permiten una respuesta mucho máseficiente.

Los registros pueden pasar de ser globales a locales y viceversa según se necesite durante lacompilación. En general, la promoción se da de local a global, ya que por ejemplo se deter-mina que un valor debe permanecer en el registro más tiempo que el contexto actual, y se lopromociona a global para que su valor no sea sobreescrito, y así evitar operaciones de stackinnecesarias.

En general, como regla invariante, el JIT asume que todo registro local está en el contexto deun block básico (por ejemplo un método). En algunas ocasiones se puede dar que el JIT rompaeste invariante y eso genera una llamada adicional a mono_handle_global_vregs (),que verifica y transforma un registro en global de ser necesario. Además, está la posibilidad deque realice la función inversa.

Por último y como regla general, si la dirección de un registro debe ser tomada, este mismo estransformado en un registro global, ya que debe garantizarse que el valor se preserve.

Asignación de Registros

En instancias posteriores a la asignacion de registros virtuales el JIT necesitará asignar registrosfísicos (en la CPU). Este proceso es realizado por el Register Allocation Module, cuyo trabajoes asignar un registro físico a cada registro virtual de manera que el registro referenciado enuna instrucción pueda ser reemplazado con el registro físico asignado, haciendo factible lageneración de código de máquina en una instancia posterior.

El asignador de registros necesita información acerca de la cantidad y tipo de argumentos deuna instrucción dada, que es obtenida de uno de los archivos de codigo fuente donde estádefinida esta información. También necesitará contar con la cantidad de registros físicos quecuenta la arquitectura en la cual se está ejecutando. Esta información se obtiene de macrosespecíficos de la arquitectura. Actualmente, solo existen dos tipos de registros : enteros y depunto flotante.

Para realizar la asignación, este módulo realiza dos pasadas : en la primera pasada se van leyen-do las instrucciones hacia adelante, recolectando información acerca de su tiempo de vida. Enla segunda, se recorre en reverso instrucción por instrucción con el fin de asignar los registrosfísicos de manera definitiva.

Estado del Asignador

El estado del asignador es almacenado en dos arrays : iassign and isymbolic. El primeroes un mapeo de vregs a hregs, mientras que el segundo es la referencia opuesta. Para un vregdado, la posición iassign[vreg] puede contener alguno de los valores de la tabla 5.3 en la páginasiguiente.

Además el asignador debe guardar información de que registros físicos están disponibles ycuáles están en uso. Esta información es almacenada en una variable utilizando una máscara

38 Capítulo 5. Mono

Page 47: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

vreg no tiene asignado hreg

hreg index (>= 0) vreg está asignado a un hreg específico.Significa que instruccionesfuturas esperanencontrar el valor del vreg en hreg.

spill slot index Las instrucciones esperarán que el valor(< -1) del vreg esté contenido en el stack. Cuando

este vreg sea utilizado como destino deunaoperación, se generará una operación queguarde el valor para evitarque sea pisado

1

Cuadro 5.3: Posibles valores de iassign[vreg].

de bits llamada ifree_mask. El siguiente es un fragmento simplicado (solo para x86) de larutina que obtiene el siguiente registro entero libre :

static inline intmono_regstate_alloc_int (MonoRegState *rs, regmask_t allow){

regmask_t mask = allow & rs->ifree_mask;

int i;

for (i = 0; i < MONO_MAX_IREGS; ++i) {if (mask & ((regmask_t)1 << i)) {

rs->ifree_mask &= ~ ((regmask_t)1 << i);return i;

}}

// No free register foundreturn -1;

}

Una vez que el registro deja de ser necesario, se libera con :

static inline voidmono_regstate_free_int (MonoRegState *rs, int reg){

if (reg >= 0) {rs->ifree_mask |= (regmask_t)1 << reg;rs->isymbolic [reg] = 0;

}}

El manejo de registro de punto flotante es similar, teniendo funciones y constantes de manerasimilar a lo expuesto para los registros enteros.

Registros Fijos

Otro de los problemas con lo que debe luchar el Runtime es que algunas arquitecturas esperanque el resultado de ciertas operaciones caiga en un registro específico. Es el caso de x86, dondepor ejemplo una operación de shift espera que el segundo argumento esté en el registro ECX.

La forma de lidiar con esto es que dentro del código fuente hay macros específicos para lasoperaciones, que permiten en tiempo de ejecución predecir estas condiciones y asignar los

5.3. Mono Runtime 39

Page 48: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

registros como se espera.

Llamadas

Las instrucciones de llamadas (Calls) necesitan un tratamiento especial por dos razones :primero, porque son propensas a pisar los valores dejados en los registros por quien hace lallamada, por lo que los valores previos a la llamada deberán ser guardados en algún lugar.Además, en algunas arquitecturas se permite pasar parámetros a través de los registros de laCPU. Usualmente en estos casos los registros utilizados son los mismos que los que se usanpara almacenar datos locales y es por eso que el asignador debe tratarlos de forma especial.

Para poder completar esta tarea con éxito se hace lo siguiente. La MonoInst que se utiliza parala llamada contiene un mapeo de vregs que almacena los valores del argumento de hregs dondeel argumento tiene que ser colocado. Cuando el asignador termina de procesar la instrucción dellamada, éste almacena los vregs en el mapa con su respectivo hreg asociado. De esta manera,es procesada como si tuviera un número de argumentos variable.

Veamos un simple ejemplo :

R33 <- 1R34 <- 2call

Cuando la instrucción call es procesada, R33 es asignado a RDI y R34 es asignado a RSI. Mástarde, cuando las dos instrucciones de asignación son procesadas, R33 y R34 ya están asignadasa un registro físico, por lo que son reemplazadas resultando el siguiente código :

RDI <- 1RSI <- 2call

5.3.5 Trampolines

Los Trampolines son pequeños fragmentos de assembler, escritos a mano, usados para realizarvarias tareas en el Runtime de Mono. Estos son generadas en tiempo de ejecución utilizandolos macros disponibles en el JIT para generar código nativo. Generalmente, además, tiene unacorrespondiente función en C que se pueden llamar en caso de que se necesite realizar una tareamucho más compleja.

El código común para todas las arquitecturas soportadas se encuentra enmini-trampolines.c , archivo que contiene las rutinas de creación de trampolines y lasfunciones en C llamadas por los éstos. Los archivos nombrados como tramp-<ARCH>.ccontienen detalles dependientes de cada arquitectura.

La mayoría, aunque no todos, se componen de dos partes principales :

una parte genérica, que contiene la mayor parte del código. Es creada por la llamada amono_arch_create_trampoline_code () desde tramp-<ARCH>.c .

una parte específica que hace el trabajo de llamar a la parte genérica, pasando unparámetro. Este parámetro y el método en sí dependen del tipo de trampolín que se esté

40 Capítulo 5. Mono

Page 49: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

MONO_TRAMPOLINE_JIT

MONO_TRAMPOLINE_JUMP

MONO_TRAMPOLINE_CLASS_INIT

MONO_TRAMPOLINE_GENERIC_CLASS_INIT

MONO_TRAMPOLINE_RGCTX_LAZY_FETCH

MONO_TRAMPOLINE_AOT

MONO_TRAMPOLINE_AOT_PLT

MONO_TRAMPOLINE_DELEGATE

MONO_TRAMPOLINE_RESTORE_STACK_PROT

MONO_TRAMPOLINE_GENERIC_VIRTUAL_REMOTING

MONO_TRAMPOLINE_MONITOR_ENTER

MONO_TRAMPOLINE_MONITOR_EXIT

Cuadro 5.4: Tipos de Trampolin disponibles.

utilizando.

Los trampolines específicos son creados tras una llamada amono_arch_create_specific_trampoline () en tramp-<ARCH>.c.

La parte genérica es la encargada de salvar el estado de la máquina en el stack, y llama a unode los métodos definidos en mini-trampolines.c con el estado, el punto de llamada, y elargumento pasado por la parte específica del Trampolín. Cuando la función en C regresa, o lohace normalmente o bifurca a una dirección devuelta por la llamada, dependiendo del tipo deTrampolín. Los tipos disponibles están definidos en el archivo mini.h y son reflejados en latabla 5.4.

Las funciones de creación tienen las siguiente firma :

gpointermono_arch_create_foo_trampoline (<args>, MonoTrampInfo **info, gboolean aot)

La función debe devolver un puntero al nuevo trampolín creado, almacenando la memoria odesde el manejador global o desde el domain manager. Si info no es NULL, este es seteadoa un puntero a una estructura del tipo MonoTrampInfo que contiene información acerca deltrampolin, como el nombre, por ejemplo. Esto es usado con dos propósitos :

Guardar una imagen de la información del trampolín cuando se está en modo ‘full-aot’.

Guardar información de depuración en modo XDEBUG.

JIT Trampolines

Estos trampolines son usados por el JIT para ser capaz de compilar un método la primera vezque es llamado. Cuando el JIT compila una instrucción de call, no lo hace realmente en esemomento. En su lugar lo que hace es crear un trampolín de tipo MONO_TRAMPOLINE_JITy emite en assembler una instrucción de call apuntando a dicho trampolín.

Durante la ejecución del programa al tratar de ejecutar un método aún no compilado, el tram-

5.3. Mono Runtime 41

Page 50: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

voidmono_arch_patch_callsite (guint8 *method_start, guint8 *orig_code,

guint8 *addr){

guint8 *code;guint8 buf [8];gboolean can_write = mono_breakpoint_clean_code (

method_start,orig_code, 8, buf, sizeof (buf));

code = buf + 8;

/* go to the start of the call instruction** address_byte = (m << 6) | (o << 3) | reg* call opcode: 0xff address_byte displacement* 0xff m=1,o=2 imm8* 0xff m=2,o=2 imm32*/code -= 6;orig_code -= 6;if ((code [1] == 0xe8)) {

InterlockedExchange ((gint32*)(orig_code + 2),(guint)addr - ((guint)orig_code + 1) - 5);

} else if (code [1] == 0xe9) {/* A PLT entry: jmp <DISP> */InterlockedExchange ((gint32*)(orig_code + 2),

(guint)addr - ((guint)orig_code + 1) - 5);} else {

printf ( "Invalid trampoline sequence: %x %x %x %x %x %x %x\n" ,code [0], code [1], code [2], code [3],code [4], code [5], code [6]);

g_assert_not_reached ();}

}

Cuadro 5.5: mono_arch_patch_callsite para x86 simplificada.

polín es llamado y éste llama a mono_magic_trampoline (), que compila el método encuestión y devuelve la dirección de memoria del código compilado al cual se hace referencia(ver Emisión de código nativo).

Este proceso es en algún punto lento, por lo que mono_magic_trampoline () trata deaplicar un parche al código que realizó el disparador de la compilación en el JIT, de manera talque la próxima vez se llame al código compilado en lugar de llamar nuevamente al trampoline.

Esta tarea es realizada por la función mono_arch_patch_callsite () entramp-<ARCH>.c y que es ejemplificada en el código de la figura 5.5.

Una documentación más completa de los trampolines puede encontrarse en el Wiki del proyecto(Mono:Runtime:Documentation:Trampolines) , donde se definen en más detalle los otros tiposde trampolines.

42 Capítulo 5. Mono

Page 51: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

voidmono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb){

MonoInst *ins;MonoCallInst *call;guint offset;guint8 *code = cfg->native_code + cfg->code_len;int max_len, cpos;

cpos = bb->max_offset;

offset = code - cfg->native_code;

MONO_BB_FOR_EACH_INS (bb, ins) {offset = code - cfg->native_code;

max_len = ((guint8 *)ins_get_spec (ins->opcode))[MONO_INST_LEN];

switch (ins->opcode) {case OP_BIGMUL:

x86_mul_reg (code, ins->sreg2, TRUE);break;case OP_BIGMUL_UN:

x86_mul_reg (code, ins->sreg2, FALSE);break;

... etc **

Cuadro 5.6: mono_arch_output_basic_block para x86 (simplificado).

5.3.6 Emisión de código nativo

El corazón de esta funcionalidad está definida en los archivos mini-<arch>.c que se en-cuentran en el codigo fuente. Específicamente a nosotros nos interesa el mini-x86.c, que esquien genera el código assembler para esta plataforma.

El método principal se llama mono_arch_output_basic_block, que toma un bloquebásico de código (un método) y recorre la lista de instrucciones generando el código binariocorrespondiente en instrucciones x86. Esta lista de instrucciones ya se encuentra en la repre-sentación interna explicada en la sección previa.

Este método es muy simple como se puede observar en la figura 5.6. Solamente es un granswitch sobre el código de operación de la instrucción a compilar y llama, según sea el caso, ala función o macro que se encarga de emitir el código binario nativo que debe ser usado.

Para completar la emisión de código para un método otras tres funciones son necesarias :

1. mono_arch_emit_prolog ()

2. mono_arch_emit_epilog ()

3. mono_arch_patch_code ()

En especial, mientras se estudiaba la documentación resultaron particularmente interesantesemit prolog y emit epilog. De la documentación de cómo portar a una nueva platafor-ma se puede leer que :

5.3. Mono Runtime 43

Page 52: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

top of stack

Return addressFrame pointer

Parameters of Drawline

Locals ofDrawSquare

stack frameforDrawSquare Return addresssubroutine

Parameters of DrawSquare

Stack pointer

Locals ofDrawLine

stack framesforDrawLinesubroutine

...

Figura 5.3: Call Stack típico de la arquitectura x86.

emitirá el código para preparar el stack frame para un método, opcionalmente lla-mando a los callbacks usados para profiling y trazado.

El ‘stack frame’, o también conocido como ‘registros de activación’, son estructuras de datosque dependen de una máquina específica para la cual fueron creados, que contienen informa-ción de estados de las subrutinas. Cada stack frame se corresponde con una llamada de unasubrutina que todavía no terminó de ser ejecutada y no ha retornado.

Por ejemplo, si una subrutina llamada DrawLine está siendo ejecutada actualmente y fue lla-mada por una subrutina llamada DrawSquare, la parte superior del call stack podría parecersea la figura 5.3.

El stack frame al tope es la subrutina que se encuentra actualmente en ejecución. En los casosmás comunes el stack frame contiene :

espacio para las variables locales de la subrutina

la dirección de retorno desde donde fue llamada

parámetros pasados a la subrutina cuando fue llamada.

El tener este dato resultó particularmente conveniente, ya que permitió darse una idea de cómopoder ejecutar una código antes de la ejecución de un método dado : simplemente generandostack frames propios y agregarlos al call stack del método que se iba a compilar.

De igual manera, existe un mono_arch_emit_epilog que permite realizar la misma tarea,pero al terminar la ejecución.

5.3.7 Assembler x86

Si vemos el assembler generado por mono podemos observar algo similar a lo siguiente duranteuna llamada a un método :

44 Capítulo 5. Mono

Page 53: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

00000000 (Test_Bar):(bb):3

0: 55 push %ebp1: 8b ec mov %esp,%ebp3: 83 ec 08 sub $0x8,%esp6: 83 ec 0c sub $0xc,%esp9: 68 c0 8f 05 00 push $0x58fc0e: e8 e5 fd ff ff call fffffdf8 (Test_Bar +0xfffffdf8)

13: 83 c4 10 add $0x10,%esp(bb):1

16: c9 leave17: c3 ret

Eso es posible verlo al ejecutar la desde la línea de comandos : mono -v -v -vprograma.exe .

Las dos últimas columnas son la representación legible de las instrucciones en assembler quese van a ejecutar.

Este es el código estándar generado por Mono al compilar un método. La llamada específica esel call de assembler y lo que está antes representa el contenido generado por el ya mencionadoemit_prelog. El código posterior representa el epilog, que en este caso es un simple return.

Teniendo conocimiento de esto es posible, entonces, agregar código adicional antes y/o despuésde ese call. Claro está, hay que cuidar el uso de los registros, orden de llamadas, y algunosdetalles más, pero podríamos, por ejemplo, generar el siguiente código en assembler :

00000000 (Test_Bar):(BB):3

0: 55 push %ebp1: 8b ec mov %esp,%ebp3: 83 ec 08 sub $0x8,%esp/* Acá empieza lo insertado */6: 83 ec 08 sub $0x8,%esp9: 55 push %ebpa: 68 34 58 2f 08 push $0x82f5834f: e8 64 6e 87 50 call 50876e78 (Test_Bar+0x50876e78)

14: 83 c4 10 add $0x10,%esp/* Acá termina */

17: 83 ec 0c sub $0xc,%esp1a: 68 c0 9f 05 00 push $0x59fc01f: e8 64 fd ff ff call fffffd88 (Test_Bar+0xfffffd88)24: 83 c4 10 add $0x10,%esp

(BB):127: c9 leave28: c3 ret

En este caso podemos observar que en el stack guardamos el puntero EBP, para así luego podervolverlo a poner previo a la llamada original. Cabe recordar que es cada cosa :

1. El ‘stack pointer’ (esp) es una dirección de memoria en donde está la pila de registros oparámetros a pasar a una función.

2. EBP se denomina frame pointer, y define la dirección de inicio desde donde un métodollamado con call va a leer los parámetros.

Como puede deducirse, en nuestro caso la función que ejecutamos antes recibe el EBP de la

5.3. Mono Runtime 45

Page 54: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

in_arg[0] = var[0]in_arg[1] = var[1]

frame pointer (EBP)

n_extra

var[n_arg]var[n_arg+1] local variables area

var[n_var-1]

spill area area for spilling mimic stack

ebxebp [ESP_Frame only]esi 0..3 callee-saved regsedi

stk0stk1 operand stack area/

out argsstkn-1

. . .

return IP

saved EBP

...

. . .

stack pointer (ESP)

. . .

Cuadro 5.7: Estructura de un frame x86.

que está interceptando, por lo que es posible leer qué parámetros se le pasó a la función originaldesde la función ‘interceptadora’.

5.3.8 Frame y Registro EBP

Este registro le indica a una función desde dónde comenzar a leer los parámetros, claro que sipodemos acceder a un ebp genérico, podemos entonces analizar qué tipo de parámetro se leestaba pasando desde el otro método.

En x86 un frame puede dividirse como muestra la figura 5.7.

Como se podrá observar más adelante, a partir de conocer la dirección de un frame en particular(el ebp para una llamada dada) y algo de información extra acerca del método en cuestión (comola cantidad y tipo de los parámetros) será posible fácilmente extraer de la memoria los valorespasados en una llamada interceptada.

5.4 Internal Calls

Una de las cosas que vamos a tener que hacer más adelante es tener la posibilidad de mapearcódigo C# con código C. Esto es necesario ya que queremos proveer una API al programadorpara interactuar con el Weaver desde C# con comodidad. La herramienta que provee Monopara realizar esta tarea es justamente las ‘Internal Calls’ y en esta sección explicaremos cómofuncionan y cómo hacer uso de ellas.

Mono soporta dos métodos de agregar llamadas internas : una estática en tiempo de compilacióndel JIT y otra dinámica en tiempo de ejecución de una aplicación dada.

46 Capítulo 5. Mono

Page 55: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

La forma dinámica está orientada a dar soporta para incrustar el runtime de mono en otroslenguajes o aplicaciones, como ser C o C++, y tiene un costo lineal de búsqueda cuando necesitaejecutar el código a partir de una firma de método dado.

Es por eso que en este caso se ha utilizado una llamada interna estática que, al definirse entiempo de compilación, permite que la búsqueda sea realizada mediante una búsqueda binariasiendo ampliamente superior al caso anteriormente mencionado.

Esta técnica es utilizada en muchas otras clases para proveer optimizaciones, como por ejemploen la clase Array.

La forma en que se define la llamada interna es la siguiente : primero, definimos en C# elmétodo que va a tener su implementación en C como internal y, además, lo marcamos con unatributo especial para que el JIT luego sepa resolver qué tiene que hacer con dicho método. Eneste caso concreto lo que tenemos es :

[MethodImplAttribute (MethodImplOptions.InternalCall)]internal extern void DefinePattern (MethodInfo callback, string return_type,

string klass, string method, Array parameters, int flags);

Después, en la definición de llamadas internas debemos agregar nuestra clase y el método,indicando el nombre de la función en C que atenderá la llamada :

ICALL_TYPE(WEAVER, "Weaving.Weaver" , WEAVER_1)

ICALL(WEAVER_1, "DefinePattern" , ves_icall_Weaving_Weaver_DefinePattern)

El único cuidado que hubo que tener es que el archivo debe quedar ordenado alfabéticamenteen cuanto a las llamadas de ICALL_TYPE se refiere, ya que si no la búsqueda binaria nofuncionará y Mono abortará la ejecución alegando que no puede encontrar el método.

El método en C que se ejecutará tendrá la siguiente firma entonces :

static voidves_icall_Weaving_Weaver_DefinePattern (MonoObject *self,MonoReflectionMethod *rmethod, MonoString *rp,MonoString *cp, MonoString *mp, MonoArray *params, int flags)

{mono_joinpoints_define_pattern (self, rmethod, rp, cp, mp, params, flags);

}

Cada parámetro recibido se corresponde en orden con los definidos anteriormente para De-finePattern, con el agregado de self que representa la instancia actual. De ser NULL significaríaque el método es abstracto.

5.4. Internal Calls 47

Page 56: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

48 Capítulo 5. Mono

Page 57: Reificación de llamadas a métodos en Mono

CAPÍTULO 6

Solución adoptada

La implementación que se presenta como solución está compuesta por diferentes módulos, queincluyen implementaciones en tres diferentes lenguajes : C#, C y Assembler. Cada lenguaje esutilizado en una parte específica de la implementación según donde se utiliza.

6.1 .NET API : Definición de Joinpoints

En esta sección analizaremos en detalle la propuesta de API pública que se ha definido paralos desarrolladores. La intención es dotar al lenguaje de mecanismos por los cuales se puedaninterceptar las llamadas a métodos y así poder ejecutar las acciones (Advices) especificadas.

Por conveniencia y comodidad, se decidió adoptar una API similar a la que provee JRockit,pero de manera simplificada.

6.1.1 La Clase Weaver

El entrelazador es el principal componente que refleja la API de cara a los desarrolladores yestá representada mediante la clase Weaver, cuya implementación sigue el patrón Singleton, afin de garantizar que solo exista una instancia de esta clase en la vida de un programa dado.

El patrón está construido aprovechando la disponibilidad de atributos en C# a diferencia deotros lenguajes, quedando la implementación como se ejemplifica a continuación :

namespace Weaving{

static public Weaver Instance {get {

if (instance == null) {instance = new Weaver();

}return instance;

}}private static Weaver instance = null;

49

Page 58: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

// Hidden constructorprivate Weaver(){

Console.WriteLine ( "[CS] Weaving.Weaver initialized ok" );}

}

Luego en nuestro programa es sencillo entonces tener acceso al weaver desde cualquier puntode un programa. Por ejemplo :

class Demo{

public static void Main(string[] args){

Weaver w = Weaver.Instance;

// ... hacer algo con el weaver ...}

}

6.1.2 Definición de Joinpoints

Al momento de definir un Joinpoint, la API nos provee de un mecanismo de suscripción decallbacks. Esto sería ya algo más cercano al concepto de Advice en AOP. Esto es así, ya quela mera definición de un Joinpoint no tiene sentido práctico, por lo que es necesario atar un‘callback’ a ser ejecutado.

La forma de suscribirse a un Joinpoint es mediante el método Subscribe de la clase weaver,cuya firma es :

public void Subscribe(MethodInfo callback, string rv, string cv, string mv,bool any_args, bool run_after)

Donde :

rv : Return Value pattern – Define qué tipos de datos debe tener el método para que seconsidere un match.

cv : Class Name pattern – Define el nombre de clase y/o nombre de espacio sobre el cualse define el Joinpoint

mv : Method Name pattern – Define el nombre del método que se intercepta

any_args : Si es verdadero, se considera que puede matchear métodos con más parámetrosque los definidos por el callback. Si es falso, espera una coincidencia exacta en la cantidadde parámetros.

run_after : Si es verdadero, corre después de la ejecución del método. En otro caso correantes.

En todos los casos donde se pueda especificar un patrón, se aceptan lo comodines “*” y “?”para así especificar si pueden coincidir más de un caracter o exactamente un caracter respectiva-mente. Además, cabe destacar que el algoritmo de match utilizado es sensible a las mayúsculas,por lo que hay que tener en cuenta que “toString” no es lo mismo que “ToString”.

50 Capítulo 6. Solución adoptada

Page 59: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Por ejemplo, si deseamos crear un Joinpoint sobre la llamada al método ToString de cualquierclase en cualquier Namespace la declaración sería :

class Aspect{

static public LogToString(Context c){

// Do some stuff here}

}

public class Demo{

public static void Main(string[] args){

MethodInfo callback = Type.GetType( "Aspect" ).GetMethod( "LogToString" );

Weaver w = Weaver.Instance;

w.Subscribe(callback, "string" , "*" , "ToString" , true, false)

// ...}

}

En general, es recomendable agrupar los callbacks en una clase, similar al concepto de Aspec-to en otras implementaciones. Para el caso puntual de este trabajo, no es necesario extenderuna clase particular, lo que permite que cualquier clase pueda funcionar como Aspecto sin másrequerimiento que el cumplimiento de la firma del método a usar como callback. Esta encapsu-lación representa en el programa una incumbencia entrelazada que hemos separado.

Si, de otro modo, solo queremos el Joinpoint declarado sobre las clases del paquete System,deberíamos declarar :

w.Subscribe(callback, "string" , "System.*" , "ToString" , true, false)

Los métodos utilizados como callback no pueden tener cualquier firma y no pueden sercualquier método. Como analizaremos más adelante, la forma en que se declara un callbackes importante a la hora de generar el patrón que se utilizará finalmente para el pattern matchingde las llamadas a método.

6.1.3 Eliminación de Joinpoints

En cualquier momento del ciclo de vida de una aplicación es posible eliminar un Joinpoint delWeaver a fin de que este deje de ejecutar el callback cuando se cumple la condición. Dadoque el patron de coincidencia en las firmas de los métodos define uniquívocamente qué call-back se ejecuta, para la API se decidió que sea éste último el parámetro por el cual se hace laeliminación.

Para eliminar un Joinpoint simplemente hace falta ejecutar el siguiente método :

public void UnSubscribe (MethodInfo callback, bool after)

6.1. .NET API : Definición de Joinpoints 51

Page 60: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Donde :

callback : la instancia que representa al callback.

after : Si es TRUE, se busca callback en los Joinpoints que corren después de la ejecucióndel método. En otro caso, los que corre antes.

Para los ejemplos del punto anterior, bastaría con llamar al siguiente código a fin de eliminar elJoinpoint del Weaver :

class Aspect{

static public LogToString(Context c){

// Do some stuff here}

}

public class Demo{

public static void Main(string[] args){

MethodInfo callback = Type.GetType( "Aspect" ).GetMethod( "LogToString" );

Weaver w = Weaver.Instance;

w.Subscribe(callback, "string" , "*" , "ToString" , true, false)

// ...

w.UnSubscribe(callback, false)}

}

En caso de que el callback sea utilizado en más de un Joinpoint, todos estos serán eliminadosdel Weaver. Si existe la posibilidad de querer apagar uno pero no otro, se deberán utilizarmúltiples callbacks.

6.1.4 Evento : “On Exit”

Existen muchos casos en donde al terminar la aplicación es de interés, por ejemplo, procesarinformación recopilada y luego guardarla en un log o archivo, a fin de poder estudiarla mástarde. Es el caso de un profiler, por nombrar alguno.

La idea básica de este callback especial es dar al programador un punto unificado donde puedaconectar un callback y ejecutar ciertas tareas. Por ejemplo, si queremos hacer una lista de lacantidad de veces que fue llamado un método de una cierta clase, podríamos escribir el siguientecódigo :

class Aspect{

static Dictionary<string, int> stats =new Dictionary<string, int>();

52 Capítulo 6. Solución adoptada

Page 61: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

static string MethodSignature(MethodInfo mi){

# Convert a MethodInfo into an human string representation.}

static public void ACall(Context c){

string k = MethodSignature(c.Method);

try {stats[k] += 1;

} catch {stats[k] = 1;

}}

static public void AtExit(Context c){

Console.WriteLine ("\n\nMethod Call Stats : ");

List<KeyValuePair<string, int>> list =new List<KeyValuePair<string, int>>(stats);

list.Sort(delegate(KeyValuePair<string, int> firstPair,

KeyValuePair<string, int> nextPair){

return nextPair.Value.CompareTo(firstPair.Value);}

);

Console.WriteLine("# Calls\t\tMethod");foreach (KeyValuePair<string, int> kvp in list) {

Console.WriteLine("{1}\t\t{0}", kvp.Key, kvp.Value);}

}}

Esta clase define básicamente dos callbacks. Uno es ACall, que cumple la función de registrarcualquier llamada a un método en un diccionario, y otro, AtExit que ordena el diccionariopor cantidad de llamadas y luego imprime la lista en pantalla. El método MethodSignatureque no se muestra simplemente sirve para hacer un string más legible en la consola.

Una vez definido nuestros callbacks podemos pasar a utilizarlo en alguna clase :

public class Normal{

public Normal(){

int n = Fact(5);Console.WriteLine ( "Fact(5) = " + n.ToString());

}

6.1. .NET API : Definición de Joinpoints 53

Page 62: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

public int Fact(int n){

if (n == 0) return 1;return n * Fact(n-1);

}

public void Metodo1(string s){

System.Console.WriteLine( "Hola " + s);}

static private void Weave(){

MethodInfo callback;

// Define some weaving stuffWeaver w = Weaver.Instance;

// Get the callbackType aspectType = Type.GetType( "Aspect" );

callback = aspectType.GetMethod( "ACall" );

w.Subscribe(callback, "*" , "Normal" , "*" , true, false);

callback = aspectType.GetMethod( "AtExit" );w.SubscribeOnExit(callback);

}

static private void Unweave(){

MethodInfo callback;

// Define some weaving stuffWeaver w = Weaver.Instance;

Type aspectType = Type.GetType( "Aspect" );

callback = aspectType.GetMethod( "ACall" );w.UnSubscribe(callback, false);

}

static public void Main(string [] args){

Weave();

// Do some callsNormal n = new Normal();

// Stop recording method callsUnweave();

n.Fact(5);

object[] p = new object[] { "Mundo!" };

Type thisType = n.GetType();MethodInfo theMethod = thisType.GetMethod( "Metodo1" );

54 Capítulo 6. Solución adoptada

Page 63: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

theMethod.Invoke(n, p);}

}

Al ejecutar el programa de ejemplo obtenemos la siguiente salida por la consola :

[mono] ~/src/tesis/ejemplos @ mono Manual.exe[CS] Mono.Weaving.Weaver initialized ok[JIT] New pattern Defined : * Normal:* (*) before in 0x9021d50[JIT] New pattern Defined : * Weaving.Weaver:Exit (*) before in

0x9021d50Fact(5) = 120[JIT] Method found. Removing pattern * Normal:* (*)[JIT] Cleaning up cache for void Normal:Unweave ()[JIT] Cleaning up cache for void Normal:.ctor ()[JIT] Cleaning up cache for int Normal:Fact (int)Hola Mundo!

Method Call Stats :# Calls Method6 public Int32 Normal:Fact(Int32 n)1 private static Void Normal:Unweave()1 public Normal Normal:.ctor()Exiting

Como puede observarse, el callback se ejecutó correctamente al finalizar el programa, pro-duciendo el efecto deseado.

6.2 Detalles de Implementación

En esta sección analizaremos varios puntos de cómo fue implementada la API en su trasfondo,pudiendo ver en detalle la lógica y el orden de ejecución de los componentes más importantesdel Weaver.

Estas implementaciones interactúan con la API que se describió en el punto anterior utilizandolas ya mencionadas Mono Internal Calls.

6.2.1 Joinpoint Metadata

La estructura básica que va a manejar el JIT internamente es el Joinpoint, que es la definiciónde un punto en la ejecución de un programa.

Internamente, la metadata de los Joinpoints definidos se alojan en dos listas: una para los fil-tros ‘antes de’ (Before) y otra para los ‘después de’ (after). Cada entrada está definida por lasiguiente estructura :

typedef struct _Pattern {// Method to call when a Match occours

6.2. Detalles de Implementación 55

Page 64: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

MonoMethod *method;// String representation of the patterngchar *pattern;// Precompiled regex for pattern matchingGPatternSpec *regex;// Instance variables where call the methodGHashTable *instances;

} Pattern;

Donde :

1. Method: Es el puntero al método en C# que atiende el Advice. En la definición de Join-ponts sería parámetro callback que pasamos en la llamada a Subscribe.

2. Pattern: La representación literal que se va a usar (meramente para poder imprimir porconsola, ya que no es usada una vez generada la regex) para hacer pattern matching.

3. regex: expresión regular pre-compilada para acelerar la operación de matching.

4. instances: instancias, según match sobre la que se ejecuta el method. Como se explicóanteriormente, si method es static, no existitría ninguna instancia.

Para cada Joinpoint definido esta estructura es guardada en una lista de manera de poderluego recorrerla. La implementación que define un Joinpoint (por efecto de una llamada aWeaver.Subscribe) en el JIT es la siguiente :

mono_joinpoints_define_pattern (MonoObject *self,MonoReflectionMethod *rmethod, MonoString *rp, MonoString *cp,MonoString *mp, MonoArray *params, int any_args, int flags)

{Pattern *pattern;MonoMethod *method = rmethod->method;gchar *str_rp = mono_string_to_utf8 (rp);gchar *str_cp = mono_string_to_utf8 (cp);gchar *str_mp = mono_string_to_utf8 (mp);gchar *str_pp = create_params_string (params);gchar *str;

/* */str = g_strdup_printf ( "%s %s:%s (%s%s)" , str_rp, str_cp, str_mp, str_pp,

any_args? "*" : "" );

g_free (str_rp);g_free (str_cp);g_free (str_mp);g_free (str_pp);

pattern = g_new0(Pattern, 1);pattern->method = method;pattern->pattern = str;pattern->regex = g_pattern_spec_new (str);pattern->instances = g_hash_table_new (g_str_hash, g_direct_equal);

if (flags) {after_patterns = g_list_prepend (after_patterns, pattern);

} else {before_patterns = g_list_prepend (before_patterns, pattern);

56 Capítulo 6. Solución adoptada

Page 65: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

}

g_print ( "[JIT] New pattern Defined : %s %s in %p\n" , pattern->pattern,

flags ? "after" : "before" , flags ? after_patterns : before_patterns);}

Como se puede observar lo que se realiza es la generación de un string que representa laexpresión regular que será luego evaluada. La forma en que este string se forma noes caprichosa y tiene que ver con cómo se puede leer dentro del JIT, qué método se está ejecu-tando en un cierto momento para así hacer la evaluación. Para el ejemplo de la sección anteriorde ToString, el patrón utilizado para el matching quedaría como :

"string *:ToString (*)"

6.2.2 Concordancia mediante patrones y firma de los métodos

Para realizar la pattern matching se decidió utilizar el Glob-style pattern matching que proveela biblioteca glib por tres razones fundamentales :

Es lo suficientemente potente para el caso de uso que requiere esta implementación.Podría llegar a utilizarse alguna biblioteca PCRE (Perl-style matching) en el futuro sifuera necesario.

La biblioteca glib ya es utilizada por Mono, evitando dependencias extras

La implementación es lo suficientemente madura para no presentar problemas de perfor-mance.

Para mejorar el rendimiento, y por consecuencia el tiempo de respuesta del match-ing, el patrón es pre-compilado y para ello es que se puede observar en el código demono_joinpoints_define_pattern la sentencia :

pattern->regex = g_pattern_spec_new (str);

Lo anterior precompila y almacena el patrón en la metadata del Joinpoint. Más adelante, durantela ejecución del programa, se utilizará este campo a fin de verificar si un método cumple con lacondición.

El trabajo de analizar todos los patrones definidos en el JIT es realizado por el métodomono_joinpoint_match que acepta como parámetro un string que representa la llama-da a un método y un booleano que indica si debemos mirar en las definiciones para ‘antes de’o ‘después de’.

Para cada patrón definido se analiza la expresión compilada del patrón con el string pasado. Sihay una coincidencia se crea una instancia de una estructura de dato Joinpoint materializandoasí el Shadow e indicandole al JIT que tenemos un nuevo Joinpoint. Una vez que obtenemostoda la lista de cuales son las coincidencias, retornamos :

GList *mono_joinpoint_match (gchar *str, int flag){

GList *iter;

6.2. Detalles de Implementación 57

Page 66: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

GList *ret = NULL;gint str_len;

if (cache_get(str, flag, &ret)) {return ret;

}

str_len = strlen (str);

iter = flag ? after_patterns : before_patterns;while (iter) {

Pattern *pattern = (Pattern *)iter->data;

if (g_pattern_match (pattern->regex, str_len, str, NULL)) {JoinPoint *jp =

mono_joinpoint_new (pattern->method, pattern->instances);

ret = g_list_prepend (ret, jp);}

iter = iter->next;}

g_hash_table_insert (flag ? after_patterns_cache : before_patterns_cache,str, ret);

return ret;}

Como puede observarse, además de la lógica descripta anteriormente, se ha implementado unmecanismo de caché, utilizando un Hash table sobre la firma del método a interceptar. Estopermite que luego de determinar todos los Joinpoints existentes para una llamada dada, lasbúsquedas siguientes puedan ser realizadas en O(1).

La primera búsqueda, al tener que recorrer el hash completo, será en el peor de los casos O(n),siendo n la cantidad de Joinpoint definidas en el Weaver. Esta condición se dará cada vez que elcaché sea invalidado ya que la llamada estará en la misma situación que al inicio del programa.

Cabe destacar que la implementación contempla además el caso de un método que no tengadefinido ningún Joinpoint. Esto se logra gracias a que el Hash table que se utiliza paraguardar el cache es capaz de diferenciar la inexistencia de una clave que guarda un valor nulo.De esta forma, el rendimiento estandar de un programa que no utiliza ninguna de las caracterís-ticas agregadas no se verá afectado.

6.2.3 Eliminación de Joinpoints

Eliminar un Joinpoint es una tarea un poco más complicada, principalmente debido al uso delcaché que se utiliza durante la ejecución.

En primer lugar, dado un callback que estamos eliminando del Weaver, lo que debemos haceres recorrer todos los Joinpoint definidos y fijarnos en cuáles se utiliza el callback. Al tenerlosalmacenados en una lista los datos, el orden de ejecución será O(n) :

58 Capítulo 6. Solución adoptada

Page 67: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

voidmono_joinpoints_remove_callback (MonoObject *self,

MonoReflectionMethod *rmethod, int flags){

GList *iter = flags ? after_patterns : before_patterns;

while (iter) {Pattern *pattern = (Pattern *)iter->data;

if (pattern->method == rmethod->method) {GHashTable *cache;

g_print ( "[JIT] Method found. Removing pattern %s\n" ,pattern->pattern);

// Remove cache occurences of this patterncache = flags ? after_patterns_cache : before_patterns_cache;g_hash_table_foreach_remove (cache,

internal_joinpoints_foreach_remove_cache, pattern);

if (flags) {after_patterns = g_list_delete_link (after_patterns, iter);

} else {before_patterns = g_list_delete_link (before_patterns, iter);

}}

iter = iter->next;}

}

Una vez encontrado el callback a eliminar, lo que tenemos que hacer es limpiar el caché, locual es una tarea un poco más compleja. Como podemos recordar del punto anterior, el cachese guarda utilizando como clave la firma del método cacheado. Es por eso que, para poderdeterminar cuáles de esas claves ya no son válidas, debemos ejecutar la expresión regular delJoinpoint que estamos eliminando contra cada una de las claves del hash de caché. Esta es unatarea costosa, haciendo que sea O(m), con m la cantidad de métodos ejecutados y guardados enel cache. Es importante acentuar que un método que no tiene callbacks también es guardadoen el cache, por lo que el número m es de esperar que sea grande y por lo tanto eliminar uncallback es lento.

La lógica que realiza la limpieza definitiva del caché utilizag_hash_table_foreach_remove. Si el método que llama retorna TRUE eliminala clave/valor del hash, y si retorna FALSE la deja, haciendo que la implementación searelativamente simple :

static gbooleaninternal_joinpoints_foreach_remove_cache (gpointer key, gpointer value,

gpointer user_data){

Pattern *pattern = (Pattern *)user_data;gchar *str = (gchar *)key;

if (g_pattern_match (pattern->regex, strlen (key), key, NULL)) {g_print ( "[JIT] Cleaning up cache for %s\n" , str);

6.2. Detalles de Implementación 59

Page 68: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

return TRUE;}

return FALSE;}

6.3 El Entrelazador

Finalmente llegamos al componente crítico de nuestro trabajo, el entrelazador propiamentedicho. Es importante resaltar que hasta este momento estuvimos construyendo herramientaspara soportar las implementaciones que veremos a continuación.

El entrelazador se compone de dos métodos fundamentales :

mono_weaver_run_before : ejecuta los callbacks antes de la ejecución de un méto-do dado

mono_weaver_run_after : ejecuta los callbacks después de la ejecución de unmétodo dado

Ambos métodos son prácticamente iguales. La primer sutil diferencia entre ambos casos, esque en el segundo se analiza el stack a partir del EBP para poder extraer el valor de retorno dela llamada al método original, a fin de poder pasarlo al callback en el contexto. La segunda, esen qué lista deben buscar los Joinpoints a evaluar.

Dadas estas particularidades, la implementaciones de ambos métodos terminan llamando a unmétodo común, que se llama mono_weaver_run_full y se muestra a continuación :

static voidmono_weaver_run_full (MonoMethod *method, char *ebp, int flags,

MonoObject *return_value){

GList *matches;

if (method->full_signature == NULL) {gchar *name = mono_method_full_name (method, TRUE);gchar *return_type =

mono_type_full_name (mono_method_signature (method)->ret);

method->full_signature = g_strdup_printf ( "%s %s" , return_type, name);

g_free (name);g_free (return_type);

}

matches = mono_joinpoint_match (method->full_signature, flags);if (matches) {

GList *tmp = matches;while (tmp) {

mono_weaver_run_joinpoint (method, ebp, flags, return_value,(JoinPoint *)tmp->data);

tmp = tmp->next;}

60 Capítulo 6. Solución adoptada

Page 69: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

}}

La lógica de este método es bastante simple. Lo primero que se verifica es si el MonoMethodque se va a ejecutar (que representa al método interceptado) ya tiene generada la firma enformato string. Esto se hace para mejorar la velocidad de ejecución y no tener que generarla unay otra vez. Como contrapartida, hace que la metadata del método en memoria sea ligeramentemayor.

Una vez que sabemos con exactitud la firma del método a evaluar, simplemente pedimos la listade coincidencias, es decir, qué Joinpoints han definido que deben ejecutarse antes o después queeste método. Nótese que la implementación es sencilla ya que la lógica de matching y búsquedase encuentra ya definida en las secciones anteriores, así como también las técnicas de caching.

Luego nos queda ejecutar cada callback, si existe alguno. El proceso de ejecutar un callback esun poco más complejo, y lo veremos a continuación.

El encargado de ejecutar un callback es el método mono_weaver_run_joinpoint, cuyaimplementación completa se encuentra en el Anexo 1.

Antes de entrar en el detalle del código de dicho método, veamos cuáles son sus responsabili-dades, a fin de que en las siguientes sub-secciones podamos explicar como se ha resuelto cadaparte del problema en detalle.

En resumidas cuentas, los pasos que debemos realizar para poder ejecutar un callback que fuedefinido por el programador son :

Ver cuántos parámetros necesita el callback y extraerlos del stack de la llamada original

Si el callback es un método no-estático, debemos crear una instancia sobre quién eje-cutarlo. Esta instancia debe permanecer viva y ser la misma en cada llamada al mismométodo interceptado

Y finalmente ejecutar el callback

6.3.1 Extracción de los parámetros

Cuando hablamos de extraer parámetros nos referimos a determinar cuántos parámetros esperarecibir el callback que se va a ejecutar en este momento y llenar esos “espacios” con los valoresque iba a recibir la llamada original. Por ejemplo, si nuestro método original que se va a ejecutares :

void Swap(1, 2);

Y nuestro callback tiene una firma como :

void Callback(int i, Context c)

Lo que estamos esperando es que el callback va a recibir dos valores: un entero que, va aser sacado de la llamada original, y una instancia de una clase, que el entrelazador les pasa atodos los callbacks con información del contexto en el que se está ejecutando. El orden de losparámetros esperados es siempre de izquierda a derecha, respetando el orden. En el ejemplo

6.3. El Entrelazador 61

Page 70: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

anterior en la llamada a Callback la variable i recibiría el valor 1. El valor 2 de la llamadaa Swap será completamente ignorada en la llamada del callback.

Entonces, la cantidad de parámetros de los originales que recibe un callback es siempre lacantidad de parámetros del callback menos uno (el Context)) :

guint callback_arguments;callback_arguments = mono_method_signature (callback)->param_count - 1;

Sabiendo ahora que son N parámetros, podemos empezar a revisar el stack de ejecución paradeterminar los valores reales que el callback debe recibir.

Como ya hemos visto anteriormente en Frame y Registro EBP, tenemos los datos necesariosdel stack mediante el registro EBP de donde comenzar a leer los valores. Dichos valores estánuno a continuación del otro en la memoria, pero el tema es: ¿cómo podemos distinguir dondetermina uno y donde comienza el siguiente?. La respuesta a priori es inquietante: no podemossaberlo.

Es ahí donde entra en juego el conocimiento del tipo de dato de cada parámetro, ya que sinconocer de qué tipo es, no podemos conocer cuánto ocupa en memoria. Por ejemplo, un Int32ocupa 32 bits, un Int64, 64 bits, un String está representado por una referencia que ocupa 32bits,etc.

Por fortuna, Mono nos provee de una manera rápida y eficiente para conocer el tipo de dato,y por consiguiente, podemos inferir el tamaño a partir de ahí. Para determinar el tipo de datosimplemente debemos llamar a:

MonoType *mono_type = mono_method_signature (method)->params [i];mono_type_get_underlying_type (mono_type)->type

En el segundo caso, el atributo type es una constante. Todos los valores posibles estándefinidos en el archivo mono/metadata/blob.h.

Es por eso que, como se puede observar en el Anexo 2, existe un gran switch que para cadaparámetro fija su tamaño y en base a eso va moviéndose por el stack :

switch (mono_type_get_underlying_type (type)->type) {case MONO_TYPE_I:case MONO_TYPE_U:

ret[i] = arg_in_stack_slot(cpos, gpointer *);break;case MONO_TYPE_BOOLEAN:case MONO_TYPE_CHAR:case MONO_TYPE_I1:case MONO_TYPE_U1:

ret[i] = arg_in_stack_slot(cpos, gint8);break;

... continua ...

En muchos casos los tipos de datos comparten el tamaño, por lo que es posible agruparlos yreutilizar parte de las conversiones.

La llamada arg_in_stack_slot que podemos observar, es un macro que utiliza Monopara hacer frente al endian de la plataforma, y es utilizado solo por comodidad, ya que en x86el endian está bien establecido de entrada.

62 Capítulo 6. Solución adoptada

Page 71: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

6.3.2 Contexto de ejecución del callback

Como vimos en el punto anterior, todo callback que es ejecutado recibe como último parámetrouna instancia de la clase Context. El propósito es proporcionarle al programador datos sobre loque está pasando alrededor a fin de lograr lógicas más complejas.

En la implementación actual, básicamente, lo que se hace es instanciar una nueva clase en lamáquina virtual de la clase Context con los datos básicos definidos en la API y enviarla.

En un futuro, esta clase podría contener el Backtrace, estados de los hilos, contexto .NET ycualquier otro dato que pueda ser relevante.

Cuando se extraen los parámetros lo que se hace es instanciar una clase C# llamadaMono.Weaving.Context y pasarle los parámetros requeridos en el constructor. Esta claseluego de que no se necesita más, es dejada a merced del garbage collector para que libere lamemoria.

6.3.3 Ejecución del callback

Los callbacks son métodos, ya sean estáticos o no, y por lo tanto deben seguir las reglas delentorno al ser ejecutados.

Esto significa que cuando el callback no es estático, necesitamos una instancia sobre quienejecutar el método. Dicha instancia, además, no puede ser de cualquier clase, debe ser de laclase a la que pertenece el callback, o la máquina virtual rechazaría el intento de ejecución.

Ejecutar un método estático en Mono haciendo llamadas en C es trivial, simplemente basta contener el MonoMethod, que en nuestro caso es el callback y ya lo conocemos de antemano, ytener un array con los parámetros. Dadas estas condiciones pasamos a ejecutar :

mono_runtime_invoke (callback, NULL, args, NULL);

El segundo parámetro (NULL) le indica que no hay instancia y por ende el método es tratadocomo estático.

Para ejecutar un callback no-estático hay que trabajar un poco más. La idea de tener un callbackno estático es poder guardar algun dato aislado de otras llamadas, por ejemplo para un profiler.Si un callback es llamado dos veces sobre el mismo método interceptado, es razonable esperary desear que la instancia sobre la cual se ejecute el callback sea la misma, a fin de poder guardardatos entre las llamadas y que estos persistan en el tiempo.

Para lograr eso es necesario tener un pool de instancias. En nuestra implementación seutilizó un hash con la firma del método interceptado como clave.

Entonces, para poder detectar instancias o crear una nueva, simplemente tenemos la lógica delookup de un hash :

handle = g_hash_table_lookup (jp->instances, method->full_signature);if (!handle) {

// Create an object to handle joinpoints on this MethodSignatureobject = mono_object_new (mono_domain_get (), parent);mono_runtime_object_init (object);// Avoid GC

6.3. El Entrelazador 63

Page 72: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

handle = mono_gchandle_new(object, TRUE);g_hash_table_insert (jp->instances, method->full_signature, handle);

} else {object = mono_gchandle_get_target (handle);

}

La implementación actual utiliza unos métodos optimizados de Mono para manejo de instan-cias, de manera que el hash realmente solo guarda Integers que hacen referencia a instanciasdentro de un pool. Esto permite un manejo eficiente ya que las instancias que son eliminadas,en realidad pueden, ser reutilizadas si no fueron recolectadas por el garbage collector.

64 Capítulo 6. Solución adoptada

Page 73: Reificación de llamadas a métodos en Mono

CAPÍTULO 7

Pruebas

Uno de los puntos en los que se trató de hacer incapié durante la investigación para realizar estetrabajo no fue solamente en su viabilidad técnica, sino que también se le dio mucha importanciaa como influían las modificaciones en el ciclo normal de ejecución de una aplicación.

El punto más crítico que se tuvo en cuenta fue el tiempo de ejecución, buscando siempre opti-mizar este sobre otro parámetros, como por ejemplo el consumo de memoria.

Es normal hoy en día encontrar grandes cantidades de memoria en los ordenadores, y hastapermitirse expandirla a bajo costo. Pero el tiempo de procesamiento es más crítico, ya queinfluye en la experiencia del usuario final de manera directa y el recurso es más caro paramejorar.

Teniendo en cuenta estos puntos se diseñaron pequeños programas para comprobar que lostiempos de ejecución teóricos se alinean con la realidad.

7.1 Ejecución de un método con N Advices

En esta prueba lo que se trata de probar es que el tiempo de ejecución de un método es O(1), esdecir, que no depende de la cantidad de Joinpoints que pueda el programador haber definido.

Como se explicó anteriormente, esto es posible ya que luego de detectar los Joinpoints para unmétodo dado, estos son guardados en un caché, haciendo que en las sucesivas llamadas obtenereste dato sea instantáneo.

Para esta prueba, lo que se generó es un programa que empeza con un Advice y toma el tiempoque le lleva ejecutar un método. Luego genera un programa con dos Advices, luego con tres ...hasta llegar a los N Advices sobre el mismo método.

Así mismo, luego se procedió a comprobar cómo respondía el método cuando se realiza lasegunda llamada, para verificar efectivamente que el orden es constante.

Como último punto se hizo una corrida para verificar que un método sin callbacks asociados,pero sí registrados en el entrelazador, sigue los mismos parámetros que en el primer caso.

Los resultados de correr dicha prueba se pueden observar en la 7.1 en la página siguiente.

65

Page 74: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

0

200

400

600

800

1000

1200

0 200 400 600 800 1000

tiem

po [

nano s

ecs

]

# callbacks

con N callbackscon 0 callbacks

(a) Tiempo de ejecución de la primera llamada

0

2

4

6

8

10

0 200 400 600 800 1000

tiem

po [

nano s

ecs

]

# callbacks

con N callbackscon 0 callback

(b) Tiempo de ejecución de la segunda llamada

Figura 7.1: Ejecución de un (1) método con N Advices..

Como es de esperarse el tiempo se mantiene constante, validando el orden de ejecución espe-rado.

7.2 Agregar o Eliminar un Joinpoint

La operación de Agregar o Eliminar Joinpoints son similares ya que en ambos casos se debede lidear con el manejo del cache interno utilizado para hacelerar la operación de ejecución, espor eso que solamente analizaremos uno de ellos, el de Eliminar un Joinpoint, ya que la lógicase mantiene en ambos casos.

El proceso de eliminar un joinpoint, como se explicó, es complejo, porque no solo elimina elJoinpoint de una lista (lo cual es O(n)), sino que además debe limpiar el caché de todos los

66 Capítulo 7. Pruebas

Page 75: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

0

200000

400000

600000

800000

1e+06

0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000

tiem

po (

nano

seg

)

# metodos ejecutados

Figura 7.2: Eliminar un Joinpoint, habiendo ejecutado N métodos antes..

métodos que puedan tener a ese Joinpoint cacheado para una futura ejecución.

Limpiar el caché significa recorrer todos los métodos cacheados y sus firmas, comparándolascon la expresión regular del Joinpoint a eliminar. Si hay un match, entonces eliminamos esaentrada del caché, para que la próxima vez que se ejecute ese método recalcule sus Joinpoints.

Ahora, durante la vida de un programa se ejecutan M métodos diferentes (M >> n), por loque el orden esperado de ejecución al eliminar un Joinpoint es O(M), siendo M la cantidad demétodos que se han ejecutado. Esto pone en clara evidencia que esta metodología de cachingtal vez no sea la indicada para situaciones donde se espera eliminar Joinpoint de manera muyseguida.

Los resultados de correr dicha prueba se pueden observar en la 7.2.

Si bien no se observa una recta perfecta (debido a que durante las pruebas hay factores externosque modifican los tiempos de respuesta, desde el mismo sistema operativo hasta aplicacionescorriendo en paralelo) se ve una clara tendencia lineal al crecimiento del tiempo de ejecución amedida que la cantidad de métodos ejecutados antes de eliminar el Joinpoint crece.

7.3 Pruebas sintetizadas

Este es el juego de programas triviales, escritos con el único objetivo de ejercitar un área parti-cular y acotada de la solución implementada. El objetivo es ver que impacto tiene la existencia

7.3. Pruebas sintetizadas 67

Page 76: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

del Weaver, sin haber definido ningún callback en la ejecución de un programa, a fin de deter-minar que no habrá impacto evidente en el uso normal de la máquina virtual.

Los programas están basados en la prueba de nombre binary-trees de The Computer Lan-guage Benchmarks Game, una colección de 12 programas escritos en alrededor de 30 lenguajesde programación para comparar su eficiencia (medida en tiempo de ejecución, uso de memoriay cantidad de líneas de código) [SHO10]. Se han elegido algunos que son relevantes por laconstrucción que tienen y que ponen a prueba la implementacion en algún punto en particular.

7.3.1 binary-trees

Se seleccionó binary-trees por tener un alto nivel de llamadas reentrantes. El programasolo manipula árboles binarios, creándolos y recorriéndolos inmediatamente (no realiza ningúntrabajo útil). La traducción a C# fue realizada por Marek Safar y forma parte del conjuntoestandar del proyecto

Código fuente:

using System;

class BinaryTrees{

const int minDepth = 4;

public static void Main(String[] args){

int n = 0;if (args.Length > 0) n = Int32.Parse(args[0]);

int maxDepth = Math.Max(minDepth + 2, n);int stretchDepth = maxDepth + 1;

int check = (TreeNode.bottomUpTree(0,stretchDepth)).itemCheck();Console.WriteLine( "stretch tree of depth {0}\t check: {1}" ,

stretchDepth, check);

TreeNode longLivedTree = TreeNode.bottomUpTree(0,maxDepth);

for (int depth=minDepth; depth<=maxDepth; depth+=2) {int iterations = 1 << (maxDepth - depth + minDepth);

check = 0;for (int i=1; i<=iterations; i++){

check += (TreeNode.bottomUpTree(i,depth)).itemCheck();check += (TreeNode.bottomUpTree(-i,depth)).itemCheck();

}

Console.WriteLine( "{0}\t trees of depth {1}\t check: {2}" ,iterations*2, depth, check);

}

Console.WriteLine( "long lived tree of depth {0}\t check: {1}" ,maxDepth, longLivedTree.itemCheck());

}

68 Capítulo 7. Pruebas

Page 77: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

struct TreeNode{

class Next{

public TreeNode left, right;}

private Next next;private int item;

TreeNode(int item){

this.item = item;this.next = null;

}

internal static TreeNode bottomUpTree(int item, int depth){

if (depth>0) {return new TreeNode(bottomUpTree(2*item-1, depth-1), bottomUpTree(2*item, depth-1), item

);} else {

return new TreeNode(item);}

}

TreeNode(TreeNode left, TreeNode right, int item){

this.next = new Next ();this.next.left = left;this.next.right = right;this.item = item;

}

internal int itemCheck(){

// if necessary deallocate hereif (next==null) return item;else return item + next.left.itemCheck() - next.right.itemCheck();

}}

}

El programa se corrió sobre las dos versiones de la máquina virtual: la original sin ningunamodificación, y en la máquina virtual compilada con el parche de Weaving. Ambas versionesfueron compiladas en la misma máquina utilizando las mismas dependencias. En la figura 7.3en la página siguiente se pueden observar los resultados de la ejecución.

7.3. Pruebas sintetizadas 69

Page 78: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

0

20

40

60

80

100

120

140

160

180

4 6 8 10 12 14 16 18 20

sin weavercon weaver

Figura 7.3: Tiempo de ejecución de BinaryTrees.

7.3.2 pidigits

Se tomó este ejemplo pues su implementación utiliza DllImport para mapear llamadas a unabiblioteca en C, y se tratará de ver si el impacto del entrelazador es o no un problema en estetipo de situación.

La traducción a C# fue realizada por Miguel de Icaza y forma parte del conjunto estandar delproyecto.

Código fuente:

using System;using System.Text;using System.Runtime.InteropServices;

public class pidigits{

GmpInteger q = new GmpInteger (), r = new GmpInteger (), s =new GmpInteger (), t = new GmpInteger ();

GmpInteger u = new GmpInteger (), v = new GmpInteger (), w =new GmpInteger ();

int i;StringBuilder strBuf = new StringBuilder (40);int n;

pidigits (int n){

this.n = n;}

private void compose_r (int bq, int br, int bs, int bt){

u.mul (r, bs);r.mul (r, bq);

70 Capítulo 7. Pruebas

Page 79: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

v.mul (t, br);r.add (r, v);t.mul (t, bt);t.add (t, u);s.mul (s, bt);u.mul (q, bs);s.add (s, u);q.mul (q, bq);

}

/* Compose matrix with numbers on the left. */private void compose_l (int bq, int br, int bs, int bt){

r.mul (r, bt);u.mul (q, br);r.add (r, u);u.mul (t, bs);t.mul (t, bt);v.mul (s, br);t.add (t, v);s.mul (s, bq);s.add (s, u);q.mul (q, bq);

}

/* Extract one digit. */private int extract (int j){

u.mul (q, j);u.add (u, r);v.mul (s, j);v.add (v, t);w.div (u, v);return w.intValue ();

}

/* Print one digit. Returns 1 for the last digit. */private bool prdigit (int y){

strBuf.Append (y);if (++i % 10 == 0 || i == n)

{if (i % 10 != 0)for (int j = 10 - (i % 10); j > 0; j--)

{strBuf.Append ( " " );

}strBuf.Append ( "\t:" );strBuf.Append (i);Console.WriteLine (strBuf);strBuf = new StringBuilder (40);

}return i == n;

}

/* Generate successive digits of PI. */void Run ()

7.3. Pruebas sintetizadas 71

Page 80: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

{int k = 1;i = 0;q.set (1);r.set (0);s.set (0);t.set (1);for (;;){

int y = extract (3);if (y == extract (4)){

if (prdigit (y))return;

compose_r (10, -10 * y, 0, 1);}

else{

compose_l (k, 4 * k + 2, 0, 2 * k + 1);k++;

}}

}

public static void Main (String[]args){

pidigits m = new pidigits (Int32.Parse (args[0]));m.Run ();

}}

[StructLayout (LayoutKind.Sequential)]struct mpz_t{

public int _mp_alloc;public int _mp_size;public IntPtr ptr;

}

class GmpInteger{

// Public methods

public GmpInteger (){

mpz_init (ref pointer);}

public GmpInteger (int value){

mpz_set_si (ref pointer, value);}

public void set (int value){

mpz_set_si (ref pointer, value);

72 Capítulo 7. Pruebas

Page 81: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

}

public void mul (GmpInteger src, int val){

mpz_mul_si (ref pointer, ref src.pointer, val);}

public void add (GmpInteger op1, GmpInteger op2){

mpz_add (ref pointer, ref op1.pointer, ref op2.pointer);}

public void div (GmpInteger op1, GmpInteger op2){

mpz_tdiv_q (ref pointer, ref op1.pointer, ref op2.pointer);}

public int intValue (){

return mpz_get_si (ref pointer);}

public double doubleValue (){

return mpz_get_d (ref pointer);}

// Non public stuff

mpz_t pointer;

[DllImport ( "gmp" , EntryPoint = "__gmpz_init" )]extern static void mpz_init (ref mpz_t value);

[DllImport ( "gmp" , EntryPoint = "__gmpz_mul_si" )]extern static void mpz_mul_si (ref mpz_t dest, ref mpz_t src,

int val);

[DllImport ( "gmp" , EntryPoint = "__gmpz_add" )]extern static void mpz_add (ref mpz_t dest, ref mpz_t src,

ref mpz_t src2);

[DllImport ( "gmp" , EntryPoint = "__gmpz_tdiv_q" )]extern static void mpz_tdiv_q (ref mpz_t dest, ref mpz_t src,

ref mpz_t src2);

[DllImport ( "gmp" , EntryPoint = "__gmpz_set_si" )]extern static void mpz_set_si (ref mpz_t src, int value);

[DllImport ( "gmp" , EntryPoint = "__gmpz_get_si" )]extern static int mpz_get_si (ref mpz_t src);

[DllImport ( "gmp" , EntryPoint = "__gmpz_get_d" )]extern static double mpz_get_d (ref mpz_t src);

}

El programa se corrió sobre las dos versiones de la máquina virtual: la original sin ninguna

7.3. Pruebas sintetizadas 73

Page 82: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

0.24

0.26

0.28

0.3

0.32

0.34

0.36

0 20 40 60 80 100

sin weavercon weaver

Figura 7.4: Tiempo de ejecución de PiDigits.

modificación y en la máquina virtual compilada con el parche de Weaving. Ambas versionesfueron compiladas en la misma máquina utilizando las mismas dependencias. En la figura 7.4se pueden observar los resultados de la ejecución.

74 Capítulo 7. Pruebas

Page 83: Reificación de llamadas a métodos en Mono

CAPÍTULO 8

Conclusión

Durante el presente trabajo hemos realizado una recorrida por las diferentes implementacionesde lenguajes orientados a aspectos, sus ventajas y sus desventajas. Además, hemos realizadoun profundo análisis de cómo funciona Mono internamente y desglosado la JIT en diferentespartes.

En ese análisis se trató de estudiar las mejores opciones para lograr la reificación de métodosdentro de Mono, a fin de proveer a los programadores de una herramienta, no solo útil, sinotambién con buenos tiempos de respuesta y bajo consumo de memoria. Este último punto esimportante, ya que de no haber logrado tiempos de respuesta aceptables, la solución no seríapráctica.

En este sentido, las técnicas de catching aplicadas al ciclo de ejecución han demostrado serefectivas en las pruebas de campo, así como en la ejecución de aplicaciones complejas sobre lamáquina virtual modificada. La experiencia de usuario final no se ha visto afectada, lo cual esun resultado alentador.

Como punto pendiente queda obtener algún feedback de la comunidad sobre la utilidad o mejo-ras sobre esta implementación a fin de aportar al proyecto un camino alternativo útil a los finesprácticos.

8.1 Puntos pendientes, problemas y limitaciones

Si bien los objetivos de este trabajo han sido alcanzados con éxito, hay varias pequeñas mejorasque han quedado pendientes y algunos problemas y limitaciones conocidas. A continuación sedescribe cada una de ellos.

Inline Methods

Uno de los puntos que han quedado fuera es la optimización que hace la máquina virtual,haciendo que los métodos puedan ser puestos inline al ser compilados, lo que resultaen stacks de ejecución más cortos y suele dar una mejora importante en la performanceglobal de un programa.

75

Page 84: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

Esto no se ha tenido en cuenta, ya que debido a cómo se hace el caché de los Joinpointsno es posible tomarlo en cuenta en la implementación actual. Sería necesario reemplazarel caché actual por completo para darle soporte.

Por el momento todos los ejemplos y pruebas han de ser corridas con--optimize=-inline para evitar que el JIT haga un método inline y el patternmatching nunca se de.

Extender la Concordancia mediante patrones

El algoritmo utilizado hoy en día es simple (aunque efectivo), y deja algunas cosas útilesfuera.

Por ejemplo, no hay forma de especificar si queremos matchear a una clase ocualquier descendiente de ella, como por ejemplo AspectJ especifica con la nomenclaturaMiClase+. El + en este caso implica que es posible que una clase hija sea tomada encuenta. Así :

class OtraClase : public MiClase {}

debería ser tomada en cuenta al momento de aplicar un callback.

Mejorar la API

Hoy se utilizan string por simplicidad a la hora de suscribir un callback. Sería útil tal vezcontar con una jerarquía de ‘matchers’ que se encarguen de diferentes aspectos :

• Concidencia del nombre de una clase

• Concidencia parcial del nombre de una clase

• Coincidencia de una clase o sus hijos

• Coincidencia sobre el namespace de las clases

8.2 Trabajos futuros

En la sección Puntos pendientes, problemas y limitaciones se mencionan varios aspectos deeste trabajo que podrían verse beneficiados por trabajos futuros, sin embargo se trata en generalde pequeñas optimizaciones o mejoras de alcance muy limitado.

A continuación se recopilan varios otros aspectos identificados durante el desarrollo del pre-sente trabajo, pero que requieren un nivel de análisis y, potencialmente, de desarrollo mayor alos ya presentados en la sección mencionada.

Extender a otras arquitecturas

Como ya se dijo antes Mono está presente en un número importante de plataformas, nosolo en x86 que fue en la que se basó este trabajo.

Al ser cada arquitectura diferente entre sí, las consideraciones que el port debe tener encuenta diferentes aspectos sobre como trabaja a fin de poder replicar lo hecho para laelegida. Para poder realizar esta tarea será necesario relevar y estudiar :

• El assembler de la plataforma y el conjunto de instrucciones

76 Capítulo 8. Conclusión

Page 85: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

• Cómo funciona el stack

• De qué manera se compone el call stack y a dónde apunta el puntero EBP

Soportar AOT (Ahead of Time)

Una de las ventajas del JIT de Mono es que permite precompilar en un shared object elassembler de un programa dado, a fin de que la próxima ejecución esté libre del delaydel JIT.

Para poder realizar esta tarea es necesario guardar metadata adicional que hoy no seguarda, como los Joinpoints y los cache para que no sea necesario rearmar las listas encada ejecución y no sumar un delay extra.

Incluir mecanismos de seguridad

Actualmente cualquier clase, que no pertenezca al core de Mono, es posible de ser inter-ceptada por un callback en alguna llamada a un método, cosa que no es del todo deseable.

Por ejemplo, se podría implementar un callback para saltear la seguridad de alguna claseespecifica o manipular la respuesta a la validación de un certificado.

Es necesario proveer al Global Assembly Cache de algún mecanismo por el cual blo-quear ciertas clases de ser interceptadas y que la máquina virtual sea capaz de seguir esainformación al pie de la letra.

8.2. Trabajos futuros 77

Page 86: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

78 Capítulo 8. Conclusión

Page 87: Reificación de llamadas a métodos en Mono

CAPÍTULO 9

Anexos

9.1 Anexo 1

static voidmono_weaver_run_joinpoint(MonoMethod *method, char *ebp, int flags,MonoObject *return_value, JoinPoint *jp)

{void *args[100];void *this;MonoObject *object = NULL;MonoMethod *callback = jp->method;guint callback_arguments;

callback_arguments = mono_method_signature (callback)->param_count - 1;if (callback_arguments > 0)

extract_method_params (method, ebp, callback_arguments, args, &this);args[callback_arguments] = create_context (this, flags, return_value,

mono_method_get_object (mono_domain_get (), method, method->klass));

// We need to detect the instance where to run the method.// If the method is STATIC, we don’t need an objectif (!(callback->flags & METHOD_ATTRIBUTE_STATIC)) {

guint32 handle;MonoClass *k = mono_class_from_mono_type (&callback->klass->byval_arg);

handle = g_hash_table_lookup (jp->instances, method->full_signature);if (!handle) {

// Create an object to handle joinpoints on this MethodSignatureobject = mono_object_new (mono_domain_get (), k);mono_runtime_object_init (object);// Avoid GChandle = mono_gchandle_new(object, TRUE);g_hash_table_insert (jp->instances, method->full_signature, handle);

} else {object = mono_gchandle_get_target (handle);

}}

mono_runtime_invoke (callback, object, args, NULL);

79

Page 88: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

}

9.2 Anexo 2

static voidextract_method_params (MonoMethod *method, char *ebp, guint count,

void **ret, void **ret_this){

int i;MonoJitArgumentInfo *arg_info;MonoMethodSignature *sig;

if (!ebp) {return;

}

sig = mono_method_signature (method);

arg_info = alloca (sizeof (MonoJitArgumentInfo) * (sig->param_count + 1));

mono_arch_get_argument_info (sig, sig->param_count, arg_info);

if (mono_method_signature (method)->hasthis) {gpointer *this = (gpointer *)(ebp + arg_info [0].offset);if (method->klass->valuetype) {

g_error ( " [JIT] ValueType not supported \n " );//printf ("value:%p, ", *arg_in_stack_slot(this, gpointer *));

} else {MonoObject *o;o = *arg_in_stack_slot(this, MonoObject *);

(*ret_this) = o;}

}

assert (mono_method_signature (method)->param_count <= count);

for (i = 0; i < count; ++i) {gpointer *cpos = (gpointer *)(ebp + arg_info [i + 1].offset);MonoType *type = mono_method_signature (method)->params [i];

if (type->byref) {ret[i] = arg_in_stack_slot(cpos, gpointer *);

} else {switch (mono_type_get_underlying_type (type)->type) {

case MONO_TYPE_I:case MONO_TYPE_U:

ret[i] = arg_in_stack_slot(cpos, gpointer *);break;case MONO_TYPE_BOOLEAN:case MONO_TYPE_CHAR:case MONO_TYPE_I1:case MONO_TYPE_U1:

ret[i] = arg_in_stack_slot(cpos, gint8);

80 Capítulo 9. Anexos

Page 89: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

break;case MONO_TYPE_I2:case MONO_TYPE_U2:

ret[i] = arg_in_stack_slot(cpos, gint16);break;case MONO_TYPE_I4:case MONO_TYPE_U4:

ret[i] = arg_in_stack_slot(cpos, int);break;case MONO_TYPE_STRING: {

MonoString *s = *arg_in_stack_slot(cpos, MonoString *);if (s) {

g_assert (((MonoObject *)s)->vtable->klass ==mono_defaults.string_class);

ret[i] = s;} else

ret[i] = NULL;break;

}case MONO_TYPE_CLASS:case MONO_TYPE_OBJECT: {

ret[i] = *arg_in_stack_slot(cpos, MonoObject *);break;

}case MONO_TYPE_PTR:case MONO_TYPE_FNPTR:case MONO_TYPE_ARRAY:case MONO_TYPE_SZARRAY:

ret[i] = *arg_in_stack_slot(cpos, gpointer);break;case MONO_TYPE_I8:case MONO_TYPE_U8:

ret[i] = arg_in_stack_slot(cpos, gint64);break;case MONO_TYPE_R4:

ret[i] = arg_in_stack_slot(cpos, float);break;case MONO_TYPE_R8:

ret[i] = arg_in_stack_slot(cpos, double);break;case MONO_TYPE_VALUETYPE:

printf( " TODO : MONO_TYPE_VALUETYPE \n " );/*printf ("[");for (j = 0; j < size; j++)

printf ("%02x,", *((guint8*)cpos +j));printf ("], ");*/break;

default:printf ( " XX, " );

}}

}}

9.2. Anexo 2 81

Page 90: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

9.3 Anexo 3

static MonoObject *create_context(void *instance, int position,

MonoObject *returned, MonoReflectionMethod *method){

void *args[4];MonoObject *context;MonoClass *klass = mono_joinpoint_context_class ();MonoMethodDesc* desc = mono_method_desc_new (

" :.ctor(CallPosition,object,object,MethodInfo) " ,FALSE);

MonoMethod* ctor_method = mono_method_desc_search_in_class (desc, klass);mono_method_desc_free (desc);

g_assert (ctor_method);

args[0] = &position;args[1] = instance;args[2] = returned;args[3] = method;

context = mono_object_new (mono_domain_get (), klass);mono_runtime_invoke (ctor_method, context, args, NULL);

return context;}

82 Capítulo 9. Anexos

Page 91: Reificación de llamadas a métodos en Mono

Bibliografía

[BGK01] Mathieu Braem, Kris Gybels, Andy Kellens, Wim Vanderperren. InducingEvolution-Robust Pointcuts.System and Software Engineering Lab, Vrije Univer-siteit Brussel.

[BKIP08] Serge Lidin. Inside Microsoft .Net IL Assembler. Microsoft Press, 2002. ISBN0735615470.

[BRAL] Brian de Alwis, Gregor Kiczales. Apostle: A Simple Incremental Weaver for a Dy-namic Aspect Language.University of British Columbia, Canada. 2003.

[DWEB] Mono Project. http://www.mono-project.com/Start 1. Novell.2011.

[HH01] Erik Hilsdale, Jim Hugunin. Advice Weaving in AspectJ. AOSD. 2004.

[HM01] Michael Haupt. Virtual Machine Support for Aspect-Oriented Programming Lan-guages. Octubre 2005.

[HM02] Michael Haupt, Mira Mezini. Virtual Machine Support for Aspects with AdviceInstance Tables.Software Technology Group, Darmstadt University of Technology.2004

[JC01] Andrew Jackson, Siobhán Clarke. SourceWeave.NET: Cross-Language Aspect-Oriented Programming.Distributed Systems Group, Department of Computer Sci-ence, Trinity College Dublin, Irelan. 2004.

[JOLI96] Krzysztof Cwalina, Brad Abrams. Framework Design Guidelines: Conventions, Id-ioms, andPatterns for Reusable .NET Libraries (2nd Edition). Addison-Wesley Pro-fessional, 2008.ISBN 0321545613.

[JRK01] JRockit JVM Support For AOP, Part 1 2.2005

1http://www.mono-project.com/Start2http://www.oracle.com/technetwork/articles/entarch/jvm-aop-1-099446.html

83

Page 92: Reificación de llamadas a métodos en Mono

Reificación de llamadas a métodos en Mono

[JRK02] JRockit JVM Support For AOP, Part 2 3.2005

[KH01] Howard Kim. AspectC#: An AOSD implementation for C#. Trinity College Dublin.2002

[PAP1] Nicolás Paez. Utilización de programación orientada a aspectos en aplicaciones en-terpriseUniversidad de Buenos Aires, Argentina 2007

[RFMT1] Ricardo Markiewicz. Generando assembler 4.2008.

[RFMT2] Ricardo Markiewicz. Pattern matching made easy 5.2008.

[RFMT3] Ricardo Markiewicz. JoinPoint Language Definition 6.2009.

[SH01] Maximilian Stoerzer, Stefan Hanenberg. A Classification of Pointcut LanguageConstructs.University of Passau, University of Duisburg-Essen. Germany 2005.

[SHO10] Brent Fulgham. The Computer Language Benchmarks Game 7.2004-2010.

[UMCS] Bill Frakes. Design for Reuse and Object Oriented Reuse Methods 8.1995.

[WOOL] Yoshiki Sato. A Study of Dynamic Weaving for Aspect-Oriented Programming-Tokyo Institute of Technology. 2005

3http://www.oracle.com/technetwork/articles/entarch/jvm-aop-22-088250.html4http://www.gazer.com.ar/2008/11/18/generando-assembler/5http://www.gazer.com.ar/2008/11/24/pattern-matching-made-easy/6http://www.gazer.com.ar/2009/06/18/joinpoint-language-definition/7http://shootout.alioth.debian.org/8http://www.umcs.maine.edu/~ftp/wisr/SEN-pap/node1.html

84 Bibliografía