ATENCIÓN: Artículo a medio escribir.
Este artículo pertenece a la serie de artículos sobre metaprogramación.

Uno de los puntos fuertes de D, es la metaprogramación.

La filosofía de D es tener un solo lenguaje para tanto la generación de código, como para la programación en sí misma.

El preprocesador de C/C++ introducía muchos problemas, así que en D, Walter decidió no tener un preprocesador a parte. D sería el propio preprocesador.

  • Soporta templates como forma de abarcar la programación genérica (bastante más potente que los generics de Java o C#)
  • Soporta compilación condicional: version, debug, static assert, static if
  • Soporta también ejecución de código en tiempo de compilación como funciones puras normales (en c++ la ejecución en tiempo de compilación se hace mediante templates, y es bastante engorroso).
  • Y soporta generación de código mediante mixins e inyección de declaraciones en cualquier ámbito mediante template mixins (una forma evolucionada, pero con otros problema que las partial classes de C#).

Templates:

En D

Casos prácticos:

La metaprogramación en D, me ha ayudado una barbaridad en el emulador de PSP. Me ha ayudado a tener un emulador sin redundancias tristes, rápido y con mucha magia.

Filosofía DRY total sin sacrificar rendimiento.

Creación de bit slices para decodificación de parámetros en instrucciones

Crear funciones optimizadas para hacer operaciones de bits

¡Un operador módulo tiene que ser muy lento! No si se hace en tiempo de compilación:

}
T extractEnum(T, uint displacement = 0)() {  
return cast(T)(((param24 >> displacement) & minMask(T.max)) % (T.max + 1));  
}  

}

Generador de valor/máscara a partir de una defición de instrucción en condiciones

http://code.google.com/p/pspemu/source/browse/trunk/src/pspemu/core/cpu/tables/Table.d?r=315

ID(“add”,    VM(“000000:rs:rt:rd:00000:100000”), “%d, %s, %t”, ADDR_TYPE_NONE, 0),

El VM(“definición”) se convierte internamente en:

ValueMask(
    value=0b_000000_00000_00000_00000_00000_100000,
    mask=0b_111111_00000_00000_00000_11111_111111,
)

ValueMask(value=0x00000020, mask=0xFC0007FF)

Para saber si una instrucción coincide hay que hacer un:
((instruction & mask) == (value & mask))

así que en vez de poner:
VM(0x00000020, 0xFC0007FF)

al hacer uso de la metaprogramación, yo me limito a colocar:
VM(“000000:rs:rt:rd:00000:100000”)

Que creo que queda mucho más claro y es menos propenso a errores. Te indica los registros que se usan, su posición y la parte constante.

Generación de decodificadores genéricos óptimos, sencilla, sin redundancias y sin posibilidad de error

http://code.google.com/p/pspemu/source/browse/trunk/src/pspemu/core/cpu/tables/SwitchGen.d?r=313

Para una decodificación óptima de instrucciones en tiempo real, se hace uso de tablas y funciones o switches. Necesitas una tabla por cada bloque de instrucciones agrupados. Y esas tablas se pueden usar en varios sitios: intérprete, recompilador dinámico, recompilador dinámico en detección de saltos, desensamblador…

Generación de delegados puente para la llamada de funciones nativas a partir de llamadas desde el guest

Las llamadas al sistema operativo, y en mi caso a los métodos HLE, se hacen mediante syscalls. En estos métodos se dispone de los registros registros y de la memoria. Teniendo un método algo así (por poner un ejemplo, ya que en la implementación puede diferir):

}
void sceDisplaySetFrameBuf(HleThread hleThread, Registers registers, Memory memory) {  
    uint topaddr = registers.R[4];  
    uint bufferwidth= registers.R[5];  
    PspDisplayPixelFormats pixelformat = cast(PspDisplayPixelFormats)registers.R[6];  
    PspDisplaySetBufSync sync= cast(PspDisplaySetBufSync)registers.R[7];  
    uint returnValue;  
    {  
        returnValue = sceDisplaySetFrameBuf_Impl(topaddr, bufferwidth, pixelformat, sync) {  
    }  
    // Ejecución de método  
    registers.R[1] = returnValue;  
}  

}

Y yo me dije a mí mismo: ¿voy a tener que hacer esto para todas las funciones de todas las librerías HLE que haga? ¿Voy a tener que extraer a mano todos los parámetros de los registros y la memoria y luego setear el registro de retorno a mano?
Y la respuesta fue: NO.
Y ahí es donde aparece la “pseudo-reflexión” de D (cuando es en tiempo de compilación no se llama reflexión) y la metaprogramación.

Mediante funciones puras que generan cadenas con código y mixins, puedo generar delegados que analicen en tiempo de compilación los parámetros de la función y generen una función “puente” con la decodificación de parámetros, la llamada a la función y setee el valor de retorno. Básicamente genera un código que hace exáctamente lo mismo que el código de ejemplo que he puesto cuando se le pasa la función que ya tiene los parámetros que tocan

// Con este código generamos la función.
HleModuleMethodBridgeGenerator.getDelegate!(sceDisplaySetFrameBuf_Impl);

Cosas que echo en falta

Si D soportase atributos/anotaciones personalizadas para métodos y clases, podría evitarme