首先,为了避免这看起来像一个XYZ问题,我想给出一些上下文(注意我没有使用Emscripten):
我正在尝试看看是否可以为用 C++ 编写的 Wasm程序实现一种热重载形式,托管在网络上。为此,我想有一段记忆,我称之为我的"世界状态"(对于任何看过手工英雄(https://handmadehero.org/)的人来说,这将是熟悉的):
struct State {
// put everything here
} state;
通常,对于具有平台层的完整C++程序,您将在平台端分配此结构,并通过代码的可重加载/dll/dylib 部分中的函数指针向该内存提供指向该内存的指针。可重新加载的代码将所有内容放入此持久内存中,因此,如果需要重新编译和重新加载代码,则所有状态将继续存在,因为内存是在未重新加载的程序部分中分配的。据我所知,这在瓦斯姆是不可能的。 首先,我必须使用 WebAssembly.Memory 的假设是否正确?--或者我可以在js中分配一个uint8array并将其用于我的持久状态,与程序内存分开吗?如果是这样,是不是更慢?
因此,只要我不使用像 WASI 这样的动态分配器,而是使用我可以控制的推送分配器,这将起作用。(我认为这是因为,假设我使用 malloc 获取内存地址并重新加载 - malloc 的内部状态将重新加载并认为所有堆内存都可用,而实际上它不可用,因此未来的分配可能会破坏以前的分配。 重新加载后,我可以先将结构复制到 js 端的临时缓冲区中,重新加载,从 Wasm 获取结构的内存位置(我将要求它存在),然后将保存的内存从 js 复制回位置。
但是,如果我使用指针,这会分崩离析,因为如果我更改程序(这是重点),__data_end
可能会更改,这将抵消所有地址!我 https://lld.llvm.org/WebAssembly.html 检查了此处的链接器标志,看看我可以控制什么。我可以指定堆栈在数据段之前,但堆仍然会在那之后,这会导致同样的问题。我还可以指定全局数据的位置,但这不是我认为的数据段,因此可变大小的数据段仍然可以偏移我的所有地址。 这里有一个很好的页面可以帮助我们可视化Wasm内存:https://dassur.ma/things/c-to-webassembly/
有人对如何实现我想要的东西有任何想法吗?我能想到的唯一选项涉及以某种方式使用 Wasm 内存之外的内存(可能更慢或不可能),仅使用堆栈内存而不使用指针(不现实,除非我可以在重新编译后自动重新计算所有指针偏移量,这将是痛苦且容易出错的),或者找到一种方法使数据段在堆栈之后并在固定地址堆, 这将保证如果数据段需要增长,堆栈和堆段不会被偏移。如果可能,另一种选择是固定数据细分受众群的最大大小。当涉及到这样的内存操作时,Wasm规范/文档并不是很好,所以我希望对可能性进行一些澄清。最后,也许我可以使用两个 Wasm 模块(但这种间接寻址不会很慢)?我可能错过了一些与内存布局相关的关键内容。
如果您需要更多详细信息,请告诉我。正如我所提到的,我以前在 C 语言中做过类似的事情,这是一种常见的快速迭代游戏开发技术。基本上我正在尝试在 Wasm(黄蜂)中重新创建它。
编辑:显然你可以直接从另一个模块调用Wasm函数。首先,您如何做到这一点,其次,访问另一个模块的内存的性能特征是什么?
编辑2:如果支持某种形式的动态链接,也许是这样? https://webassembly.org/docs/dynamic-linking/
WebAssembly 模块在三个不同的地方保存变量状态:
- 线性存储器
- 与执行堆栈关联的局部变量
- 全局变量
其中,主机环境只能访问全局变量和线性内存,并且可能可序列化,以便在热重载模块时缓存它们。当然,没有办法直接访问和存储当前的调用堆栈。
如果我想实现这一点,我会在 WebAssembly 中创建自己的状态机,将其存储在线性内存中的已知位置。
Wasm被组织成模块,模块定义了四种相关的实体:函数,内存,表,全局。代码位于函数中,而其他三个表示模块的状态。
现在,有趣的是,所有这四种实体类型都可以导入和导出。此外,所有这些都可以在模块外部创建,例如,通过 JS API。
因此,模拟代码交换的一种方法是设置模块,以便在外部创建所有三个状态片段并将其导入到模块中。这样,您可以在外部保持活动状态,并在可用时将它们传递给升级后的模块。(您还需要确保升级后的模块不使用数据/元素段或以覆盖预先存在的状态的方式启动函数。
当然,这仅在模块状态的形状在升级之间没有变化时才有效。 例如,内存中没有新的全局,没有新的数据布局,否则新代码将无法理解旧状态。这实际上是问题的困难部分,但它独立于Wasm的细节。