Cairoのあれこれ
ap:Access Pointer?未使用のメモリセル?
気付き、tips
中括弧は暗黙の引数を渡すために利用される
assert系はsolidityで言うところのrequireとかに近い
こういうところはRustに似てるかも
ptr=pointer
コンパイル後にABIが出力されるのはいつも通り
@externalみたいに修飾子でアクセス権を表すのも似てる
疑問
なぜ未定義のpool_balanceにおいてr/wが可能なのか?
ポインタと普通の変数の使い分け
暗黙の引数と普通の仮引数の違いは?
関数 foo() が hash2() を呼び出す場合、foo() は組み込みポインタ (hash_ptr) も取得して返さなければならず、foo() を呼び出すすべての関数も同じです。このパターンは非常によくあることなので、Cairo には「暗黙の引数」という構文上の工夫があります。次の hash2 の実装を見てみましょう (特に関数宣言に注目してください)。
code:a
from starkware.cairo.common.cairo_builtins import HashBuiltin
func hash2{hash_ptr : HashBuiltin*}(x, y) -> (z):
# Create a copy of the reference and advance hash_ptr.
let hash = hash_ptr
let hash_ptr = hash_ptr + HashBuiltin.SIZE
# Invoke the hash function.
hash.x = x
hash.y = y
# Return the result of the hash.
# The updated pointer is returned automatically.
return (z=hash.result)
end
中括弧はhash_ptrをImplicit arguments として宣言しています。これは自動的に関数に引数と戻り値を追加します。
高レベルの return 文を使っている場合は、明示的に hash_ptr を返す必要はない。
Cairo コンパイラは、単に hash_ptr 参照の現在のバインディングを返すだけです。
hash2 は次に利用できるインスタンスへのポインタを返さなければならないので、参照の再バインディングを追加しました: let hash_ptr = hash_ptr + HashBuiltin.SIZE. その効果はreturn文にのみ(暗黙のうちに)あることに注意してください。
つまりは組み込み関数とかのポインタとを区別するため?
テストはpythonで書く
文法
stringはfield elementとして扱われる
code:string
//which is equivalent to
関数が存在しない場合などは、apが未定義として扱われる(revoke)
Typed reference
fpにx, y, zの3つのメモリセルからなる構造体へのポインタが格納されているとする。
しかし、これでは、プログラマがyのオフセットを保持しなければならない。
構造体を定義することでこれを回避する
code:a
struct MyStruct:
member x : felt
member y : felt
member z : felt
end
Cairo コンパイラは、構造体の先頭からのメンバのオフセットを計算し,オフセットには MyStruct.x, MyStruct.y, MyStruct.z を使ってアクセスでる(たとえば MyStruct.z = 2) 。
さらに、構造体の合計サイズは、MyStruct.SIZE を使用して取得できる
[fp + 1] を [fp + MyStruct.y] に置き換えることができる このパターンはかなり多く繰り返されるので、Cairo では以下のような型付き参照(Typed reference)の定義をサポートしてる
code:a
let ptr : MyStruct* = cast(fp, MyStruct*) //let ptr = cast(fp, MyStruct*) も同じ assert ptr.y = 10
# which will subsequently compile to [fp + 1]. 一般に,refname.membername(ここではptr.y)という構文は,refnameが値valと型Tを持つ型付き参照,T.membernameがメンバー定義で,val + T.membername とコンパイルされる. Casting
Cairo は、フィールド要素 ( felt ) やポインター、構造体といった型をサポートしている
たとえば、レジスタ ap や fp の値、そして整数リテラルはすべて felt
ここで <type> には felt (フィールド要素)、T (上で説明したように T 構造体)、 あるいは別の型へのポインタ (T* や felt** など) を指定できる
Local valiable
ap レジスタを基準にするため、命令によっては取り消される Temporary 変数(一時変数)とは異なり、local 変数は fp レジスタを基準にする
関数のスコープ内では、最初のローカル変数は fp + 0 への参照、2番目のローカル変数は fp + 1 への参照となる ローカル変数を使用する場合は、apを進めるように注意する
Cairo コンパイラは定数 SIZEOF_LOCALS を自動生成する。これは、同じスコープにあるローカル変数の累積サイズ (セル数) と等しくなる
code:local
func main():
ap += SIZEOF_LOCALS
local x # x will be a reference to fp + 0. local y # y will be a reference to fp + 1. x = 5
y = 7
ret
end
さらに、Cairo には alloc_locals という命令があり、これは ap += SIZEOF_LOCALS に変換され、1 行でローカルを定義して値を代入することもできる。
code:a
local x = <expr>
ローカル変数が同じ行で初期化されていない限り、local指示自体はcairo命令に変換されず(これもtempvarとの違い)、単に参照定義に変換される
これが、ap の値を手動で増やさなければならない理由のひとつ
現在のバージョンの Cairo では、ローカル変数の型は明示的に指定する必要があり (指定しない場合は felt を使用)、初期化値の型から推論することはない
ローカル変数の型は、2種類の方法で指定することができる
code:local_type
local x : T* = <expr>
local y : T = <expr>
最初のものは、セルを1つ確保し、それをT型構造体へのポインタと見なす。
したがって、x.a は [fp + 0 + T.a] と等価に使うことができる(aはTのメンバであると仮定) 2番目はT.SIZEセルを確保するもの(xの定義により上の例ではfp + 1から始まっている)
さらに、y 自身が構造体のアドレス(fp + 1 ではなく、fp + 1)を参照している。つまり、y を使おうとするとエラーになる可能性がある。例えば、以下のようになります。 tempvar z = y
というのは、ap = fp とコンパイルしなければならないから。 これは Cairo では有効な命令ではなく、それでも、__fp__という変数を定義することで、コードがコンパイルできるようになる
これは、「レジスタの値にアクセスする」で説明するとおりです。