编译:登链翻译计划;来源:soliditylang.org
Solidity编译器的最新版本v0.8.22的发布。0.8.22版本的编译器包含了一系列的语言和编译器改进,例如文件级事件定义、unchecked
循环增量优化、支持导入EVM汇编JSON 等等。
重要提示
本次发布废弃了对低于 Constantinople(君士坦丁堡) 版本的 EVM 的支持,这些版本越来越难以维护。这些古老的版本在以太坊主网和测试网络上早已过时,我们怀疑它们对其他网络也不再相关。复杂的代码路径和解决方案会减慢针对新版本的功能开发和测试的速度,因此我们希望在未来的编译器版本中停止支持它们。
新功能亮点
unchecked 循环增量
在增加循环计数器时使用 unchecked
算术运算是为了 gas 优化[7]的常见做法。让我们以以下循环和计数器 i 的示例来说明:
for (uint i = 0; i < array.length; ++i) {
acc += array[i]; // i is not modified by the loop body
}
在许多情况下(详见下文的精确条件),比较操作将确保 i 永远不会达到其类型的最大值,因此可以安全地假设在达到最大值之前循环将停止。在这种情况下,对计数器进行安全检查将是多余的,也是一种 gas 的浪费。这就会鼓励用户使用冗长的 unchecked
模式,将计数器增量包装在循环体内的unchecked
算术块中,绕过检查:
for (uint i = 0; i < array.length;) {
acc += array[i];
unchecked { i++; } // i gets incremented without overflow checks -- less gas used
}
Solidity 0.8.22 引入了一种溢出检查优化,自动生成了对于循环计数器的 unchecked
算术增量。这个新的优化消除了在循环体中使用类似前面示例中的冗长 unchecked
增量模式的需要。
相比之下,新的优化使用户能够在不牺牲gas效率的情况下返回到原始的、更易读的代码。
新优化避免溢出检查的精确条件如下:
-
循环条件是形如
i < ...
的比较,其中 i 是一个局部变量(从现在开始称为“循环计数器”)。 -
此比较必须在与循环计数器相同的类型上执行,即右侧的类型必须可以隐式转换为循环计数器的类型,以使循环计数器在比较之前不会被隐式扩展。
-
循环计数器必须是内置整数类型的局部变量。
-
循环表达式必须是循环计数器的前缀或后缀递增,即 i++ 或 ++i。
-
循环计数器不能在循环条件或循环体中被修改。
为了澄清第二个条件,考虑以下代码片段:
for (uint8 i = 0; i < uint16(1000); ++i) {
// loop body
}
在这种情况下, i 在比较之前被转换为 uint16,并且条件实际上永远不会为假,因此无法删除递增的溢出检查。
另外,请注意, <
是唯一会触发该优化的比较运算符。故意排除了运算符 <=
和其他运算符。此外,该运算符必须是内置的 – 用户定义的 <
不符合条件。
该优化是直接的且总是有益的,因此即使使用通用设置 settings.optimizer.enabled
禁用了优化器的其余部分,它也会被启用。可以通过在标准 JSON 输入中将 settings.optimizer.details.simpleCounterForLoopUncheckedIncrement
设置为 false
来明确关闭它。无法使用命令行界面禁用它。
调整Yul优化器以重新生成零字面量
新版本在 0.8.20 版本引入的 PUSH0 操作码的支持基础上进行了构建,通过将 Rematerialiser[8] 优化步骤扩展为始终重新生成零字面量而不是将其存储为变量引用,从而允许使用 PUSH0 而不是 DUP 来降低gas成本。为确保有效执行此操作,将 Rematerialiser 和 UnusedPruner[9] 步骤添加到了Yul优化器的默认清理序列中。
添加对导入 EVM 汇编 JSON 的支持(实验性)
这个新版本添加了对导入 EVM 汇编的实验性支持,为外部工具在字节码生成之前执行超级优化提供了可能性。此功能的主要目的是定义一种低级 EVM 汇编的序列化格式,使编译器生成的汇编可以被导出、修改和重新导入,从而恢复正常的编译过程。
重要提示:这是一个实验性功能,目前不适用于生产环境。我们在此版本中提供此功能,以便你尝试并提供反馈。
允许在文件级别定义事件
Solidity 0.8.22 允许你在文件级别定义事件。现在,事件定义可以放在合约范围之外。这为代码组织提供了另一种选择,无需人为地将事件包装在库中。
此外,此版本还修复了一个错误,该错误导致在为代码发出在外部合约或接口中定义的事件时生成 NatSpec 时出错。在上一个版本(0.8.21)中,Solidity编译器添加了对在当前合约未继承的合约和接口中定义的事件的限定访问的支持,但该错误阻止了该功能的完全使用。
通过此错误修复和允许文件级别事件定义,Solidity的最新版本使用户能够编译以下示例而不会出现任何错误:
interface I {
event ForeignEvent();
}
contract C {
event ForeignEvent();
}
event E();
contract D {
function f() public {
// Emitting a foreign event would trigger an internal error on 0.8.21
emit I.ForeignEvent();
emit C.ForeignEvent();
// Emitting a file-level event. New feature.
emit E();
}
}
完整的更改日志
语言特性
-
允许在文件级别定义事件。
编译器特性
-
代码生成器:当计数变量不会溢出时,删除某些
for
循环的冗余溢出检查。 -
命令行界面:添加
--no-import-callback
选项,防止编译器加载未在 CLI 或标准 JSON 输入中明确给出的源文件。 -
命令行界面:添加实验性的
--import-asm-json
选项,可以导入以--asm-json
使用的格式的 EVM 汇编。 -
命令行界面:对于在编译 pipeline 之外产生的错误消息,使用适当的严重性和着色。
-
EVM:弃用对“homestead”、“tangerineWhistle”、“spuriousDragon”和“byzantium” EVM 版本的支持。
-
解析器(Parser):删除实验性的错误恢复模式(
--error-recovery
/settings.parserErrorRecovery
)。 -
SMTChecker:支持用户定义的运算符。
-
Yul 优化器:如果支持
PUSH0
,优先使用零字面量而不是将零值存储在变量中。 -
Yul 优化器:在默认的清理序列的末尾运行
Rematerializer
和UnusedPruner
步骤。
Bug 修复
-
代码生成器:修复通过 via-IR 代码生成器输出的结果依赖于导入回调中发现的文件的问题。在某些情况下,不同的 AST ID 分配会改变内部调度中函数的顺序,导致表面上不同但在语义上等效的字节码。
-
NatSpec:修复在请求合约的用户文档或开发文档时出现内部错误的问题,该合约发出了在外部合约或接口中定义的事件。
-
SMTChecker:修复编码错误,导致循环在完成后展开。
-
SMTChecker:修复常量条件检查的不一致性,当 while 或 for 循环在条件检查之前展开时。
-
Yul 优化器:修复在 CSE 期间受编译器生成的Yul变量名影响替换决策的问题,在某些情况下导致不同(但等效)的字节码。