Shiika/クロージャ
例
code:shiika
var a = 1
f = fn() { a }
a = 2
無名関数(lambda)は外側のローカル変数をキャプチャできる
コンパイル結果(予定)
code:llvm
; a) 無名関数本体。仮引数のあとに、キャプチャした変数のアドレスをとる
define %Object* @lambda_1(%Object* %self, %Array* %captures) {
%a = %capturesの0番目
; %aを必要な型にキャスト
ret %Object* %value
}
; b) 呼び出し側はこれ(+bitcast)に展開する
define %Object* @"Fn0#call"(%Fn0* %self) {
%"@func" = %selfの@func (型は Shiika::Internal::Ptr)
%"@captures" = %selfの@captures (型は Array<Shiika::Internal::Ptr>)
%result = call %"@func"(%self, %"@captures")
}
; c) 無名関数の生成箇所
define @user_main() {
%a = ...
%func = @lambda_2(をbitcastしたもの)
%f = call %"Meta:Fn0#new", %func, %captures
}
; d) lambdaがネストしてるケースがある
define @lambda_2(%self, %captures) {
%b = ...
%func = @lambda_1(をbitcastしたもの)
%f = call %"Meta:Fn0#new", %func, %captures
}
これを生成するために何が必要か
a)
capturesの何番目を何にキャストしたら値が取れるか
b)
ivarを取得して渡すだけなので特に必要なものはない
c)
capturesを構成するコードを生成する
ローカル変数またはメソッド引数のなかで、キャプチャされているものをcapturesに入れる
captureしたと分かるのは、Hir::convert_bare_nameのタイミング
この情報を上流に反映させないといけない
exprsを処理する前にctxに空配列をセット
処理しおわったら配列を取り出す
配列には何を入れるか
変数の種類による
ローカル変数なら名前
メソッド引数ならインデックス
もう一つ、lambda_nの%capturesから取るパターンがある
内側lambdaがあるとき
この場合capturesの何番目かが必要
簡単なケースから考える
toplevel→lambda
トップのローカル変数を参照
method→lambda
メソッドのローカル変数を参照
メソッドの引数を参照
method→lambda→lambda
メソッドのローカル変数を参照
メソッドの引数を参照
ラムダのローカル変数を参照
ラムダの引数を参照
これで全部か?
三段のケースを考える
メソッドの仕事
Fn作成時にcapturesに必要な値を入れる
中間ラムダの仕事
Fn作成時に自分のcapturesから次のcapturesに値をコピー
n段を考える
code:txt
def foo
fn(a1){
b1 = 1
fn(a2) {
b2 = 2
...
fn(ai) {
bi = i
c1 + ... + ci*2 (cjは a1 ... ai, b1 ... bi のいずれか; 重複なし)
}
cjを解決する際、「どこで見つけたか」の情報を入れる必要がある
a) 各lambdaに名前をつける
名前は文字列でなくても、整数でよい
b) 「いくつ上のスコープか」という数字を入れる
スコープを抜ける際に全部デクリメントする必要あり
同じ段に複数のlambdaがあるケースがある
code:txt
a = 0
fn(a1){
fn(b1){
fn(c1) { a }
}
fn(b2){
fn(c2) { }
}
}
# a1,b1,c1作成時、aをcapturesとして渡す必要がある
# b2,c2作成時は、aは使わないので渡さない
責務を分割する
ラムダ内(値を使用する側)では、必要な値はすべて%caputresに入っているので、それの何番目かだけを指定すればよい
ラムダ作成箇所では、ラムダで使用されるものを%capturesとしてFn.newに渡す
使用され得るもの:ローカル変数、メソッド/ラムダ引数、ラムダのcaptures内の値
うーん
ごちゃごちゃ考えるより、まずは簡単な方法で実装してしまったほうが良い気がしてきた
簡単な方法とは???
ラムダを見つけたとき
まず中身をHIRに変換
それを解析して、以下を見つける
このラムダがキャプチャするもの
中のラムダがキャプチャしてるものも含む
ラムダごとにctxを作るのだから、そいつがVecを持てばいいような気がしてきた
見つけた順にpushしていく
内部ラムダがあった場合は、ctxを回収する際にVecにappendする
ただし所有者が自分であるものは取り除く
重複するものも取り除く(?)
code:txt
x = 0
# このfnの作成時は x をcapturesとして渡す fn(a1){
a11 = 0
a12 = 0
fn(b1){
b11 = a11
b12 = 0
fn(c1) {
x + a1 + a11 + a12 + b1 + b11 + b12 + c1
}
}
fn(b2){
a11
fn(c2) {
c2 + b2 + a12 + x
}
}
}
やること
HirMakerContextにidを付加
barenameを解決する際、
外側のスコープのlvarであれば、capturesに(owner, name)を追加し、capturesから値を取得するコードを生成
外側のスコープのargであれば、capturesに(owner, idx)を追加し、capturesから値を取得するコードを生成
lambdaの生成時、
capturesを構成するコードを生成
ownerが自分であれば、lvar/argとして取得
ownerが外側であれば、capturesに追加し、capturesから取得するコードを生成
↑コードを生成と書いたけど、HIRを生成、だったわ
以下のHIRを追加
HirCaptureRef(idx)
HirCreateCapture(values)
なんやかんやあって
実装できました〜