WebAssembly 中 call_indirect 的错误结果

Wrong result of call_indirect in WebAssembly

不明白如何正确使用call_indirect。

在网上搜索,我发现在 web assembly 中让函数指针工作并不容易。函数地址存储在 Table 中,基本上是 i32 标识符。 好吧..让我们编码吧!

定义中间表示中的语义(调用-indirect.ll文件):

define i32 @call_indirect_method(i32 (i32, i32)* %callee, i32 %arg, i32 %arg2) {
%t = call i32 %callee(i32 %arg, i32 %arg2)
ret i32 %t
}

编写程序(program.c文件):

#include <emscripten.h>

int EMSCRIPTEN_KEEPALIVE call_indirect_method();

int EMSCRIPTEN_KEEPALIVE sum(int a, int b)
{
    return (a + b);
}

int EMSCRIPTEN_KEEPALIVE calc(int a, int b)
{
    return call_indirect_method();
}

一堆 js (index.js):

const fs = require("fs");
const raw_source = new Uint8Array(fs.readFileSync(`module.wasm`));
const wasm_module = new WebAssembly.Module(raw_source);

var table = new WebAssembly.Table({initial: 1, element: "anyfunc"});
var memory = new WebAssembly.Memory({initial: 1});

const _exp = new WebAssembly.Instance(wasm_module, {
    "env": {
        __memory_base: 0,
        __table_base: 0,
        memoryBase: 0,
        tableBase: 0,
        memory: memory,
        table: table,
        nullFunc_X: () => console.info("nullFunc_X()"),
        jsCall_X: () => console.info("jsCall_X()"),
        abort: err => {
            throw new Error('abort ' + err);
        },
        abortOnCannotGrowMemory: err => {
            throw new Error('abortOnCannotGrowMemory ' + err);
        },
        abortWhosebug: err => {
            throw new Error('abortWhosebug ' + err); // Wellcome here if compile with -O0 level.
        }
    }
}).exports;

table.set(0, _exp._sum);

console.log(_exp._calc(1, 2)); // expect to be 3, got 0 instead

我构建 .wasm 的方式:

emcc -O1 -s WASM=1 -s SIDE_MODULE=1 -s ONLY_MY_CODE=1 -s FILESYSTEM=0 -s TOTAL_MEMORY=65536 -s TOTAL_STACK=1024 -s ENVIRONMENT='web' -s ALLOW_MEMORY_GROWTH=0 -s NO_EXIT_RUNTIME=1 -s STACK_OVERFLOW_CHECK=0 call-indirect.ll program.c -o module.wasm

最后,致电:

node index.js

当一切为 "assembled" - 我在控制台中得到 0。但期待 3.

另外(一件小事,但是..)如果您尝试构建没有优化的代码 -O0 它将由于堆栈溢出错误而中止。不知道为什么。任何的想法? (是的,我已经在尝试增加内存了)。

所以,也许有人知道我的情况出了什么问题?

嗯,看来你应该为你的函数创建一个额外的 table。

几点注意事项:

  • 看起来这个table会被放在堆内存的开头,但这不是100%的信息;
  • 而且,当你有不止一种函数指针类型时,我不会检查大小写;
  • 此外,您可能会在 JS 中使用 table 的初始大小。
#include <emscripten.h>

typedef int (*FPTR_TYPE)(int,int);
FPTR_TYPE fMathOp;

int EMSCRIPTEN_KEEPALIVE add(int a, int b)
{
    return (a + b);
}

int EMSCRIPTEN_KEEPALIVE sub(int a, int b)
{
    return (a - b);
}

FPTR_TYPE my_table[] = {
    add,
    sub
};

int EMSCRIPTEN_KEEPALIVE calc(int a, int b)
{
    return fMathOp(a, b);
}

所以"call-indirect.ll"可能会掉线。