内容简介:原文链接: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语言完成,内联库可以用来在某些方面增强语言的能力。
内联汇编在当编译器没办法得到有效率的代码时非常有用。但需要留意的是内联汇编语言写起来是比较难的,因为编译器不会进行一些检查,所以你应该只在复杂的,且你知道你在做什么的事情上使用它。
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 字节。
6
函数风格
你可以像使用字节码那样在操作码之后键入操作码。例如,把 3 与内存位置 0x80 处的数据相加就是:
由于通常很难看到某些操作码的实际参数是什么,所以 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
这个特性使用起来还是有点麻烦,因为在调用过程中堆栈偏移量发生了根本变化,因此对局部变量的引用将会出错。
8
标签
EVM 汇编的另一个问题是 jump 和 jumpi 函数使用绝对地址,这些绝对地址很容易改变。 Solidity 内联汇编提供了标签,以便更容易地使用 jump。注意,标签具有底层特征,使用循环、if 和 switch 指令(参见下文)而不使用标签也能写出高效汇编代码。 以下代码用来计算斐波那契数列中的一个元素。
请注意:只有汇编程序知道当前栈高度时,才能自动访问堆栈变量。如果 jump 源和目标的栈高度不同,访问将失败。 虽然我们可以这么使用 jump,但在这种情况下,你不应该去访问任何栈里的变量(即使是汇编变量)。
此外,栈高度分析器还可以通过操作码(而不是根据控制流)检查代码操作码,因此在下面的情况下,汇编程序对标签 two 处的堆栈高度会产生错误的印象:
9
汇编局部变量声明
你可以使用 let 关键字来声明只在内联汇编中可见的变量,实际上只在当前的 {...} 块中可见。 下面发生的事情应该是:let 指令将创建一个为变量保留的新数据槽,并在到达块末尾时自动删除。 你需要为变量提供一个初始值,它可以只是 0,但它也可以是一个复杂的函数风格表达式。
10
赋值
可以给汇编局部变量和函数局部变量赋值。请注意:当给指向内存或存储的变量赋值时,你只是更改指针而不是数据。
有两种赋值方式:函数风格和指令风格。对于函数风格赋值(变量 := 值),你需要在函数风格表达式中提供一个值,它恰好可以产生一个栈里的值; 对于指令风格赋值(=: 变量),则仅从栈顶部获取数据。对于这两种方式,冒号均指向变量名称。赋值则是通过用新值替换栈中的变量值来实现的。
11
If
if 语句可以用于有条件地执行代码,且没有“else”部分;如果需要多种选择,你可以考虑使用“switch”(见下文)。
12
Swicth
作为“if/else”的非常初级的版本,你可以使用 switch 语句。它计算表达式的值并与几个常量进行比较。选出与匹配常数对应的分支。 与某些编程语言容易出错的情况不同,控制流不会从一种情形继续执行到下一种情形。我们可以设定一个 fallback 或称为 default 的默认情况。
13
循环
汇编语言支持一个简单的 for-style 循环。For-style 循环有一个头,它包含初始化部分、条件和迭代后处理部分。 条件必须是函数风格表达式,而另外两个部分都是语句块。如果起始部分声明了某个变量,这些变量的作用域将扩展到循环体中(包括条件和迭代后处理部分)。
下面例子是计算某个内存区域中的数值总和。
For 循环也可以写成像 while 循环一样:只需将初始化部分和迭代后处理两部分留空。
14
函数
汇编语言允许定义底层函数。底层函数需要从栈中取得它们的参数(和返回 PC),并将结果放入栈中。调用函数的方式与执行函数风格操作码相同。
函数可以在任何地方定义,并且在声明它们的语句块中可见。函数内部不能访问在函数之外定义的局部变量。这里没有严格的 return 语句。
如果调用会返回多个值的函数,则必须使用 a,b:= f(x) 或 let a,b:= f(x) 的方式把它们赋值到一个元组。
下面例子通过平方和乘法实现了幂运算函数。
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日)
清华、牛津、中科院等博士与专家研发
精品小班面授课,与老师面对面交流
半年可免费复训,永久社群答疑服务
来源:链块学院
本文由布洛克专栏作者发布,代表作者观点,版权归作者所有,不代表布洛克科技观点
——TheEnd——
关注“布洛克科技”
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- iframe内联框架之巧妙跨域
- 提升go编译器内联程度
- 重学前端:块级元素与内联元素
- 内联第三方依赖到自己的包中
- [译] Go语言inline内联的策略与限制
- [译] Go 语言 inline 内联的策略与限制
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Web Designer's Idea Book
Patrick Mcneil / How / 2008-10-6 / USD 25.00
The Web Designer's Idea Book includes more than 700 websites arranged thematically, so you can find inspiration for layout, color, style and more. Author Patrick McNeil has cataloged more than 5,000 s......一起来看看 《The Web Designer's Idea Book》 这本书的介绍吧!