Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loader: allow importing wasm modules #18972

Closed
wants to merge 1 commit into from

Conversation

devsnek
Copy link
Member

@devsnek devsnek commented Feb 24, 2018

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

loader

/cc @nodejs/modules

@nodejs-github-bot nodejs-github-bot added the lib / src Issues and PRs related to general changes in the lib or src directory. label Feb 24, 2018
@devsnek devsnek added experimental Issues and PRs related to experimental features. esm Issues and PRs related to the ECMAScript Modules implementation. labels Feb 24, 2018
@devsnek
Copy link
Member Author

devsnek commented Feb 24, 2018

const importLookup = {};
for (const name of imports)
importLookup[name] = Buffer.from(name).toString('base64');

Copy link
Member

@jdalton jdalton Feb 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the name decoding 👆 about?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the names can be paths/urls which don't make very nice identifiers in js

Copy link
Member

@jdalton jdalton Feb 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you post an example, please?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@jdalton jdalton Feb 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhh so then it becomes

import * as someBase64Thing from "./add.mjs"

Then you capture that someBase64Thing namespace object, and then assign it to another object { "./add.mjs": someBase64Thing }, and then pass it along to get resolved, and then imported into it at
new WebAssembly.Instance(module, reflect.imports).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep

@devsnek devsnek force-pushed the feature/esm-wasm-import branch 3 times, most recently from d01894c to d774051 Compare February 24, 2018 07:46

assert.strictEqual(add(2, 3), 5);

import('../fixtures/es-modules/add.wasm').then((ns) => {
Copy link
Contributor

@giltayar giltayar Feb 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing that this dynamic import will get the module from the cache and thus not instantiate it. This is good, as it checks wasm and cache together, but unfortunately it doesn't check dynamic importing a wasm module.

Perhaps add another import('../fixtures/es-modules/add.wasm?cache-avoider').then(...)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not checking dynamic instantiation (we know that works), i'm checking the namespace and default exports

@@ -90,3 +90,15 @@ translators.set('json', async (url) => {
}
});
});

translators.set('wasm', async (url) => {
const bytes = await readFileAsync(new URL(url));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other translators have a "debug" line that outputs the action (i.e. "loading wasm ${url}"). I would suggest adding it here.

for (const name of exports)
reflect.exports[name].set(instance.exports[name]);
}, imports);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the error thrown in case of a bad or non-existent wasm file good enough? Does it include, at least, the path to the problematic module? I would definitely also add a test to verify that an exception is thrown for a bad or non-existent module.

Copy link
Member Author

@devsnek devsnek Feb 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-existant -> DefaultResolve won't resolve the file and MODULE_NOT_FOUND

bad -> WebAssembly.compile rejects and goes up the chain

i'll add another tests for errors


const importLookup = {};
for (const name of imports)
importLookup[name] = Buffer.from(name).toString('base64');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't base64 also include the characters + and / which are illegal JS identifiers? Also, a base64 string can start with a digit, which is not allowed in JS identifiers.

You can solve this by replacing + with _, / with $, and adding a letter (e.g. i) at the beginning of the identifier.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ou should also remove the trailing = signs that are used for padding base64. Or replace them also with $.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i already add $ at the beginning below, but you are right about the other stuff, a huge oversight on my part 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devsnek .toString('hex')

@giltayar
Copy link
Contributor

Pardon my ignorance, and this isn't really part of this PR, but why does CreateDynamicModule.js have to do the dance of two modules? Can't it just create one module which imports what is needed, exports some variables that it let-s (without values), and then calls the executor, allowing it to change the exported variables?

@devsnek
Copy link
Member Author

devsnek commented Feb 24, 2018

@giltayar we need to delay that final callback until actual execution time, while still already having the reflect pre-evaluated, its a nasty situation (i've tried a few times to consolidate it)

@bmeck
Copy link
Member

bmeck commented Feb 24, 2018

We should hold off on this until the WASM working group is ready and designed their exact behavior with respect to things like importing JS from WASM and live bindings.

CC: @linclark

Copy link
Member

@bmeck bmeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs WASM group feedback

@bmeck
Copy link
Member

bmeck commented Feb 24, 2018

Ah, to note: in https://tc39.github.io/ecma262/#sec-source-text-module-records

A Source Text Module Record can exist in a module graph with other subclasses of the abstract Module Record type. However, non-source text Module Records must not participate in dependency cycles with Source Text Module Records.

Is my main concern with respect to loading JS.

and lack of liveness from our ESM facades is the other concern. Since WASM would suddenly not be able to have live exports with this.

@devsnek devsnek force-pushed the feature/esm-wasm-import branch 2 times, most recently from 627be2a to c6a5756 Compare February 24, 2018 13:55
@weswigham
Copy link

@bmeck do we know the reason the spec forbids circular dependencies involving both JS and non-JS modules? Is it just because non-JS execution can't be defined, so guaranteeing the "well-behaved-ness" of the execution of the circularly dependent set is hard when it includes non-JS code (even though for wasm at least it seems pretty straightforward since it has similar binding properties to esm itself)? Or was it forbidden so it could be dealt with at a later date? Or was it forbidden because cycles in general are a bad design practice? The lack of commentary in the spec makes it difficult to know the rationale for the limitation.

@devsnek
Copy link
Member Author

devsnek commented Feb 24, 2018

@weswigham from irc:

<bradleymeck> I'll make a March agenda item to remove that paragraph from JS spec
<devsnek> `non-source text Module Records must not participate in dependency cycles with Source Text Module Records.`
<devsnek> this is the line you were referring to right
<bradleymeck> yes
<bradleymeck> that line was added after TC39 looked at CJS, but doesn't really make sense
<bradleymeck> it only relates to other module systems that are 1. synchronous 2. able to cause evaluation cycles with ESM 3. can dynamically grow their list of exported names
<bradleymeck> which are few... very few
<bradleymeck> and CJS interop we have doesn't have #2 and #3

@linclark
Copy link

We should hold off on this until the WASM working group is ready and designed their exact behavior with respect to things like importing JS from WASM and live bindings.

Just to update on timing, I added WASM ES modules to the agenda for the March 6 WASM community group call. I also plan to present at the WASM working group face-to-face in April.

do we know the reason the spec forbids circular dependencies involving both JS and non-JS modules?

The issue where it was introduced is here: tc39/ecma262#916 (in this commit tc39/ecma262@0f36dba). I'll be discussing the WASM ES modules work with Domenic, who introduced it, so can ask him what needs to happen for it to be removed.

@weswigham
Copy link

weswigham commented Feb 26, 2018

It looks like it's just a matter of the [[Status]] internal member being on the SourceTextModuleRecord and not AbstractModuleRecord (and thereby not having the correct possible values for the resolution algorithm), and the prose seems to reinforce that. An abstract class nestled between the two (perhaps CycleCapableModuleRecord) which adds the appropriate [[Status]] member (which is the referenced instead of SourceTextModuleRecord in the prose in question) would solve the problem, at least from the spec perspective.

@MylesBorins MylesBorins added the modules-agenda Issues and PRs to discuss during the meetings of the Modules team. label Feb 27, 2018
@BridgeAR BridgeAR added the blocked PRs that are blocked by other issues or PRs. label Mar 6, 2018
@weswigham
Copy link

Ignoring the obvious desire of for a wasm import to work like a similar js one (defs a good one), I feel it's worth mentioning that philosophically, the spec cares as little about wasm modules as it does json documents right now (or any other non-esm module, or how a non-esm graph could be reentrant with a esm one), yeah? If node is... Deferring to? Depending on? other projects to decide how to load them, it'd certainly be nice if all these not-really-js imports we're handled similarly or predictably (or at least that their loading behaviors could be specified predictably), rather than all of them being implemented and/or behaving slightly differently. Now, I'm not saying this must be a loader (it's a good thing to have on by default)... But this should probably be buildable as a userspace loader, too, right? ¯_(ツ)_/¯ it'd certainly be nice to have it as a loader that was just on by default, which'd allow it to be overridden/recomposed/reused/depended on like userspace loaders.

Oh, also, shouldn't, like, there be a simplified API like this to load wasm in cjs? Or is it expected that once this goes in dynamic import will essentially be that in cjs?

@MylesBorins MylesBorins removed the modules-agenda Issues and PRs to discuss during the meetings of the Modules team. label Apr 10, 2018
@BridgeAR
Copy link
Member

What is the status here? Should this stay open?

@devsnek
Copy link
Member Author

devsnek commented Apr 28, 2018

@BridgeAR blocked because we're waiting to see what happens with wasm integrating with es modules and its a new feature and modules team doesn't want to land new features

@TimothyGu
Copy link
Member

@devsnek Do you think it would better for us to close this until the WebAssembly WG figure out the spec side of things?

@devsnek
Copy link
Member Author

devsnek commented Apr 28, 2018

@TimothyGu probably an okay idea

@devsnek devsnek closed this Apr 28, 2018
@devsnek devsnek deleted the feature/esm-wasm-import branch June 25, 2018 07:20
@devsnek devsnek restored the feature/esm-wasm-import branch June 25, 2018 07:20
@devsnek devsnek deleted the feature/esm-wasm-import branch June 25, 2018 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked PRs that are blocked by other issues or PRs. esm Issues and PRs related to the ECMAScript Modules implementation. experimental Issues and PRs related to experimental features. lib / src Issues and PRs related to general changes in the lib or src directory.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants