為什麼編譯的 Solidity 程式碼中跳轉地址計算如此復雜?
我有以下簡單的 Solidity 合約:
pragma solidity ^0.4.0; contract Test { function Test() { intfunc(5); } uint8 store; function intfunc (uint8 a) internal { store = a * 9; } }
我正在使用Remix對其進行編譯,並且得到了在地址 0x11 和 0x1E 之間無法解釋的字節碼(為方便起見,我在左側包含了十六進制地址):
//Standard preamble: 0x00: PUSH1 0x60 PUSH1 0x40 MSTORE CALLVALUE ISZERO PUSH1 0xB JUMPI INVALID //Beginning of Test() constructor: 0x0B: JUMPDEST JUMPDEST PUSH1 0x20 PUSH1 0x5 //Here's the really strange code: 0x11: PUSH5 0x100000000 0x17: PUSH1 0x7 0x19: PUSH1 0x25 0x1B: DUP3 0x1C: MUL 0x1D: OR 0x1E: DIV //Here we jump to the intfunc() function 0x1F: JUMP //Here we come back from intfunc() and jump to rest of the Test() constructor 0x20: JUMPDEST JUMPDEST PUSH1 0x3B JUMP //intfunc() itself: 0x25: JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH1 0xFF NOT AND PUSH1 0x9 DUP4 MUL PUSH1 0xFF AND OR SWAP1 SSTORE JUMPDEST POP JUMP //The rest of the Test() constructor and the rest of the code is here... 0x3B: JUMPDEST ... //(The rest isn't really relevant to this question)
0x11 和 0x1E 之間的程式碼是怎麼回事——為什麼這麼麻煩?不能用簡單的“ PUSH1 0x25 ”代替嗎?僅僅為了計算 0x25 值而經過所有這些奇怪的步驟不是浪費氣體嗎?
此外,0x17 指令中的數字 7 是從哪裡來的?這似乎完全沒有意義。
請注意,對於“優化”和“未優化”混音模式,我得到了類似的程式碼。
任何見解將不勝感激!
此行為是在此 Github PR中引入的。它看起來與在建構子上下文中儲存函式呼叫標籤的優化有關。
左移 32 位
MUL 0x0100000000
和操作由文件libsolidity/codegen/CompilerUtils.cppOR
中的函式插入。隨後的 32 位右移由ExpressionCompiler.cpp插入,它呼叫rightShiftNumberOnStack。pushCombinedFunctionEntryLabel
在某些情況下,這對於包裝需要儲存在契約儲存中的跳轉標籤可能非常有用(因此可以節省大量氣體 - 儲存很昂貴)。在這個簡單的契約的情況下,它似乎只是一個不必要的殘餘。
如果我們編譯一個簡單的合約,比如:
contract C { uint store=45; }
我們將得到(我使用的是編譯器 0.4):
00 PUSH1 60 02 PUSH1 40 04 MSTORE 05 PUSH1 2d //value to store 45 07 PUSH1 00 //storage address 09 PUSH1 00 // useless 11 POP //useless 12 SSTORE
但是,如果我們改變情況
uint
來uint8
改變。我們將得到一個更長的字節碼:00 PUSH1 60 02 PUSH1 40 04 MSTORE 05 PUSH1 2d //value 07 PUSH1 00 //storage address 09 PUSH1 00//mask offset 11 PUSH2 0100// multiplier 14 EXP 15 DUP2 16 SLOAD 17 DUP2 18 PUSH1 ff 20 MUL 21 NOT 22 AND 23 SWAP1 24 DUP4 25 MUL 26 OR 27 SWAP1 28 SSTORE 29 POP
所以有什麼問題?
當我們使用時,
uint
我們直接使用 32 字節的字,但是當我們使用時,uint8
我們只需要儲存字中的第一個字節將其與儲存槽中的其他值放在一起,因此我們需要執行一些操作以避免任何數據覆蓋。我認為編譯器會將 0x2d 填充到 32 字節,因此插槽中的其他值將被覆蓋,我們將只保留第一個字節 (2d)。為了避免這個問題,我們使用
sload
載入單詞上的先前值並使用位運算(MUL NOT AND SWAP1 DUP4 MUL OR)來計算(使用的 div 和 mul 用於移位值)要儲存在將填充值 0x2d00000…00000 和前一個值組合在一起的插槽,最後我們呼叫sstore
以保存結果。