Solidity内嵌汇编学习
Posted MateZero
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Solidity内嵌汇编学习相关的知识,希望对你有一定的参考价值。
这里我们接下来学习Solidity官方文档内嵌汇编的第二个示例学习VectorSum
。本文依旧以Solidity 0.8.7官方文档进行学习。
有了第一个示例的学习做铺垫,我们这里的学习就容易多了,什么也不说,直接上示例源码:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
library VectorSum
// This function is less efficient because the optimizer currently fails to
// remove the bounds checks in array access.
function sumSolidity(uint[] memory _data) public pure returns (uint sum)
for (uint i = 0; i < _data.length; ++i)
sum += _data[i];
// We know that we only access the array in bounds, so we can avoid the check.
// 0x20 needs to be added to an array because the first slot contains the
// array length.
function sumAsm(uint[] memory _data) public pure returns (uint sum)
for (uint i = 0; i < _data.length; ++i)
assembly
sum := add(sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
// Same as above, but accomplish the entire code within inline assembly.
function sumPureAsm(uint[] memory _data) public pure returns (uint sum)
assembly
// Load the length (first 32 bytes)
let len := mload(_data)
// Skip over the length field.
//
// Keep temporary variable so it can be incremented in place.
//
// NOTE: incrementing _data would result in an unusable
// _data variable after this assembly block
let data := add(_data, 0x20)
// Iterate until the bound is not met.
for
let end := add(data, mul(len, 0x20))
lt(data, end)
data := add(data, 0x20)
sum := add(sum, mload(data))
这里要吐槽一下CSDN的markdown编辑器,当前还不支持Solidity代码块,平常我使用的Typora是支持的。
因为这里的代码比较简单,所以我们既不修改合约也不进行单元测试了,直接学习代码即可。
第一个函数sumSolidity
是传统的操作,当然也是推荐的操作。除非我们知道自己在做什么并且必须这么做,否则我们一般不使用内嵌汇编。
这里注释提到,因为边界检查无法移除,所以无法优化。
第二个函数:sumAsm
。对过注释我们知道,该函数移除了边界检查,那么边界检查在哪发生的呢?通过对比我们很容易得到结论,边界检查发生在sumSolidity
函数的_data[i]
操作中。那么是怎么避免的呢?通过直接读取相应的内存数据而不是通过索引访问来避免。 注意,动态数组也是一种动态大小类型的数据,所以变量_data
是存储的它的内存地址。并且它也有一个长度前缀,该前缀同样为32字节,和bytes
类型一样。这样,sum := add(sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
这行代码就很容易理解了。
add(_data, 0x20)
是数据地址加上长度前缀,这样就得到第一个元素的地址了。
mul(i, 0x20)
是第i
个元素的偏移量,因为一个uint256是32字节,所以这里是 i * 0x20
。其它,就算函数参数中的_data
是个uint8
数组,由于会拓展为256
位,所以这里的偏移量还是i * 0x20
。在EVM内部,不管是uint几,都是统一转换为uint256进行计算的。
第一个元素地址+ 偏移量,就得到了每个元素的地址,然后mload
读取该字节内容就是该元素的值了。
最后是在循环中累加sum
,很简单。
第三个函数更进了一步,将循环也放在内嵌汇编中去了。这里他使用的是地址作为索引进行循环遍历。
首先得到数组的长度,也就是_data
的第一个字节(长度前缀),再次注意这里_data
的值为它的起始内存地址,而不是真实内容。
接下来将_data
的地址加上0x20
(32),也就是得到第一个真实元素的内存地址。
接下来是for循环遍历,这里的语法稍微古怪一下,我们记住都好。它同样遵循初始条件,迭代条件和迭代操作这三个部分,不过每一部分都是单独一行。
初始条件为计算了最后一个元素结束后的内存地址,同样是第一个元素的内存地址加上整体偏移量。注意这里不是最后一个元素的内存地址,因为最后一个元素的内存地址的偏移量为(len-1) * 0x20
。在最后一个元素的内存地址再加了一个0x20
(最后一个元素内容),表示数组结束了。
迭代条件为一个lt
函数,它比较两个参数的值,如果第一个参数小于第二个参数,也就是只要元素的内存地址没有到结束地址,就返回1,否则返回0。这里可以看到1其实代表的是true,0代表的是false。
迭代操作一般用来更新索引,这里也不例外,相当于地址后移一个元素(32字节)。
循环体内的代码就很简单了,取出每个元素的值(mload
),然后进行累加。
好了,今天的学习就到此结了。由于水平有限,有什么写的不正确的恳请大家指正。
以上是关于Solidity内嵌汇编学习的主要内容,如果未能解决你的问题,请参考以下文章