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),然后进行累加。

好了,今天的学习就到此结了。由于水平有限,有什么写的不正确的恳请大家指正。

猜你喜欢

转载自blog.csdn.net/weixin_39430411/article/details/124376961