--- date: 2024-06-08 22:40 --- #memo #mimium まあいつものサンプルを考える ```rust fn fbdelay(input:float,fb:float,dtime:float)->float{ return input + delay(self,dtime)*fb } fn twodelay (input:float,dtime:float)->float{ return fbdelay(input,dtime,0.7)+fbdelay(input,dtime*2,0.8) } fn dsp (input:float)->float{ return twodelay(input,400)+twodelay(input,800) } ``` コールツリーとしては ```  dsp  | | twodelay(1) twodelay(2) | | | | fbdelay(1) fbdelay(2) fbdelay(3) fbdelay(4) | | | | delay,feed, delay,feed delay,feed delay,feed ``` - 実行中のVMから見れば、簡約されたあとにfeedの項が置き換えられて出てくるわけではない - 関数の定義にジャンプしてみて初めてfeedが現れる - しかし、そのfeedがどこの情報を保持しているかは呼び出し元の情報が必要 - コール元の情報を関数の引数として渡す、というのが前とったやり方 - feedのコールツリーを持つやり方はポインタを辿ってく形になるのであんまやりたくない - VMがクロージャをsetUpvalue/getUpvalueで解決してるような感じで解決できないか - callの命令は今call、callCls、callExtの3種類存在 - これはVM上の参照するテーブルが違うので必要 - 例えば、VMマシン側に、`feed_baseptr`みたいな情報を持っておく - callのオペランドの中にこのbaseptrからのオフセットバイト数を含めるようにする - いや、コンパイル時、call命令の前にsetFeedAddressみたいな命令を差し込めばそれでいいのか - で、delay命令が来たときは現在のアドレスから必要な長さをリングバッファとして使用する - getfeed命令が来たときは現在のアドレスからその型分のバイト数を取り出して、、、どうする? - 即値用の、getfeedfloatと、それ以外のgetfeedptrは分けたほうがいいな - call命令、delay、getfeed命令が終わるたびにbaseptrの場所は戻される - いや、最後にsetfeedしなきゃいけないからself用のbaseptrに戻す必要があるのかな - 場合によってはdelayの結構長いタイム分をオフセットしないといけないわけだから、命令長が足りなくならんかこれ - オフセットの値を即値でやるのと、レジスタからロードする2パターンの命令持っておけば良い まあこんな感じで行けそう ``` state_size: fn fbdelay(input,fb,dtime){ // reg: input,fb,dtime getfeedfloat // load feed to reg3 pushfeedoffset 8 // shift feed_base by 64bit delay 3 2 4// write feed and pop head from dtime address offset, push to reg4 mulF 4 2 5// multiplay result of delay(reg4) and fb(reg2) addF 0 5 6 popfeedoffset 8 // shift feed_base by 64bit setfeed 6 return 6 } fn twodelay (input,dtime){ // reg:input,dtime movConst 'fbdelay' 2 mov 0 3 mov 1 4 movConst '0.7' 5 call 2 3 1 //reg2 is result of fbdelay mov 1 3 movConst '2.0' 4 mulF 3 4 5 mov 5 3 movConst 'fbdelay' 4 mov 0 5 mov 3 6 movConst '0.8' 7 pushfeedoffset 8 call 4 3 1 //reg4 is result of second fbdelay popfeedoffset addF 2 4 5 return 5 } fn dsp (input){ movConst 'twodelay' 1 mov 0 2 movConst '400' 3 call 1 2 1 //reg1 is result of twodelay movConst 'twodelay' 2 mov 0 3 movConst '800' 4 pushfeedoffset 8 call 2 2 1 addf 1 2 3 return 3 } ``` delayはどうしておくかというと、最初の8バイトは現在のリングバッファの位置、残りをメモリという扱いにすれば良い。ただメモリのサイズを決定すんのがなあ〜 ```rust fn fbdelay(input:float,fb:float,dtime:float)->float{ return input + delay(self,dtime)*fb } fn twodelay (input:float,dtime:float)->float{ return fbdelay(input,dtime,0.7)+fbdelay(input,dtime*2,0.8) } fn dsp (input:float)->float{ return twodelay(input,400)+twodelay(input,800) } ``` ### だめかも 関数型を受け取って関数を返す`filterbank`的な例がこれだとダメなことに気がついた。少なくともリニアでフラットな内部状態ストレージでは実現無理。 ```rust fn onepole(x,g){ x*(1.0-g) + self*g } fn filterbank(n,filter){ let next = filterbank(n-1,filter); if (n>0){ |x,freq| filter(x,freq+n*100) +next(x,freq) }else{ |x,freq| filter(x,freq+n) } } let myfilter = filterbank(3,onepole) ``` みたいな感じだとすると ``` fn onepole(x,g){ movc 2 "1" mov 3 1 sub 2 2 3 mul 2 0 2 mov 3 1 getstate 4 0 mul 3 3 4 add 3 2 3 } fn lambda_true(x,freq){ // x:0 freq:1 getupvalue 2 0 //get n movc 3 "100" mul 2 2 3 add 1 1 2 getupvalue 2 1 //get filter mov 4 0 mov 3 1 callcls 2 2 1 getupvalue 3 2 //get next mov 4 0 mov 5 1 callcls 3 2 1 movc 3 "1" add 3 3 4 ret 3 1 } fn lambda_false(x,freq){// x:0 freq:1 getupvalue 2 0 //get n add 2 2 1 getupvalue 3 1 //get filter mov 4 0 mov 5 2 callcls 3 2 1 ret 3 1 } fn filterbank (n,filter){ //n:0 filter: 1 movc 2 "-1" sub 2 0 2 mov 3 "filterbank" mov 4 1 mov 5 2 call "filterbank" 2 1 //now stack 2 is a closure "next" mov 3 0 movc 4 "0" eq 3 3 4 jmpifneg 3 :_else: closure 3 "lambda_true" jmp :return: :else: closure 3 "lambda_false" :return: ret 3 1 } ```