Skip to content

gen-host-js: Top-level await compatibility via $init promise#414

Merged
guybedford merged 7 commits intobytecodealliance:mainfrom
guybedford:tla-compat
Nov 7, 2022
Merged

gen-host-js: Top-level await compatibility via $init promise#414
guybedford merged 7 commits intobytecodealliance:mainfrom
guybedford:tla-compat

Conversation

@guybedford
Copy link
Copy Markdown
Contributor

@guybedford guybedford commented Nov 4, 2022

This adds a new compatibility feature, --tla-compat which is enabled under the --compat flag as well. Top-level await support is wide, but still gives some gap on browser compatibility (around 5% of browsers support modules but not TLA), so is still useful for maximum JS support.

When used, all top-level Wasm loads and instantiations that rely on await get wrapped in a single $init promise, which is exported and must be awaited before any component functions can be called. This only applies to ESM instance mode and not --instantiation.

If calling any exported component functions before initialization has been completed, an error is thrown informing that the $init promise must first be awaited. The $init export name is used as it should be an invalid world export name I believe.

Copy link
Copy Markdown
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

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

This seems like a reasonable feature, but the internals feel pretty heavy-handed with lots of if/else strewn throughout code generation. Instead of this structure could something be generated along the lines of:

async function init() {
    // current init, always using `await`
}

// if --tla-compat
init()

// if not-compat
await init()

That way I think just the exports would get the let-vs-otherwise definition treatment?

@guybedford
Copy link
Copy Markdown
Contributor Author

@alexcrichton the problem is I don't know of a single JS optimizer short of closure that will unwrap async function init () {}; await init(). Byte size in JS is important and a feature of this output.

@guybedford
Copy link
Copy Markdown
Contributor Author

I've managed to remove one branch with an update commit. The remaining 6 branches are for:

  • One branch for the $init function itself
  • One branch for exported functions to throw if called when unitialized
  • Three branches to hoist the memory, realloc and postReturn variable definitions from const function definitions to out-scope let definitions. I'd prefer not to collapse this branch since that would involve removing const declarations which could be useful optimization information for an optimizer on non-tla-compat source
  • One branch to vary the WebAssembly instantiation routine from setting the local const versus global let variable in TLA compat.

With the current output size constraints I'm not sure I can simplify more than this, but I'm open to refactoring suggestions.

@guybedford
Copy link
Copy Markdown
Contributor Author

Ok, I've unified on just the single branch as discussed, leaving any unwrapping to rather be the job of an optimizer.

@guybedford guybedford merged commit 4dd2884 into bytecodealliance:main Nov 7, 2022
@guybedford guybedford deleted the tla-compat branch November 7, 2022 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants