今回の記事はSolidity Assembly入門という連載記事の第3回目です。
この連載ではSolidityのコードをコンパイルした時に生成されるopcodeについて解説していきます。
この連載ではSolidityのコードをデバッグするのに必要な知識を得られることを目的としています。
前回の記事はこちら。
y-nakajo.hatenablog.com
第3回目の今回は、Solidityのメモリ領域の取り扱いについて説明します。
メモリの慣習について
Solidity Document #conventions-in-solidity
フリーメモリの定義と取り扱いについては公式のドキュメントに詳しい説明があります。今回の解説記事では公式ドキュメントの内容を実例を踏まえて説明します。まぁ公式のドキュメント読めばこれ以降特に読まなくてもいいかも。。。。
Solidityのメモリマッピング
Solidityではメモリ上の0x80までの領域は予約領域で、それ以降をフリーメモリ領域としています。内訳としては以下の通りです。
- 0x00 〜 0x3f: スクラッチ領域。storageに定義された配列の要素の先頭アドレスを求める時とかに使われる。直ぐ消えてもいいデータを扱う領域。
- 0x40 〜 0x5f: フリーメモリのポインタ。この領域に格納されたアドレス以降が未使用のメモリ領域。メモリをアロケートする場合はここの値が増えていく。
- 0x60 〜 0x7f: 常に0で、dynamic memory arrayの初期化に使われるらしい。この領域を参照しているopcodeは見たことないので具体的にどういう時に使われるのかはよくわからない。
- 0x80 〜 : これ以降は全てフリーメモリ。
このように、Solidityでmemoryを使う処理をする場合は、0x80以降にデータが格納されていきます。debuggerなどで0x40の値が更新された場合は、新しくメモリ領域がアロケートされた時になります。
メモリ利用の例
メモリのアロケートが行われる処理として以下のものがあります。
- 関数の引数に配列が渡された時。(bytes, stringも含む)
- メモリ上のデータを操作する処理を実行した時。(keccakが一番有名かな)
- メモリ上に配列やstructを宣言した時。(i.e. uint[] memory arrays;)
- 外部contractから関数の戻り値として動的データを受け取る時。
これらの処理を実行する時はメモリがアロケートされます。以下で具体例を見ていきたいと思います。
メモリ上に配列を宣言する例
ethfiddle.com
この例ではメモリ上に引数で渡された要素数を持つ配列を宣言しています。戻り値として次のフリーメモリポインタアドレスを返しています。引数の数字を増やすことで、戻り値のメモリポインタアドレスも増えていくことがわかると思います。
動的な値を使った関数の呼び出し
ethfiddle.com
次の例は少し複雑なものになっています。ここでは、uintのみとstructを引数で受け取る別々のメソッドでメモリのアロケートが行われるかを確認しています。
動的な値を引数と、戻り値に宣言された関数を呼び出す時は必要な分のメモリがアロケートされていることがわかります。
逆に、uintを引数にとる関数はstackのみでデータの受け渡しが可能なため、メモリはアロケートされていません。
まとめ
Solidityでデバッグをする際はメモリ上の0x40への値の読み書きに注意してみることで、メモリ上にどんなデータが格納されていってるかを把握することができます。
また、自分でassemblyを書く場合も、後続の処理のために必ず0x40の値を書き換え、必要分のメモリをアロケートしてください。
今回は、casper FFGをSolidityに置き換える作業の息抜きとして記事を書いているので少し簡単ですがこれくらいで終わりとします。
PS. casper FFGをSolidityに置き換えてくれる協力者を募集中です!一緒にEthereum Communityへ貢献していきましょう!