Design: 提案:为原生 wasm 主机导入解析语法

创建于 2019-06-19  ·  25评论  ·  资料来源: WebAssembly/design

我想为原生 wasm 主机中的导入指令提出一种语法和解析机制(所以,目前主要是 WASI,也许还有 node.js)。

基本原理

我渴望的模型是@lukewagner所说的无共享链接; 在本提案中,我将其称为无信任组合

在这个模型中,wasm 开发人员可以从类似 npm 的模块生态系统中提取; 每个模块都是自包含的,有自己的依赖项,通常不在全局安装的包上。 这也是 snapcraft 和 flatpack 选择的模型; 它的主要好处是保证在这个模型中安装的每个程序都是相同的,这对于错误报告和避免依赖地狱问题非常有帮助。

(说真的,依赖地狱很糟糕;可以缓解这些问题的导入系统的便利性不容小觑)

这个模型的 wasm 版本也应该是安全的,即使面对恶意代码,也因此得名“无信任组合”。 本机导入解析系统应该尊重对象功能的基本原则:不受信任的代码只能访问已明确传递给该代码的数据。 换句话说,即使主机对该文件具有文件系统权限,子子子子依赖项也不应该能够导入/usr/bin/someprogram.wasm

现在,直接从另一个 wasm 模块导入一个 wasm 模块目前几乎是不可能的。 一起使用 wasm 模块需要在 JS 嵌入器中编写一些胶水代码。 有一个即将推出的esm 集成提案,但是:

  • 该提案针对 JS 嵌入,并基于 ES 环境做出假设。

  • 该提案没有指定路径名解析机制; 假设最终机制是依赖于实现的,node.js 的路径名解析并没有完全通过 wasm 的“无信任”要求。

简而言之,wasm 需要一种标准的、安全的、非 JS 机制来从其他模块导入模块。 虽然此标准可能并非由 WASI 专门使用,但 WASI 涵盖了大多数用例。

提议

在实例化阶段(例如WebAssembly.instantiatewasm_instance_newwasm::Instance::make ),WebAssembly 编译器采用可选的文件系统根对象和可选的导入映射对象。

文件系统根目录通常是.wasm文件所在的目录。导入映射对象是平台相关的。

解析导入字符串时,编译器应用以下算法:

  • 检查路径是否是有效的 URI 路径(或其他方案)。
  • 检查路径是否是相对的,并且不包括...段。
  • 如果第一段以@为前缀,则将其映射到主机导入映射中定义的路径。
  • 如果第一段以$为前缀,请检查当前文件夹以及直到导入根目录的所有父文件夹中的wasi_import_map.json文件; 返回地图第一次具有与段匹配的条目。

主机导入( @

此语法用于导入主机功能,例如@wasi/fs@stdjs/global/Array

它的好处是不会与现有的 JS 导入约定冲突(只要“wasi”和“stdjs”被保留为 NPM 组织)。

映射导入( $

此语法用于以 OCAP 安全的方式从当前目录导入数据。

基本原则是,默认情况下, .wasm文件只能访问其当前目录和子目录中的文件; 但是,一个目录可以有一个wasi_import_map.json文件,它声明了子目录中的.wasm文件可以访问的挂钩。

例如:

myWasmModule/
    index.wasm
    foo/
        wasi_import_map.json
        foo.wasm
        foobar/
            foobar.wasm
    bar/
        bar.wasm

在上面的例子中:

  • index.wasm可以导入foo.wasmfoobar.wasmbar.wasm
  • foo.wasmbar.wasm不能相互导入或index.wasm
  • foobar.wasm可以导入foo.wasm如果foo/wasi_import_map.json具有该路径的密钥。
  • foo/wasi_import_map.json不能让foo.wasmfoobar.wasm访问index.wasmbar.wasm

导入映射有几个用例:

  • 允许包管理器应用依赖项重复数据删除以减少编译时间并提高代码缓存大小。

  • 提供 shims 和 polyfills。

  • 使用不同的库进行调试和发布版本。

为什么不使用 WICG 的导入地图?

有一个论点是,重用WICG 的导入地图提案(从现在开始使用 WIM)会更简单,并且$@的特殊情况是无关紧要的复杂性。

一些对立面:

  • WIM 解决了与 WASI 导入地图不同的问题。 WIM 基于在静态地址进行 fetch 调用通常是安全的,并且不会泄漏数据的原则。 相反,该提案依赖于 OCAP 安全模型。

  • 为正常、主机和映射导入使用单独的语法允许开发人员和工具专门传达他们的意图; 它还鼓励更具可读性的错误消息。

网络用例

请注意,此提案适用于本地 wasm 解释器。 浏览器如何解析导入指令完全独立于该提案。

但是,如果要围绕这种导入结构开发包管理器生态系统,像 Webpack 和 Rollup 这样的打包器可以开始提供该结构的浏览器友好版本,就像他们目前对 npm 所做的那样。


不管怎样,这是我的提议。 欢迎任何反馈:)

最有用的评论

我也一直觉得需要这样的东西。 但是,我觉得如果不引入一些全新的、拓扑上“更大”的概念(包含多个模块),例如“包”(像 npm 等包管理器中的代码单元)或“容器”,我们还没有能力指定任何东西"(可以由容器运行时执行的代码单元),因为如上所述,我们不能假设存在一些我们可以谈论其路径的环境文件系统。 一旦你有了“包”或“容器”的概念(我认为这两个都是有意义的,对于不同的部署阶段),那么就更容易具体了解解决方案是如何工作的。

我还分享了@littledan的目标,即无论定义什么都应该在 JS/ESM 环境内部和外部具有相同的解释。 这样你就可以拥有一个单独的 wasm 模块包,既可以在 Node 中使用,也可以在外部使用。

所有25条评论

如果我们不使用特定语法,而是建议 wasi 实现应用 libpreload 样式功能规则来导入解析,该怎么办? 如果你有a.wasmb.wasm你可以在你的 wasi 清单中声明../b.wasm以便你可以从a.wasm导入它

这将使 wasi 与它可以加入的其他随机系统(例如浏览器和节点)更加兼容。

我喜欢这个提议的大方向。 防止库任意获取进口似乎很重要。 我也喜欢这个建议相当轻量级。 不过,有几个想法。

  • 您对导入地图的描述有点不完整。 地图条目a: b/c是否允许在名称$a b/c的想法? 如果是这样,我不确定这如何允许填充或重新映射库,因为导入模块需要知道名称是(重新)映射的。 为什么需要$前缀?

  • 以更像模块的方式处理目录会很好,其中每个目录还可以限制它向外“导出”到周围的文件。 这样的“出口地图”将是对定义面向环境的进口地图的补充。 如果给出了导出映射,则只能从模块或周围或同级目录中的导入/导出映射访问其中列出的文件/路径。

  • 我强烈同意使用标准 URI 引用作为命名机制。 但是, @$前缀有点滥用相对引用的概念( @a/b是 URI 术语中的相对引用,而这里分配的语义非常参考的绝对形式)。

  • Bikeshedding:使用 JSON 导入地图有很多好处吗? 正如您所描述的那样,它们仅由路径对列表组成,因此像 JSON 这样的复杂且混乱的格式可能对此有点过分。

如果是这样,我不确定这如何允许填充或重新映射库

我在考虑依赖注入,其中(例如)两个模块需要使用相同的 react 实例可以导入$react并依赖项目根来提供映射。

不过,现在我想起来了,这确实提出了当两个文件/模块导入同一个文件/模块时该怎么办的问题。 他们应该创建两个单独的实例,还是一个共享实例? 两个答案都有其用例。

我还认为可能有一种机制可以让多个 wasm_import_map 文件用于调试、发布、单元测试等,但这似乎是过早的功能蠕变。 Wasm 并不需要成为 Cmake。

为什么需要 $ 前缀?

它允许更清晰的错误消息和更快的问题诊断。

Cannot find host module "@foobar"

对比

Cannot find file "some/path/foobar.wasm"

对比

Key "$foobar" not found in imports maps:
- "some/path/wasm_import_map.json"
- "some/wasm_import_map.json"

没有前缀意味着您要么有很长的错误消息列出所有可能性,或者更有可能的是,您会遇到与 C 编译器相同的模糊导入错误,即“无法解析 foobar”并让您猜测原因。

以更像模块的方式处理目录会很好,其中每个目录还可以限制它向外“导出”到周围的文件。

我认为导出地图不会那么有用。 一个想要导出的模块,比如说,一个“绑定”子模块和一个“网络”子模块,可能在根目录下只有bindings.wasmnetwork.wasm文件。

限制从父目录导入的机制对于文件系统安全是必要的,但您不需要(也几乎不能)限制从子目录导入。

使用 JSON 导入地图有很多好处吗?

并不真地。 我也在考虑 protobuff,但实际上,任何格式都可以。 只需要一种或两种默认格式。

我在考虑依赖注入,其中(例如)两个模块需要使用相同的 react 实例可以导入 $react 并依赖项目根来提供映射。

您可以通过命名约定通过$指示参数,这并不意味着需要将其作为语法要求进行烘焙。 显然,并非所有用途都是参数化(也称为“依赖注入”,因为一些困惑的灵魂将其重命名为:))。 对于匀场,我认为您需要能够重新映射任意模块,包括系统模块(即,您所描述的以@开头的名称)。

错误消息似乎是一个可以解决的问题。 特别是因为您描述的系统非常简单,即名称只能在任何父目录或其中的映射中找到。

我认为导出地图不会那么有用。 一个想要导出的模块,比如说,一个“绑定”子模块和一个“网络”子模块,可以只在根目录下有 bindings.wasm 和 network.wasm 文件。

这会阻止任何人通过嵌套目录中的导入映射模块来覆盖意图吗?

错误消息似乎是一个可以解决的问题。

嗯,是的,它们也适用于#include ,但是对失败的库包含进行故障排除可能会非常困难。

无论哪种方式,我认为强迫用户指定他们的意图是有内在价值的。 它为工具设计师创造了一个成功的坑,他们清楚地知道用户想要什么; 它使用户可以更轻松地直观地了解工具链的工作原理。

对于匀场,我认为您需要能够重新映射任意模块,包括系统模块(即,您所描述的以 @ 开头的名称)。

关于任意模块,我特地写了建议来避免这种情况。 如果您可以按照 WICG 导入映射允许的方式对任何导入进行填充,那么底层设计的健壮性和 KISS 就会降低,并且您会面临很多名称阴影错误/漏洞利用。

(这不是对 WICG 导入地图的批评;就像我说的,它们针对不同的用例)

我认为依赖注入参数化对于大多数匀场用例来说已经足够了,对于更具侵入性的匀场,你总是可以有一个工具“手动”用其他文件替换一些文件。

回复:系统模块,我认为填充它们将在WebAssembly.instantiate API 级别完成,因为最常见的用例是单元测试。

语法总是可以与$合并,但同样,表达意图是有价值的。

(另外,在最坏的情况下,我们弃用 @ 和 $ 并使它们成为可选的命名约定;相反的进展破坏了兼容性)

这会阻止任何人通过嵌套目录中的导入映射模块来覆盖意图吗?

不,但我不确定这是个问题。

至于覆盖模块编写者的意图,那是模块用户的特权。 如果他们想破坏模块的内部结构,他们需要自担风险,无论如何他们都可以通过分叉模块来做到这一点。

就沙盒安全而言,我想如果恶意模块以破坏不变量的方式导入与其他程序共享的某些外部模块(例如,恶意代码导入 /usr/bin/game_engine/save_progress ,而不是导入/usr/bin/game_engine/save_progress /usr/bin/game_engine/save_progress/unsafe_filesystem_functions )。

不过,可能有解决方法,而且我认为无论如何都不会有很多可能的攻击媒介。

但我想如果我的目标是“成功坑”和“安全沙盒”方法,这是 ocap 和 WASI 的重点,这些也需要考虑。

该名称只能在任何父目录或其中的地图中找到。

其中的地图,是的,任何父目录,不。

访问父目录中的文件会破坏沙盒,并且是不允许的。

.wasm文件只能访问:

  • .wasm文件在他们的目录中
  • .wasm子目录中的文件
  • wasm_import_map.json父目录中的文件,直到导入根目录

导入地图本身遵循相同的规则,并且只能访问子目录中的 wasm 文件和导入父目录中的地图。

我认为强迫用户指定他们的意图是有内在价值的。

我非常同意,但在这种情况下,它似乎过早地禁止了与不同意图相关的有效用例。

至于覆盖模块编写者的意图,那是模块用户的特权。 如果他们想破坏模块的内部结构,他们会自行承担风险

是的……我以前听过这个论点;)。 不幸的是,这不是博弈论在实践中的表现。 如果没有办法明确表达和(在某种程度上)强制执行这种意图,那么有人覆盖它,因为没有什么能阻止他们。 只需要一个受欢迎的客户就可以做到这一点,突然模块编写者意识到所有的负担都在他们身上,以保留非官方的 API 或从传递客户那里得到所有的火,因为她/他的更新破坏了他们的代码。 去过也做过; 这是一个设计良好的系统应该有助于避免的情况。

双重门控进口是门控出口。 Wasm 模块有意通过两者支持适当的封装,上一层应该也是如此。

访问父目录中的文件会破坏沙盒,并且是不允许的。

对,我的意思是仅在父目录中导入映射,“或”是草率的措辞。

去过也做过; 这是一个设计良好的系统应该有助于避免的情况。

很公平。

我非常同意,但在这种情况下,它似乎过早地禁止了与不同意图相关的有效用例。

需要明确的是,您对类型前缀的主要保留是:

  • 它们会阻止更通用的系统允许的特定用例吗?
  • 或者它们代表了一种设计约束,因此它们可能会阻止我们没有预料到的未来用例?

我不觉得是后者,但如果是的话,我必须反对,因为我建议的设计不是任意的,并且涵盖了需要解决的问题,就像未预料到的未来用例一样。

如果是前者,你有没有具体的例子,以便我调整我的建议? 显然,我们想要设计的 wasm 包生态系统还不存在,但即使是一个模糊的“我们在目录 Y 中有 X,我们想要填充 Z”的用例也会对我有所帮助。

需要明确的是,我包含前缀的原因是:

  • 明确传达用户意图。
  • 避免阴影问题,例如,一个包因为导入了一个名为streams.wasm的文件而中断,并且一个名为stream的包已添加到根项目中。
  • 为系统库保留语法,而不用担心现有的包名称

我很好奇 WASM/WASI 是否应该涵盖任何模块行为。 我的第一个想法是它们不应该,因此无论模块系统如何,它们都可以在很多地方使用。 wasmtime 加载和链接 WASM 模块的方式与节点加载和链接 WASM 模块的方式非常不同,但理想情况下它们都应该“正常工作”。

我想知道是否有可能在其中一些细节上在 web 和非 web 环境之间保持一致。

例如,对于肤浅的细节:虽然我提倡使用@namespace/modulename ,但网络似乎正朝着namespace:modulename的方向发展内置模块,这对我来说似乎很好,并且应该适用于非网络环境也是如此。

更广泛地说,Wasm/ESM 集成提案确实使用了 JS 管道,但它这样做是为了编码一些在 JS 之外同样有意义的东西,例如,你不能有多个 Wasm 模块循环导入彼此的函数导出(在实例化过程中出错)。 我想知道我们是否可以努力制定 ESM 集成规范,以使这些跨环境语义更加清晰。

wasmtime 加载和链接 WASM 模块的方式

我很确定 wasmtime 目前根本没有链接 WASM 模块。 这不是一个记录的功能,它在我的测试中凭经验不起作用,并且查看源代码, Resolver接口仅使用Namespace (匹配wasi_unstable和其他硬编码名称)和NullResolver

我的第一个想法是它们不应该,因此无论模块系统如何,它们都可以在很多地方使用。

但这并不是它在实践中的真正运作方式。 说“我们应该有非常通用的标准,以便每个人都可以做最适合他们需要的事情”通常会变成“有十几个相互竞争的约定/非正式标准,没有一个能正确解决问题空间,大多数工具只能理解 2 或 3 个其中”。

尽管我提倡使用@namespace/modulename,但网络似乎正朝着命名空间:内置模块的modulename 发展

当然。 除了语法,我对区分文件系统导入、参数化导入和主机导入的想法更感兴趣。

更广泛地说,Wasm/ESM 集成提案确实使用了 JS 管道,但它这样做是为了编码一些在 JS 之外同样有意义的东西,例如,你不能有多个 Wasm 模块循环导入彼此的函数导出(在实例化过程中出错)。

是的,我认为这是这个提案最缺乏的地方。 我需要更好地定义链接语义,并在 esm-integration repo 上提出问题来讨论重叠。

我的意思更像是,我们应该避免指定特定的东西,比如导入语法和解析(虽然有 ocap 规则和类似的东西很酷)。 希望集成 wasm 的系统可能已经在使用某种语法和解析系统,这可能与我们的冲突。 它甚至可能没有路径和文件的概念。

我没有一个简单的用例。 但是,从概念上讲,只有两种模块引用:确定的,它引用固定的已知模块,通常是同一组件的一部分,以及不确定的,它们是有效的参数,由模块的客户端解析。 (有趣的是,Wasm 本身只有后者。)

从这个角度来看,我并不完全清楚第三种添加了什么。 我可以看到拥有一个可以模拟其他类型的可自定义覆盖机制是多么有用。 但这只有在它可以同时采用两种句法形式时才有意义。 OTOH,如果它不能覆盖其他,那么它在语义上与包导入有什么不同——即, @$之间的更深层次的区别是什么,以及为什么硬连线有利于不同之处?

也许您的答案是,差异仅在于预期解决方案的语用学上,但命名约定可能就足够了。

好的,我想我现在更好地理解了你的问题。

@ 和 $ 之间更深层次的区别是什么,为什么硬连线这种区别是有益的?

我计划了一个更长的答案,但在我写它的时候,我开始质疑这种差异的效用。

我的理由是,我们希望确定的引用仅指向子目录以确保沙盒安全,但我们还需要一些机制来创建对父目录的确定引用……除非我实际上不确定我们是否这样做?

我考虑的用例主要是填充和替代构建(单元测试 vs 调试 vs 发布),但这些并不真的需要在包系统中烘焙。

我仍然认为,如果我们添加标准导入贴图,那么我们需要以一种不会意外产生阴影问题的方式添加它们,但我不确定导入贴图在离线导入解决方案中是否有用。


无论如何,假设我们放弃$语法并导入映射,您认为提案的其余部分是否值得标准化?

修改后的提案将是:

  • 将可选的文件系统根对象添加到实例化 API( WebAssembly.instantiatewasm_instance_newwasm::Instance::make

  • 工具约定中,使用上述沙盒规则添加将导入字符串解析为 URI 的规范。

    • 为主机导入制定语法( @foo/barfoo:bar或 W3C 确定的任何内容)。

以后可以添加潜在的导入图/导出图/匀场机制。

我也一直觉得需要这样的东西。 但是,我觉得如果不引入一些全新的、拓扑上“更大”的概念(包含多个模块),例如“包”(像 npm 等包管理器中的代码单元)或“容器”,我们还没有能力指定任何东西"(可以由容器运行时执行的代码单元),因为如上所述,我们不能假设存在一些我们可以谈论其路径的环境文件系统。 一旦你有了“包”或“容器”的概念(我认为这两个都是有意义的,对于不同的部署阶段),那么就更容易具体了解解决方案是如何工作的。

我还分享了@littledan的目标,即无论定义什么都应该在 JS/ESM 环境内部和外部具有相同的解释。 这样你就可以拥有一个单独的 wasm 模块包,既可以在 Node 中使用,也可以在外部使用。

我有一种强烈的感觉,我们需要开始协调和调整这些提案,以预期需要一个整体的包装或容器概念。 我们不应该排除网络,但我们也应该保持module sandbox不变。 该提案与@lukewagner和 @jgravelle-google 的 WebIDL 提案、@littledan 和@linclark的 ESM 集成提案以及WASI标准@sunfishcode @tschneidereit 相关。

我看到了 Webassembly 模块之间的链接和接口的关键潜力在于:

  • 以可行且相当有效的方式在同一线程中链接以不同编程语言编写的模块,以避免序列化或 IPC
  • 包含不受信任的模块(如缓冲区溢出或恶意)中的安全漏洞,模块沙箱具有明确定义的(类似应用程序的)权限(或在 WASI 术语中的对象功能),从模块外部强制执行,模块接口作为安全门
  • 允许构建一个通用模块的生态系统,该生态系统可以轻松重用,而无需将它们编译为单个模块甚至是本机可执行文件,这使得 npm install 可以轻松用于 wasm 模块,并具有可接受的性能折衷
  • 允许更好的加载时间和缓存小型 WebAssembly 模块,而不是大型单体“包”(包管理器,如带有 tink、entropic 和 yarn 的 npm 正在朝着基于文件的散列/缓存的方向发展,使用全局单例实例而不是运送复制冗余文件的整个包。此外,像 webpack 这样的打包工具正在评估跳过与 http2 捆绑的可能性。

我认为 Webassembly 的module sandbox将模块内部不受信任的代码与外部隔离开来是非常独特的,并且在使用具有安全漏洞(例如缓冲区溢出或恶意模块)的 3rd 方代码时可能会引入安全门。甚至在社区内也被广泛忽视,社区中的知名专家提出了一些相反的强烈主张。

我担心的是,如果我们不提高对这个模块沙盒功能的认识并协调这些模块间提案以保留它,我们可能会在它出现之前失去这个功能,基本上我们会回到处理过程中沙箱。 我知道存在允许从模块沙箱中读取内存的侧通道定时攻击,但至少它是只读的,并且可以限制损坏,例如,如果在数千个应用程序中使用的 jpg 解码器模块受到损害,它就无法回家如果其接口只能访问图像缓冲区但没有网络 API,则立即发送您的密码。

如果做得好,Webassembly 可能是一个新的儿童安全(r) 即插即用模块化生态系统的开始,这将有助于解决或至少包含当前软件系统中的一些流行和深层问题,例如兼容性、安全性、管理努力(容器)和代码的短生命周期。

我认为这种基于文件系统的方法是实用的,实际上可能非常接近所需的方法,因为在 ES 模块中具有中央 index.js 的文件夹已被证明是单个文件之上的一个有用的自然下一个更大的模块单元这仍然允许有效的文件缓存,同时保持文件相互依赖。 但是我认为该模块不应该能够直接在其自己的目录之外导入模块,需要一个全局唯一引用,如已解析的 npm 包名称,并且需要由主机检查安全性。

对此有什么想法吗?

保持整个模块沙箱的东西是好的。 但是,我会避免关注模块来自哪里(依赖于实现)之类的东西,而更多地关注模块之间的关系。 例如,即使在文件系统中(并且有很多文件系统,并不是所有的文件系统都有文件树的概念),对于这些能力系统的规则(向上遍历、向下遍历、兄弟文件、符号链接、等等)。 JS 的WebAssembly API 甚至没有 wasm 存储位置的概念,它们只是漂浮在 VM 周围的数据块。

@devsnek完全同意它应该是一个抽象,不依赖于文件系统甚至文件系统的实现,而是一个更通用和安全的包分层包含概念。

分级遏制

从不相交的节点树的意义上说,它真的需要分层吗? 还是您的意思是有向图(即“带循环的树”)? 有些情况(实际上并没有那么不寻常)需要“伪装成循环依赖的非循环依赖”——模块M1A依赖于模块M2B ,同时C依赖于M2M1中的D上,而没有(简单/支持/内置/可能)对模块及其数据进行深入查询以查明它是否实际上是一个循环的方法。 仅在模块级别(具有不相交节点的树)上解决此类依赖关系将使此类事情变得不可能,从而使开发等变得不必要地复杂化。

@dumblob对于子树外的导入:

但是我认为该模块不应该能够直接在其自己的目录之外导入模块,需要一个全局唯一引用,如已解析的 npm 包名称,并且需要由主机检查安全性。

我看到递归父子关系可能会有所帮助,其中父母 - 不仅是主机 - 向其孩子授予权限。 父级只能授予其已从其父级传递的权限。 这可能包括导入未在子树中定义但父级可访问的模块的权限。 它还可以决定将其中的模块导出到它自己的父级。

在给定的示例中, jpg-decoder Wasm 模块依赖于simd-math Wasm 模块。 jpg-decoder将使用全局唯一包标识符(例如[email protected]/simd-math@^1.3.4 )声明依赖项,并从其父级请求访问它。 如果父模块可以访问该模块并且被父模块认为是“儿童安全”,则授予访问权限并将导入的simd-math模块传递给jpg-decoder

回复:文件系统

首先,让我们列出什么是技术栈。

你有:

  • .wasm二进制文件
  • WebAssembly API (C/C++/JS),
  • WebAssembly主机(手写 JS、Webpack、WASI 解释器),
  • 主机的持久存储(通常是文件系统),
  • 一个 wasm包管理器
  • 一个中央包存储库
  • 用于上传到该存储库的工具。

因此,严格来说,wasm 二进制文件和实例化 API 不需要是文件系统感知的。 二进制文件只需要一个标准的导入语法,API 只需要一个回调,它可以将解析的导入 URI 传递给它。 例如:

WebAssembly.instantiateStreaming(
    fetch('someFile.wasm'),
    {
        // ...
        resolveImport: (parsedURI) => { ... }
    }
);

假设我们使用类似于上述示例的 API,那么主机理论上可以存储他们想要的任何模块; 它们可以从非文件系统存储中提取,例如通过将 URI 解释为对 SQL 数据库的请求。

在实践中,任何想要重用社区制作代码的开发人员仍然需要使用社区制作的模块填充该数据库,这些模块实际上总是构建在文件系统环境中。

因此,虽然上述堆栈的主机部分可以与文件系统无关,但它必须与类似文件系统的包格式以及本质上类似于树的依赖系统(具有自己的子依赖的依赖系统)交互自己的子子依赖,不同级别之间没有包名冲突)。

总的来说,这一切都回到了@lukewagner@ttraenkler所说的,在我们确定导入方案之前,我们需要对包是什么(如果不是他们使用的文件系统格式)有一个整体的定义


从不相交的节点树的意义上说,它真的需要分层吗? 还是您的意思是有向图(即“带循环的树”)? 有些情况(实际上并没有那么不寻常)需要“伪装成循环依赖的非循环依赖”——模块 M1 的 A 依赖于模块 M2 的 B,同时 C 依赖于 M2在 M1 中的 D 上,而没有(简单/支持/内置/可能)对模块及其数据进行深入查询以查明它是否实际上是一个循环的方法。 仅在模块级别(具有不相交节点的树)上解决此类依赖关系将使此类事情变得不可能,从而使开发等变得不必要地复杂化。

一旦你收缩模块内子图,这些半循环依赖真的很常见吗?

我希望,如果您将每个模块视为单个节点,那么在 99.9% 的用例中,您最终会得到一个导入 DAG 或一个导入树。

我认为有一个导入解析方案是有意义的,它假设依赖关系可能在单个包中是循环的,但在其他情况下是树状的(npm 模型),有一些非循环异常(WASI、主机库、对等依赖项、等等)。

好的,在这个建议上来回走动之后,我喜欢它的方向。 但是,我认为没有必要要求特定的前缀或任何其他类型的“安全”机制。 这些应该由 build-system/pkg-manager 处理,而不是解释器。 一个安全的 pkg-manager 应该能够正确地指定完整的依赖层次结构并将其提供给 wasi 解释器,然后它会按照被告知的方式进行解释。 这种关系_可能_看起来像这样(半有效的 json):

[
  {
    "path": "./hash2k3jc/mod/foo.wasi",
    "dependencies": [
      {
        "import": ["mod", "bar"],  # (import "mod" "bar" ...)
        "path": "./hash2k3jc/bar.wasi",
      }
    ]
  },
  {
    "path": "./hash2k3jc/mod/bar.wasi",
    "dependencies": [],
  },
]

这里“mod foo”有 1 个依赖项(“mod bar”),它本身没有依赖项。

这是 wasi 文件 ( path ) 的平面列表,并指定如何将导入语句映射到特定的 wasi 文件 ( dependenciesimportpath ) .

您注意到此构建系统选择使用哈希值是 100% 明确且安全的。 其他构建系统(即封闭的、内部的)可以选择更加宽松以允许就地更新。 关键是这种表示可以支持任何一种方法。

IMO 这最接近地反映了https://webassembly.org/docs/dynamic-linking/——对 wasi 文件或解释器完全没有要求,而是选择提供最大的灵活性和简单性。

注意:我认为假设 wasi 解释器的文件系统是合适的。

注意:我正在开发“网络构建系统”唤醒,并希望(最终)将 wasi 作为主要目标 _and_ 构建环境。 我希望能够发送已签名的依赖项,以便共享库比打包世界更轻量级。

注意:我认为假设 wasi 解释器的文件系统是合适的。

我其实有完全相反的意见:眨眼:。 现在已经有严重的 WASM 用例,其中绝对没有文件系统而且永远不会(例如,没有文件系统的新的或实验性的操作系统,但只是提供最基本的持久存储寻址的类似 DB 的编程接口 - 以最简单的方式形式它可能是一个非易失性 RAM 硬件,不需要像文件系统这样的树状结构)。 所以文件系统不能是 WASI 解释器的先决条件(恕我直言,即使是通用WASI 包管理器也不需要类似文件系统的存储,而是应该使用线性持久存储而不是树状存储或块存储或键值存储或任​​何东西)。

可能,但我也不想要求所有依赖项都在一个连续的存储块上:微笑:(例如使用positionsize而不是path )。 path可以用作类似 DB 的编程接口中的键,不是吗? 也许我们应该把它称为path ... key以外的东西?

我认为有一些机制可以通过键查找二进制 blob,这是包管理器的一个非常重要的特性:smile:

在 js 规范中,我们实际上并没有给模块任何类型的说明符。 我们通过(referringModuleInstance, importString)执行解析,主机必须自己理解是否应该允许导入,解析到哪里等等。我认为 Wasm 目前甚至更模糊,WASI 可以施加额外的约束,例如“如果解析的结果 (referringModuleInstance, importString) 不应该被导入模块传递访问,则使用 xyz 代码退出”。 对于 wasmtime,“可传递访问”可能涉及类 unix 的文件权限。 对于 fastly/cloudflare 之类的东西,它可能意味着“同一个用户上传档案的一部分”。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

chicoxyzzy picture chicoxyzzy  ·  5评论

ghost picture ghost  ·  7评论

bobOnGitHub picture bobOnGitHub  ·  6评论

spidoche picture spidoche  ·  4评论

Artur-A picture Artur-A  ·  3评论