バックエンドとして LLVM を使用する言語にクロージャー (ラムダ) を追加し始めました。常にインライン化できる単純なケースのために実装しました。つまり、クロージャー定義自体のコードは、使用されている場所でインライン化されるため、生成する必要はありません。
ただし、クロージャーが常にインライン化されていない場合 (たとえば、インライン化されていない別の関数に渡される場合) にクロージャーのコードを生成する方法。好ましくは、呼び出しサイトは、通常の関数またはクロージャーが渡されるかどうかを気にせず、通常の関数として呼び出す必要があります。
合成名で関数を生成できましたが、参照環境を追加の引数として使用する必要があり、その関数を、必要な追加の引数を知らない別の関数に渡すことはできませんでした。
私は、LLVM のトランポリン組み込み関数を使用して 1 つの可能な解決策を考えました。これは、関数から単一のパラメーターを「削除」し、1 つ少ないパラメーターを受け取るトランポリン関数へのポインターを返します。この場合、クロージャー用に生成された関数が参照環境を最初のパラメーターとして受け取った場合、それを切り取って、クロージャーが実際に宣言するのとまったく同じ数のパラメーターを受け取る関数を取り戻すことができます。これは実行可能ですか?効率的?より良い解決策はありますか?
コード例:
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)
}
ここで、これが にインライン化されず、個別にコンパイルされる可能性があり、そこで呼び出しサイトを変更できないと想像してみましょdef main() = 3 + 4 + 5
うapplyFunctionTo
。トランポリンを使用すると、生成されるコードは次のようになると思います (疑似コードで表現、* はポインターを意味します)。
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
}
これは正しいと思いますか?