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

时间:2017-04-23 13:12:44

标签: 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?

1 个答案:

答案 0 :(得分:2)

您注意到很多内容是在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时,堆栈必须到达某个地方。 C ++堆栈可以在WebAssembly中有3个位置:

  1. 在执行堆栈上(每个操作码推送并弹出值,因此add弹出2然后按1)。
  2. 作为当地人。
  3. Memory
  4. 请注意,您不能取地址1.和2.构造函数将*this传递给函数,因此编译器必须将该值放在{{1}上}。 Memory中的堆栈在哪里? Emscripten为您照顾它!它决定将内存堆栈指针存储在地址4,因此Memory。然后来自LLVM的四个(i32.load offset=4 (i32.const 0))位于该地址的偏移处,因此alloca将获取堆栈位置(我们在本地(i32.add (get_local $0) (i32.const 48))中加载)并获得其偏移量。这是$0的价值。

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

    您拥有的最后一段代码:

    1. 加载内存堆栈指针。
    2. 从中减去64。
    3. 存储堆栈指针。
    4. 这是你的功能序幕。如果你查看函数的最后一部分,你会找到匹配的结尾,它会向指针添加64。这为四个*this腾出了空间。它是(非官方)WebAssembly ABI的一部分,每个函数负责在内存中为其变量增加和缩小堆栈。

      为什么是64?那是4 x 16,这对于那四个alloca个实例来说就足够了:它们每个都有3个FirstClass,在存储时每个都被四舍五入到16个字节,用于对齐。在C ++中尝试i32(它是12),然后尝试分配它们的数组(它们每个都填充4个字节,因此每个条目都是对齐的)。这只是C ++常用实现的一部分,与LLVM或WebAssembly无关。