Empecé a agregar cierres (lambdas) a mi lenguaje que usa LLVM como back-end. Los he implementado para casos simples en los que siempre pueden incluirse, es decir, no es necesario generar el código para la definición de cierre, ya que está inlineado cuando se usa.¿Cómo implementar de manera eficiente cierres en LLVM IR?
Pero cómo generar el código para un cierre en caso de que el cierre no esté siempre en línea (por ejemplo, se pasa a otra función que no está en línea). Preferiblemente, a los sitios de llamadas no les debería importar si se les pasan funciones regulares o cierres y los llamarían funciones normales.
Podría generar una función con un nombre sintético, pero tendría que tomar el entorno de referencia como un argumento adicional y esa función no podría pasarse a otra función que no sabe acerca del argumento adicional necesario.
He pensado en una posible solución utilizando los intrínsecos de trampolín de LLVM, que "suprimen" un único parámetro de una función, devolviendo un puntero a una función de trampolín que toma un parámetro menos. En este caso, si la función generada para el cierre tomó el entorno de referencia como el primer parámetro, podría suprimirlo y recuperar una función que toma exactamente tantos parámetros como declara el cierre. ¿Esto es factible? ¿Eficiente? ¿Hay mejores soluciones?
ejemplo Código:
def applyFunctionTo(value: Int, f: (Int) -> Int) = f(value)
def main() = {
val m := 4;
val n := 5;
val lambda := { (x: Int) => x + m + n };
applyFunctionTo(3, lambda)
}
Ahora, imaginemos que esto no llegaría a inline def main() = 3 + 4 + 5
, y que applyFunctionTo
posiblemente podría ser compilado por separado, y no podemos cambiar el sitio llamado allí. Con cama elástica, me imagino que el código generado sería algo como esto (expresado en pseudocódigo, * significa puntero):
def main$lambda$1(env: {m: Int, n: Int}*, x: Int) = x + env.m + env.n
def main() = {
m = 4
n = 5
env* = allocate-space-for {Int, Int}
env = {m, n}
tramp* = create-trampoline-for(main$lambda$1*, env*)
return applyFunctionTo(3, tramp*)
// release memory for env and trampoline if the lambda didn't escape
}
¿Le parece justo?
No hay diferencia entre la implementación de cierres y la implementación de objetos con métodos virtuales. –
Es posible que tenga razón, sin embargo, el idioma no tendrá métodos virtuales (todavía). Al menos tendrá cierres y muchas otras cosas antes de eso. Podría agregar algunas características en un orden tonto porque solo lo hago con fines de aprendizaje, principalmente. Solo espero que eventualmente llegue algo útil. –
Quise decir que no hay ninguna razón para inventar algo nuevo para los cierres: simplemente puede hacer lo mismo que, digamos, un compilador de C++.Lo más probable es que ya sea lo más eficiente que se puede hacer. –