# Wasm介绍之4:函数调用

image.png

上一篇文章介绍了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     | 
└───────────┘          └───────────┘

函数调用

函数调用指令属于控制指令,一共有两条:callcall_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例子展示了calllocal.getlocal.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应用。

推荐阅读更多精彩内容