webassembly学习-动态链接

一、动态链接技术

搞过C/C++编程的开发人员都知道,动态库技术是今天几乎大型程序的标配。而动态库技术上,涉及到的一个最基本的问题就是动态加载动态链接技术。做一项更基础的中间语言,WebAssembly 必须提供类似的动态链接技术。其和c/c++的动态库中的动态链接技术一样,通过模块的导入和导出允许N个实例化模块来共享函数、线性内存、 表和常量。(在Iinux中,动态库动态链接使用dlopen函数)。同样在WASM中启用加载和动态运行时的动态链接,尤其是非本地的一些状态都可以通过动态链接来实现对其的共享。因此,对WebAssembly的相关开发编译工具需要支持类似的功能。
一般来说,WebAssembly需要由宿主来提供运行环境,所以宿主环境必须提供这种动态导入实例化模块的方法。

二、WASM如何动态加载

先看一段代码:

int test1(){ 
   return 100; 
}
int test2(){ 
   return 200; 
}
#include <stdio.h>

int test1(); 
int test2();
int main() { 
   int result = test1() + test2(); 
   return result; 
}

使用相关工具命令编译:

emcc test1.c test2.c main.c -s SIDE_MODULE=1 -o maintest.wasm

然后使用WasmToWat这个工具(https://webassembly.github.io/wabt/demo/wasm2wat/)来转换成文本:

(module 
   (type $t0 (func (result i32))) (type $t1 (func)) 
   (type $t2 (func (param i32))) (type $t3 (func (param i32 i32) (result i32))) 
   (import "env" "stackSave" (func $env.stackSave (type $t0))) 
   (import "env" "stackRestore" (func $env.stackRestore (type $t2))) 
   (import "env" "__memory_base" (global $env.__memory_base i32)) 
   (import "env" "__table_base" (global $env.__table_base i32)) 
   (import "env" "memory" (memory $env.memory 0)) 
   (import "env" "table" (table $env.table 0 funcref)) 
   (func $f2 (type $t1) 
      (call $__wasm_apply_relocs)
   )
   (func $__wasm_apply_relocs (export "__wasm_apply_relocs") (type $t1)) 
   (func $test1 (export "test1") (type $t0) (result i32) 
      (local $l0 i32) 
      (local.set $l0 
         (i32.const 100)
      )
      (return 
         (local.get $l0)
      )
   )
   (func $test2 (export "test2") (type $t0) (result i32) 
      (local $l0 i32) 
      (local.set $l0 
         (i32.const 200)) 
      (return 
         (local.get $l0)
      )
   ) 
   (func $__original_main 
      (export "__original_main") 
      (type $t0) 
      (result i32) 
      (local $l0 i32) 
      (local $l1 i32) 
      (local $l2 i32) 
      (local $l3 i32) 
      (local $l4 i32) 
      (local $l5 i32) 
      (local $l6 i32) 
      (local $l7 i32) 
      (local $l8 i32) 
      (local $l9 i32) 
      (local.set $l0(call $env.stackSave))
      (local.set $l1 (i32.const 16))
      (local.set $l2 (i32.sub (local.get $l0) (local.get $l1)))
      (call $env.stackRestore (local.get $l2) ) (local.set $l3(i32.const 0)) 
      (i32.store offset=12 (local.get $l2) (local.get $l3)) 
      (local.set $l4 (call $test1)) 
      (local.set $l5 (call $test2)) 
      (local.set $l6 (i32.add (local.get $l4) (local.get $l5))) 
      (i32.store offset=8 (local.get $l2) (local.get $l6)) 
      (local.set $l7 (i32.load offset=8 (local.get $l2))) 
      (local.set $l8 (i32.const 16)) 
      (local.set $l9 (i32.add (local.get $l2) (local.get $l8))) 
      (call $env.stackRestore (local.get $l9)) (return(local.get $l7))
   )
   (func $main 
      (export "main") 
      (type $t3) 
      (param $p0 i32) 
      (param $p1 i32) 
      (result i32) 
      (local $l2 i32) 
      (local.set $l2 
      (call $__original_main)) 
      (return (local.get $l2))
   ) 
   (func $__post_instantiate (export "__post_instantiate") (type $t1) (call $f2)) 
   (global $__dso_handle (export "__dso_handle") i32 (i32.const 0))
)

在经过上述的动作后,WebAssembly会产生导入和导出两个方向的标记即功能imp和功能exp。如果在实际的应用中使用了libc则会增加对线性内存及相关变量的导入。在C和c++的应用中,前面提到过,使用dloepn,dlsym,dlopen将编译并实例化一个新模块,将编译后的实例存储在主机环境表中,并将索引返回给调用者。dlsym将获得此索引,将实例从表中取出,搜索实例相关的导出,将找到的函数附加到函数表并返回表附加到Caller的元素的索引。
需要说明的是,WebAssembly 中 C 函数指针的表示是函数表的索引,因此上述方案与dlsym函数指针的返回值完全一致。
其实大家都明白,能不能提供一个标准的ABI接口标准,这才是重要的事情,可惜,目前在C/C++是还无法做到。

而要在JS中使用这个例程可以如下操作:

<meta charset="UTF-8">
   
      <script>
         var wasmMemory = new WebAssembly.Memory({'initial': 256,'maximum': 65536}); 
         const importObj = {
            env: {
               stackSave: n => 2, stackRestore: n => 3, //abortStackOverflow: () => {
                  throw new Error('overflow'); 
               }, 
               table: new WebAssembly.Table({ 
                  initial: 0, maximum: 65536, element: 'anyfunc' 
               }), __table_base: 0,
               memory: wasmMemory, __memory_base: 256 
            } 
         };
         fetch("maintest.wasm") .then(bytes => bytes.arrayBuffer()) .then(
            module => WebAssembly.instantiate(module, importObj)
         )
         .then(finalcode => {        
            console.log(finalcode);     
            console.log(WebAssembly.Module.imports(finalcode.module)); 
            console.log(finalcode.instance.exports.test1());    
            console.log(finalcode.instance.exports.test2());   
            console.log(finalcode.instance.exports.main()); 
         });
      </script>
   

本文的例程使用是芒果文档相关例程:
https://www.imangodoc.com/29060.html

三、、总结

webassembly目前的效率已经非常接近于本地运行的代码,一些3D软件和大型的游戏的测试表明,其相对于传统的Javascript有着压倒性的优势。其实认真分析看来,还是解释型的弱语言终究要牺牲效率和速度,wasm不过是掉了一个花枪而已。不过话又说回来,这个花枪掉得好,让开发者明白,没有什么不能够通过底层技术的互相融通来实现更高级的用法。
或许以后就是大杂烩的时代,没有什么你好他坏的说法,看习惯而已,一家之言,不足一哂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值