はじめに
初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
今回は「EVMのジャンプ先を命令内の相対オフセットで表し、実行前に制御フローを読み取りやすくするEIP8013」についてまとめていきます。
EIP8013は、EVMに静的な相対ジャンプ命令を追加する提案です。
従来の JUMP や JUMPI は、ジャンプ先をスタック上の値から決めます。
これに対して EIP8013 は、命令そのものに符号付きの相対オフセットを持たせ、ジャンプ先を実行前に検証できるようにします。
以下にまとめられている提案を解説しながらまとめていきます。
概要
EIP8013は、EVMに以下の5つの命令を追加します。
| 命令 | オペコード | 役割 |
|---|---|---|
RJUMP |
0xe0 |
無条件に相対位置へ移動します。 |
RJUMPI |
0xe1 |
条件が真の時だけ相対位置へ移動します。 |
RJUMPV |
0xe2 |
スタックから取り出した値でジャンプ表を選びます。 |
RJUMPSUB |
0xe3 |
サブルーチンへ相対ジャンプします。 |
RJUMPSUBV |
0xe4 |
ジャンプ表でサブルーチンを選びます。 |
どの命令も、ジャンプ先を命令列の中へ埋め込まれた即値から決めます。
RJUMP、RJUMPI、RJUMPSUB は2バイトの相対オフセットを持ちます。
RJUMPV と RJUMPSUBV は、max_index と複数の相対オフセットを持つジャンプ表を使います。
従来の JUMP / JUMPI と EIP8013 の違いは以下の図です。
左側の従来方式では、ジャンプ先アドレスがスタック上の値として現れます。
そのため、解析ツールや実装は、実行時のスタック値を考えながら移動先を確認する必要があります。
右側の EIP8013 では、移動先が命令内の相対オフセットから決まるため、実行前に制御フローを読み取りやすくなります。
この提案は、既存の JUMP や JUMPI をすぐに削除するものではありません。
静的に決められる制御フローでは RJUMP 系を使えるようにし、動的ジャンプが必要な場面は残します。
動機
EVMのジャンプは非常に柔軟です。
JUMP と JUMPI はスタックからジャンプ先を取り出すため、実行時に移動先を決められます。
一方で、この柔軟さは、バイトコード解析や検証を難しくします。
たとえば、ある JUMP の移動先が実行時の計算結果で変わる場合、解析ツールは「どこへ飛ぶか」をすぐには決められません。
さらに、移動先として有効な位置を示すために JUMPDEST が必要です。
この仕組みは安全性のために重要ですが、静的に分かるジャンプまで同じ扱いにすると、コードサイズや実行コストも増えます。
実際には、すべての制御フローが動的である必要はありません。
if、while、switch、関数呼び出しのような多くの処理は、コンパイラが生成する時点で移動先を決められます。
EIP8013 は、そのような制御フローを相対オフセットで表せるようにします。
目的は大きく3つあります。
- ジャンプ先の検証を実行前に済ませやすくする。
-
JUMPDESTを減らし、コードサイズとガスを削減する。 - コンパイラ、静的解析、事前コンパイル、ZK向けの回路生成などで扱いやすい制御フローにする。
この背景には、EVMの静的制御フローを整える一連の議論があります。
特に EIP7979 は、CALLSUB、ENTERSUB、RETURNSUB というサブルーチン向け命令と、検証済みコードの考え方を扱います。
EIP8013 はこの検証の考え方を拡張し、相対ジャンプ系の命令が有効な命令位置だけを指すように確認します。
仕様
EIP8013 の中心は、命令の直後の位置を基準にした相対オフセットです。
提案ではこの基準位置を PC_post_instruction と呼びます。
これは、オペコードと即値をすべて読み終えた直後の PC です。
relative_offset は16ビットの符号付き整数として、2の補数、ビッグエンディアンでエンコードされます。
そのため、前方にも後方にもジャンプできます。
最大の正方向は 32767 バイトです。
相対オフセットの考え方は以下の図です。
基準は RJUMP 命令の先頭ではなく、即値を読み終えた直後です。
そこから相対オフセットを足した位置が移動先になります。
移動先は命令の先頭である必要があり、PUSH などの即値データ内やコード範囲外は無効です。
5つの命令
追加される命令の役割は以下です。
RJUMP
RJUMP は無条件の相対ジャンプです。
2バイトの relative_offset を読み、PC を PC_post_instruction + relative_offset に設定します。
従来の PUSH で絶対位置を積んで JUMP する処理を、1つの命令と即値で表せます。
移動先は命令内に書かれているため、実行前の検証で有効な命令位置かどうかを確認できます。
RJUMPI
RJUMPI は条件付きの相対ジャンプです。
スタックから condition を1つ取り出し、condition が0ではない時だけ相対ジャンプします。
condition が0なら、ジャンプせずにそのまま後続命令へ進みます。
従来の JUMPI では、条件と移動先の両方がスタックに関わります。
RJUMPI では、条件だけをスタックから取り、移動先は命令内の相対オフセットから決めます。
RJUMPV
RJUMPV はジャンプ表を使う相対ジャンプです。
スタックから case を取り出し、その値をインデックスとして相対オフセット表を参照します。
エンコードは max_index と max_index + 1 個の relative_offset です。
max_index は8ビットの符号なし値なので、表には最大256個の移動先を入れられます。
case > max_index の時はジャンプせずに、そのまま後続命令へ進みます。
この挙動により、switch 文のような分岐を表しやすくなります。
また、RJUMPV 0 relative_offset は、case が0の時だけジャンプし、それ以外では後続命令へ進む書き方になります。
提案では、この書き方を ISZERO RJUMPI relative_offset の代わりに使える場面があると説明しています。
RJUMPSUB
RJUMPSUB はサブルーチンへ入るための相対ジャンプです。
PC_post_instruction をリターンスタックへ積み、PC を PC_post_instruction + relative_offset に設定します。
移動先は ENTERSUB でなければなりません。
ここでの ENTERSUB と RETURNSUB は、EIP7979 が導入するサブルーチン向け命令です。
RJUMPSUB は、CALLSUB と同じ「戻り先を保存してサブルーチンへ入る」仕組みを、相対オフセットで直接使えるようにします。
RJUMPSUBV
RJUMPSUBV はジャンプ表でサブルーチンを選ぶ命令です。
スタックから case を取り出し、該当する相対オフセットを使って ENTERSUB へ移動します。
case > max_index の時はジャンプせず、後続命令へ進みます。
RJUMPV と同じく、表のサイズは最大256です。
違いは、移動先が通常の命令位置ではなく ENTERSUB に限定される点です。
検証ルール
EIP8013 は、EIP7979 の検証アルゴリズムを拡張します。
検証では、すべての RJUMP 系命令が有効な移動先を指しているかを確認します。
無効な移動先は以下です。
- コード範囲外。
-
PUSHの即値データ内。 -
RJUMP、RJUMPI、RJUMPVなどの即値データ内。 -
RJUMPSUB/RJUMPSUBVなのに、移動先がENTERSUBではない位置。
RJUMP、RJUMPI、RJUMPV の移動先は JUMPDEST でもよいですが、必須ではありません。
命令の先頭であれば、JUMPDEST 以外の命令へ移動できます。
一方で、RJUMPSUB と RJUMPSUBV は ENTERSUB だけを移動先にできます。
この違いにより、通常の制御フローとサブルーチン呼び出しを分けて検証できます。
ガスコスト
提案では、ジャンプ先の検証を事前に済ませられるため、実行時に動的ジャンプと同じ確認を繰り返す必要はないとしています。
推奨されているコストは以下です。
| 命令 | 推奨コスト |
|---|---|
RJUMP |
2 |
RJUMPI |
4 |
RJUMPV |
4 |
RJUMPSUB |
5 |
RJUMPSUBV |
5 |
従来の JUMP や JUMPI と比べて、移動先の検証を静的に済ませられる点がコスト低下の理由です。
また、移動先ごとに JUMPDEST を置かなくてもよい場面では、JUMPDEST 1バイト分のデプロイコストと、実行時の JUMPDEST コストも削減できます。
設計根拠
相対アドレスを使う理由は、コード片を移動しやすくするためです。
絶対アドレスでジャンプ先を書くと、コードの前後へ命令を挿入した時に移動先を再計算しやすくなります。
相対オフセットなら、まとまったコード片を別の場所へ置いても、その内部の移動関係を保ちやすくなります。
16ビットの即値を採用する理由は、表現できる距離と命令サイズのバランスです。
8ビットでは、後方へ戻れる距離や関数間の移動には不足しやすくなります。
一方で、16ビットなら大きなコードでも多くの相対移動を表せます。
EIP170 は、コントラクトの実行コードサイズ上限を 24,576 バイトに定めています。
EIP3860 は、initcode の上限を 49,152 バイトに定めています。
提案では、これらの上限を踏まえて16ビットの相対オフセットで十分だと説明しています。
EIP170については以下の記事を参考にしてください。
EIP3860については以下の記事を参考にしてください。
PUSHn JUMP の組み合わせに割引を与えない点も重要です。
既存命令の並びへ特別な最適化ルールを入れると、ガス計算やEVM実装の内部表現が複雑になります。
EIP8013 は、既存命令の意味を特別扱いするのではなく、新しい命令として静的ジャンプを定義します。
JUMPDEST を不要にできる点も設計上の大きな理由です。
従来の動的ジャンプでは、どこへ飛べるかを制限するために JUMPDEST が必要でした。
しかし、静的ジャンプでは移動先が即値に書かれているため、解析時に有効な移動先を直接確認できます。
そのため、すべての移動先へ JUMPDEST を置く必要はありません。
互換性
EIP8013 は新しいオペコードを追加する提案です。
既存の JUMP、JUMPI、JUMPDEST の挙動を直接変更するものではありません。
そのため、既存バイトコードとの互換性リスクはないとされています。
ただし、コンセンサスルールとして新しい命令を追加するため、導入にはハードフォークが必要です。
クライアント実装は、5つの命令の実行ルールだけでなく、即値の読み取り、ジャンプ表、検証ルール、ガスコストをそろえる必要があります。
また、EIP8013 は EIP7979 を前提にしています。
特に RJUMPSUB と RJUMPSUBV は、ENTERSUB と RETURNSUB によるサブルーチンの仕組みを使います。
そのため、サブルーチン系の相対ジャンプだけを切り離して導入する設計ではありません。
テストケース
提案では、検証と実行の両方でテストすべきケースを挙げています。
中心になるのは、相対オフセットが有効な命令位置を指しているか、条件や表のインデックスで正しく分岐するかです。
検証では以下を確認します。
| ケース | 期待される扱い |
|---|---|
RJUMP / RJUMPI / RJUMPV が JUMPDEST を指す |
有効です。 |
RJUMP / RJUMPI / RJUMPV が JUMPDEST 以外の命令先頭を指す |
有効です。 |
| 正方向、負方向、0の相対オフセット | 移動先が有効なら認められます。 |
RJUMPV / RJUMPSUBV の表サイズが1から256 |
範囲内なら有効です。 |
| 即値が途中で切れている | 無効です。 |
| 移動先がコード範囲外 | 無効です。 |
移動先が PUSH や RJUMP 系の即値データ内 |
無効です。 |
RJUMPSUB / RJUMPSUBV の移動先が ENTERSUB ではない |
無効です。 |
実行では以下を確認します。
| 命令 | 確認内容 |
|---|---|
RJUMP |
正方向、負方向、0の相対移動。 |
RJUMPI |
条件が0の時に進み、0ではない時に相対移動すること。 |
RJUMPV |
case が表内の時に該当先へ移動し、表外の時に進むこと。 |
RJUMPSUB |
ENTERSUB へ入り、RETURNSUB で戻れること。 |
RJUMPSUBV |
表で選んだ ENTERSUB へ入り、表外なら進むこと。 |
RJUMPV と RJUMPSUBV では、case > max_index の時に例外停止するのではなく、ジャンプせずに進みます。
この挙動により、ジャンプ表の直後へデフォルト処理を置きやすくなります。
セキュリティ
EIP8013 の安全性で重要なのは、即値を持つ命令を正しく検証することです。
相対オフセットが PUSH データ内やコード範囲外を指すと、命令列の解釈が実装ごとにずれる恐れがあります。
そのため、実行前の検証で移動先を必ず弾く必要があります。
静的な相対ジャンプは、実行時に移動先を再確認しなくてよい設計です。
これは実行コストを下げる利点ですが、裏返すと、検証アルゴリズムの実装ミスがそのままコンセンサス差につながります。
特に、ジャンプ表のサイズ、即値の境界、RJUMPSUB 系の ENTERSUB 制約は慎重にそろえる必要があります。
提案では、RJUMPV と RJUMPSUBV の表は最大256エントリに制限されています。
そのため、表の読み取りそのものが大きなDoS要因になりにくい設計です。
引用
Greg Colvin (@gcolvin), Alex Beregszaszi (@axic), Andrei Maiboroda (@gumb0), Paweł Bylica (@chfast), "EIP-8013: Static relative jumps and calls for the EVM," Ethereum Improvement Proposals, no. 8013, August 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-8013.
最後に
今回は「EVMのジャンプ先を命令内の相対オフセットで表し、実行前に制御フローを読み取りやすくするEIP8013」についてまとめてきました。
EVMの動的ジャンプは柔軟ですが、解析や検証の負担を大きくします。
EIP8013 は、静的に決められる制御フローを RJUMP 系の命令で表し、ジャンプ先の検証を実行前へ移します。
これにより、コードサイズ、実行コスト、解析しやすさの面で改善を目指します。
特に重要なのは、JUMPDEST を必要としない相対ジャンプと、ENTERSUB へ入るサブルーチン向け相対ジャンプを分けている点です。
通常の分岐、ジャンプ表、サブルーチン呼び出しをそれぞれ明示できるため、コンパイラや検証ツールが制御フローを扱いやすくなります。
今後もEthereum関連の規格や技術についてまとめていきます。


