WebAssembly - 动态链接

  • 简述

    动态链接是在运行时将两个或多个模块链接在一起的过程。
    为了演示动态链接的工作原理,我们将使用 C 程序并使用 Ecmascript sdk 将其编译为 wasm。
    所以我们在这里 -
    test1.c
    
    int test1(){ 
       return 100; 
    }
    
    test2.c
    
    int test2(){ 
       return 200; 
    }
    
    主程序
    
    #include <stdio.h>
    int test1(); 
    int test2();
    int main() { 
       int result = test1() + test2(); 
       return result; 
    }
    
    在 main.c 代码中,它使用了在 test1.c 和 test2.c 中定义的 test1() 和 test2()。让我们看看如何在 WebAssembly 中链接这些模块。
    编译上述代码的命令如下:使用 SIDE_MODULE =1 进行动态链接,如命令所示。
    
    emcc test1.c test2.c main.c -s SIDE_MODULE=1 -o maintest.wasm
    
    使用 https://webassembly.github.io/wabt/demo/wasm2wat/ 上提供的 WasmtoWat 将获得 maintest.wasm 的 WebAssembly 文本格式。
    
    (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 文本格式定义了一些导入,如下所示 -
    
    (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))
    
    这是在 emcc(emscripten sdk) 编译代码时添加的,它处理 WebAssembly 中的内存管理。
  • 使用导入和导出

    现在要查看输出,我们必须定义您可以在 .wat 代码中看到的导入 -
    
    (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))
    
    上述条款解释如下 -
    • env.stackSave - 它用于堆栈管理,这是由 emscripten 编译代码提供的功能。
    • env.stackRestore - 它用于堆栈管理,这是由 emscripten 编译代码提供的功能。
    • env.__memory_base - 它是一个不可变的 i32 全局偏移量,用于 env.memory 并为 wasm 模块保留。模块可以在其数据段的初始化程序中使用这个全局变量,以便将它们加载到正确的地址。
    • env.__table_base - 它是一个不可变的 i32 全局偏移量,用于 env.table 并为 wasm 模块保留。模块可以在其表元素段的初始化程序中使用这个全局变量,以便它们以正确的偏移量加载。
    • env.memory - 这将包含需要在 wasm 模块之间共享的内存详细信息。
    • env.table - 这将包含需要在 wasm 模块之间共享的表详细信息。
    导入必须在 javascript 中定义如下 -
    
    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 
       } 
    };
    

    例子

    以下是在 WebAssembly.instantiate 中使用 importObj 的 javascript 代码。
    
    <!DOCTYPE html> 
    <html>
       <head>
          <meta charset="UTF-8">
       </head>
       <body>
          <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>
       </body>
    </html>
    

    输出

    输出如下 -
    进口