Skip to content

Commit ab810cb

Browse files
fix(affine-vscode): marshal closure-pointer handlers, not table indices (Refs #199) (#202)
PR-5c. After PR-5a (#201) the function-value callback ABI passes a handler as a closure pointer, not a bare __indirect_function_table index. wrapHandler still did tbl.get(idx)() — wrong on the wasm path (it would index the table with a heap pointer). wrapHandler now reads the 8-byte closure pair from exported memory ([i32 function_id @ +0][i32 env_ptr @ +4], per codegen.ml), looks the compiled lambda up by function_id in __indirect_function_table, and invokes it with env_ptr as the first argument (the closure calling convention, codegen.ml:794), zero-filling any further declared params (the `Unit` handler arg) via fn.length. Returns the handler's result. registerCommand / onDidSaveTextDocument call sites unchanged; the now-misnamed `handlerIdx` params renamed to `handlerPtr`. JS-only (mod.js is not dune-built — no 253 impact). node --check clean. Correctness is by construction against the documented closure layout + calling convention. Unblocks the rsr-certifier pilot (PR-5d). Refs #199 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1e7dfc7 commit ab810cb

1 file changed

Lines changed: 23 additions & 11 deletions

File tree

packages/affine-vscode/mod.js

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,28 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) {
4747
return new TextDecoder("utf-8").decode(bytes);
4848
}
4949

50-
// ── Wasm-table callbacks → JS callable ─────────────────────────────
51-
// Wasm function-pointer args (e.g. command handlers) come in as table
52-
// indices. Wrap each in a JS thunk that re-enters the Wasm module.
53-
function wrapHandler(idx) {
50+
// ── Wasm closure callbacks → JS callable ───────────────────────────
51+
// Post-#199 (function-value callback ABI) a handler arrives as a
52+
// *closure pointer*, not a bare table index: an 8-byte heap pair
53+
// [i32 function_id @ +0][i32 env_ptr @ +4] (codegen.ml). To invoke,
54+
// read the pair from exported memory, look the compiled lambda up in
55+
// __indirect_function_table by function_id, and call it with env_ptr
56+
// as the first argument (the closure calling convention), zero-filling
57+
// any further declared params (e.g. the `Unit` handler arg).
58+
function wrapHandler(closurePtr) {
5459
return () => {
5560
const inst = getInstance();
56-
const tbl = inst && inst.exports && inst.exports.__indirect_function_table;
61+
if (!inst || !inst.exports || !inst.exports.memory) return;
62+
const tbl = inst.exports.__indirect_function_table;
5763
if (!tbl) return;
58-
const fn = tbl.get(idx);
59-
if (typeof fn === "function") fn();
64+
const dv = new DataView(inst.exports.memory.buffer);
65+
const fnId = dv.getInt32(closurePtr, true);
66+
const envPtr = dv.getInt32(closurePtr + 4, true);
67+
const fn = tbl.get(fnId);
68+
if (typeof fn !== "function") return;
69+
const args = [envPtr];
70+
while (args.length < fn.length) args.push(0);
71+
return fn(...args);
6072
};
6173
}
6274

@@ -66,9 +78,9 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) {
6678
// import map's top-level keys must match.
6779
const Vscode = {
6880
// ── vscode.commands ──────────────────────────────────────────────
69-
registerCommand: (namePtr, handlerIdx) => {
81+
registerCommand: (namePtr, handlerPtr) => {
7082
const name = readString(namePtr);
71-
const handler = wrapHandler(handlerIdx);
83+
const handler = wrapHandler(handlerPtr);
7284
const disposable = vscode.commands.registerCommand(name, handler);
7385
return reg(disposable);
7486
},
@@ -302,8 +314,8 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) {
302314
},
303315

304316
// ── Events ─────────────────────────────────────────────────────
305-
onDidSaveTextDocument: (handlerIdx) => {
306-
const thunk = wrapHandler(handlerIdx);
317+
onDidSaveTextDocument: (handlerPtr) => {
318+
const thunk = wrapHandler(handlerPtr);
307319
// The vscode event ships a TextDocument; we deliberately drop it at
308320
// the FFI boundary (see Vscode.affine docstring). Handlers that
309321
// need the saved file path can call editorActiveFilePath().

0 commit comments

Comments
 (0)