松浦 知也 Matsuura Tomoya
1a9ffb8228
All checks were successful
Build / build (push) Successful in 2m36s
212 lines
5.7 KiB
Markdown
212 lines
5.7 KiB
Markdown
---
|
||
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
|
||
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
|
||
}
|
||
```
|
||
|
||
|
||
あ、でも`callcls`命令実行の時だけ、最初にgetstateを実行、暗黙的に読み出して、その中身のポインタへ飛べばいいのか
|
||
|
||
Closureを作った時にどうやってstateのメモリを拡張するかというと、ヒープ上のクロージャのデータ構造自体にstateへのポインタを持たせないと無理?
|