Solidity可以通過(guò)兩種不同的方式生成EVM字節(jié)碼:直接從Solidity到EVM操作碼(“舊codegen”),或者通過(guò)Yul(“new codegen”或“IR-based codegen”)中的中間表示(“IR”)。
引入基于 IR 的代碼生成器,不僅使代碼生成更加透明和可審計(jì),而且還實(shí)現(xiàn)了跨功能的更強(qiáng)大的優(yōu)化傳遞。
您可以使用標(biāo)準(zhǔn) json 中的選項(xiàng)在命令行上啟用它,我們鼓勵(lì)每個(gè)人嘗試一下!--via-ir
{"viaIR": true}
出于幾個(gè)原因,舊的和基于IR的代碼生成器之間存在微小的語(yǔ)義差異,主要是在我們不希望人們依賴這種行為的領(lǐng)域。本節(jié)重點(diǎn)介紹舊的和基于 IR 的編碼機(jī)之間的主要區(qū)別。
本節(jié)列出了僅語(yǔ)義的更改,因此可能會(huì)在現(xiàn)有代碼中隱藏新的和不同的行為。
在繼承的情況下,狀態(tài)變量初始化的順序已更改。
順序曾經(jīng)是:
所有狀態(tài)變量在開(kāi)始時(shí)都為零初始化。
評(píng)估從最派生到大多數(shù)基本協(xié)定的基本構(gòu)造函數(shù)參數(shù)。
初始化整個(gè)繼承層次結(jié)構(gòu)中的所有狀態(tài)變量,從最基本到最派生。
對(duì)線性化層次結(jié)構(gòu)中從最基本到最派生的所有協(xié)定運(yùn)行構(gòu)造函數(shù)(如果存在)。
新訂單:
所有狀態(tài)變量在開(kāi)始時(shí)都為零初始化。
評(píng)估從最派生到大多數(shù)基本協(xié)定的基本構(gòu)造函數(shù)參數(shù)。
對(duì)于線性化層次結(jié)構(gòu)中從最基本到最派生的每個(gè)合約:
初始化狀態(tài)變量。
運(yùn)行構(gòu)造函數(shù)(如果存在)。
這會(huì)導(dǎo)致合約的差異,其中狀態(tài)變量的初始值依賴于另一個(gè)合約中構(gòu)造函數(shù)的結(jié)果:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.1; contract A { uint x; constructor() { x = 42; } function f() public view returns(uint256) { return x; } } contract B is A { uint public y = f(); }
以前,將設(shè)置為 0。這是因?yàn)槲覀儗⑹紫瘸跏蓟癄顟B(tài)變量:首先,設(shè)置為0,并且在初始化時(shí),將返回0,導(dǎo)致也為0。使用新規(guī)則,將設(shè)置為42。我們首先初始化為 0,然后調(diào)用 A 的構(gòu)造函數(shù),該構(gòu)造函數(shù)設(shè)置為 42。最后,在初始化時(shí),返回 42,導(dǎo)致為 42。y
x
y
f()
y
y
x
x
y
f()
y
刪除存儲(chǔ)結(jié)構(gòu)時(shí),包含結(jié)構(gòu)成員的每個(gè)存儲(chǔ)槽都將完全設(shè)置為零。以前,填充空間保持不變。因此,如果結(jié)構(gòu)中的填充空間用于存儲(chǔ)數(shù)據(jù)(例如,在合約升級(jí)的上下文中),您必須知道現(xiàn)在也會(huì)清除添加的成員(雖然過(guò)去不會(huì)清除它)。delete
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.1; contract C { struct S { uint64 y; uint64 z; } S s; function f() public { // ... delete s; // s occupies only first 16 bytes of the 32 bytes slot // delete will write zero to the full slot } }
對(duì)于隱式刪除,我們具有相同的行為,例如,當(dāng)結(jié)構(gòu)數(shù)組被縮短時(shí)。
函數(shù)修飾符的實(shí)現(xiàn)方式與函數(shù)參數(shù)和返回變量略有不同。如果在修飾符中多次計(jì)算占位符,這尤其有效。在舊的代碼生成器中,每個(gè)函數(shù)參數(shù)和返回變量在堆棧上都有一個(gè)固定的槽。如果函數(shù)由于多次使用或在循環(huán)中使用而多次運(yùn)行,則在下次執(zhí)行函數(shù)時(shí),對(duì)函數(shù)參數(shù)或返回變量值的更改可見(jiàn)。新的代碼生成器使用實(shí)際函數(shù)實(shí)現(xiàn)修飾符,并傳遞函數(shù)參數(shù)。這意味著對(duì)函數(shù)體的多次計(jì)算將獲得相同的參數(shù)值,并且對(duì)返回變量的影響是,對(duì)于每次執(zhí)行,它們都會(huì)重置為其默認(rèn)(零)值。_;
_;
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0; contract C { function f(uint a) public pure mod() returns (uint r) { r = a++; } modifier mod() { _; _; } }
如果在舊代碼生成器中執(zhí)行,它將返回 ,而在使用新代碼生成器時(shí)將返回。f(0)
2
1
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.1 <0.9.0; contract C { bool active = true; modifier mod() { _; active = false; _; } function foo() external mod() returns (uint ret) { if (active) ret = 1; // Same as ``return 1`` } }
該函數(shù)返回以下值:C.foo()
舊代碼生成器:因?yàn)榉祷刈兞吭诘谝淮吻笾抵皟H初始化為一次,然后被 .它不會(huì)在第二次評(píng)估中再次初始化,也不會(huì)顯式分配它(由于 ),因此它保留其第一個(gè)值。1
0
_;
return 1;
_;
foo()
active == false
新的代碼生成器:因?yàn)樗袇?shù)(包括返回參數(shù))將在每次評(píng)估之前重新初始化。0
_;
將數(shù)組從內(nèi)存復(fù)制到存儲(chǔ)以不同的方式實(shí)現(xiàn)。舊的代碼生成器總是復(fù)制完整的單詞,而新的代碼生成器在其結(jié)束后剪切字節(jié)數(shù)組。舊行為可能導(dǎo)致在陣列結(jié)束后(但仍在同一存儲(chǔ)插槽中)復(fù)制臟數(shù)據(jù)。這會(huì)導(dǎo)致某些合約的差異,例如:bytes
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.1; contract C { bytes x; function f() public returns (uint r) { bytes memory m = "tmp"; assembly { mstore(m, 8) mstore(add(m, 32), "deadbeef15dead") } x = m; assembly { r := sload(x.slot) } } }
以前會(huì)返回(它具有正確的長(zhǎng)度,并且正確的前8個(gè)元素,但隨后它包含通過(guò)程序集設(shè)置的臟數(shù)據(jù))?,F(xiàn)在它正在返回(它具有正確的長(zhǎng)度和正確的元素,但不包含多余的數(shù)據(jù))。f()
0x6465616462656566313564656164000000000000000000000000000000000010
0x6465616462656566000000000000000000000000000000000000000000000010
對(duì)于舊代碼生成器,表達(dá)式的計(jì)算順序是未指定的。對(duì)于新的代碼生成器,我們嘗試按源代碼順序(從左到右)進(jìn)行評(píng)估,但不保證。這可能導(dǎo)致語(yǔ)義差異。
例如:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.1; contract C { function preincr_u8(uint8 a) public pure returns (uint8) { return ++a + a; } }
該函數(shù)返回以下值:preincr_u8(1)
舊代碼生成器:3(),但返回值通常未指定1 + 2
新代碼生成器:4()但不保證返回值2 + 2
另一方面,函數(shù)參數(shù)表達(dá)式由兩個(gè)代碼生成器以相同的順序計(jì)算,但全局函數(shù)和 .例如:addmod
mulmod
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.1; contract C { function add(uint8 a, uint8 b) public pure returns (uint8) { return a + b; } function g(uint8 a, uint8 b) public pure returns (uint8) { return add(++a + ++b, a + b); } }
該函數(shù)返回以下值:g(1, 2)
舊代碼生成器:()但返回值通常未指定10
add(2 + 3, 2 + 3)
新代碼生成器:但不能保證返回值10
全局函數(shù)的參數(shù),由舊代碼生成器從右到左計(jì)算,由新代碼生成器從左到右計(jì)算。例如:addmod
mulmod
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.1; contract C { function f() public pure returns (uint256 aMod, uint256 mMod) { uint256 x = 3; // Old code gen: add/mulmod(5, 4, 3) // New code gen: add/mulmod(4, 5, 5) aMod = addmod(++x, ++x, x); mMod = mulmod(++x, ++x, x); } }
該函數(shù)返回以下值:f()
舊代碼生成器:和aMod = 0
mMod = 2
新的代碼生成器:和aMod = 4
mMod = 0
新的代碼生成器對(duì)可用內(nèi)存指針施加了 () 的硬限制。如果分配的值超過(guò)此限制,則會(huì)恢復(fù)。舊的代碼生成器沒(méi)有此限制。type(uint64).max
0xffffffffffffffff
例如:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >0.8.0; contract C { function f() public { uint[] memory arr; // allocation size: 576460752303423481 // assumes freeMemPtr points to 0x80 initially uint solYulMaxAllocationBeforeMemPtrOverflow = (type(uint64).max - 0x80 - 31) / 32; // freeMemPtr overflows UINT64_MAX arr = new uint[](solYulMaxAllocationBeforeMemPtrOverflow); } }
函數(shù) f() 的行為如下:
舊代碼生成器:在大內(nèi)存分配后將數(shù)組內(nèi)容清零時(shí)耗盡氣體
新的代碼生成器:由于可用內(nèi)存指針溢出而恢復(fù)(不會(huì)耗盡氣體)
舊的代碼生成器使用代碼偏移量或標(biāo)記作為內(nèi)部函數(shù)指針的值。這尤其復(fù)雜,因?yàn)檫@些偏移量在構(gòu)造時(shí)和部署后是不同的,并且值可以通過(guò)存儲(chǔ)跨越此邊界。因此,兩個(gè)偏移量在構(gòu)造時(shí)被編碼為相同的值(不同的字節(jié))。
在新的代碼生成器中,函數(shù)指針使用按順序分配的內(nèi)部 ID。由于無(wú)法通過(guò)跳轉(zhuǎn)進(jìn)行調(diào)用,因此通過(guò)函數(shù)指針的調(diào)用始終必須使用內(nèi)部調(diào)度函數(shù),該函數(shù)使用語(yǔ)句來(lái)選擇正確的函數(shù)。switch
該 ID 是為未初始化的函數(shù)指針保留的,這些指針在調(diào)用時(shí)會(huì)導(dǎo)致調(diào)度函數(shù)中的死機(jī)。0
在舊的代碼生成器中,內(nèi)部函數(shù)指針使用一個(gè)特殊函數(shù)進(jìn)行初始化,該函數(shù)總是會(huì)導(dǎo)致死機(jī)。這會(huì)導(dǎo)致在構(gòu)造時(shí)對(duì)存儲(chǔ)中的內(nèi)部函數(shù)指針進(jìn)行存儲(chǔ)寫(xiě)入。
舊代碼生成器僅在操作之前執(zhí)行清理,其結(jié)果可能受臟位值的影響。新的代碼生成器在任何可能導(dǎo)致臟位的操作后執(zhí)行清理。希望優(yōu)化器足夠強(qiáng)大,可以消除冗余的清理操作。
例如:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.1; contract C { function f(uint8 a) public pure returns (uint r1, uint r2) { a = ~a; assembly { r1 := a } r2 = a; } }
該函數(shù)返回以下值:f(1)
舊代碼生成器:(,fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe
00000000000000000000000000000000000000000000000000000000000000fe
)
新的代碼生成器:(,00000000000000000000000000000000000000000000000000000000000000fe
00000000000000000000000000000000000000000000000000000000000000fe
)
請(qǐng)注意,與新代碼生成器不同,舊代碼生成器在位不賦值 () 之后不執(zhí)行清理。這會(huì)導(dǎo)致分配不同的值(在內(nèi)聯(lián)程序集塊內(nèi))以在舊代碼生成器和新代碼生成器之間返回值。但是,在將 的新值 分配給 之前,這兩個(gè)代碼生成器都會(huì)執(zhí)行清理。a = ~a
r1
a
r2
更多建議: