WebAssembly - dynamiczne łączenie
Łączenie dynamiczne to proces, w którym dwa lub więcej modułów zostanie połączonych ze sobą podczas działania.
Aby zademonstrować, jak działa dynamiczne łączenie, użyjemy programu w C i skompilujemy go do wasm przy użyciu Ecmascript sdk.
Więc mamy -
test1.c
int test1(){
return 100;
}
test2.c
int test2(){
return 200;
}
main.c
#include <stdio.h>
int test1();
int test2();
int main() {
int result = test1() + test2();
return result;
}
W kodzie main.c wykorzystuje metody test1 () i test2 (), które są zdefiniowane w test1.c i test2.c. Sprawdźmy, jak połączyć te moduły w WebAssembly.
Polecenie kompilacji powyższego kodu jest następujące: użyj SIDE_MODULE = 1 do dynamicznego łączenia, jak pokazano w poleceniu.
emcc test1.c test2.c main.c -s SIDE_MODULE=1 -o maintest.wasm
Korzystanie z usługi WasmtoWat, która jest dostępna pod adresem https://webassembly.github.io/wabt/demo/wasm2wat/, otrzyma format tekstowy WebAssembly z maintest.wasm.
(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))
)
Format tekstowy WebAssembly ma pewne importy zdefiniowane, jak pokazano poniżej -
(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))
Jest to dodawane podczas kompilacji kodu przez emcc (emscripten sdk) i zajmuje się zarządzaniem pamięcią w WebAssembly.
Praca z importami i eksportami
Teraz, aby zobaczyć dane wyjściowe, będziemy musieli zdefiniować importy, które możesz zobaczyć w kodzie .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))
Powyższe terminy są wyjaśnione w następujący sposób -
env.stackSave - Służy do zarządzania stosem, funkcjonalności udostępnianej przez skompilowany kod emscripten.
env.stackRestore - Służy do zarządzania stosem, funkcjonalności udostępnianej przez skompilowany kod emscripten.
env.__memory_base- Jest to niezmienne globalne przesunięcie i32, które jest używane w pamięci env i zarezerwowane dla modułu wasm. Moduł może wykorzystać ten globalny w inicjalizatorze swoich segmentów danych, dzięki czemu są one ładowane pod poprawnym adresem.
env.__table_base- Jest to niezmienne globalne przesunięcie i32, które jest używane w tabeli env.table i zarezerwowane dla modułu wasm. Moduł może użyć tego globalnego w inicjatorze swoich segmentów elementu tabeli, aby były ładowane z odpowiednim przesunięciem.
env.memory - Będzie to zawierało szczegóły pamięci, które są wymagane do współdzielenia między modułami wasm.
env.table - Będzie to zawierało szczegóły tabeli, które mają być współdzielone między modułami wasm.
Import należy zdefiniować w javascript w następujący sposób -
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
}
};
Przykład
Poniżej znajduje się kod javascript, który wykorzystuje importObj wewnątrz WebAssembly.instantiate.
<!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>
Wynik
Dane wyjściowe są następujące -