智能合约基础语言(十):Solidity内联汇编

栏目: 编程语言 · 发布时间: 5年前

内容简介:原文链接:http://www.liankuai.tech/public/technology/108.html

原文链接:http://www.liankuai.tech/public/technology/108.html

1

目录

☞概念

☞语法

☞操作码

☞字面量

☞函数风格

☞访问外部函数与变量

☞标签

☞定义局部变量

☞赋值

☞Switch

☞循环

☞函数

☞内联汇编中注意事项

☞Solidity中的惯例

2

概念

通常我们通过库代码,来增强语言,实现一些精细化的控制,Solidity为我们提供了一种接近于EVM底层的语言,内联汇编,允许与Solidity结合使用。由于EVM是栈式的,所以有时定位栈比较麻烦,Solidty的内联汇编为我们提供了下述的特性,来解决手写底层代码带来的各种问题:

• 允许函数风格的操作码:mul(1, add(2, 3))等同于push1 3 push1 2 add push1 1 mul

• 内联局部变量:let x := add(2, 3) let y := mload(0x40) x := add(x, y)

• 可访问外部变量:function f(uint x) { assembly { x := sub(x, 1) } }

• 标签:let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))

• 循环:for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }

• switch语句:switch x case 0 { y := mul(x, 2) } default { y := 0 }

• 函数调用:function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }

需要注意的是内联汇编是一种非常底层的方式来访问EVM虚拟机。他没有Solidity提供的多种安全机制。

2.1 示例

下面的例子提供了一个库函数来访问另一个合约,并把它写入到一个bytes变量中。有一些不能通过常规的Solidity语言完成,内联库可以用来在某些方面增强语言的能力。

智能合约基础语言(十):Solidity内联汇编

内联汇编在当编译器没办法得到有效率的代码时非常有用。但需要留意的是内联汇编语言写起来是比较难的,因为编译器不会进行一些检查,所以你应该只在复杂的,且你知道你在做什么的事情上使用它。

智能合约基础语言(十):Solidity内联汇编

3

语法

内联汇编语言也会像Solidity一样解析注释,字面量和标识符。所以你可以使用//和/**/的方式注释。内联汇编的在Solidity中的语法是包裹在assembly { ... },下面是可用的语法,更详细的语法请参考官方API https://solidity.readthedocs.io/en/v0.4.21/assembly.html。

• 字面量。如0x123,42或abc(字符串最多是32个字符)

• 操作码(指令的方式),如mload sload dup1 sstore,后面有可• 支持的指令列表

• 函数风格的操作码,如add(1, mlod(0))

• 标签,如name:

• 变量定义,如let x := 7 或 let x := add(y, 3)

• 标识符(标签或内联局部变量或外部),如jump(name),3 x add

• 赋值(指令风格),如,3 =: x。

• 函数风格的赋值,如x := add(y, 3)

• 支持块级的局部变量,如{ let x := 3 { let y := add(x, 1) } }

4

操作码

如果一个操作码有参数(通过在栈顶),那么他们会放在括号。需要注意的是参数的顺序可以颠倒(非函数风格,后面会详细说明)。用-标记的操作码不会将一个参数推到栈顶,而标记为*的是非常特殊的,所有其它的将且只将一个推到栈顶。

在后面的例子中,mem[a...b)表示成位置a到位置b(不包含)的memory字节内容,storage[p]表示在位置p的strorage内容。

操作码pushi和jumpdest不能被直接使用。在语法中,操作码被表示为预先定义的标识符。

5

字面常量

你可以直接键入十进制或十六进制符号来作为整型常量使用,这会自动生成相应的 PUSHi 指令。 下面的代码将计算 2 加 3(等于 5),然后计算其与字符串 “abc” 的按位与。字符串在存储时为左对齐,且长度不能超过 32 字节。

智能合约基础语言(十):Solidity内联汇编

6

函数风格

你可以像使用字节码那样在操作码之后键入操作码。例如,把 3 与内存位置 0x80 处的数据相加就是:

智能合约基础语言(十):Solidity内联汇编

由于通常很难看到某些操作码的实际参数是什么,所以 Solidity 内联汇编还提供了一种“函数风格”表示法,同样功能的代码可以写做:

智能合约基础语言(十):Solidity内联汇编

函数风格表达式内不能使用指令风格的写法,即 1 2 mstore(0x80, add) 是无效汇编语句, 它必须写成 mstore(0x80, add(2, 1)) 这种形式。对于不带参数的操作码,括号可以省略。

注意,在函数风格写法中参数的顺序与指令风格相反。如果使用函数风格写法,第一个参数将会位于栈顶。

7

访问外部变量和函数

通过简单使用它们名称就可以访问 Solidity 变量和其他标识符。对于内存变量,这会将地址而不是值压入栈中。 存储变量是不同的,因为存储变量的值可能不占用完整的存储槽,因此其“地址”由存储槽和槽内的字节偏移量组成。 为了获取变量 x 所使用的存储槽,你可以使用 x_slot,并用的 x_offset 获取其字节偏移量。

在赋值语句中(见下文),我们甚至可以使用 Solidity 局部变量来赋值。

对于内联汇编而言的外部函数也可以被访问:汇编会将它们的入口标签(带有虚拟函数解析)压入栈中。Solidity 中的调用语义为:

• 调用者压入 return label、arg1、arg2、...、argn

• 被调用方返回 ret1、ret2、...、retm

这个特性使用起来还是有点麻烦,因为在调用过程中堆栈偏移量发生了根本变化,因此对局部变量的引用将会出错。

智能合约基础语言(十):Solidity内联汇编

8

标签

EVM 汇编的另一个问题是 jump 和 jumpi 函数使用绝对地址,这些绝对地址很容易改变。 Solidity 内联汇编提供了标签,以便更容易地使用 jump。注意,标签具有底层特征,使用循环、if 和 switch 指令(参见下文)而不使用标签也能写出高效汇编代码。 以下代码用来计算斐波那契数列中的一个元素。

智能合约基础语言(十):Solidity内联汇编

请注意:只有汇编程序知道当前栈高度时,才能自动访问堆栈变量。如果 jump 源和目标的栈高度不同,访问将失败。 虽然我们可以这么使用 jump,但在这种情况下,你不应该去访问任何栈里的变量(即使是汇编变量)。

此外,栈高度分析器还可以通过操作码(而不是根据控制流)检查代码操作码,因此在下面的情况下,汇编程序对标签 two 处的堆栈高度会产生错误的印象:

智能合约基础语言(十):Solidity内联汇编

9

汇编局部变量声明

你可以使用 let 关键字来声明只在内联汇编中可见的变量,实际上只在当前的 {...} 块中可见。 下面发生的事情应该是:let 指令将创建一个为变量保留的新数据槽,并在到达块末尾时自动删除。 你需要为变量提供一个初始值,它可以只是 0,但它也可以是一个复杂的函数风格表达式。

智能合约基础语言(十):Solidity内联汇编

10

赋值

可以给汇编局部变量和函数局部变量赋值。请注意:当给指向内存或存储的变量赋值时,你只是更改指针而不是数据。

有两种赋值方式:函数风格和指令风格。对于函数风格赋值(变量 := 值),你需要在函数风格表达式中提供一个值,它恰好可以产生一个栈里的值; 对于指令风格赋值(=: 变量),则仅从栈顶部获取数据。对于这两种方式,冒号均指向变量名称。赋值则是通过用新值替换栈中的变量值来实现的。

智能合约基础语言(十):Solidity内联汇编

11

If

if 语句可以用于有条件地执行代码,且没有“else”部分;如果需要多种选择,你可以考虑使用“switch”(见下文)。

智能合约基础语言(十):Solidity内联汇编

12

Swicth

作为“if/else”的非常初级的版本,你可以使用 switch 语句。它计算表达式的值并与几个常量进行比较。选出与匹配常数对应的分支。 与某些编程语言容易出错的情况不同,控制流不会从一种情形继续执行到下一种情形。我们可以设定一个 fallback 或称为 default 的默认情况。

智能合约基础语言(十):Solidity内联汇编

13

循环

汇编语言支持一个简单的 for-style 循环。For-style 循环有一个头,它包含初始化部分、条件和迭代后处理部分。 条件必须是函数风格表达式,而另外两个部分都是语句块。如果起始部分声明了某个变量,这些变量的作用域将扩展到循环体中(包括条件和迭代后处理部分)。

下面例子是计算某个内存区域中的数值总和。

智能合约基础语言(十):Solidity内联汇编

For 循环也可以写成像 while 循环一样:只需将初始化部分和迭代后处理两部分留空。

智能合约基础语言(十):Solidity内联汇编

14

函数

汇编语言允许定义底层函数。底层函数需要从栈中取得它们的参数(和返回 PC),并将结果放入栈中。调用函数的方式与执行函数风格操作码相同。

函数可以在任何地方定义,并且在声明它们的语句块中可见。函数内部不能访问在函数之外定义的局部变量。这里没有严格的 return 语句。

如果调用会返回多个值的函数,则必须使用 a,b:= f(x) 或 let a,b:= f(x) 的方式把它们赋值到一个元组。

下面例子通过平方和乘法实现了幂运算函数。

智能合约基础语言(十):Solidity内联汇编

15

注意事项

内联汇编语言可能具有相当高级的外观,但实际上它是非常低级的编程语言。函数调用、循环、if 语句和 switch 语句通过简单的重写规则进行转换, 然后,汇编程序为你做的唯一事情就是重新组织函数风格操作码、管理 jump 标签、计算访问变量的栈高度,还有在到达语句块末尾时删除局部汇编变量的栈数据。 特别是对于最后两种情况,汇编程序仅会按照代码的顺序计算栈的高度,而不一定遵循控制流程;了解这一点非常重要。此外,swap 等操作只会交换栈内的数据,而不是变量位置。

16

Solidity惯例

与 EVM 汇编语言相比,Solidity 能够识别小于 256 位的类型,例如 uint24。为了提高效率,大多数算术运算只将它们视为 256 位数字, 仅在必要时才清除未使用的数据位,即在将它们写入内存或执行比较之前才会这么做。这意味着,如果从内联汇编中访问这样的变量,你必须先手工清除那些未使用的数据位。

Solidity 以一种非常简单的方式管理内存:在 0x40 的位置有一个“空闲内存指针”。如果你打算分配内存,只需从此处开始使用内存,然后相应地更新指针即可。

内存的开头 64 字节可以用来作为临时分配的“暂存空间”。“空闲内存指针”之后的 32 字节位置(即从 0x60 开始的位置)将永远为 0,可以用来初始化空的动态内存数组。

在 Solidity 中,内存数组的元素总是占用 32 个字节的倍数(是的,甚至对于 byte[] 都是这样,只有 bytes 和 string 不是这样)。 多维内存数组就是指向内存数组的指针。动态数组的长度存储在数组的第一个槽中,其后才是数组元素。

-END-

在职学习区块链,转行区块链工程师

-月薪30-50k-

现在报名面授班即可减免学费1000元

(优惠截止10月24日)

清华、牛津、中科院等博士与专家研发

精品小班面授课,与老师面对面交流

半年可免费复训,永久社群答疑服务

智能合约基础语言(十):Solidity内联汇编

来源:链块学院

本文由布洛克专栏作者发布,代表作者观点,版权归作者所有,不代表布洛克科技观点

——TheEnd——

关注“布洛克科技”

智能合约基础语言(十):Solidity内联汇编


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法分析导论(第2版)(英文版)

算法分析导论(第2版)(英文版)

[美]Robert Sedgewick(罗伯特•塞奇威克)、[美]Philippe Flajolet(菲利普•弗拉若莱) / 电子工业出版社 / 2015-6 / 128.00元

《算法分析导论(第2版)(英文版)》全面介绍了算法的数学分析中所涉及的主要技术。涵盖的内容来自经典的数学课题(包括离散数学、初等实分析、组合数学),以及经典的计算机科学课题(包括算法和数据结构)。《算法分析导论(第2版)(英文版)》的重点是“平均情况”或“概率性”分析,书中也论述了“最差情况”或“复杂性”分析所需的基本数学工具。 《算法分析导论(第2版)(英文版)》第 1 版为行业内的经典著......一起来看看 《算法分析导论(第2版)(英文版)》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换