From 0f58eb490328a7174514064f257fa2f8b82b1aa9 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 18 Mar 2025 16:20:01 -0700 Subject: [PATCH] Support cyclic reexports for Wasm --- document/core/appendix/embedding.rst | 65 +++++++++++++++++++++ document/core/syntax/modules.rst | 26 ++++++++- document/core/util/macros.def | 1 + document/js-api/index.bs | 87 +++++++++++++++++++--------- 4 files changed, 152 insertions(+), 27 deletions(-) diff --git a/document/core/appendix/embedding.rst b/document/core/appendix/embedding.rst index 96fa6b2f..b9452b9d 100644 --- a/document/core/appendix/embedding.rst +++ b/document/core/appendix/embedding.rst @@ -217,6 +217,71 @@ Modules && \qquad (\iff \X{ex}^\ast = m.\MEXPORTS \wedge {} \vdashmodule m : \externtype^\ast \to {\externtype'}^\ast) \\ \end{array} +.. index:: direct export +.. _embed-direct-exports: + + +:math:`\F{module\_direct\_exports}(\module) : (\name, \externtype)^\ast` +........................................................................ + +1. Pre-condition: :math:`\module` is :ref:`valid ` with external import types :math:`\externtype^\ast` and external export types :math:`{\externtype'}^\ast`. + +2. Let :math:`\export^\ast` be the :ref:`exports ` :math:`\module.\MEXPORTS`. + +3. Let :math:`\X{result}` be the empty sequence. + +4. For each :math:`\export_i` in :math:`\export^\ast` and corresponding :math:`\externtype'_i` in :math:`{\externtype'}^\ast`, do: + + a. Let :math:`\import_j = \edexportimport(\module, \export_i.\EDESC)`. + + b. If :math:`\import_j = \epsilon`, then append the pair :math:`(\export_i.\ENAME, \externtype'_i)` to :math:`\X{result}`. + +5. Return :math:`\X{result}`. + +.. math:: + ~ \\ + \begin{array}{lclll} + \F{module\_direct\_exports}(m) &=& (\X{ex}.\ENAME, \externtype')^\ast \\ + && \qquad (\iff \X{ex}^\ast = m.\MEXPORTS \\ + && \qquad\quad \wedge~\edexportimport(m, \X{ex}.\EDESC) = \epsilon \\ + && \qquad\quad \wedge~\vdashmodule m : \externtype^\ast \to {\externtype'}^\ast \\ + && \qquad\quad \wedge~\externtype' = \X{ex}.\EDESC) \\ + \end{array} +.. index:: indirect export, re-export +.. _embed-indirect-exports: + + +:math:`\F{module\_indirect\_exports}(\module) : (\name, \name, \name)^\ast` +........................................................................... + +1. Pre-condition: :math:`\module` is :ref:`valid ` with external import types :math:`\externtype^\ast` and external export types :math:`{\externtype'}^\ast`. + +2. Let :math:`\import^\ast` be the :ref:`imports ` :math:`\module.\MIMPORTS`. + +3. Let :math:`\export^\ast` be the :ref:`exports ` :math:`\module.\MEXPORTS`. + +4. Let :math:`\X{result}` be the empty sequence. + +5. For each :math:`\export_i` in :math:`\export^\ast`, do: + + a. Let :math:`\import_j = \edexportimport(\module, \export_i.\EDESC)`. + + b. If :math:`\import_j \neq \epsilon`, then: + + i. Append the triple :math:`(\export_i.\ENAME, \import_j.\IMODULE, \import_j.\INAME)` to :math:`\X{result}`. + +6. Return :math:`\X{result}`. + +.. math:: + ~ \\ + \begin{array}{lclll} + \F{module\_indirect\_exports}(m) &=& (\X{ex}.\ENAME, \X{im}.\IMODULE, \X{im}.\INAME)^\ast \\ + && \qquad (\iff \X{ex}^\ast = m.\MEXPORTS \\ + && \qquad\quad \wedge~\X{im} = \edexportimport(m, \X{ex}.\EDESC) \\ + && \qquad\quad \wedge~\X{im} \neq \epsilon \\ + && \qquad\quad \wedge~\vdashmodule m : \externtype^\ast \to {\externtype'}^\ast) \\ + \end{array} + .. index:: module, module instance .. _embed-instance: diff --git a/document/core/syntax/modules.rst b/document/core/syntax/modules.rst index ec32935c..8c95451c 100644 --- a/document/core/syntax/modules.rst +++ b/document/core/syntax/modules.rst @@ -327,7 +327,7 @@ The |MSTART| component of a module declares the :ref:`function index `. Exportable definitions are :ref:`functions `, :ref:`tables `, :ref:`memories `, and :ref:`globals `, which are referenced through a respective descriptor. +A *direct export* is one where the export references a function, table, memory, or global that is defined within the module itself rather than being imported. + +An *indirect export* (or *re-export*) is one where the export references a function, table, memory, or global that the module imports. + +For an export :math:`\export` in module :math:`m`, the export is direct when :math:`\edexportimport(m, \export.\EDESC) = \epsilon` and indirect otherwise, where +the import corresponding to an export descriptor :math:`\export.\EDESC` is defined by: + +.. math:: + \begin{array}{lclll} + \F{exportimport}(m, \exportdesc) &=& m.\MIMPORTS[i] && (\iff \exportdesc = \EDFUNC~\funcidx \\ + &&&& \quad \wedge~\exists~i~\colon~\funcidx = |\{j ~|~ j < i \\ + &&&& \quad \quad \wedge~m.\MIMPORTS[j].\IDESC = \IDFUNC~\typeidx' \}|) \\ + \F{exportimport}(m, \exportdesc) &=& m.\MIMPORTS[i] && (\iff \exportdesc = \EDTABLE~\tableidx \\ + &&&& \quad \wedge~\exists~i~\colon~\tableidx = |\{j ~|~ j < i \\ + &&&& \quad \quad \wedge~m.\MIMPORTS[j].\IDESC = \IDTABLE~\tabletype' \}|) \\ + \F{exportimport}(m, \exportdesc) &=& m.\MIMPORTS[i] && (\iff \exportdesc = \EDMEM~\memidx \\ + &&&& \quad \wedge~\exists~i~\colon~\memidx = |\{j ~|~ j < i \\ + &&&& \quad \quad \wedge~m.\MIMPORTS[j].\IDESC = \IDMEM~\memtype' \}|) \\ + \F{exportimport}(m, \exportdesc) &=& m.\MIMPORTS[i] && (\iff \exportdesc = \EDGLOBAL~\globalidx \\ + &&&& \quad \wedge~\exists~i~\colon~\globalidx = |\{j ~|~ j < i \\ + &&&& \quad \quad \wedge~m.\MIMPORTS[j].\IDESC = \IDGLOBAL~\globaltype' \}|) \\ + \F{exportimport}(m, \exportdesc) &=& \epsilon && (\otherwise) \\ + \end{array} + Conventions ........... diff --git a/document/core/util/macros.def b/document/core/util/macros.def index 06b60fd6..3ee1edab 100644 --- a/document/core/util/macros.def +++ b/document/core/util/macros.def @@ -346,6 +346,7 @@ .. |edtables| mathdef:: \xref{syntax/modules}{syntax-exportdesc}{\F{tables}} .. |edmems| mathdef:: \xref{syntax/modules}{syntax-exportdesc}{\F{mems}} .. |edglobals| mathdef:: \xref{syntax/modules}{syntax-exportdesc}{\F{globals}} +.. |edexportimport| mathdef:: \xref{syntax/modules}{syntax-export}{\F{exportimport}} .. Instructions, terminals diff --git a/document/js-api/index.bs b/document/js-api/index.bs index ec224142..8d964cb1 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -126,6 +126,8 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: module_instantiate; url: appendix/embedding.html#embed-module-instantiate text: module_imports; url: appendix/embedding.html#embed-module-imports text: module_exports; url: appendix/embedding.html#embed-module-exports + text: module_direct_exports; url: appendix/embedding.html#embed-module-direct-exports + text: module_inirect_exports; url: appendix/embedding.html#embed-module-indirect-exports text: instance_export; url: appendix/embedding.html#embed-instance-export text: func_alloc; url: appendix/embedding.html#embed-func-alloc text: func_type; url: appendix/embedding.html#embed-func-type @@ -1443,8 +1445,24 @@ WebAssembly Module Records have the following methods:
-

ResolveExport ( |exportName|, resolveSet ) Concrete Method

+

ResolveExport ( |exportName|, |resolveSet| ) Concrete Method

+1. If |resolveSet| is not present, set |resolveSet| to « ». 1. Let |record| be this WebAssembly Module Record. +1. Let |module| be |record|.\[[ModuleSource]].\[[Module]]. +1. [=list/iterate|For each=] Record |r| of |resolveSet|, + 1. If |record| and |r|.\[[Module]] are the same Module Record and |exportName| is |r|.\[[ExportName]], + 1. Return null. +1. Append the record { \[[Module]]: |record|, \[[ExportName]]: |exportName| } to |resolveSet|. +1. [=list/iterate|For each=] (|name|, |importedModuleName|, |importName|, type) in [=module_indirect_exports=](|module|), + 1. If |name| is equal to |exportName|, + 1. Let |importedModule| be [$GetImportedModule$](|record|, |importedModuleName|). + 1. Let |resolved| be [=?=] |importedModule|.ResolveExport(|importName|, |resolveSet|). + 1. If |resolved| is null or ~AMBIGUOUS~, + 1. Return |resolved|. + 1. If |resolved|.\[[Module]] is a WebAssembly Module Record, + 1. Return |resolved|. + 1. Note: This fall-through case allows indirect exports referencing JS values to be treated as captured direct bindings in the environment record, + with live bindings unsupported. Only live bindings between Wasm globals are supported for Wasm exports. 1. If the [=export name list=] of |record| contains |exportName|, return { \[[Module]]: |record|, \[[BindingName]]: |exportName| }. 1. Otherwise, return null. @@ -1463,8 +1481,23 @@ WebAssembly Module Records have the following methods: 1. Let |record| be this WebAssembly Module Record. 1. Let |env| be [$NewModuleEnvironment$](null). 1. Set |record|.\[[Environment]] to |env|. -1. For each |name| in the [=export name list=] of |record|, - 1. Perform ! |env|.CreateImmutableBinding(|name|, true). +1. Let |module| be |record|.\[[ModuleSource]].\[[Module]]. +1. [=list/iterate|For each=] (|importedModuleName|, |name|, type) in [=module_imports=](|module|), + 1. Let |importedModule| be [$GetImportedModule$](|record|, |importedModuleName|). + 1. Let |resolution| be |importedModule|.ResolveExport(|name|). + 1. If |resolution| is null or ~AMBIGUOUS~, throw a {{SyntaxError}} exception. +1. [=list/iterate|For each=] (|name|, |importedModuleName|, |importName|, type) in [=module_indirect_exports=](|module|), + 1. Let |importedModule| be [$GetImportedModule$](|record|, |importedModuleName|). + 1. Let |resolved| be [=?=] |importedModule|.ResolveExport(|importName|). + 1. Assert: |resolved| is not null or ~AMBIGUOUS~. + 1. If |resolved|.\[[Module]] is not a WebAssembly Module Record, + 1. Note: This case corresponds to indirect exports to non-WebAssembly Module Record environment records, which are always snapshotted. + 1. Perform [=!=] |env|.CreateImmutableBinding(|name|, true). +1. [=list/iterate|For each=] (|name|, |externtype|) of [=module_direct_exports=](|module|), + 1. If |externtype| is of the form [=global=] [=var=] valtype, + 1. Perform [=!=] |env|.CreateMutableBinding(|name|, false). + 1. Otherwise, + 1. Perform [=!=] |env|.CreateImmutableBinding(|name|, true).
@@ -1482,14 +1515,15 @@ WebAssembly Module Records have the following methods: 1. Let |resolutionInstance| be |resolution|.\[[Module]].\[[Instance]]. 1. If |resolutionInstance| is ~empty~ then, 1. Throw a {LinkError} exception. - 1. Let |resolutionModule| be |resolution|.\[[Module]].\[[ModuleSource]].\[[Module]]. - 1. Let |resolutionName| be |resolution|.\[[BindingName]]. - 1. Let |externval| be [=instance_export=](|resolutionInstance|, |resolutionName|). - 1. Assert: |externval| is not [=error=]. - 1. Assert: [=module_exports=](|resolutionModule|) contains an element (|resolutionName|, type). - 1. Let |externtype| be the value of |type| for the element (|resolutionName|, |type|) in [=module_exports=](|resolutionModule|). - 1. If |importtype| is not an [=extern subtype=] of |externtype|, throw a {{LinkError}} exception. - 1. [=list/Append=] |externval| to |imports|. + 1. Otherwise, + 1. Let |resolutionModule| be |resolution|.\[[Module]].\[[ModuleSource]].\[[Module]]. + 1. Let |resolutionName| be |resolution|.\[[BindingName]]. + 1. Let |externval| be [=instance_export=](|resolutionInstance|, |resolutionName|). + 1. Assert: |externval| is not [=error=]. + 1. Assert: [=module_exports=](|resolutionModule|) contains an element (|resolutionName|, type). + 1. Let |externtype| be the value of |type| for the element (|resolutionName|, |type|) in [=module_exports=](|resolutionModule|). + 1. If |importtype| is not an [=extern subtype=] of |externtype|, throw a {{LinkError}} exception. + 1. [=list/Append=] |externval| to |imports|. 1. Otherwise, 1. Let |env| be |resolution|.\[[Module]].\[[Environment]]. 1. Let |v| be [=?=] |env|.GetBindingValue(|resolution|.\[[BindingName]], true). @@ -1528,23 +1562,24 @@ WebAssembly Module Records have the following methods: 1. [=Instantiate the core of a WebAssembly module=] |module| with |imports|, and let |instance| be the result. 1. Set |record|.\[[Instance]] to |instance|. 1. [=list/iterate|For each=] (|name|, |externtype|) of [=module_exports=](|module|), - 1. If |externtype| is of the form [=global=] |mut| |globaltype|, - 1. Assert: |externval| is of the form [=external value|global=] |globaladdr|. - 1. Let [=external value|global=] |globaladdr| be |externval|. - 1. Let |global_value| be [=global_read=](|store|, |globaladdr|). - 1. If |globaltype| is not [=v128=], - 1. Note: The condition above leaves unsupported JS values as uninitialized in TDZ and therefore as a reference error on - access. When integrating with shared globals, they may be excluded here similarly to v128 above. - 1. Perform [=!=] |record|.\[[Environment]].InitializeBinding(|name|, [=ToJSValue=](|global_value|)). - 1. If |mut| is [=var=], then associate all future mutations of |globaladdr| with the ECMA-262 binding record for |name| in - |record|.\[[Environment]], such that |record|.\[[Environment]].GetBindingValue(|resolution|.\[[BindingName]], true) - always returns [=ToJSValue=]([=global_read=](|store|, |globaladdr|)) for the current [=surrounding agent=]'s - [=associated store=] |store|. - 1. Otherwise, - 1. Perform ! |record|.\[[Environment]].InitializeBinding(|name|, ! Get(|instance|.\[[Exports]], |name|)). + 1. |record|.\[[Environment]].HasBinding(|name|) is true, + 1. If |externtype| is of the form [=global=] |mut| |globaltype|, + 1. Assert: |externval| is of the form [=external value|global=] |globaladdr|. + 1. Let [=external value|global=] |globaladdr| be |externval|. + 1. Let |global_value| be [=global_read=](|store|, |globaladdr|). + 1. If |globaltype| is not [=v128=], + 1. Note: The condition above leaves unsupported JS values as uninitialized in TDZ and therefore as a reference error on + access. When integrating with shared globals, they may be excluded here similarly to v128 above. + 1. Perform [=!=] |record|.\[[Environment]].InitializeBinding(|name|, [=ToJSValue=](|global_value|)). + 1. If |mut| is [=var=], then associate all future mutations of |globaladdr| with the ECMA-262 binding record for |name| in + |record|.\[[Environment]], such that |record|.\[[Environment]].GetBindingValue(|resolution|.\[[BindingName]], true) + always returns [=ToJSValue=]([=global_read=](|store|, |globaladdr|)) for the current [=surrounding agent=]'s + [=associated store=] |store|. + 1. Otherwise, + 1. Perform ! |record|.\[[Environment]].InitializeBinding(|name|, ! Get(|instance|.\[[Exports]], |name|)). Note: The linking semantics here for Wasm to Wasm modules are identical to the WebAssembly JS API semantics as if passing the -the exports object as the imports object in instantiation. When linking Wasm module imports to JS module exports, the JS API semantics +the exports object as the imports object in instantiation. When linking Wasm module imports to JS module bindings, the JS API semantics are exactly followed as well. It is only in the case of importing Wasm from JS that WebAssembly.Global unwrapping is observable on the WebAssembly Module Record Environment Record.