webassembly运行时是否应该创建指令的AST



我正在用Java构建一个web汇编解释器,现在正在考虑如何对指令进行最佳建模。这是我第一次尝试构建编译器/运行时。

目前,我已经将一个函数建模为一个类,该类具有类型、局部变量列表和从二进制存储格式检索的指令byte[]代码。

在执行过程中,我只是在指令的每个字节上迭代(我从ByteBuffer中得到,它封装了byte[](,并根据值进行切换,如下所示:

public void execute() {
int opCode = code.get();
// 0x0b is the end of the current block
while (opCode != 0x0b) {
if (opCode == 0x20) {
localGet();
} else if (opCode == 0x7c) {
i64add();
} else if (opCode == 0x6a) {
i32add();
} else {
throw new UnsupportedOperationException(String.format("Opcode %d not yet supported", opCode));
}
opCode = code.get();
}
}

这是一个";"好";方法我还可以想象在模块加载期间解析每条指令并为每条指令创建单独的类(例如LocalGet(,然后创建AST。但我想知道这在这里是否真的有必要,因为指令似乎只是一个线性列表,一个接一个(块除外,我认为我必须创建一个辅助堆栈来跟踪嵌套级别(。

此外,webassembly引用根据主堆栈列出了每条指令的确切步骤,所以我不确定AST会带来什么(因为我不是从终端节点开始解析,而是线性地处理指令(。

经过一些尝试和错误,我的方法最终是这样的:

  • 创建Instruction接口

    public interface Instruction {
    void execute(Store store, WasmStack stack);
    }
    
  • 为每条指令创建类,这些类将立即值作为字段(例如LocalGet指令(保存,并包含webassembly文档中的实际逻辑。

  • 在实例化之前解析模块的字节码,并将每个解析的指令附加到List<Instruction>

  • 在运行时,只需从列表中检索当前指令,然后执行该方法,即可顺序执行每条指令(就像函数指针一样(。

最困难的问题是分支结构,特别是block指令,它需要知道块末尾的位置。因此,在解析过程中,我创建了一个简单的堆栈Deque<Instruction>,在这里我推送每个控制指令(即由end终止的每个指令(,当遇到end时,我修改原始控制指令以设置end的位置(因为它位于堆栈的顶部,但也已添加到指令列表中(。

在运行时,end指令然后通过从execute方法内部更改i来更改函数指针(这只是一个简单的for (int i = 0; i < instructions.size(); i++)。这实际上是实现控制流的一种简单方法,因为可以跳过指令或返回。

最新更新