Design: 禁止重复导入?

创建于 2021-03-03  ·  44评论  ·  资料来源: WebAssembly/design

(本期捕获了CG-03-02 上演示,目的是获得更多反馈,为未来某个 CG 会议的投票做准备。)

Module Linking 提案建议将两级导入重铸为单级导入实例的糖。 例如:

(module
  (import "a" "foo" (func))
  (import "a" "bar" (func))
)

将脱糖为与以下相同的 AST:

(module
  (import "a" (instance
    (export "foo" (func))
    (export "bar" (func)))
)

一个问题是目前 wasm 允许重复导入(具有相同或不同的签名):

(module
  (import "a" "b" (func))
  (import "a" "b" (func))
)

而不允许重复导出。 因此,将上述脱糖为:

(module
  (import "a" (instance
    (export "b" (func))
    (export "b" (func)))
)

将无效。 module-linking/#7讨论了协调这种差异的各种方法,但最简单的解决方案是追溯禁止重复导入。

Bug 1647791添加了遥测来测量 Firefox 中的重复导入,Firefox Beta 86

另一个禁止重复导入的原则性原因是具有不同签名的重复导入只能由主机实现,这打破了允许 wasm 始终能够虚拟化(polyfill、mock 等)wasm 导入的总体目标。

根据具体的经验,规范测试套件曾经有重复的导入(用于打印功能的不同“重载”),但是这被删除了,因为它给想要运行测试的人带来了问题。

因此,可以说这种限制对于 wasm 生态系统来说通常是一个很好的限制,即使将模块链接提案的担忧放在一边。

最有用的评论

重复导入对于从 JS 等动态语言调用导入非常有用,其中相同的函数可以具有各种签名,并且一旦边界的类型系统变得更丰富,我希望它被使用得更多而不是更少,比如可以调用带有数字或字符串参数,或带有一两个参数的相同函数。 我觉得删除此功能对于与 JS 的互操作来说非常不幸。

所有44条评论

如果我们不做这个改变,模块链接提案会发生什么坏事? 这是否只是意味着这种脱糖不会奏效,因此进口的规范描述会更加复杂? 或者它会更根本地干扰模块链接提案的功能? (我一点也不反对这种改变,我只是想更好地理解动机。)

我记得在 Luke 的演讲中,我们目前不允许重复导出。

如果我们取消此限制并允许重复导出怎么办? 这为模块链接打开了设计空间,以另一种方式解决这个问题。

目前,引擎只需要检查导入/导出名称有两个原因:UTF-8 格式良好,以及重复导出检查。 我们已经在线下讨论了解除 UTF-8 限制,但也许我们可以同时解除两者。

重复导出会使基于名称的链接不明确。

@tlively如果我们不禁止重复导入,那么我们在module-linking/#7 中讨论的替代解决方案似乎会给验证和链接的实现增加显着的复杂性,特别是在询问给定模块 A 是否在实例化时从模块 B 导入的时间是可以接受的。

@titzer我认为添加重复导出最终会为每个源语言集成创建一堆难题。 例如,在 JS API 中:重复导出是否显示为执行动态重载解析的单个 JS 函数对象? Web IDL 做到了这一点,而且相当复杂。 如果我尝试从 wasm(通过WebAssembly.instantiate )导入这个 JS 函数会发生什么? 我并不是说这是一个不可能的设计问题,但如果有一个选项可以完全避免 N 语言集成的这些困难/混乱的问题,那似乎更可取。

重复导入对于从 JS 等动态语言调用导入非常有用,其中相同的函数可以具有各种签名,并且一旦边界的类型系统变得更丰富,我希望它被使用得更多而不是更少,比如可以调用带有数字或字符串参数,或带有一两个参数的相同函数。 我觉得删除此功能对于与 JS 的互操作来说非常不幸。

@lukewagner ,由于我不熟悉模块链接提案,这可能是一个坏主意,但不是另一个选择是保持 MVP 导入和实例导入不同吗? 如果它们不统一,MVP 导入仍然可以重复,并且不需要复杂的交叉类型。

重复导入对于从 JS 等动态语言调用导入非常有用,其中相同的函数可以具有各种签名,并且一旦边界的类型系统变得更丰富,我希望它被使用得更多而不是更少,比如可以调用带有数字或字符串参数,或带有一两个参数的相同函数。 我觉得删除此功能对于与 JS 的互操作来说非常不幸。

这个。 这是一千倍。 我是一个为程序集脚本开发人员提供测试框架的项目的主要开发人员,我认为这可能是目前 Web 程序集最重要的部分之一。

0.00014% 包括as- pect ,它是运行程序,它是不断增长的 AssemblyScript 生态系统的关键部分。

重复导入对于从 JS 等动态语言调用导入非常有用,其中相同的函数可以有不同的签名,

如果至少有一个替代方案,那就太好了,因为 WebAssembly 的起源是使用 JavaScript 的浏览器。 让我们为该目标保持轻松。

@tlively这是可能的替代选项之一,是的,但不幸的是,它有两种方法可以做大致相同的事情,这通常是很好避免的,而且它还为实现创造了更多需要关注的案例在验证、子类型检查和实例化期间。

@jtenner @trusktr需要明确的是,您仍然可以多次导入同一个 JS 函数,唯一的区别是您导入的每个不同的签名都需要不同的名称。 所以,例如,而不是发射:

(module
  (import "env" "log" (func (param i32)))
  (import "env" "log" (func (param f32)))
  (import "env" "log" (func (param i64)))
  (import "env" "log" (func (param f64)))
  ...
)

通过以下方式实例化:

WebAssembly.instantiate(module, { env: { log:log } });

你可以发出:

(module
  (import "env" "log_i32" (func (param i32)))
  (import "env" "log_f32" (func (param f32)))
  (import "env" "log_i64" (func (param i64)))
  (import "env" "log_f64" (func (param f64)))
  ...
)

实例化为:

WebAssembly.instantiate(module, { env: { log_i32:log, log_f32:log, log_i64:log, log_f64:log } })

这是您可以做出的可能改变还是以某种方式令人望而却步?

这是您可以做出的可能改变还是以某种方式令人望而却步?

禁止? 不。如果我所要做的就是更改我链接的函数的名称,这很好。 烦人,但还好。 我只是想确保我被正确地听到了。

让我们为该目标保持轻松。

JavaScript 开发人员将受此更改的影响最大。 由于我们是“网络组装”,我们应该非常谨慎地对待网络开发人员,以应对此类未来问题。

如果能让事情变得更好,我完全赞成改变。 因此,按照我认为最重要到最不重要的顺序,我有以下问题:

  1. 这是否使 Web 成为更好的开发场所?

就我所见? 也许。 它迫使开发人员对导入更加明确,并阻止 JavaScript 开发人员做更少的 javascript 事情。 也许更多的限制会带来更多的创造力。 一个似乎从未有过的对话是这如何影响 C# 和 Java 开发人员。 我相信这也可能对编写 Web 程序集编译器的 C# 开发人员产生负面影响,但也许这种复杂性还不错。

  1. 这是否使 WebAssembly 更容易实现、维护和更快地运行?

我不知道这个问题的答案。 编译器开发人员会更精通这类事情。 我的猜测是对于试图维护 wasm 引擎的开发人员来说更方便。 是的,它会更快,或者至少它会更明确,更容易验证模块状态。

  1. 此更改是否会对依赖先前未更改行为的开发人员产生负面影响?

据我所知,使用这个功能的少数人,他们的东西会坏掉,他们会不高兴不得不回去尝试找出如何修复他们的软件。

在为 AssemblyScript 维护测试框架软件后,我开始意识到像这样的小改动不会影响典型的最终开发人员。 这些变化会影响维护生态系统软件、开发工具和编译器的人员。 像这样的规范中的细微变化可能会导致非常大的拉取请求,需要数天或数周才能完成。

我最后的 tl;dr 意见:我不喜欢这种改变,但如果你要做出改变,就先把创可贴取下来。 现在开始连锁反应,并让它发挥作用。

我看到了另一个缺点:

(module
  (import "env" "log_i32_f32_f64" (func (param i32 f32 f64)))
  (import "env" "log_f32_i32_f64" (func (param f32 i32 f64)))
  ...
)

当您应该保留原始导入名称(用于解压)并且不能应用压缩导入名称的 binaryen 传递时,它可能会增加二进制大小。

但是对于一般情况这不是太大的问题

我看到的核心问题是用户必须预先知道所有的参数和参数类型才能手动修改导入对象,而目前编译器可以假设例如Math是作为一个整体导入的,并生成导入在编译期间遇到导入调用时具有相同名称但不同的参数或参数类型。 这里的一个简单例子是Math.max ,稍微复杂一点的是具有截然不同签名的类型化数组构造函数,并且它只会变得更复杂,例如 Node.js API,它通常允许在中间省略参数同时还允许完全不同的类型,当然还有最终的用户代码,这是人们永远不知道的。 当然,每当 Wasm 模块的源代码中相关的某些内容发生更改时,您还必须再次重新访问导入对象。 那都会非常难过。

我想我在 CG 会议上无法表达的是,我认为模块链接提案试图将需求从基于名称的链接下推到引擎中,对我来说,这里有一个层,以便这些需求可以做到这一点对我来说似乎很自然不被推倒。 例如,当我们限制 core wasm 的二进制格式时,我们声明引擎必须检查和拒绝不符合的模块。 对于类型安全、合理的尺寸限制以及其他对引擎很重要的事情,这是完全合理的。

我认为名称与引擎无关。 它们只对托管嵌入很重要,引擎真的不需要查看(更不用说拒绝)名称。 这在其他评论者提到的 JS 重载情况中得到了证明。

我对 UTF-8 的评论是朝着这个方向的; 除了拒绝坏名外,引擎实际上不需要 UTF-8 处理; 重复导出也是如此。 这闻起来特别有趣,因为我突然不得不从头开始实现一个 UTF-8 验证器......嗯。 此外,重复导出检查是向导使用哈希表的唯一地方。 并不是说一百行左右的代码是一个危机,而是在面向计算的 VM 练习中它是一个奇怪的鸭子。

我理解模块链接提案的目标是为 Wasm 提供一个标准的基于名称的链接解决方案,但出于多种原因,我认为这有一些风险,无法以足够通用的方式工作,我们应该诉诸于禁止那些从引擎的角度来看,在引擎级别上正在完善。

所以我支持我之前的建议,我们应该更加自由,也许取消对重复出口的限制。

如果模块链接提案现在想要拒绝由于命名问题而无法链接的模块,那么作为核心 wasm 之上的层限制似乎完全没问题。 但是就像我说的那样,模块链接提案有可能无法解决很多编程语言的链接问题,相反,我们需要一个更具可编程性的解决方案,其中涉及一流模块的 API。 但这可能是一个较长的对话,这是一个相当狭窄的问题。 这就是我在这里思考的背景。

啊,有趣的@titzer。 我一直有类似的想法。 简而言之,编译和链接是相互关联的,但仍然是不同的,这个问题是一个金丝雀,它们耦合得太紧密了。

我们可以想象将 WebAssembly 的所有链接相关方面都放在一个单独的部分中。 “编译”部分仍然会有导入,但它们没有命名(超出它们的索引)——只是“链接”部分将负责填充的漏洞。 “编译”部分可能仍然有导出,但它们又是未命名的,“链接”部分负责提供名称。

因此,链接部分可以包含特定于嵌入器的内容。 例如,接口类型的链接部分可以提供adapter_func s,而 JS 的链接部分可以安装rtt字典,如@tebbi的 WebAssembly/gc# 并且对那个特定的嵌入器非常有用。 模块类型提案本质上是另一个链接嵌入器,部分紧张在于那里最有意义的东西(例如用于简单链接解析的单个名称)并不是在 JS 中最有意义的东西(例如重载名称)。

附录:这对于支持抖动很有用。 模块希望它们的函数可以直接从它们动态生成的附加模块中调用,而不必将这些函数暴露给嵌入式链接系统。

正如@titzer所说,这也超出了本讨论的范围,但我也想将我的想法

@dcodeIO在我见过的所有场景中,实例化 wasm 模块的 JS 代码是由生成 wasm 模块的同一个工具链生成的(通常 b/c 实例化往往需要一堆胶水代码),因此我不知道这个 impl 细节会冒泡给用户。 但你有不同的看法吗?

我想澄清和强调的一件事:从纯粹的 JS 生态系统 POV 来看,重复导入是一种危险,因为 JS API 和 ESM 集成都无法连接两个 wasm 实例 A 和 B,其中 B 具有重复导入我们希望用 A 来满足。A 可能能够导出具有合适签名的两个函数,但 B 将只能导入其中的 1 个(具有一个签名,通常无法同时满足两个导入)。

@titzer我理解希望将名称排除在核心 wasm 之外,但要实现您的目标,而不是像目前提议的那样导入模块:

(module
  (import "other" (module
    (import "foo" (func))
    (export "bar" (func))
  ))
)

我们必须从导入类型中删除"foo""bar" ,留下一些纯粹的位置:

(module
  (import "other" (module
    (import (func))
    (export (func))
  ))
)

如果我们期待单独的编译场景(例如,将共享库分解为广泛重用的模块),那么这将导致旧的 C++ 脆弱基础问题的 wasm 版本,因为我们将完全依赖于位置顺序; 这就是导入/导出名称修复的内容。 这是你提议的吗? (请注意,一流的模块引用和/或一流的实例/实例化都具有相同的问题;它是正交的。)

但是,如果这不是您的提议,那么我不明白我们如何才能摆脱名称作为模块链接的重要组成部分以及重复导入/导出导致的歧义。

不,我不是在提议位置链接。 我同意这确实非常脆弱。

但是考虑到 Jawa 用例以及接口类型,突出的是需要实际解析并将逻辑应用于导入。 在 Jawa 中,导入名称不是字符串,而是一种语言中的实际表达式,并且通过导入参数,它们可以将类型和函数作为导入。 对于与 Java 或任何高级语言恕我直言的后期绑定,没有纯粹的 wasm 解决方案。 一个低级的基于名称的链接解决方案可能服务于使用非常有限的语言集构建的小众 wasm 生态系统,但从长远来看,它并不能解决高级语言的用例。 因此,IMO 正在建立一种机制来创建新的、非常有限的生态系统,而不是添加一个足够通用的机制来建立更高级别的链接。

因此,我对一流模块的评论。 从长远来看,我们将需要一个反射和 JITing API,它将允许完全可编程的链接和一个非常严格的基于名称的匹配系统并不能真正解决任何语言的实际问题或给它们足够的绳索。

而且基于名称的匹配也非常脆弱。 因为我们还没有任何导入的参数化机制,语言不得不求助于名称修改,这既丑陋又脆弱,甚至不能支持子类型和重载等简单的事情。

抱歉延长评论; 我们现在真的在讨论一个更大的问题,可能不应该像这里那样讨论一个狭隘的问题。

在我见过的所有场景中,实例化 wasm 模块的 JS 代码是由生成 wasm 模块的同一个工具链生成的(b/c 实例化通常需要一堆胶水代码)

例如,AssemblyScript 不会生成特定于模块的粘合代码,但会尽量避免这样做。 它只发出类型定义(用于开发目的)以与我们称之为“加载器”的小型通用伴随 JS 库(可以对更高级别的导出名称进行分解以便更方便地从 JS 使用)一起使用,但使用它通常是可选的,尤其是当主要使用 AS 作为一种稍微更好的语法来生成最小的 Wasm 模块时,比如压缩算法(例如,只是类似 C 的函数导出),或者在为 WASI 编译时。 我希望我们最终不再需要修改名称,例如MyClass#myMethod ,但我不确定这是否与此问题有关。

重复导入是一种危险,因为 JS API 和 ESM 集成都无法将两个 wasm 实例 A 和 B 连接起来,其中 B 有一个我们希望用 A 满足的重复导入

我原以为这种情况会在路上的某个地方产生错误,是的,可能是在将 Wasm 导出链接到具有无法满足的签名不匹配的 Wasm 导入期间,而在使用时它会在运行时导致许多有趣的行为与 JS 导入或任何其他动态语言的导入不匹配的签名。

我不清楚为什么需要使用相同的名称来导入多个具有不同签名的 Wasm 函数,因为 Wasm 函数具有固定的签名(与 JS 宿主函数不同,JS 主机函数将所有参数和返回类型强制转换为给定的签名)。 我误解了前提吗?

如果给定的导入是一个 Wasm 函数,这种情况下出现错误似乎是完全正常的,除非标准被扩展为允许指定具有不同签名的函数来调用自动强制。

虽然我最初对删除重复导入持谨慎态度,但我现在强烈支持此更改。

正如@lukewagner指出的那样,关键的观察结果是无法在各种环境中处理重复导入。 正如他所说,即使对于 JS 用例,这也是有问题的。

最重要的是,它打破了一个基本的模块化属性,即所有导入都可以由 Wasm 或主机模块同等实现。 这对于虚拟化等用例很重要。

重复导入有效地在客户端强制重载或动态类型机制,这很糟糕,因为并非每个客户端都有支持它的方法(包括其他 Wasm 模块)。

正如测试套件的历史所证明的那样,这在实践中是一个真正的问题。

@titzer ,恐怕我没有跟上。 明确命名似乎是参数化的正交问题。 即使我们有后者,我们仍然希望确保名称唯一地确定一个(参数化的)实体。 可能更是如此,因为当这些其他方式变得更复杂时,通过其他方式解析重载的导入名称会变得更加复杂?

就像我说的,名称对引擎无关紧要,因此它们的唯一性对引擎也无关紧要。 它们对 _linkers_ 很重要,因此如果它想强制导入名称(或导出名称)的唯一性,它是一个特定的链接解决方案的选择。 极限中的链接器最终将需要一个完全可编程的解决方案,这不仅是一流的模块和实例,还有 JITing 和增量构建它们的能力。

我认为所有的麻烦都来自于试图将模块从基于名称的导入到导出的静态类型函子 [*]。 这样做的结果是名称成为类型的一部分,并且将该类型引入 wasm 语言需要类型检查,因此需要名称匹配规则。 但问题在于这些名称是低级名称,类似于损坏的 C/C++ 名称。 这样做的最终结果是三件坏事:

  1. 我们已经看到:wasm 甚至无法连接到确实有过载的主机环境
  2. 链接不断受到单独使用低级名称可以和不能表达的内容的限制,导致每个新的 wasm 功能都存在持续的设计问题
  3. 它不提供不能使用任何低级名称修改方案的语言,这需要一个可编程和 JITing 解决方案。

这些问题直接源于引入静态模块类型的愿望,因为这是由引擎为简单的链接解决方案进行基于名称的匹配所激发的。 但实际上,引擎永远不应该看名字; 另一件事 - 链接器 - 应该查看名称以及它们是否应该为引擎解析它们。 是的,解决这个问题的一种方法是,该 API 确实向链接器提供了导入向量,并且它返回了一个导出向量——这就是我在 Wizard 中所做的,也是 C 嵌入 API 的工作方式。

[ ] 我曾经也这么认为,wasm 模块是从导入到导出的函数。 但这缺少一些重要的东西:来自高级语言的链接逻辑。 内容的*描述,但使用链接器理解的源语言。 一个简单的字符串匹配_不_就足够了。

@titzer ,我同意模块链接提案没有为许多用例提供足够强大的链接模型(例如,它无法替换 C 目标文件),但我不认为提供通用链接模型是提案的目标。 我认为它是当前基于字符串的导入/导出系统的扩展,它对于 Web 上常见的捆绑器样式链接已经足够且有用,并且已经在某种程度上融入了 Wasm 的模块结构。 更高级别的特定于语言的链接器可以继续在命名方面做任何他们想做的事情,因为它们的模块化无论如何都必须位于 WebAssembly 之上。

@titzer如果导入和导出的名称不是模块类型的一部分,那么您打算如何避免您说您也想避免的脆弱性问题?

我也不明白你是如何解决虚拟化问题的。 阅读@rossberg的评论,我意识到虚拟化是这里要解决的更基本的预先存在的问题,它实际上与模块链接提案无关。

我对这种变化表示同情,但查看 WebAssembly/module-linking#7,似乎没有考虑很多选项。 有很多关于交集类型的讨论,但这些讨论没有提到使用交集类型的转换不适用于强制链接系统,例如 JS API,这是人们在这里提到的一些用例所依赖的。 考虑的所有选项似乎都基于表面语言模块构造(主要基于 Andreas 对特定功能语言的模块的了解),但似乎没有太多考虑低级模块构造。 一个低级的模块系统很容易使这里的问题变得毫无意义,同时仍然服务于模块链接提案的目标(包括基于名称的解析)。 同时,可以更轻松地构建许多重要的扩展。

@RossTate ,我不确定您在这里所说的“低级模块系统”是什么,或者它与已经讨论过的有何不同。 如果您对不同的解决方案有想法,也许可以在https://github.com/WebAssembly/module-linking/issues/7上讨论它们,以将这个问题集中在禁止重复导入的特定建议解决方案上。

我在https://github.com/WebAssembly/module-linking/issues/7#issuecomment -791063260 上留下了一张便条,询问这是否可以简单地作为模块链接验证的一部分而不会导致现有代码的向后兼容性问题在野外。

这正是我花了这么长时间才面对的问题。 模块没有类型而不考虑导入处理器,该处理器赋予与导入相关的名称和约束的含义。 这就像询问文本文件的类型是什么。 当然,文本文件可以包含用任何语言编写的文本,因此如果没有上下文,这个问题就没有意义。 当我们修复语言时,我们甚至可以开始讨论文件中的类型(如果有的话)。 在这里,模块也是一样的。 模块不是函子,即从导入(带名称)到导出的纯函数。 他们需要一个链接系统来赋予进口意义。 在我们过了那个点之前,我所说的其余部分都没有任何意义。 我觉得这是模块链接提案中的一个非常基本的假设,甚至还没有出现进行讨论。 我觉得我们并没有真正质疑它。 我似乎在重复自己,因为我实际上在质疑比看起来礼貌更深的问题。 当然,我并不是说不礼貌,但我确实想挑战模块需要看起来像函子。 我们甚至没有质疑这一点。

我可以举一个又一个的例子来说明为什么模块显然不是函子。

  1. 将 wasm 模块链接到 JavaScript 需要通过一个导入对象——一个导入对象实际上可以是一个代理或以其他方式将图灵完备逻辑应用于名称(例如解码它们),甚至具有状态,这使得它不可能转换为函子。 从字面上看,它在操作上被定义为一种算法,通过导入查询 JavaScript 对象导入。 整个链接生态系统不能用模块链接来表达。

  2. C 嵌入 API 也是如此。 它为嵌入器提供了一个模块作为一个完全可检查的实体,完整的名称、种类和签名供检查。 为了实例化一个模块,嵌入器可以应用他们喜欢的任何逻辑,只要他们简单地提供到引擎的绑定(是的,以位置方式)以进行实例化。 此外,一个完全可编程且不通过模块链接表达的生态系统。

  3. Jawa和Pywa也是这样。 Jawa 是完全静态类型的,但它的导入是在导入语言中编码的,简单的名称匹配是不够的; Jawa 运行时必须综合 (JIT) 新函数、类型和其他绑定以提供给引擎。 Pywa 是完全动态类型的,它的 JS 实现确实以类似的方式使用重载。

模块及其导入仅在链接方案、语言、导入处理器的上下文中才有意义,无论您想怎么称呼它。 同样,它们不是函子。 它们没有没有它们所处的上下文的类型。模块导入是对它们从外部世界需要什么的描述,这些描述是用引擎不能理解的外语编写的。 它们只有 Wasm 类型,因此模块本身可以根据 Wasm 的类型系统独立于其导入进行单独的类型检查,并且可以根据导入约束中阐明的假设检查导入。 同样,在我们开始尝试将模块变成 wasm-y 类型的函子之前,这里没有任何关于名称的内容。 但那些名字是紫色的! 这就是所有源语言的东西幸存下来的地方。 当然,用字符串比较摸索它是行不通的。

考虑到这一点,希望制作一个“简洁”的链接解决方案并不是一件坏事。 当名字不太疯狂时,这是有道理的。 这就像创建一个小生态系统,其中名称非常简单,简单的匹配就足够了。 它只是不需要是原始的,因为它不是正确的原始。 这应该很清楚,因为当我们得到真正需要的东西时,它会直接将其假设融入 wasm 的核心,这有可能成为绊脚石或奇怪的疣,这是一个完全可编程的链接 API。 如果我们有用于链接的可编程 API,那么模块链接建议可能只是一个库。 所以我们应该考虑如何将 C 嵌入 API 变成更多标准的 wasm API,然后编写模块链接作为针对该 API 的库。

@lukewagner “脆弱性”问题:请注意,我不主张丢弃名称。 链接系统显然需要名称。 但是引擎不需要它们。 对于引擎和链接器之间的 API,我的意思是引擎可以将导入作为有序向量传递给链接器,链接器可以解析(可能是它们的子集),并按位置返回它们。 模块本身不需要依赖位置。

是的,对于这个问题,有一个很好的重复导入用例,我已经考虑了一段时间,是的,重复导入取决于他们的论点:共享(或不共享)内联缓存。 例如,如果我们要导入一个执行 JavaScript 属性访问的函数,我们可能会给它命名为“GetProperty[foo]”。 如果我们只导入一次,引擎应该使用什么内联缓存? 它应该检查 wasm 代码来为他们生成一个新的调用站点吗? 好吧,如果一个模块可以多次使用完全相同的名称导入相同的函数,但每次都获得一个新的 IC,那么模块就可以控制引擎如何适应其各种多态性。 (当然,我们总是可以在字符串的末尾粘贴一些愚蠢的 uniquifying 垃圾,但是为什么要这么麻烦?这只是解决了我们在 wasm 中没有使用的完全不同的东西的限制。

@titzer

独立地,我学到的这里的根本问题仍然是:wasm 模块是否应该能够虚拟化 wasm 导出? 完全搁置“如何将 wasm 模块链接在一起?”的问题,这似乎是向 CG 提出的基本原则问题。 我想知道是否值得一个单独的设计问题来专注于这个问题,独立于有关链接的问题?

虚拟化很重要,可编程链接肯定会支持这一点。

我实际上并不认为我所谈论的与今天的 wasm 有很大不同,而且我所谈论的不仅仅是针对爪哇。 在考虑链接 .NET 或其他语言运行时时也会出现同样的情况。 我在上面给出了三个示例,其中两个是现存的嵌入,由于嵌入到另一种语言中,因此具有完全的可编程性。

我非常愿意为 CG 制作一份演示文稿。

Fwiw,根据我的理解,titzer 的建议使 Wasm 可用于更多用例而不是更少用例,这比替代方案更符合我的期望。 鉴于在其他地方出于类似原因已经在类似问题上发生了所有争议,我什至建议可能需要对 Wasm 的方向进行更广泛的讨论。

例如,我发现这样的语句

您提出的观点与今天如何指定、实施和广泛谈论 wasm 的方式有很大不同,所以我认为有责任向 CG 提出这种观点的改变并获得一些支持——我不不要认为这是你可以断言是wasm的本质。

在上下文中深切关注。 在如何指定 wasm 方面可能存在更广泛的问题,我不清楚哪一方正在离开。

@titzer我主要指的是你所说的这一部分(以及所有的推论):

模块没有类型而不考虑导入处理器,该处理器赋予与导入相关的名称和约束的含义。 这就像询问文本文件的类型是什么。

这将与当前规范(例如,这部分)有很大不同。

@lukewagner感谢您向我指出这一点。

在那个公式中,(正如我认为关于模块链接的讨论所指出的),导入和导出是向量,因此是位置。 要将一个模块的(位置)导出绑定到另一个模块的(位置)导入,您需要一个映射函数。 我只是指出“简洁”的精确名称匹配功能只是一种选择,我们已经有另外两个嵌入,其中映射功能可以在宿主语言中完全编程。

@titzer是的,但请注意:尽管 JS API 是完全可编程的,但如果我给你一个具有重复导入(不同类型)的 wasm 模块,你就不能使用 JS API 来实现那些带有 wasm 导出的导入由于在WebAssembly.instantiate()使用名称查找。 ESM-integration 也是如此。 虽然我可以想象这两个链接方案的理论扩展以支持重复导入虚拟化,但我很难想象每个人都有足够的动力来实际指定和实现额外的复杂性。

因此,我认为我们必须提出的问题是:“在 wasm 生态系统中不可避免地出现的无数 wasm 链接方案最终会支持重复导入的虚拟化吗?” 而且,根据我们当前的样本集,这看起来不太可能。

想象一下,WebAssembly 导入和导出没有与之关联的字符串。 JS API 将使用纯粹的位置索引来访问导入和导出。 据我所知,让现有的工具链在这样的设置下工作会很容易(相对而言)。 (在某些情况下,它可能更容易一些,因为像 emscripten 这样的东西不必生成任意名称来连接“内联”JS 代码(a la EM_ASM )。)

在这样的世界里,就不会有虚拟化问题。 也就是说,虚拟化问题是由选择使用名称而不是位置索引引起的。 甚至可以通过在 JS API 中使两者都可用,以向后兼容的方式解决这个问题。

关键是非位置导入/导出实际上是特定于嵌入器的装饰,用于促进将 wasm 模块集成到生态系统中。 如何指定这些装饰品确实应该适应生态系统的(现有或预期的)规范。

例如,通过 ESM 集成,其他模块导出的 JS 值可以重载,但 WebAssembly 不支持重载,因此 ESM 进出口装饰设计应该弥合这一差距,以便 WebAssembly 模块可以代替尽可能多的 JS模块尽可能。 这可以通过使用重复导入和类型导向强制来提取从另一个 JS 模块导入的函数的不同重载来完成。 甚至可以允许重复导出,只要导出的类型都是具有足够不相交/可区分参数的函数,以便它们可以组合成一个重载的 JS 函数。 当然还有其他设计选项,各有利弊。

作为对比示例,假设 WebAssembly 的主要嵌入语言主要使用命名参数作为函数而不是位置参数。 然后导入-导出装饰看起来会大不相同,函数的导出为每个参数分配名称。 但在底层,WebAssembly 模块仍然只是位置导入和导出。

这就是为什么我建议通过将事情降低一步来解决问题。 “核心”WebAssembly 模块链接,即用于将同一文件中定义的 WebAssembly 模块链接在一起(例如,用于将接口类型适配器模块链接到它所适应的模块)应该是位置性的。 “嵌入式” WebAssembly 模块链接,即用于将 WebAssembly 和特定嵌入器中单独文件中定义的特定于嵌入器的模块链接在一起的模块,将是特定于嵌入器的部分,并且主要负责定义嵌入器之间的转换 -特定构造和 WebAssembly 构造,例如强制函数、向导出的函数/实例/模块添加装饰,或将(单/双/列表)名称与位置导入相关联。 那么“核心”WebAssembly 从不担心数字和位置以外的任何事情,重复导入等问题成为特定于嵌入器的问题,而不是“核心”问题。

带着这种心态,这个问题可以改写为“对于 JS API 或 ES 模块,我们是否应该允许两个导入与同一个字符串相关联?” 答案对模块链接提议没有意义。 这是一个有用的问题,但目前很难得出一个好的答案,而主要论点是基于(我认为)不应该存在的耦合。

我同意位置导入/导出避免了整个问题的重复。 但是,我想避免的问题是,当您在不同的时间进行单独的编译时,您必须开始关心版本控制,位置导入/导出具有经典的脆弱基类问题,这在组件系统中得到了具体体现像基于(位置)C++ vtable 布局的 COM。

现在,如果我明白@RossTate@titzer被提出,这是一个应该在主机层来解决,允许主机名介绍适合于该主机的一个问题。 我看到的问题仍然存在于该答案中,它本质上是按主机划分 wasm 工具生态系统。 例如,如果 clang 希望进行任何形式的链接(例如,分解出一个常见的libc (这是这种新兴的高度-多模块 wasm 执行平台))——我需要教 clang 关于它所针对的所有不同的具体主机,并让主机成为 clang 的参数。 此外,现在生成的输出只能在该主机上运行,​​从而无法生成可跨主机重用的.wasm模块。 为了实现共享工具和可重用模块,我们需要某种与链接相关的通用抽象编译目标(特别是不假设名称和实例之间 1:1 关联的目标,这对于许多用例来说是不够的)) ——这正是模块链接提案希望建立的。

如此说来,我可以想像,其中模块连接操作是加入如上述芯WASM和下面的JS API主机无关SPEC-层的混合解决方案。 这将允许生成与主机无关的工具和模块(针对这个与主机无关的规范层),同时允许主机嵌入完全避开这一层,直接使用专门的链接方案实例化核心模块。

这听起来像是解决这些相互冲突的目标的合理方法吗?

谢谢, @lukewagner对我的

我认为混合/两层解决方案是我一直试图建议的,其中两层对应于“内部”和“外部”链接。 内部链接将是定位的,并指定构建/连接/实例化模块的指令——这是每个 WebAssembly引擎(带有模块链接)必须实现的。 外部链接将特定于嵌入器,并指定如何从其他文件等构建(位置)模块,例如将文件名和/或字符串名称与导入/导出相关联——这是 WebAssembly 嵌入必须实现的。 这种关注点分离将使跨多个生态系统重用 WebAssembly 程序的“核心”方面和 WebAssembly 引擎的“核心”方面变得更加容易。 (我也认为这种分离有可能清理内部模块链接的各个方面。)

但是,正如您所提到的,有两个“外部”定制可能会导致严重的破坏。 因此,虽然我认为“内部”和“外部”链接应该分开,但我认为拥有一个“通用”外部链接标准很有用。 有些人可能会开发更专业的外部链接系统,以便更好地适应或利用特定生态系统的细节(使用更专业的工具来做到这一点),但许多程序的需求将符合简单的通用标准。

所以,总而言之,我认为应该有一个“内部”链接系统(这是低级的,例如位置),并且应该有可能有多个“外部”链接系统位于其之上,但只有一个“通用”标准化这样的系统到位。 那有意义吗? (不幸的是,这些都没有说明当前的重复导入问题,但至少尝试描述这个问题在这个空间中的位置,如果有道理的话。)

好的,所以考虑一下这种变化在短期内具体意味着什么:将模块链接重新构建为核心规范之上的“公共链接”层(如果我们愿意,也可以在 JS/Web 规范下)可能意味着我们根本不需要向核心规范添加任何内容。 特别是,核心模块的导入/导出可以保持位置,这样模块链接层在执行实例化时完全忽略这些名称。 基于此,我们可以用“否”的答案来结束这个问题,在这种情况下,我会回到@titzer的观点之上,为了对称,我们也应该允许重复导出,例如,核心模块由模块链接层专门实例化的可以简单地为所有导入/导出字符串提供 0 长度的字符串——我们不会强制核心模块包含无意义的"""a""b" , "c" ... 导出名称只是为了安抚验证。

(将来,我们可以讨论使用模块/实例的位置版本扩展核心 wasm,但我认为这对于模块链接提案所关注的初始用例和需求集来说不是必要的。)

@rossberg @titzer @tlively 有什么想法吗?

我担心编写核心 Wasm 导入和导出必须严格定位会关闭https://github.com/WebAssembly/gc/issues/148的大门,这建议引入新的importexport机制来解决进出口问题必然是不对称的。 建议的机制本质上在核心 Wasm 级别引入了类型的弱链接,我不确定它如何在没有字符串标识符的情况下在位置上工作。

显然这是假设性的,因为没有就将 GC 提案朝那个方向推进的共识,但我个人认为对于 GC 提案来说这是一个足够有吸引力的选择,我会难过地看到它被像这样的不相关的发展变得不那么可行马上。 我是否错过了建议更改在位置上起作用的方法或误解了这个问题?

啊,有趣的联系,@tlively! 这让我想到了几个想法。

一种想法与静态链接有关。 就像命名导入有助于外部链接模块的可变性一样,默认导入也是如此(就像函数的命名和默认参数一样)。 默认导入可以在不破坏向后兼容性的情况下向模块添加新的可配置性选项。 在两级链接方法中,在内部层我会将其表示为两个模块(模块 A 有一个导入,模块 B 有一个导出),而在外部层,语义将是“如果给这个导入的参数,将该参数链接到 A,否则将 B 链接到 A”,以便定义默认值的 B 仅在需要时使用。

另一个想法与动态链接有关。 这里字符串用作全局键。 importexport本质上是说“导入与此键关联的值,如果尚未定义该值,则将以下内容全局注册为与该键对应的值”。 (同样,这一切都在外部链接层中。)

尽管 WebAssemby/gc#148 是关于名义类型的,但我会注意到上述两个想法也适用于异常。 对于静态链接,我希望模块的常见模式是导入一个异常事件,该事件默认情况下是新生成的,但可以实例化以使用与其他模块(实例?)相同的事件,以便共享非本地控制跟他们。 对于动态链接,如果真的不希望有一个中央运行时模块(人们已经知道我的想法),那么任何有异常的语言都需要在动态链接的模块和importexport之间协调异常事件

整洁的! 如果我理解正确, importexport机制可以被认为是模块链接层的一部分,它通过隐式创建一个“默认”导出模块来导入定义的类型,从而对位置导入和导出进行脱糖。 这对我来说似乎是合理的,所以我对这个方向没有进一步的担忧。

我认为分层论点是双向的。 您同样可以争辩说,将他们用于消除 im/exports 歧义的任何(高级名称、额外信息)配对方案映射到低级名称的明确方案应该是更高层的责任。

对于较低级别,似乎总是尽可能明确和明确。 这使系统及其接口保持简单——并将处理更复杂方案的负担放在需要它的生态系统上,而不是每个人身上。

至于将链接提案作为一个单独的层,这在抽象上听起来不错,但我有点怀疑我们能否顺利进行。 例如,该提案目前允许内部模块为静态实体(如模块或类型)引用外部名称空间,这似乎相当重要,并且无法映射到核心规范(除非通过等效于 lambda 提升的转换,这会破坏预期的组合模式,除非我们还引入了导入的部分应用/实例化,即“模块闭包”的一种形式)。

例如,该提案目前允许内部模块为静态实体(如模块或类型)引用外部名称空间

这是我觉得可以使用一些简化的方面之一。

除非我们还引入了导入的部分应用/实例化,即“模块闭包”的一种形式

这是我认为可以帮助实现这种简化的技术之一。

我通常支持将链接规范向上移动一层的想法; 这基本上就是我一直在争论的。 引擎不需要知道名字。

@rossberg我通常认为“明确和明确”对下层来说很好,但这个例子更多的是关于是否规定性,下层应该做得好,不要对与他们无关的事情进行规定,海事组织。

https://github.com/WebAssembly/design/issues/1399#issuecomment -808401005 中有一个相关的观察,其中问题描述了在调用 Wasm 导出时调用失败的问题,其中省略了i64参数,在在从 JS 调用到 Wasm 时,允许重复导出并选择具有匹配数量的导出,这将是上下文中的首选解决方案。

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

相关问题

Artur-A picture Artur-A  ·  3评论

spidoche picture spidoche  ·  4评论

thysultan picture thysultan  ·  4评论

JimmyVV picture JimmyVV  ·  4评论

artem-v-shamsutdinov picture artem-v-shamsutdinov  ·  6评论