# EIP-6206 - JUMPF 與非回傳函式
# 注意事項
本文並沒有經過作者外的其他審查者審核,因此若內容有誤,請到 issue 區提出問題,我會儘速修改,謝謝。
# 摘要
本 EIP 是對於 EIP-4750 EOF 函式的擴充,提供尾端呼叫最佳化機制,透過加入了一個新指令 JUMPF
,可在不新增回傳堆疊框架的情況下跳轉至其他程式碼區段。
同時也擴充了型別區段的格式,允許標註某個區段為「非回傳區段」,並為跳轉至這類區段的 JUMPF
指令提供簡化的堆疊驗證邏輯。
# 目的
在常見的函式設計中,某些函式在執行結束時只會呼叫另一個函式然後立即回傳。JUMPF
能在不需建立新回傳堆疊框架的情況下,切換至另一個區段執行,進而最佳化這類尾端呼叫行為。
若在驗證階段已能確定某函式永遠不會將控制流程回傳給呼叫端,則使用 JUMPF
時可將其視為終止指令,允許操作數堆疊在執行終止時保留額外資料。這將提供編譯器更多最佳化空間,無論是在程式碼體積或 gas 消耗上皆具優勢。
此特性特別適用於以 REVERT
結尾的錯誤處理輔助函式。這些輔助函式經常在多個分支中重複使用,若無需在 JUMPF
前先移除堆疊上的多餘項目,就能以更有效率的方式進行抽取與重用。
# 實際例子:費波那契數列計算
費波那契數列定義如下:
- F(0) = 0
- F(1) = 1
- F(n) = F(n-1) + F(n-2),當 n > 1
# 傳統方法(使用 CALLF 與 RETF)
區段 0(主程式): PUSH 10 // 計算 F(10) CALLF 1 // 呼叫費波那契函式 STOP // 結束執行
區段 1(fib 函式): DUP1 // 複製 n PUSH 2 LT // n < 2 ? JUMPI base_case // 是的話跳到基本情況
DUP1 PUSH 1 SWAP1 SUB // n-1 CALLF 1 // 呼叫 F(n-1)
SWAP1 PUSH 2 SWAP1 SUB // n-2 CALLF 1 // 呼叫 F(n-2)
ADD // F(n-1) + F(n-2) RETF
base_case: RETF // 回傳 n(F(0) 或 F(1))
# 使用尾端遞迴與 JUMPF 最佳化版本
我們可以將此函式改寫為尾端遞迴形式,並在最後使用 JUMPF
:
區段 0(主程式,非回傳): PUSH 10 // n PUSH 0 // a = F(0) PUSH 1 // b = F(1) JUMPF 1 // 進入 fibTail 區段
區段 1(fibTail 函式): // Stack: [n, a, b] DUP1 ISZERO JUMPI return_a // 若 n == 0,回傳 a
SWAP1 DUP3 ADD // 計算 a+b SWAP2 PUSH 1 SWAP1 SUB // n-1 SWAP1 SWAP2 // 參數變為 [a+b, n-1, b]
JUMPF 1 // 尾呼叫 fibTail(n-1, b, a+b)
return_a: SWAP1 SWAP2 POP POP RETF
# 執行流程比較
傳統方法: 使用 JUMPF 優化:
+—————+ +––––––––+
| CALLF fib(10) | | JUMPF fibTail |
+—————+ +––––––––+
| |
v v
+—————+ +––––––––+
| 建立回傳框架 | | 無需建立回傳框架 |
+—————+ +––––––––+
| |
v v
遞迴呼叫 迭代執行
(堆疊框架增長) (不增加堆疊框架)
| |
v v
+—————+ +––––––––+
| 多次 CALLF/RETF| | 多次 JUMPF |
+—————+ +––––––––+
| |
v v
+—————+ +––––––––+
| 回傳堆疊展開 | | 當 n=0 時回傳 |
+—————+ +––––––––+
# 規格
# 型別區段變更
若某區段在執行後永遠不會回傳,則可標記為「非回傳區段」。此類區段的 outputs
欄位在型別區段中設為 0x80
。
第一個程式碼區段,即 main 程式區段必須為非回傳區段,且不接受任何輸入。
# 執行語意
新增指令:JUMPF (0xe5)
JUMPF
具一個立即參數target_section_index
,為 16 位元無號 big-endian 整數,代表目標的程式碼區段。- 若執行後的堆疊深度超過
1024 - type[target].max_stack_increase
,則導致異常終止。 - 執行時會將目前區段設為目標區段,
PC
設為 0,但不更動回傳堆疊。 - 消耗 5 gas。
- 不對操作數堆疊進行 pop 或 push 操作。
對於傳統位元組碼,執行 JUMPF
將觸發異常終止。
# 程式碼驗證
對每個 JUMPF
:
target_section_index
必須小於區段總數。- 需滿足以下條件之一:
type[current].outputs
≥type[target].outputs
- 或
type[target].outputs == 0x80
- 若跳轉至非回傳區段,需滿足:
stack_height_min >= type[target].inputs
- 若跳轉至可回傳區段,需滿足:
stack_height_min == stack_height_max == type[current].outputs - type[target].outputs + type[target].inputs
stack_height_max ≤ 1024 - type[target].max_stack_increase
JUMPF
被視為終止指令,不可有後續指令- 若任何
RJUMP*
偏移至JUMPF
的立即參數部分,驗證失敗 CALLF
不可呼叫非回傳區段(即outputs == 0x80
)
# 非回傳狀態驗證
當且僅當某區段不含 RETF
且僅使用 JUMPF
指向其他非回傳區段時,該區段才可被標記為非回傳。
# 原理
# 為何允許跳轉至輸出較少的區段?
為避免重複程式碼,JUMPF
允許從輸出較多的區段跳轉至輸出較少的區段,只要這些額外的輸出已在目前區段補足。這樣一來,共用的輔助函式就可以通用使用,無需硬性對應輸出數量。
傳統函式模式: 使用 JUMPF 的優化模式:
+––––+ +––––+
| 函式 A | | 函式 A |
+––––+ +––––+
| |
| CALLF B | JUMPF B
| |
v v
+––––+ +––––+
| 函式 B | | 函式 B |
+––––+ +––––+
| |
| RETF | (無需回傳)
| |
v |
+––––+ |
| 函式 A | |
+––––+ |
| |
| RETF |
v v