上一篇文章介绍了WebAssembly(简称Wasm)内存和相关指令,这篇文章将介绍变量指令和函数调用指令。
全局变量
Wasm模块可以定义或者导入全局变量。导入时,可以限定全局变量的类型和可修改性(mutability)。定义时,除了限定类型和可修改性还可以给定初始值。下面是一个WAT例子,展示了全局变量的导入和定义:
(module
(import "env" "g1" (global $g1 i32)) ;; immutable
(import "env" "g2" (global $g2 (mut f32))) ;; mutable
(global $g3 (mut i32) (i32.const 123)) ;; mutable
(global $g4 (mut i64) (i64.const 456)) ;; mutable
(global $g5 f32 (f32.const 1.5)) ;; immutable
(global $g6 f64 (f64.const 2.5)) ;; immutable
(func $main
;; $g3 = $g1
(global.get $g1)
(global.set $g3)
)
)
复制代码
变量指令一共5条,其中2条用来读写全局变量,下面分别介绍。
global.get
global.get
指令(操作码0x23
)把全局变量的值推入栈顶,全局变量的索引由指令的立即数参数(32位无符号整数)给定。下面是global.get
指令的示意图:
bytecode:
...][ global.get ][ global_idx ][...
stack:
| | | |
| | ➘|globals[i] |
| d | | d |
| c | | c |
| b | | b |
| a | | a |
└───────────┘ └───────────┘
global.set
global.set
指令(操作码0x24
)从栈顶弹出一个操作数,赋值给全局变量(弹出的操作数必须和全局变量类型相同)。和global.get
指令一样,全局变量的索引也是由指令的立即数参数给定。下面是global.set
指令的示意图:
bytecode:
...][ global.set ][ global_idx ][...
stack:
| | | |
| | | |
| d |➚ | | # globals[global_idx] = d
| c | | c |
| b | | b |
| a | | a |
└───────────┘ └───────────┘
局部变量
全局变量的作用域是整个Wasm模块,局部变量的作用域则是整个函数。下面是一个WAT例子,展示了局部变量的定义和使用:
(module
(func $main (param $a i32) (param $b f32)
(local $c i32)
(local $d i64)
(local $e f32)
(local $f f64)
;; $c = $a
(local.get $a)
(local.set $c)
)
)
从上面的例子可以看到,函数的参数本质上其实就是局部变量。变量指令的其余3条用来读写局部变量,下面分别介绍。
local.get
local.get
指令(操作码0x20
)和global.get
指令类似,只不过读的是局部变量。下面是local.get
指令的示意图:
bytecode:
...][ local.get ][ local_idx ][...
stack:
| | | |
| | ➘| locals[i] |
| d | | d |
| c | | c |
| b | | b |
| a | | a |
└───────────┘ └───────────┘
local.set
local.set
指令(操作码0x21
)和global.set
指令类似,只不过写的是局部变量。下面是local.set
指令的示意图:
bytecode:
...][ local.set ][ local_idx ][...
stack:
| | | |
| | | |
| d |➚ | | # locals[local_idx] = d
| c | | c |
| b | | b |
| a | | a |
└───────────┘ └───────────┘
local.tee
local.tee
指令(操作码0x22
)指令和local.set
指令类似,差别在于local.tee
指令会把操作数留在栈顶。下面是local.tee
指令的示意图:
bytecode:
...][ local.tee ][ local_idx ][...
stack:
| | | |
| | | |
| d |➚ ➘| d | # locals[local_idx] = d
| c | | c |
| b | | b |
| a | | a |
└───────────┘ └───────────┘
函数调用
函数调用指令属于控制指令,一共有两条:call
和call_indirect
。本文只介绍call
指令,call_indirect
和其余控制指令将在后续文章中介绍。
call
call
指令(操作码0x10
)进行函数调用,函数索引由指令的立即数参数(32位无符号整数)指定。在执行该指令之前,需要把函数的参数准备好,按顺序放在栈顶(最左边的参数在最下面)。指令执行完毕后,参数已经从栈顶弹出,取而代之的是函数的返回值(如果有的话)。Wasm1.0规范规定函数的返回值最多不超过一个,后续版本可能会放开这个限制。下面是call
指令的示意图(假设被调用函数接受两个i32
类型的参数,返回一个i32
类型的值):
bytecode:
...][ call ][ func_idx ][...
stack:
| | | |
| | | |
| d |➚ | |
| c |➚ ➘| r | # funcs[func_idx](c,d)
| b | | b |
| a | | a |
└───────────┘ └───────────┘
下面的WAT例子展示了call
、local.get
、local.set
等指令的用法:
(module
(func $main (export "main") (result i32)
(call $max (i32.const 20) (i32.const 80))
)
(func $max (param $a i32) (param $b i32) (result i32)
(local.get $a)
(local.get $b)
(i32.gt_s (local.get $a) (local.get $b))
(select)
)
)
*本文由CoinEx Chain开发团队成员Chase撰写。CoinEx Chain是全球首条基于Tendermint共识协议和Cosmos SDK开发的DEX专用公链,借助IBC来实现DEX公链、智能合约链、隐私链三条链合一的方式去解决可扩展性(Scalability)、去中心化(Decentralization)、安全性(security)区块链不可能三角的问题,能够高性能的支持数字资产的交易以及基于智能合约的Defi应用。