From 4c2c9e62bb7b04ac7df6cc66633bc7c407a33742 Mon Sep 17 00:00:00 2001 From: Cheng Shao <terrorjack@type.dance> Date: Fri, 15 Nov 2024 03:33:55 +0000 Subject: [PATCH] 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 f3bfe31e6190d4161b0225e1e8bd0c8088f2c54d) --- libraries/ghci/GHCi/ObjLink.hs | 3 +++ utils/jsffi/dyld.mjs | 3 ++- utils/jsffi/post-link.mjs | 41 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/libraries/ghci/GHCi/ObjLink.hs b/libraries/ghci/GHCi/ObjLink.hs index 1875918a921..4f63aa2f9b5 100644 --- a/libraries/ghci/GHCi/ObjLink.hs +++ b/libraries/ghci/GHCi/ObjLink.hs @@ -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 () diff --git a/utils/jsffi/dyld.mjs b/utils/jsffi/dyld.mjs index c6cfedbece4..db9c8b382d6 100755 --- a/utils/jsffi/dyld.mjs +++ b/utils/jsffi/dyld.mjs @@ -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( diff --git a/utils/jsffi/post-link.mjs b/utils/jsffi/post-link.mjs index 9e3e0643512..b5493f35817 100755 --- a/utils/jsffi/post-link.mjs +++ b/utils/jsffi/post-link.mjs @@ -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"), { -- GitLab