bend: fix C tier LinkError — switch EM_JS to --js-library mergeInto
Browser instantiation of lumbda-c.wasm failed with:
Aborted(LinkError: import object field 'js_lumbda_bend_call'
is not a Function)
The wasm correctly required env.js_lumbda_bend_call as a function
import, and the EM_JS-generated function existed in the glue —
emcc placed the function declaration at depth 0 of createLumbdaC
where wasmImports lives, so it SHOULD have been hoisted into scope
at instantiate time. It worked in node test runs but threw on
browser load. The exact emcc-version cause is fuzzy; the fix is
to use the textbook mechanism instead of guessing.
New file wasm/c/bend-call-library.js — Emscripten JS library with
mergeInto(LibraryManager.library, { js_lumbda_bend_call: function(...) }).
mergeInto lands the function directly inside wasmImports under the
mangled name _js_lumbda_bend_call (auto-prefix), wired to the
js_lumbda_bend_call import. No scope guessing.
lumbda_wasm_entry.c — drops EM_JS, keeps the C wrapper bi_bend_call_wasm
and declares extern int js_lumbda_bend_call(...) so emcc emits the
env import that the library fills.
wasm/Makefile — adds --js-library c/bend-call-library.js to C_LDFLAGS
and lists the library as a dependency so changes trigger a rebuild.
Verified: deployed glue at lumbda.com now contains the mergeInto
binding (js_lumbda_bend_call:_js_lumbda_bend_call inside wasmImports).
End-to-end smoke via mocked XHR in node returns the expected
(ok pong) round-trip from the demo's first probe step.
This commit is contained in:
parent
1458ebf77a
commit
af0bf9cb35
11 changed files with 125 additions and 6 deletions
|
|
@ -85,7 +85,17 @@ C_SOURCES := $(addprefix $(C_SRC_DIR)/, \
|
|||
# are never executed. mmap PROT_EXEC would fail in WASM anyway; harmless.
|
||||
C_JIT_STUB := c/jit-stub.c
|
||||
|
||||
C_CFLAGS := -O2 -DLUMBDA_WASM -std=c11 -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE \
|
||||
# bdwgc integration attempt — DEFERRED. See c/lumbda_wasm_entry.c
|
||||
# header comment for the full status: bdwgc builds cleanly with emcc
|
||||
# via CMake, but option-1 (binaryen --spill-pointers) crashes in our
|
||||
# emsdk version and option-2 (GC_disable + manual gcollect at safe
|
||||
# points) compiles but triggers a runtime fault on the first eval.
|
||||
# Likely a missed root somewhere in the eval call chain. Leaving the
|
||||
# pieces in place (BDWGC_DIR check + commented include) so the next
|
||||
# session can iterate without rebuilding bdwgc.
|
||||
|
||||
C_CFLAGS := -O2 -DLUMBDA_WASM -std=c11 \
|
||||
-D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE \
|
||||
-I$(C_SRC_DIR) -Wno-everything
|
||||
C_LDFLAGS := -s WASM=1 -s MODULARIZE=1 -s EXPORT_ES6=1 \
|
||||
-s EXPORT_NAME=createLumbdaC \
|
||||
|
|
@ -94,11 +104,12 @@ C_LDFLAGS := -s WASM=1 -s MODULARIZE=1 -s EXPORT_ES6=1 \
|
|||
-s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=32MB \
|
||||
-s STACK_SIZE=8MB \
|
||||
-s ENVIRONMENT=web,worker,node \
|
||||
-s SINGLE_FILE=0
|
||||
-s SINGLE_FILE=0 \
|
||||
--js-library c/bend-call-library.js
|
||||
|
||||
c: $(DIST)/c/lumbda-c.js $(DIST)/c/lumbda-c.wasm $(DIST)/c/lumbda-c.loader.js
|
||||
|
||||
$(DIST)/c/lumbda-c.js $(DIST)/c/lumbda-c.wasm: $(C_SOURCES) $(C_JIT_STUB) c/lumbda_wasm_entry.c
|
||||
$(DIST)/c/lumbda-c.js $(DIST)/c/lumbda-c.wasm: $(C_SOURCES) $(C_JIT_STUB) c/lumbda_wasm_entry.c c/bend-call-library.js
|
||||
@mkdir -p $(@D)
|
||||
EMSDK=$(EMSDK_DIR) EMSDK_NODE=$(EMSDK_DIR)/node/22.16.0_64bit/bin/node \
|
||||
PATH=$(EMSDK_DIR)/node/22.16.0_64bit/bin:$(EMSDK_DIR)/upstream/emscripten:$$PATH \
|
||||
|
|
|
|||
42
wasm/c/bend-call-library.js
Normal file
42
wasm/c/bend-call-library.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// wasm/c/bend-call-library.js — Emscripten JS library exposing
|
||||
// js_lumbda_bend_call to the C tier wasm.
|
||||
//
|
||||
// Used over EM_JS because EM_JS-generated function declarations
|
||||
// can end up in a scope that's not visible at WebAssembly.instantiate
|
||||
// time (we saw a LinkError "import object field 'js_lumbda_bend_call'
|
||||
// is not a Function" in browsers). mergeInto + --js-library lands
|
||||
// the function correctly inside emcc's wasmImports object.
|
||||
//
|
||||
// The function mirrors the asm tier's env.bend_call import and the
|
||||
// pyodide tier's _lumbdaPyBendCall: sync XHR POST to whatever URL
|
||||
// globalThis._lumbdaCBendUrl was set to (the loader exposes
|
||||
// setBendUrl that mutates it).
|
||||
|
||||
mergeInto(LibraryManager.library, {
|
||||
js_lumbda_bend_call: function(payload_ptr, payload_len, resp_buf, resp_cap) {
|
||||
var bendUrl = globalThis._lumbdaCBendUrl;
|
||||
if (!bendUrl) {
|
||||
var msg = "no bend URL configured";
|
||||
var n = Math.min(msg.length, resp_cap - 1);
|
||||
for (var i = 0; i < n; i++) HEAPU8[resp_buf + i] = msg.charCodeAt(i);
|
||||
return n;
|
||||
}
|
||||
try {
|
||||
var payload = UTF8ToString(payload_ptr, payload_len);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", bendUrl, false); // sync
|
||||
xhr.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
xhr.send(payload);
|
||||
var resp = xhr.responseText || "";
|
||||
var bytes = new TextEncoder().encode(resp);
|
||||
var n = Math.min(bytes.length, resp_cap);
|
||||
HEAPU8.set(bytes.subarray(0, n), resp_buf);
|
||||
return n;
|
||||
} catch (e) {
|
||||
var err = "bend error: " + (e.message || String(e));
|
||||
var n = Math.min(err.length, resp_cap - 1);
|
||||
for (var i = 0; i < n; i++) HEAPU8[resp_buf + i] = err.charCodeAt(i);
|
||||
return n;
|
||||
}
|
||||
},
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -27,6 +27,12 @@ async function _bootstrap() {
|
|||
|
||||
_init();
|
||||
|
||||
// /tmp must exist before (portal-snapshot! ...) writes there — the
|
||||
// C builtin fopen("w")s the path directly so a missing dir is a
|
||||
// hard error. Emscripten's MEMFS gives us /tmp on recent versions
|
||||
// but make it idempotent.
|
||||
try { module.FS.mkdir("/tmp"); } catch (e) { /* exists */ }
|
||||
|
||||
// bend!-call host bridge — js_lumbda_bend_call inside the wasm
|
||||
// (EM_JS in lumbda_wasm_entry.c) reads globalThis._lumbdaCBendUrl
|
||||
// for the destination, then does sync XHR POST and writes the
|
||||
|
|
@ -34,6 +40,22 @@ async function _bootstrap() {
|
|||
// global so runner.js can propagate a saved playground URL.
|
||||
return {
|
||||
setBendUrl(url) { globalThis._lumbdaCBendUrl = url || null; },
|
||||
// portalSave / portalLoad bridge MEMFS to the JS side so the
|
||||
// REPL can persist checkpoints to the encrypted vault and
|
||||
// restore them on a fresh worker. Names come from the worker
|
||||
// (REPL caller validates alphanumeric) so path traversal isn't
|
||||
// a concern.
|
||||
portalSave(name) {
|
||||
const path = `/tmp/${name}.portal`;
|
||||
try {
|
||||
const bytes = module.FS.readFile(path);
|
||||
return new TextDecoder().decode(bytes);
|
||||
} catch (e) { return null; }
|
||||
},
|
||||
portalLoad(name, blob) {
|
||||
const path = `/tmp/${name}.portal`;
|
||||
module.FS.writeFile(path, blob);
|
||||
},
|
||||
async evalLisp(src) {
|
||||
outBuf = [];
|
||||
errBuf = [];
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -27,6 +27,12 @@ async function _bootstrap() {
|
|||
|
||||
_init();
|
||||
|
||||
// /tmp must exist before (portal-snapshot! ...) writes there — the
|
||||
// C builtin fopen("w")s the path directly so a missing dir is a
|
||||
// hard error. Emscripten's MEMFS gives us /tmp on recent versions
|
||||
// but make it idempotent.
|
||||
try { module.FS.mkdir("/tmp"); } catch (e) { /* exists */ }
|
||||
|
||||
// bend!-call host bridge — js_lumbda_bend_call inside the wasm
|
||||
// (EM_JS in lumbda_wasm_entry.c) reads globalThis._lumbdaCBendUrl
|
||||
// for the destination, then does sync XHR POST and writes the
|
||||
|
|
@ -34,6 +40,22 @@ async function _bootstrap() {
|
|||
// global so runner.js can propagate a saved playground URL.
|
||||
return {
|
||||
setBendUrl(url) { globalThis._lumbdaCBendUrl = url || null; },
|
||||
// portalSave / portalLoad bridge MEMFS to the JS side so the
|
||||
// REPL can persist checkpoints to the encrypted vault and
|
||||
// restore them on a fresh worker. Names come from the worker
|
||||
// (REPL caller validates alphanumeric) so path traversal isn't
|
||||
// a concern.
|
||||
portalSave(name) {
|
||||
const path = `/tmp/${name}.portal`;
|
||||
try {
|
||||
const bytes = module.FS.readFile(path);
|
||||
return new TextDecoder().decode(bytes);
|
||||
} catch (e) { return null; }
|
||||
},
|
||||
portalLoad(name, blob) {
|
||||
const path = `/tmp/${name}.portal`;
|
||||
module.FS.writeFile(path, blob);
|
||||
},
|
||||
async evalLisp(src) {
|
||||
outBuf = [];
|
||||
errBuf = [];
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -27,6 +27,12 @@ async function _bootstrap() {
|
|||
|
||||
_init();
|
||||
|
||||
// /tmp must exist before (portal-snapshot! ...) writes there — the
|
||||
// C builtin fopen("w")s the path directly so a missing dir is a
|
||||
// hard error. Emscripten's MEMFS gives us /tmp on recent versions
|
||||
// but make it idempotent.
|
||||
try { module.FS.mkdir("/tmp"); } catch (e) { /* exists */ }
|
||||
|
||||
// bend!-call host bridge — js_lumbda_bend_call inside the wasm
|
||||
// (EM_JS in lumbda_wasm_entry.c) reads globalThis._lumbdaCBendUrl
|
||||
// for the destination, then does sync XHR POST and writes the
|
||||
|
|
@ -34,6 +40,22 @@ async function _bootstrap() {
|
|||
// global so runner.js can propagate a saved playground URL.
|
||||
return {
|
||||
setBendUrl(url) { globalThis._lumbdaCBendUrl = url || null; },
|
||||
// portalSave / portalLoad bridge MEMFS to the JS side so the
|
||||
// REPL can persist checkpoints to the encrypted vault and
|
||||
// restore them on a fresh worker. Names come from the worker
|
||||
// (REPL caller validates alphanumeric) so path traversal isn't
|
||||
// a concern.
|
||||
portalSave(name) {
|
||||
const path = `/tmp/${name}.portal`;
|
||||
try {
|
||||
const bytes = module.FS.readFile(path);
|
||||
return new TextDecoder().decode(bytes);
|
||||
} catch (e) { return null; }
|
||||
},
|
||||
portalLoad(name, blob) {
|
||||
const path = `/tmp/${name}.portal`;
|
||||
module.FS.writeFile(path, blob);
|
||||
},
|
||||
async evalLisp(src) {
|
||||
outBuf = [];
|
||||
errBuf = [];
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue