webassembly - 理解类结构和构造函数调用

标签 webassembly

玩过循环、分支、表格和所有那些不错的操作符后,我几乎开始对这种语言感到满意,足以创造一些有用的东西,但有些逻辑我仍然不明白。请耐心等待,因为它会有点长。

问题:有人可以解释翻译的代码是如何工作的吗?我在下面进一步添加了具体问题。

首先这里是一些我一直在转换的琐碎的 C++ 代码:

class FirstClass {
  int prop1 = 111;
  int prop2 = 222;
  int prop3 = 333;

  public:
  FirstClass(int param1, int param2) {
    prop1 += param1 + param2;  

  }
};

class SecondClass {
  public:
  SecondClass() {

  }
};

int main() {
  FirstClass firstClass1(10, 5);
  FirstClass firstClass2(30, 15);
  FirstClass firstClass3(2, 4);
  FirstClass firstClass4(2, 4);
}

翻译成:
(module
  (table 0 anyfunc)
  (memory $0 1)
  (export "memory" (memory $0))
  (export "main" (func $main))
  (func $main (result i32)
    (local $0 i32)
    (i32.store offset=4
      (i32.const 0)
      (tee_local $0
        (i32.sub
          (i32.load offset=4
            (i32.const 0)
          )
          (i32.const 64)
        )
      )
    )
    (drop
      (call $_ZN10FirstClassC2Eii
        (i32.add
          (get_local $0)
          (i32.const 48)
        )
        (i32.const 10)
        (i32.const 5)
      )
    )
    (drop
      (call $_ZN10FirstClassC2Eii
        (i32.add
          (get_local $0)
          (i32.const 32)
        )
        (i32.const 30)
        (i32.const 15)
      )
    )
    (drop
      (call $_ZN10FirstClassC2Eii
        (i32.add
          (get_local $0)
          (i32.const 16)
        )
        (i32.const 2)
        (i32.const 4)
      )
    )
    (drop
      (call $_ZN10FirstClassC2Eii
        (get_local $0)
        (i32.const 2)
        (i32.const 4)
      )
    )
    (i32.store offset=4
      (i32.const 0)
      (i32.add
        (get_local $0)
        (i32.const 64)
      )
    )
    (i32.const 0)
  )
  (func $_ZN10FirstClassC2Eii (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
    (i32.store offset=8
      (get_local $0)
      (i32.const 222)
    )
    (i32.store offset=4
      (get_local $0)
      (i32.const 222)
    )
    (i32.store
      (get_local $0)
      (i32.add
        (i32.add
          (get_local $1)
          (get_local $2)
        )
        (i32.const 111)
      )
    )
    (get_local $0)
  )
)

所以现在我对这里实际发生的事情有一些疑问。虽然我认为我理解了大部分内容,但仍有一些事情我不确定:

例如,查看构造函数及其签名:
(func $_ZN10FirstClassC2Eii (param $0 i32) (param $1 i32) (param $2 i32) (result i32)

它有以下参数:(param $0 i32)我假设是在 main 函数中定义了一些局部的。让我们说一些内存。但是,我们知道 main 函数中有 4 个实例,这意味着所有这些实例都保存在同一个 (local $0 i32) 中。但是有不同的偏移量,我是对还是错?

接下来让我们看一下对构造函数的调用:
(drop
  (call $_ZN10FirstClassC2Eii
    (i32.add
      (get_local $0)
      (i32.const 32)
    )
    (i32.const 30)
    (i32.const 15)
  )
)

我们调用构造函数并传入 3 个参数。究竟是什么添加呢?我们是否在本地添加空间?仔细观察,对于每个构造函数调用,这个数字都会减少 16(我从上到下阅读代码),这大约是一个单词的大小。我不知道这是什么意思。

最后我们有:
(i32.store offset=4
  (i32.const 0)
  (tee_local $0
    (i32.sub
      (i32.load offset=4
        (i32.const 0)
      )
      (i32.const 64)
    )
  )
)

它甚至加载了什么,为什么要减法?我的意思是它设置一个本地并返回它,以便我们可以将它存储在偏移量为 4 的线性内存中?偏移 4 与什么有关?

最佳答案

您注意到的很多内容都在 C++ 到某些编译器 IR 的转换中。由于您使用的工具基于 LLVM,如果您想进行探索,我建议您查看 LLVM 的 IR。 Here's your example, also unoptimized, in LLVM IR .这很有趣,因为 WebAssembly 发生在这个 LLVM IR 之后,所以你可以看到 C++ 的部分转换。也许我们可以理解它!

构造函数与 C++ 中的所有非静态函数类成员一样,具有隐式 *this范围。这就是第零个参数。为什么是这样i32 ?因为 WebAssembly 中的所有指针都是 i32 .

在 LLVM IR 中,这是:

define linkonce_odr void @FirstClass::FirstClass(int, int)(%class.FirstClass*, i32, i32) unnamed_addr #2 comdat align 2 !dbg !29 {

哪里%class.FirstClass**this指针。稍后,当降低到 WebAssembly 时,它会变成 i32 .

对于您的以下问题......调用构造函数时有什么补充?我们必须创建 *this ,然后将它们分配到堆栈上。 LLVM 如此执行这些分配:
  %1 = alloca %class.FirstClass, align 4
  %2 = alloca %class.FirstClass, align 4
  %3 = alloca %class.FirstClass, align 4
  %4 = alloca %class.FirstClass, align 4

所以它的堆栈思想包含四个 FirstClass 类型的变量。 .当我们降低到 WebAssembly 时,堆栈必须去某个地方。 WebAssembly 中有 3 个 C++ 堆栈可以放置的地方:
  • 在执行堆栈上(每个操作码都压入和弹出值,所以 add 弹出 2 然后压入 1)。
  • 作为本地人。
  • Memory .

  • 注意不能取1.和2的地址。构造函数传递*this到一个函数,所以编译器必须将该值放在 Memory 上. Memory中的堆栈在哪里? Emscripten 为您处理!它决定将内存中的堆栈指针存储在地址 4,因此 (i32.load offset=4 (i32.const 0)) .四人alloca然后来自 LLVM 位于该地址的偏移量处,因此 (i32.add (get_local $0) (i32.const 48))正在获取堆栈位置(我们在本地 $0 中加载)并获取其偏移量。这就是 *this 的值.

    请注意,优化后,绝大多数 C++ 堆栈上的变量不会在内存中结束!大多数将被推送/弹出,或存储在 WebAssembly 本地(其中有无穷大)。这类似于 x86 或 ARM 等其他 ISA:将本地变量放在寄存器中更好,但这些 ISA 只有少数几个。因为 WebAssembly 是一个虚拟 ISA,我们可以承受无限的局部变量,所以 LLVM/Emscripten 必须具体化到内存中的堆栈要小得多。它们必须被具体化的唯一时间是当它们的地址被获取时,或者它们通过引用传递(实际上是一个指针),或者一个函数有多个返回值(WebAssembly 将来可能会支持)。

    您拥有的最后一点代码:
  • 加载内存中的堆栈指针。
  • 从中减去 64。
  • 存储回堆栈指针。

  • 那是你的函数序言。如果您查看函数的最后部分,您会发现匹配的结尾将 64 添加回指针。这为四个人腾出了空间alloca .它是(非官方)WebAssembly ABI 的一部分,每个函数负责为其变量增加和缩小内存中的堆栈。

    为什么是64?那是 4 x 16,刚好容纳这四个 FirstClass 的空间实例:它们各持有 3 i32存储时每个四舍五入到 16 个字节,以进行对齐。试用 sizeof(FirstClass)在 C++ 中(它是 12),然后尝试分配它们的数组(它们每个都将填充 4 个字节,以便每个条目对齐)。这只是 C++ 通常实现的一部分,与 LLVM 或 WebAssembly 无关。

    关于webassembly - 理解类结构和构造函数调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43571620/

    相关文章:

    javascript - Emscripten:自定义生成的 js,使其以自定义方式加载 .wasm 文件

    c++ - 是否可以只使用 QT 和 WebAssembly(而不是 HTML + CSS + JavaScript)来开发前端 Web?

    rust - Rust stdweb无法编译

    javascript - Node.js 单线程机制

    webpack - rust/Wasm : Module not found: Error: Can't resolve 'env' in

    c++ - 我如何告诉 em++ 找到 WS2tcpip.h

    javascript - 如何从 JavaScript 执行 Kotlin WebAssembly 函数?

    javascript - 我怎样才能拥有类型的功能(arr : T[]) => T in C++ that compile to WebAssembly?

    trigonometry - Webassembly 触发功能可能吗?

    media-player - UNO 中对 Web Assembly 的媒体播放器支持