Ho iniziato ad aggiungere chiusure (lambda) alla mia lingua che utilizza LLVM come back-end. Li ho implementati per casi semplici in cui possono essere sempre in linea, ad esempio il codice per la definizione della chiusura stessa non ha bisogno di essere generato, in quanto è sottolineato dove usato.Come implementare in modo efficiente chiusure in LLVM IR?
Ma come generare il codice per una chiusura nel caso in cui la chiusura non sia sempre in linea (ad esempio, viene passata ad un'altra funzione che non è in linea). Preferibilmente, i siti di chiamata non dovrebbero preoccuparsi se sono passati funzioni o chiusure regolari e li chiamerebbero come normali funzioni.
Potrei generare una funzione con un nome sintetico, ma dovrebbe prendere l'ambiente di riferimento come argomento aggiuntivo e quella funzione non può essere passata a un'altra funzione che non conosce l'argomento extra necessario.
Ho pensato ad una possibile soluzione utilizzando gli intrinseci del trampolino di LLVM, che "asportano" un singolo parametro da una funzione, restituendo un puntatore a una funzione trampolino che prende un parametro in meno. In questo caso, se la funzione generata per la chiusura prendesse l'ambiente di riferimento come primo parametro, potrei asportarlo e recuperare una funzione che richiede esattamente quanti parametri come la chiusura dichiara effettivamente. Questo suono è fattibile? Efficiente? Ci sono soluzioni migliori?
Codice esempio:
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)
}
Ora, lascia immaginare che questo non sarebbe ottenere inline a def main() = 3 + 4 + 5
, e che applyFunctionTo
sarebbe eventualmente essere compilato separatamente, e non possiamo cambiare il sito di chiamata lì. Con trampolino, immagino che il codice generato sarebbe qualcosa di simile (espresso in pseudocodice, * significa puntatore):
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
}
Vi sembra giusto?
Non esiste alcuna differenza tra l'implementazione di chiusure e l'implementazione di oggetti con metodi virtuali. –
È possibile che tu abbia ragione, tuttavia, la lingua non avrà metodi virtuali (ancora). Almeno avrà chiusure e molte altre cose prima. Potrei aggiungere alcune funzionalità in un ordine stupido perché lo sto facendo solo per scopi di apprendimento, soprattutto. Spero solo che alla fine arrivi qualcosa di utile. –
Intendevo che non c'è motivo di inventare qualcosa di nuovo per le chiusure: puoi semplicemente fare la stessa cosa che, come dire, un compilatore C++ sta già facendo.È probabile che sia già la cosa più efficiente da fare. –