Skip to content
Snippets Groups Projects
Commit 69d97c5e authored by Cheng Shao's avatar Cheng Shao
Browse files

wasm: add Note [Variable passing in JSFFI] as !13583 follow up

This patch adds a note to explain how the magic variables like
`__ghc_wasm_jsffi_dyld` are brought into scope of JSFFI code snippets,
as follow up work of !13583.

(cherry picked from commit f3bfe31e)
parent 28b18d26
No related branches found
No related tags found
No related merge requests found
......@@ -76,6 +76,9 @@ loadDLL f =
evaluate =<< js_loadDLL (toJSString f)
pure $ Right nullPtr
-- See Note [Variable passing in JSFFI] for where
-- __ghc_wasm_jsffi_dyld comes from
foreign import javascript safe "__ghc_wasm_jsffi_dyld.loadDLL($1)"
js_loadDLL :: JSString -> IO ()
......
......@@ -629,7 +629,8 @@ class DyLD {
// Fulfill the ghc_wasm_jsffi imports. Use new Function()
// instead of eval() to prevent bindings in this local scope to
// be accessed by JSFFI code snippets.
// be accessed by JSFFI code snippets. See Note [Variable passing in JSFFI]
// for what's going on here.
Object.assign(
import_obj.ghc_wasm_jsffi,
new Function(
......
......@@ -52,6 +52,47 @@ export function parseSections(mod) {
return recs;
}
// Note [Variable passing in JSFFI]
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// The JSFFI code snippets can access variables in globalThis,
// arguments like $1, $2, etc, plus a few magic variables: __exports,
// __ghc_wasm_jsffi_dyld and __ghc_wasm_jsffi_finalization_registry.
// How are these variables passed to JSFFI code? Remember, we strive
// to keep the globalThis namespace hygiene and maintain the ability
// to have multiple Haskell-wasm apps coexisting in the same JS
// context, so we must not pass magic variables as global variables
// even though they may seem globally unique.
//
// The solution is simple: put them in the JS lambda binder position.
// Though there are different layers of lambdas here:
//
// 1. User writes "$1($2, await $3)" in a JSFFI code snippet. No
// explicit binder here, the snippet is either an expression or
// some statements.
// 2. GHC doesn't know JS syntax but it knows JS function arity from
// HS type signature, as well as if the JS function is async/sync
// from safe/unsafe annotation. So it infers the JS binder (like
// "async ($1, $2, $3)") and emits a (name,binder,body) tuple into
// the ghc_wasm_jsffi custom section.
// 3. After link-time we collect these tuples to make a JS object
// mapping names to binder=>body, and this JS object will be used
// to fulfill the ghc_wasm_jsffi wasm imports. This JS object is
// returned by an outer layer of lambda which is in charge of
// passing magic variables.
//
// In case of post-linker for statically linked wasm modules,
// __ghc_wasm_jsffi_dyld won't work so is omitted, and
// __ghc_wasm_jsffi_finalization_registry can be created inside the
// outer JS lambda. Only __exports is exposed as user-visible API
// since it's up to the user to perform knot-tying by assigning the
// instance exports back to the (initially empty) __exports object
// passed to this lambda.
//
// In case of dyld, all magic variables are dyld-session-global
// variables; dyld uses new Function() to make the outer lambda, then
// immediately invokes it by passing the right magic variables.
export async function postLink(mod) {
let src = (
await fs.readFile(path.join(import.meta.dirname, "prelude.mjs"), {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment