Assembly是指使用汇编器转换为机器代码的任何低级编程语言。汇编语言与物理机或虚拟机相关联,因为它们实现了其指令集。一条指令只是告诉 CPU 执行一些基本任务,例如将两个数字相加
为什么Solidity中使用Assembly?
**细粒度控制(**汇编给了你更多的控制权来执行一些仅靠 Solidity 可能无法实现的逻辑。例如,指向特定的内存插槽。)
降低gas成本
增强功能
assembly { ... }出现在solidity内部叫内联汇编,{}之内的变量不能与外面进行访问,不同的内联汇编块不共享名称空间
demo
function add(uint x, uint y) public pure returns (uint) {
assembly {
// 创建一个新变量 `result`
// -> 使用 `add` 操作码计算 `x + y` 的和
// -> 将值赋给 `result`
let result := add(x, y) / / x + y
// 使用 `mstore` 操作码,以:
// -> 将 `result` 存储在内存中
// -> 在内存地址 0x0
mstore(0x0, result) // 将结果存储在内存中
// 从内存地址 0x0 返回 32 个字节
return(0x0, 32)
}
}
声明变量必须用let :=
该let
指令在幕后做了什么?
在 EVM 的内部工作中,let
执行以下操作:
创建一个新的栈槽
新槽是为变量保留的。
当到达块的末尾时,插槽将再次自动删除
文字也以与 Solidity 相同的方式编写。但是,字符串文字最多可以包含 32 个字符。
局部变量
function assembly_local_var_access() public pure {
uint b = 5;
assembly { // defined inside an assembly block
let x := add(2, 3)
let y := 10
z := add(x, y)
}
assembly { // defined outside an assembly block
let x := add(2, 3)
let y := mul(x, b)
}
}
solidity
function for_loop_solidity(uint n, uint value) public pure returns(uint) {
for ( uint i = 0; i < n; i++ ) {
value = 2 * value;
}
return value;
}
Assembly
function for_loop_assembly(uint n, uint value) public pure returns (uint) {
assembly {
for { let i := 0 } lt(i, n) { i := add(i, 1) } {
value := mul(2, value)
}
mstore(0x0, value)
return(0x0, 32)
}
}
没有while字段,但是可以用for改造
assembly {
let x := 0
let i := 0
for { } lt(i, 0x100) { } { // while(i < 256), 100 (hex) = 256
x := add(x, mload(i))
i := add(i, 0x20)
}
}
if 必须要有{}框住主体
assembly { if slt(x, 0) { x := sub(0, x) } // Ok
if eq(value, 0) revert(0, 0) // Error, curly braces needed}
多个条件考虑switch
case列表不需要大括号,但case主体需要大括号。
所有 case 值都需要具有 1) 相同的类型,以及 2) 不同的值。
如果涵盖了表达式类型的所有可能值,则不允许出现default case。
assembly {
let x := 34
switch lt(x, 30)
case true {
// do something
}
case false {
// do something els
}
default {
// this is not allowed
}
}
Solidity 支持 if { ... } else { ... } 语句、while、do 和 for 循环,但不支持 switch 语句。
当比较的值与一种情况匹配时,控制流停止。它不会从一个case继续到下一个case
assembly {
function allocate(length) -> pos {
pos := mload(0x40)
mstore(0x40, add(pos, length))
}
let free_memory_pointer := allocate(64)
}
从堆栈中获取参数
将结果入栈
使用符号指定返回值->
没有明确的return
说法,只需在最终语句中将其分配给返回变量,如果您在汇编函数中编写return,它将停止整个执行上下文(内部消息调用),而不仅仅是当前的汇编函数
关键字leave
可以放在汇编函数体内的任何位置,以停止其执行流程并退出,该函数将返回最后分配给返回变量的任何值
操作码总是 从堆栈的顶部获取参数(在括号中给出) 。
标有-
(第二列)的操作码不会将项目压入堆栈。对于列表中的大多数,它们返回内存中的值
所有其他操作码将项目压入堆栈(它们的*“返回”*值)。
操作码标有F
、H
和自 Frontier、Homestead、拜占庭和君士坦丁堡版本的以太坊以来B
就C
存在。
mem[a...b)
表示从 position 开始的内存字节数a
(但不包括 position b
。
storage[p]
表示位置 的存储内容p
返回多个值
assembly {
function f() -> a, b {}
let c, d := f()
}
// Functional style
mstore(0x80, add(mload(0x80), 3))
为了提高效率,EVM Assembly 将每个值都视为 256 位数字。
高阶位仅在必要时被清除(例如:在执行比较或写入内存之前)