diff --git a/docs/cli-test-port-plan.md b/docs/cli-test-port-plan.md index 42f5535..0128d05 100644 --- a/docs/cli-test-port-plan.md +++ b/docs/cli-test-port-plan.md @@ -10,7 +10,7 @@ Replace the Python-based network test runner and plugins with UCE unit tests inv - [x] UCE CLI tests cover every current Python plugin behavior or explicitly document a deliberate replacement. - [x] Existing W5/WASM gates use the UCE CLI test runner instead of `tests/run_network_tests.py`. - [x] Python test runner/plugins are deleted after parity validation. -- [x] Full suite passes on `uce-dev` with `WASM_BACKEND_ENABLED=1`. +- [x] Full suite passes on `uce-dev` with wasm-only unit execution. ## Current State diff --git a/docs/wasm-runtime-architecture.md b/docs/wasm-runtime-architecture.md index ac1db47..2ddce43 100644 --- a/docs/wasm-runtime-architecture.md +++ b/docs/wasm-runtime-architecture.md @@ -1,9 +1,11 @@ # UCE WASM Runtime Architecture -Status: current as of the W7 cutover (June 2026). This document describes the -**runtime architecture as built** — the process topology, the wasm membrane, -the unified request dispatch, and the central WebSocket broker. For the -historical motivation and the phased migration plan, see +Status: current as of the W7e native-pipeline removal (June 2026). This document +describes the **runtime architecture as built** — the process topology, the +wasm membrane, the unified request dispatch, and the central WebSocket broker. +Native `.so` unit execution/dlopen fallback has been removed; the parser and +preprocessor remain only as the front-end that emits C++ for wasm side-module +compilation. For the historical motivation and the phased migration plan, see [`WASM-PROPOSAL.md`](../WASM-PROPOSAL.md); this file is the steady-state reference that proposal points at. @@ -116,10 +118,10 @@ __uce_[_] path and looks up the export's funcref slot; `exists` lets callers probe a unit without instantiating it. -`wasm_backend_should_handle(request, entry_unit)` gates whether the wasm path -takes the request (vs. the legacy native compiler path, still selectable while -the native pipeline is retired). With `WASM_BACKEND_ENABLED=1` this is the -default for all unit execution. +`wasm_backend_should_handle(request, entry_unit)` checks whether the wasm +backend is initialized and the requested artifact/handler is currently +available. If an artifact is cold or stale, dispatch compiles it on demand via +`get_shared_unit()` and rechecks. There is no native unit-execution fallback. --- @@ -171,9 +173,9 @@ The `UCE_*` params are set by whichever broker forwarded the request: `request.resources.websocket_*` and `request.connection` from them before invoking `__uce_websocket`. -Each branch has a native-compiler fallback (`compiler_invoke*`) used only when -`wasm_backend_should_handle` declines — the holdout path during native -retirement. +If a unit still cannot be served after the on-demand wasm compile, the worker +returns a clean 500 with the wasm/compile error; it does not execute native unit +code. --- @@ -294,7 +296,6 @@ header free-functions are `inline`. The wasm backend exposes only declarations | Key | Default | Meaning | |---|---|---| -| `WASM_BACKEND_ENABLED` | `1` | Route unit execution through the wasm path. | | `WASM_BACKEND_VERBOSE` | `0` | Emit `X-UCE-Wasm-*` workspace timing headers (benchmark only). | | `FCGI_SOCKET_PATH` | `/run/uce.sock` | Worker pool FastCGI socket (brokers forward here). | | `CLI_SOCKET_PATH` | `/run/uce/cli.sock` | Worker CLI socket. | @@ -308,7 +309,7 @@ header free-functions are `inline`. The wasm backend exposes only declarations - **Regression gate**: `scripts/run_cli_tests.sh --include-wasm-kill` runs the in-runtime CLI suite (`site/tests/cli_runner.uce`) plus the site test pages. - Current baseline: **86 passed, 0 failed**. + Current baseline: **87 passed, 0 failed**. - **WebSocket end-to-end**: a headless client performs a raw WS handshake to `:HTTP_PORT` with path `/site/tests/websockets.ws.uce` (self-resolving `SCRIPT_FILENAME`) and asserts the `hello-ack` frame — exercising the full diff --git a/etc/uce/settings.cfg b/etc/uce/settings.cfg index 1b94315..84205db 100644 --- a/etc/uce/settings.cfg +++ b/etc/uce/settings.cfg @@ -21,13 +21,10 @@ SITE_DIRECTORY=site # ENABLE JIT COMPILATION WHEN A PAGE REQUEST HITS A STALE OR MISSING UNIT JIT_COMPILE_ON_REQUEST=1 -# OPTIONAL W2 WEBASSEMBLY SIDE-MODULE COMPILATION BESIDE NATIVE .so UNITS -COMPILE_WASM_UNITS=0 +# WASM SIDE-MODULE COMPILER USED FOR .uce UNITS WASM_COMPILE_SCRIPT=scripts/compile_wasm_unit -# DEFAULT PAGE RENDER BACKEND. W5 defaults to the WASM backend with explicit -# native fallbacks for host-owned surfaces that are not membrane APIs yet. -WASM_BACKEND_ENABLED=1 +# WASM RUNTIME SETTINGS. Unit execution is always routed through wasm. WASM_BACKEND_VERBOSE=0 WASM_CORE_PATH=/Code/uce.openfu.com/uce/bin/wasm/core.wasm WASM_MEMORY_LIMIT_BYTES=536870912 diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh index b707daf..4269e9f 100755 --- a/scripts/build_linux.sh +++ b/scripts/build_linux.sh @@ -9,11 +9,11 @@ GF="uce_fastcgi" mkdir bin > /dev/null 2>&1 mkdir bin/tmp > /dev/null 2>&1 mkdir bin/assets > /dev/null 2>&1 +mkdir bin/wasm > /dev/null 2>&1 mkdir work > /dev/null 2>&1 COMPILER="clang++" -# -rdynamic is a link-time flag (exports the binary's symbols so dlopen'd .uce -# units resolve the runtime against it); the -c compiles below do not need it. +# -rdynamic is a link-time flag; the -c compiles below do not need it. FLAGS="-g -w -Wall -$OPT_FLAG -std=c++20 -fpermissive -ffast-math" # Wasmtime C++ API — needed only by the wasm backend object (src/wasm). @@ -30,8 +30,8 @@ SRCFLAGS="-D EXEC_NAME=\"$GF\" -D PLATFORM_NAME=\"linux\"" # bin/sqlite3.o vendored SQLite amalgamation (depends only on its own source) # bin/wasm.o wasm backend + worker + wasmtime.hh (src/wasm) # bin/main.o linux_fastcgi.cpp + the uce_lib core amalgamation -# All link into the single -rdynamic binary, so the dlopen unit model is -# unchanged. Delete bin/*.o to force a clean rebuild. +# All link into the single -rdynamic binary. Delete bin/*.o to force a clean +# rebuild. # Rebuild $1 if it is missing or anything under the remaining find-args is newer. needs_rebuild() { @@ -41,6 +41,14 @@ needs_rebuild() { return 1 } +# core.wasm: guest runtime loaded by the native wasm backend. +if needs_rebuild bin/wasm/core.wasm src/wasm/core.cpp src/lib src/wasm/core_hostcalls.syms src/wasm/core_libc_exports.syms scripts/build_core_wasm.sh; then + echo "Compiling wasm core..." + bash scripts/build_core_wasm.sh || exit 1 +else + echo "Reusing bin/wasm/core.wasm" +fi + # SQLite: vendored C, depends only on its own source (not our headers). if needs_rebuild bin/sqlite3.o src/3rdparty/sqlite/sqlite3.c src/3rdparty/sqlite/sqlite3.h; then echo "Compiling SQLite..." diff --git a/scripts/compile_wasm_unit b/scripts/compile_wasm_unit index 078b20a..007bd00 100755 --- a/scripts/compile_wasm_unit +++ b/scripts/compile_wasm_unit @@ -50,7 +50,7 @@ build_pch_if_needed() { return 0 fi mkdir -p "$PCH_DIR" - if [ -s "$PCH_FN" ]; then + if [ -s "$PCH_FN" ] && [ -z "$(find src/lib -maxdepth 1 -name '*.h' -type f -newer "$PCH_FN" -print -quit)" ]; then return 0 fi "$SDK/bin/clang++" "${COMMON_FLAGS[@]}" \ diff --git a/scripts/wasm/run_w5.sh b/scripts/wasm/run_w5.sh index 81d1c5a..bc75453 100755 --- a/scripts/wasm/run_w5.sh +++ b/scripts/wasm/run_w5.sh @@ -1,94 +1,19 @@ #!/bin/bash -# W5 parity/performance gate for the config-selectable WASM backend. -# Runs on k-uce. Requires root because the live service reads /etc/uce/settings.cfg. +# Retired W5 parity/performance gate. +# +# W7e removed the config-selectable native backend and all native unit +# execution. There is no longer a valid WASM_BACKEND_ENABLED=0 baseline to +# switch to here; doing so would only create a misleading service state. set -euo pipefail -cd "$(dirname "$0")/../.." +cat >&2 <<'EOF' +run_w5.sh is retired: UCE unit execution is wasm-only after W7e. +Use the supported regression gate instead: -if [ "${EUID:-$(id -u)}" -ne 0 ]; then - echo "run_w5.sh must run as root so it can switch /etc/uce/settings.cfg and restart uce.service" >&2 - exit 1 -fi + bash scripts/build_linux.sh && systemctl restart uce.service && sleep 3 \ + && bash scripts/run_cli_tests.sh --include-wasm-kill -OUT=${UCE_W5_OUT:-/tmp/uce/wasm-w5} -CONFIG=${UCE_CONFIG:-/etc/uce/settings.cfg} -mkdir -p "$OUT" -BACKUP="$OUT/settings.cfg.before-w5" -cp "$CONFIG" "$BACKUP" - -restore_on_error() { - if [ "${UCE_W5_KEEP_BACKEND:-0}" != "1" ]; then - cp "$BACKUP" "$CONFIG" - systemctl restart uce.service >/dev/null 2>&1 || true - fi -} -trap restore_on_error EXIT - -set_backend() { - local enabled="$1" - local tmp - tmp=$(mktemp) - awk -F= -v enabled="$enabled" ' - BEGIN { found = 0 } - /^WASM_BACKEND_ENABLED=/ { print "WASM_BACKEND_ENABLED=" enabled; found = 1; next } - { print } - END { if(!found) print "WASM_BACKEND_ENABLED=" enabled } - ' "$CONFIG" > "$tmp" - for kv in \ - 'WASM_BACKEND_VERBOSE=0' \ - 'WASM_CORE_PATH=/Code/uce.openfu.com/uce/bin/wasm/core.wasm' \ - 'WASM_MEMORY_LIMIT_BYTES=536870912' \ - 'WASM_EPOCH_DEADLINE_TICKS=200' \ - 'WASM_EPOCH_PERIOD_MS=50' - do - key=${kv%%=*} - if ! grep -q "^${key}=" "$tmp"; then - printf '%s\n' "$kv" >> "$tmp" - fi - done - cat "$tmp" > "$CONFIG" - rm -f "$tmp" - systemctl restart uce.service >/dev/null - sleep 1 -} - -summary_total() { - awk '/^Summary:/ { print $2; found=1 } END { if(!found) print 0 }' "$1" -} - -# Native reference baseline. -set_backend 0 -scripts/run_cli_tests.sh > "$OUT/native-warmup.txt" || true -scripts/run_cli_tests.sh > "$OUT/native-network.txt" -python3 tests/wasm_benchmark.py --out-dir "$OUT/native-benchmark" --samples "${UCE_W5_BENCH_SAMPLES:-20}" --timeout 30 >/dev/null - -# WASM default backend with W5 native fallbacks for host-owned surfaces. -set_backend 1 -scripts/run_cli_tests.sh --include-wasm-kill > "$OUT/wasm-warmup.txt" || true -scripts/run_cli_tests.sh --include-wasm-kill > "$OUT/wasm-network.txt" -python3 tests/wasm_benchmark.py \ - --out-dir "$OUT/benchmark" \ - --backend-label wasm \ - --compare-native-json "$OUT/native-benchmark/benchmark.json" \ - --samples "${UCE_W5_BENCH_SAMPLES:-20}" \ - --timeout 30 -python3 tests/wasm_site_audit.py --out-dir "$OUT" >/dev/null - -network_cases=$(summary_total "$OUT/wasm-network.txt") -if [ "$network_cases" -lt 80 ]; then - echo "W5 HARNESS: FAIL" - echo "wasm network ran $network_cases cases, expected at least 80" - exit 1 -fi - -echo 'W5 HARNESS: PASS' -echo "network_cases=$network_cases benchmark_rows=see benchmark.json" -echo "reports=$OUT" - -if [ "${UCE_W5_KEEP_BACKEND:-0}" = "1" ]; then - trap - EXIT - printf 'WASM backend left enabled in %s\n' "$CONFIG" -else - cp "$BACKUP" "$CONFIG" - systemctl restart uce.service >/dev/null - trap - EXIT -fi +For performance comparisons, use tests/wasm_benchmark.py against the current +wasm backend; native-baseline artifacts under docs/wasm-baselines are historical +only. +EOF +exit 2 diff --git a/src/lib/compiler-parser.cpp b/src/lib/compiler-parser.cpp index 4460c14..e80650f 100644 --- a/src/lib/compiler-parser.cpp +++ b/src/lib/compiler-parser.cpp @@ -564,7 +564,10 @@ String compiler_preprocess_shared_unit_char_wise(Request* context, SharedUnit* s parsed_content.resize(parsed_content.length() - current_line.length()); nibble(current_line, "\""); String unit_name = nibble(current_line, "\""); - SharedUnit* sub_su = compiler_load_shared_unit(context, unit_name, su->src_path, true); + String resolved_unit = unit_name; + if(resolved_unit != "" && resolved_unit[0] != '/') + resolved_unit = expand_path(resolved_unit, su->src_path); + SharedUnit* sub_su = (resolved_unit == "" ? 0 : get_shared_unit(context, resolved_unit, true)); if(sub_su) parsed_content.append("#include \"" + sub_su->bin_path + "/" + sub_su->pre_file_name + "\"\n"); } diff --git a/src/lib/compiler.cpp b/src/lib/compiler.cpp index 0009946..426dd29 100644 --- a/src/lib/compiler.cpp +++ b/src/lib/compiler.cpp @@ -10,14 +10,6 @@ namespace { -const char* UCE_SETUP_SYMBOL = "__uce_set_current_request"; -const char* UCE_RENDER_SYMBOL = "__uce_render"; -const char* UCE_COMPONENT_SYMBOL = "__uce_component"; -const char* UCE_WEBSOCKET_SYMBOL = "__uce_websocket"; -const char* UCE_CLI_SYMBOL = "__uce_cli"; -const char* UCE_SERVE_HTTP_SYMBOL = "__uce_serve_http"; -const char* UCE_ONCE_SYMBOL = "__uce_once"; -const char* UCE_INIT_SYMBOL = "__uce_init"; const u64 UCE_UNIT_ABI_VERSION = 6; struct SharedUnitFilesystemState @@ -33,9 +25,6 @@ struct SharedUnitFilesystemState time_t metadata_time = 0; time_t compile_output_time = 0; time_t setup_template_time = 0; - time_t compiler_header_time = 0; - time_t compiler_source_time = 0; - time_t runtime_binary_time = 0; time_t compiler_abi_time = 0; time_t required_time = 0; u64 metadata_abi_version = 0; @@ -53,10 +42,6 @@ struct SharedUnitCompileCheck bool needs_compile = false; }; -void compiler_unload_failed_shared_unit(SharedUnit* su); -bool compiler_run_unit_init(Request* context, SharedUnit* su, String* error_out = 0); -bool compiler_run_unit_once_if_needed(Request* context, SharedUnit* su, String* error_out = 0); - bool compiler_jit_compile_on_request_enabled(Request* context) { if(!context || !context->server) @@ -132,13 +117,6 @@ String compiler_unit_metadata_text(Request* context, SharedUnit* su) ); } -bool compiler_wasm_unit_compile_enabled(Request* context) -{ - if(!context || !context->server) - return(false); - return(config_bool("COMPILE_WASM_UNITS", false)); -} - String compiler_wasm_compile_script(Request* context) { String script = "scripts/compile_wasm_unit"; @@ -207,17 +185,11 @@ void compiler_restore_persisted_failure(SharedUnit* su, const SharedUnitFilesyst time_t compiler_runtime_abi_time(Request* context) { - if(!context || !context->server) - return(0); - - String root = context->server->config["COMPILER_SYS_PATH"]; - return(std::max({ - file_mtime(root + "/src/lib/compiler.h"), - file_mtime(root + "/src/lib/compiler.cpp"), - file_mtime(root + "/src/lib/compiler-parser.h"), - file_mtime(root + "/src/lib/compiler-parser.cpp"), - file_mtime(root + "/bin/uce_fastcgi.linux.bin") - })); + (void)context; + // Do not force every unit stale on every server/core rebuild. The stable unit + // ABI is tracked explicitly in compiler_unit_input_signature() via + // UCE_UNIT_ABI_VERSION; bump that when generated-unit/runtime ABI changes. + return(0); } String compiler_registry_file_name(Request* context) @@ -335,9 +307,6 @@ SharedUnitFilesystemState inspect_shared_unit_filesystem(Request* context, Share context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"] ); - state.compiler_header_time = file_mtime(context->server->config["COMPILER_SYS_PATH"] + "/src/lib/compiler.h"); - state.compiler_source_time = file_mtime(context->server->config["COMPILER_SYS_PATH"] + "/src/lib/compiler.cpp"); - state.runtime_binary_time = file_mtime(context->server->config["COMPILER_SYS_PATH"] + "/bin/uce_fastcgi.linux.bin"); state.compiler_abi_time = compiler_runtime_abi_time(context); state.metadata_time = file_mtime(su->meta_file_name); state.compile_output_time = file_mtime(su->compile_output_file_name); @@ -371,17 +340,9 @@ SharedUnitFilesystemState inspect_shared_unit_filesystem(Request* context, Share state.metadata_input_signature == state.current_input_signature ); state.required_time = std::max({state.source_time, state.setup_template_time, state.compiler_abi_time}); - state.compiled_time = file_mtime(su->so_name); - if(compiler_wasm_unit_compile_enabled(context)) - { - time_t wasm_time = file_mtime(su->wasm_name); - // The wasm backend serves the .wasm, so a missing/stale .wasm must - // trigger a rebuild even when the .so is current. Treat the unit as - // compiled only as of the OLDER artifact; a missing .wasm (mtime 0) - // forces recompile via the existing needs_compile == (compiled_time==0). - if(wasm_time == 0 || wasm_time < state.compiled_time) - state.compiled_time = wasm_time; - } + // Native .so execution has been removed; compile freshness is the wasm + // side-module freshness. + state.compiled_time = file_mtime(su->wasm_name); return(state); } @@ -572,8 +533,6 @@ String compiler_status_from_filesystem(const SharedUnitFilesystemState& state, S return("not_compiled"); if(compile_check.needs_compile) return("stale"); - if(su && su->so_handle) - return("loaded"); return("compiled"); } @@ -686,85 +645,6 @@ void setup_unit_paths(Request* context, SharedUnit* su, String file_name) //su->setup_file_name = su->bin_path + "/" + su->src_file_name + ".setup.h"; } -void load_shared_unit(Request* context, SharedUnit* su) -{ - su->on_render = 0; - su->on_component = 0; - su->on_websocket = 0; - su->on_once = 0; - su->on_init = 0; - su->on_setup = 0; - su->so_handle = 0; - su->compiler_messages = ""; - - if(!file_exists(su->so_name)) - { - if(su->opt_so_optional) - { - su->compile_status = "not_compiled"; - return; - } - auto fs_state = inspect_shared_unit_filesystem(context, su); - if(compiler_failure_retry_deferred(context, su, fs_state)) - { - compiler_restore_persisted_failure(su, fs_state); - return; - } - //printf("(i) unit file not found: %s\n", su->so_name.c_str()); - su->compiler_messages = "unit file not found"; - su->compile_status = "not_compiled"; - su->compile_error_status = su->compiler_messages; - su->last_error = time(); - return; - } - - su->so_handle = dlopen(su->so_name.c_str(), RTLD_NOW); - if(su->so_handle) - { - su->last_compiled = file_mtime(su->so_name); - su->last_loaded = time(); - su->compile_status = "loaded"; - su->compile_error_status = ""; - char *error; - su->on_setup = (request_handler)dlsym(su->so_handle, UCE_SETUP_SYMBOL); - if ((error = dlerror()) != NULL) - printf("Error - %s in %s\n", error, su->file_name.c_str()); - su->on_render = (request_ref_handler)dlsym(su->so_handle, UCE_RENDER_SYMBOL); - dlerror(); - su->on_component = (request_ref_handler)dlsym(su->so_handle, UCE_COMPONENT_SYMBOL); - dlerror(); - su->on_websocket = (request_ref_handler)dlsym(su->so_handle, UCE_WEBSOCKET_SYMBOL); - dlerror(); - su->on_cli = (request_ref_handler)dlsym(su->so_handle, UCE_CLI_SYMBOL); - dlerror(); - su->on_once = (request_ref_handler)dlsym(su->so_handle, UCE_ONCE_SYMBOL); - dlerror(); - su->on_init = (request_ref_handler)dlsym(su->so_handle, UCE_INIT_SYMBOL); - dlerror(); - su->api_declarations = split(file_get_contents(su->api_file_name), "\n"); - String init_error = ""; - if(!compiler_run_unit_init(context, su, &init_error)) - { - if(init_error != "") - su->compiler_messages = init_error; - compiler_unload_failed_shared_unit(su); - } - //else - // printf("(i) loaded unit %s\n", su->file_name.c_str()); - } - else - { - const char* dl_error = dlerror(); - su->compiler_messages = "could not open " + su->so_name; - if(dl_error && String(dl_error) != "") - su->compiler_messages += ": " + String(dl_error); - su->compile_status = "load_error"; - su->compile_error_status = su->compiler_messages; - su->last_error = time(); - printf("Error loading unit %s, %s\n", su->file_name.c_str(), su->compiler_messages.c_str()); - } -} - /*String compile_setup_file(Request* context, SharedUnit* su) { String result = @@ -860,63 +740,40 @@ void compile_shared_unit(Request* context, SharedUnit* su) file_put_contents(su->pre_path + "/" + su->pre_file_name, preprocess_shared_unit(context, su)); file_put_contents(su->api_file_name, join(su->api_declarations, "\n")); - if(!su->opt_so_optional) - su->compiler_messages = trim(shell_exec(shell_escape(context->server->config["COMPILE_SCRIPT"])+" "+ - shell_escape(su->src_path)+" "+ - shell_escape(su->bin_path)+" "+ - shell_escape(su->file_name)+" "+ - shell_escape(su->pre_file_name)+" "+ - shell_escape(su->bin_file_name) - )); + su->compiler_messages = trim(shell_exec(shell_escape(compiler_wasm_compile_script(context))+" "+ + shell_escape(su->src_path)+" "+ + shell_escape(su->bin_path)+" "+ + shell_escape(su->file_name)+" "+ + shell_escape(su->pre_file_name)+" "+ + shell_escape(su->wasm_file_name) + )); - if(su->compiler_messages.length() == 0 && !su->opt_so_optional && compiler_wasm_unit_compile_enabled(context)) - { - // The wasm side-module build is best-effort: a unit that cannot compile - // to wasm (e.g. try/catch) still works via the native .so and falls back - // to native at request time. Record the failure, never fail the unit on - // it. On failure, remove any stale .wasm so the backend won't serve it. - String wasm_messages = trim(shell_exec(shell_escape(compiler_wasm_compile_script(context))+" "+ - shell_escape(su->src_path)+" "+ - shell_escape(su->bin_path)+" "+ - shell_escape(su->file_name)+" "+ - shell_escape(su->pre_file_name)+" "+ - shell_escape(su->wasm_file_name) - )); - if(wasm_messages.length() > 0) - { - file_put_contents(su->wasm_check_file_name, wasm_messages + "\n"); - file_unlink(su->wasm_name); - printf("(i) wasm side-module unavailable for %s (native .so serves it)\n", su->file_name.c_str()); - } - } + if(su->compiler_messages.length() == 0 && !file_exists(su->wasm_name)) + su->compiler_messages = "wasm compile script completed without creating " + su->wasm_name; if(su->compiler_messages.length() > 0) { String raw_messages = su->compiler_messages; file_put_contents(su->compile_output_file_name, raw_messages + "\n"); + file_put_contents(su->wasm_check_file_name, raw_messages + "\n"); + file_unlink(su->wasm_name); compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", raw_messages); printf("%s \n", compiler_format_compile_failure(su, raw_messages).c_str()); } else { - load_shared_unit(context, su); - if(su->so_handle) - { - file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su)); - file_unlink(su->compile_output_file_name); - } - else if(trim(su->compiler_messages) != "") - { - file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n"); - } + su->last_compiled = file_mtime(su->wasm_name); + file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su)); + file_unlink(su->compile_output_file_name); + file_unlink(su->wasm_check_file_name); compiler_record_compile_result( su, time_precise() - comp_start, - (su->so_handle != 0), - (su->so_handle ? "loaded" : "load_error"), + file_exists(su->wasm_name), + (file_exists(su->wasm_name) ? "compiled" : "compile_error"), compiler_error_status(su) ); - printf("(i) compiled unit %s in %f s\n", + printf("(i) compiled wasm unit %s in %f s\n", (su->pre_path + "/" + su->pre_file_name).c_str(), time_precise() - comp_start); } @@ -980,23 +837,10 @@ SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name } else { - load_shared_unit(context, su); - if(!su->so_handle) - { - if(!force_recompile && retry_deferred) - compiler_restore_persisted_failure(su, state); - else if(!force_recompile && !jit_enabled) - { - compiler_restore_persisted_failure(su, state, "jit_compile_disabled"); - if(trim(su->compiler_messages) == "") - { - su->compiler_messages = "JIT compilation on request is disabled"; - su->compile_error_status = su->compiler_messages; - } - } - else - compile_shared_unit(context, su); - } + su->api_declarations = split(file_get_contents(su->api_file_name), "\n"); + su->last_compiled = state.compiled_time; + su->compile_status = compiler_status_from_filesystem(state, su); + su->compile_error_status = ""; } auto observed_state = inspect_shared_unit_filesystem(context, su); @@ -1013,60 +857,129 @@ SharedUnit* get_shared_unit(Request* context, String file_name, bool opt_so_opti return(compiler_get_shared_unit_internal(context, file_name, opt_so_optional, false)); } -SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path, bool opt_so_optional) +String compiler_error_page_unit(Request* context, String config_key) { + if(!context || !context->server) + return(""); + String configured = trim(first( + context->server->config[config_key], + context->server->config[to_upper(config_key)] + )); + if(configured == "") + return(""); + // Resolve relative to where the server was started, not the volatile + // per-unit working directory (which is also stale right after a fault). + String resolved = compiler_resolve_unit_path(context, configured, process_start_directory()); + if(resolved == "" || !file_exists(resolved)) + return(""); + return(resolved); +} - context->stats.invoke_count++; +bool compiler_unit_compile_pending(Request* context, String file_name) +{ + String normalized = compiler_normalize_unit_path(context, file_name); + if(normalized == "" || !file_exists(normalized)) + return(false); + if(compiler_reusable_cached_unit(context, normalized, false, false)) + return(false); - file_name = compiler_resolve_unit_path(context, file_name, current_path); - if(file_name == "") - return(0); - if(file_exists(file_name)) - compiler_track_known_unit(context, file_name); - else - compiler_untrack_known_unit(context, file_name); + SharedUnit probe; + setup_unit_paths(context, &probe, normalized); + auto state = inspect_shared_unit_filesystem(context, &probe); + if(!shared_unit_compile_check(state).needs_compile) + return(false); + // A persisted, still-current failure is a compiler error, not a build in + // progress: let the load path surface it. + if(compiler_failure_retry_deferred(context, &probe, state)) + return(false); + return(true); +} - //printf("(i) load '%s'\n", file_name.c_str()); +void unit_render(String file_name) +{ + unit_render(file_name, *context); +} - //switch_to_system_alloc(); - auto su = get_shared_unit(context, file_name, opt_so_optional); - //switch_to_arena(context->mem); - if(!su) - { - printf("Error loading unit %s\n", file_name.c_str()); - if(compiler_can_write_response(context)) - print("Error loading unit: " + file_name); - return(0); - } - else if(su->compiler_messages.length() > 0) - { - String display_messages = su->compiler_messages; - if(su->compile_status == "compile_error") - display_messages = compiler_format_compile_failure(su, su->compiler_messages); - printf("%s\n", display_messages.c_str()); - if(compiler_can_write_response(context) && context->stats.invoke_count == 1) - { - DValue error_info; - error_info["type"] = "compiler_error"; - error_info["status"] = su->compile_status; - error_info["source"] = su->file_name; - error_info["compiler_output"] = su->compiler_messages; - error_info["generated_cpp"] = compiler_generated_cpp_path(su); - error_info["details"] = display_messages; - if(compiler_render_error_page(context, "page_compiler_error", 500, "UCE Unit Compile Error", error_info)) - return(0); - context->set_status(500, "UCE Unit Compile Error"); - context->header["Content-Type"] = "text/plain; charset=utf-8"; - } - if(compiler_can_write_response(context)) - print(display_messages); - return(0); - } - else - { - return(su); - } +void unit_render(String file_name, Request& request) +{ + request.set_status(500, "Native Unit Render Removed"); + print("native unit_render() is unavailable; units must render through the wasm backend: " + file_name); +} +String component_resolve(String name) +{ + (void)name; + return(""); +} + +bool component_exists(String name) +{ + return(component_resolve(name) != ""); +} + +String component_error_banner(String message) +{ + return("
" + html_escape(message) + "
"); +} + +void component_render(String name) +{ + DValue props; + component_render(name, props, *context); +} + +void component_render(String name, Request& context) +{ + DValue props; + component_render(name, props, context); +} + +void component_render(String name, DValue props) +{ + component_render(name, props, *context); +} + +void component_render(String name, DValue props, Request& context) +{ + (void)props; (void)context; + print(component_error_banner("native component_render() is unavailable; components must render through the wasm backend: " + trim(name))); +} + +String component(String name) +{ + DValue props; + return(component(name, props, *context)); +} + +String component(String name, Request& context) +{ + DValue props; + return(component(name, props, context)); +} + +String component(String name, DValue props) +{ + return(component(name, props, *context)); +} + +String component(String name, DValue props, Request& context) +{ + ob_start(); + component_render(name, props, context); + return(ob_get_close()); +} + +SharedUnit* unit_load(String file_name) +{ + String resolved = compiler_resolve_unit_path(context, file_name); + return(resolved == "" ? 0 : get_shared_unit(context, resolved, true)); +} + +DValue* unit_call(String file_name, String function_name, DValue* call_param) +{ + (void)call_param; + print("Error: native unit_call() is unavailable; units must call through the wasm workspace: ", file_name, "::", function_name); + return(0); } String compiler_site_directory(Request* context) @@ -1094,17 +1007,12 @@ StringList compiler_scan_site_units(Request* context) { if(walk_error) { - printf("(!) proactive scan warning in %s: %s\n", - site_directory.c_str(), - walk_error.message().c_str()); + printf("(!) proactive scan warning in %s: %s\n", site_directory.c_str(), walk_error.message().c_str()); walk_error.clear(); continue; } - std::error_code entry_error; - if(!it->is_regular_file(entry_error)) - continue; - if(entry_error) + if(!it->is_regular_file(entry_error) || entry_error) continue; auto path = it->path().string(); if(path.length() >= 4 && path.substr(path.length() - 4) == ".uce") @@ -1115,9 +1023,7 @@ StringList compiler_scan_site_units(Request* context) StringList compiler_list_known_units(Request* context) { - return(compiler_with_registry_lock(context, [&]() { - return(compiler_read_known_units_unlocked(context)); - })); + return(compiler_with_registry_lock(context, [&]() { return(compiler_read_known_units_unlocked(context)); })); } void compiler_set_known_units(Request* context, StringList files) @@ -1134,7 +1040,6 @@ void compiler_track_known_unit(Request* context, String file_name) file_name = compiler_normalize_unit_path(context, file_name); if(file_name == "" || !compiler_is_known_unit_file(file_name)) return; - compiler_with_registry_lock(context, [&]() { auto files = compiler_read_known_units_unlocked(context); if(std::find(files.begin(), files.end(), file_name) != files.end()) @@ -1150,7 +1055,6 @@ void compiler_untrack_known_unit(Request* context, String file_name) file_name = compiler_normalize_unit_path(context, file_name); if(file_name == "") return; - compiler_with_registry_lock(context, [&]() { auto files = compiler_read_known_units_unlocked(context); files.erase(std::remove(files.begin(), files.end(), file_name), files.end()); @@ -1180,7 +1084,6 @@ DValue unit_info(String path) DValue info; if(!context) return(info); - String resolved_path = compiler_resolve_unit_path(context, path); if(resolved_path == "") return(info); @@ -1188,9 +1091,7 @@ DValue unit_info(String path) SharedUnit* su = 0; auto it = context->server->units.find(resolved_path); if(it != context->server->units.end()) - { su = it->second; - } else { auto known_units = compiler_list_known_units(context); @@ -1263,7 +1164,7 @@ DValue unit_info(String path) info["metadata_build_token"] = fs_state.metadata_build_token; compiler_tree_set_bool(info, "known", compiler_has_known_unit_cached(context, resolved_path)); compiler_tree_set_bool(info, "current_unit", resolved_path == compiler_current_unit_path(context)); - compiler_tree_set_bool(info, "loaded", su->so_handle != 0); + compiler_tree_set_bool(info, "loaded", file_exists(su->wasm_name)); compiler_tree_set_bool(info, "source_exists", fs_state.source_exists); compiler_tree_set_bool(info, "compiled_exists", fs_state.compiled_time != 0); compiler_tree_set_bool(info, "metadata_exists", fs_state.metadata_exists); @@ -1273,15 +1174,13 @@ DValue unit_info(String path) compiler_tree_set_bool(info, "stale", shared_unit_compile_check(fs_state).needs_compile && !compiler_failure_retry_deferred(context, su, fs_state)); compiler_tree_set_bool(info, "retry_deferred", compiler_failure_retry_deferred(context, su, fs_state)); compiler_tree_set_bool(info, "cache_stale", shared_unit_cache_is_stale(context, su)); - compiler_tree_set_bool(info, "has_render", su->on_render != 0); - compiler_tree_set_bool(info, "has_component", su->on_component != 0); - compiler_tree_set_bool(info, "has_websocket", su->on_websocket != 0); - compiler_tree_set_bool(info, "has_setup", su->on_setup != 0); + compiler_tree_set_bool(info, "has_render", false); + compiler_tree_set_bool(info, "has_component", false); + compiler_tree_set_bool(info, "has_websocket", false); + compiler_tree_set_bool(info, "has_setup", false); compiler_tree_set_bool(info, "has_error", trim(compiler_error_status(su)) != ""); - compiler_tree_push_strings(info["exports"], exports); info["exports_text"] = exports_text; - return(info); } @@ -1289,7 +1188,6 @@ StringList units_list() { if(!context) return(StringList()); - auto known_units = compiler_list_known_units(context); for(auto& it : context->server->units) known_units.push_back(it.first); @@ -1300,870 +1198,10 @@ bool unit_compile(String path) { if(!context) return(false); - String resolved_path = compiler_resolve_unit_path(context, path); if(resolved_path == "") return(false); - compiler_track_known_unit(context, resolved_path); auto su = compiler_get_shared_unit_internal(context, resolved_path, false, true); - return(su && trim(su->compiler_messages) == "" && su->so_handle != 0); -} - -namespace { - -struct UnitInvocationScope -{ - Request* context = 0; - String previous_working_directory; - String previous_unit_file; - - UnitInvocationScope(Request* context, SharedUnit* su) - { - this->context = context; - previous_working_directory = cwd_get(); - previous_unit_file = context->resources.current_unit_file; - cwd_set(su->src_path); - context->resources.current_unit_file = su->file_name; - } - - ~UnitInvocationScope() - { - if(!context) - return; - context->resources.current_unit_file = previous_unit_file; - cwd_set(previous_working_directory); - } -}; - -enum class UnitCallMacroKind -{ - none, - render, - component, - once, - init, - cli, - serve_http -}; - -struct UnitCallMacroTarget -{ - UnitCallMacroKind kind = UnitCallMacroKind::none; - String handler_name; -}; - -struct RequestPropsScope -{ - Request* context = 0; - DValue previous_props; - - RequestPropsScope(Request* context, const DValue& props) - { - this->context = context; - if(this->context) - { - previous_props = this->context->props; - this->context->props = props; - } - } - - ~RequestPropsScope() - { - if(context) - context->props = previous_props; - } -}; - -void compiler_unload_failed_shared_unit(SharedUnit* su) -{ - if(!su) - return; - if(su->so_handle) - dlclose(su->so_handle); - su->so_handle = 0; - su->api_functions.clear(); - su->on_setup = 0; - su->on_render = 0; - su->on_component = 0; - su->on_websocket = 0; - su->on_cli = 0; - su->on_once = 0; - su->on_init = 0; -} - -bool compiler_run_unit_init(Request* context, SharedUnit* su, String* error_out) -{ - if(!su || !su->on_init) - return(true); - if(!context) - { - if(error_out) - *error_out = "internal error: INIT() requires a Request context"; - return(false); - } - if(!su->on_setup) - { - if(error_out) - *error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name; - return(false); - } - - UnitInvocationScope invoke_scope(context, su); - su->on_setup(context); - try - { - su->on_init(*context); - return(true); - } - catch(...) - { - su->runtime_error_status = "uncaught exception during INIT"; - su->compile_status = "load_error"; - su->compile_error_status = su->runtime_error_status; - su->last_error = time(); - if(error_out) - *error_out = su->runtime_error_status; - return(false); - } -} - -bool compiler_run_unit_once_if_needed(Request* context, SharedUnit* su, String* error_out) -{ - if(!su || !su->on_once) - return(true); - if(!context) - { - if(error_out) - *error_out = "internal error: ONCE() requires a Request context"; - return(false); - } - if(!su->on_setup) - { - if(error_out) - *error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name; - return(false); - } - - if(context->once_units.find(su->file_name) != context->once_units.end()) - return(true); - - context->once_units.insert(su->file_name); - UnitInvocationScope invoke_scope(context, su); - su->on_setup(context); - try - { - su->on_once(*context); - return(true); - } - catch(...) - { - context->once_units.erase(su->file_name); - su->runtime_error_status = "uncaught exception during ONCE"; - su->last_error = time(); - if(error_out) - *error_out = su->runtime_error_status; - throw; - } -} - -String unit_call_macro_trim(String function_name) -{ - function_name = trim(function_name); - if(function_name.length() >= 2 && function_name.substr(function_name.length() - 2) == "()") - function_name = trim(function_name.substr(0, function_name.length() - 2)); - return(function_name); -} - -UnitCallMacroTarget unit_call_macro_target(String function_name) -{ - UnitCallMacroTarget target; - function_name = unit_call_macro_trim(function_name); - - if(function_name == "RENDER") - { - target.kind = UnitCallMacroKind::render; - return(target); - } - if(function_name.rfind("RENDER:", 0) == 0) - { - target.kind = UnitCallMacroKind::render; - target.handler_name = trim(function_name.substr(7)); - return(target); - } - if(function_name == "COMPONENT") - { - target.kind = UnitCallMacroKind::component; - return(target); - } - if(function_name.rfind("COMPONENT:", 0) == 0) - { - target.kind = UnitCallMacroKind::component; - target.handler_name = trim(function_name.substr(10)); - return(target); - } - if(function_name == "ONCE") - { - target.kind = UnitCallMacroKind::once; - return(target); - } - if(function_name == "INIT") - { - target.kind = UnitCallMacroKind::init; - return(target); - } - if(function_name == "CLI") - { - target.kind = UnitCallMacroKind::cli; - return(target); - } - if(function_name == "SERVE_HTTP") - { - target.kind = UnitCallMacroKind::serve_http; - return(target); - } - if(function_name.rfind("SERVE_HTTP:", 0) == 0) - { - target.kind = UnitCallMacroKind::serve_http; - target.handler_name = trim(function_name.substr(11)); - return(target); - } - - return(target); -} - -} - -String component_normalize_path(String name) -{ - name = trim(name); - if(name.length() >= 4 && name.substr(name.length() - 4) == ".uce") - return(name); - return(name + ".uce"); -} - -void component_parse_target(String target, String& file_name, String& render_name) -{ - target = trim(target); - render_name = ""; - auto render_split_pos = target.find(":"); - if(render_split_pos != String::npos) - { - render_name = trim(target.substr(render_split_pos + 1)); - target = trim(target.substr(0, render_split_pos)); - } - file_name = target; -} - -String component_resolve_path(String name, Request* request_context = 0) -{ - String target_name = trim(name); - String file_name; - String render_name; - component_parse_target(target_name, file_name, render_name); - - if(file_name == "") - { - if(target_name.rfind(":", 0) == 0 && request_context && request_context->resources.current_unit_file != "") - file_name = request_context->resources.current_unit_file; - else - return(""); - } - - StringList candidates; - auto push_candidate = [&] (String candidate) { - if(candidate == "") - return; - candidates.push_back(candidate); - }; - - push_candidate(file_name); - push_candidate(component_normalize_path(file_name)); - - if(file_name.rfind("components/", 0) != 0) - { - push_candidate("components/" + file_name); - push_candidate(component_normalize_path("components/" + file_name)); - } - - std::map seen; - for(auto& candidate : candidates) - { - if(seen[candidate]) - continue; - seen[candidate] = true; - String resolved = candidate; - if(resolved[0] != '/') - resolved = expand_path(resolved, cwd_get()); - if(file_exists(resolved)) - return(resolved); - } - - return(""); -} - -String page_render_handler_symbol(String render_name) -{ - render_name = trim(render_name); - if(render_name == "" || render_name == "render") - return(UCE_RENDER_SYMBOL); - return(String(UCE_RENDER_SYMBOL) + "_" + safe_name(render_name)); -} - -String component_handler_symbol(String render_name) -{ - render_name = trim(render_name); - if(render_name == "") - return(UCE_COMPONENT_SYMBOL); - return(String(UCE_COMPONENT_SYMBOL) + "_" + safe_name(render_name)); -} - -String serve_http_handler_symbol(String handler_name) -{ - handler_name = trim(handler_name); - if(handler_name == "") - return(UCE_SERVE_HTTP_SYMBOL); - return(String(UCE_SERVE_HTTP_SYMBOL) + "_" + safe_name(handler_name)); -} - -String compiler_missing_request_handler_message(UnitCallMacroKind kind, String handler_name) -{ - handler_name = trim(handler_name); - if(kind == UnitCallMacroKind::render) - { - if(handler_name == "" || handler_name == "render") - return("no RENDER() entry point"); - return("no RENDER:" + handler_name + "() entry point"); - } - if(kind == UnitCallMacroKind::component) - { - if(handler_name == "") - return("no COMPONENT() entry point"); - return("no COMPONENT:" + handler_name + "() entry point"); - } - if(kind == UnitCallMacroKind::once) - return("no ONCE() entry point"); - if(kind == UnitCallMacroKind::cli) - return("no CLI() entry point"); - if(kind == UnitCallMacroKind::serve_http) - { - if(handler_name == "") - return("no SERVE_HTTP() entry point"); - return("no SERVE_HTTP:" + handler_name + "() entry point"); - } - if(kind == UnitCallMacroKind::init) - return("no INIT() entry point"); - return("request handler not found"); -} - -bool compiler_prepare_request_handler(Request* context, SharedUnit* su, String* error_out = 0, bool run_once = false) -{ - if(!su->on_setup) - { - if(error_out) - *error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name; - return(false); - } - if(run_once && !compiler_run_unit_once_if_needed(context, su, error_out)) - return(false); - return(true); -} - -void compiler_execute_request_handler( - Request* context, - SharedUnit* su, - request_ref_handler handler, - bool count_request, - String runtime_error_status -) -{ - UnitInvocationScope invoke_scope(context, su); - su->on_setup(context); - f64 render_start = time_precise(); - compiler_begin_render_result(su, count_request); - try - { - handler(*context); - compiler_record_render_result(su, time_precise() - render_start, true); - } - catch(...) - { - compiler_record_render_result( - su, - time_precise() - render_start, - false, - runtime_error_status - ); - throw; - } -} - -request_ref_handler get_request_handler_symbol(SharedUnit* su, String symbol, request_ref_handler default_handler = 0) -{ - if(default_handler && symbol == "") - return(default_handler); - - auto it = su->api_functions.find(symbol); - if(it != su->api_functions.end()) - return((request_ref_handler)it->second); - - auto handler = (request_ref_handler)dlsym(su->so_handle, symbol.c_str()); - dlerror(); - su->api_functions[symbol] = (void*)handler; - return(handler); -} - -request_ref_handler get_page_render_handler(SharedUnit* su, String render_name) -{ - String symbol = page_render_handler_symbol(render_name); - if(symbol == UCE_RENDER_SYMBOL) - return(su->on_render); - return(get_request_handler_symbol(su, symbol)); -} - -request_ref_handler get_component_handler(SharedUnit* su, String render_name) -{ - String symbol = component_handler_symbol(render_name); - if(symbol == UCE_COMPONENT_SYMBOL) - return(su->on_component); - return(get_request_handler_symbol(su, symbol)); -} - -request_ref_handler get_serve_http_handler(SharedUnit* su, String handler_name) -{ - return(get_request_handler_symbol(su, serve_http_handler_symbol(handler_name))); -} - -bool compiler_invoke_loaded_request_handler( - Request* context, - SharedUnit* su, - request_ref_handler handler, - UnitCallMacroKind kind, - String handler_name, - bool count_request, - String runtime_error_status, - String* error_out = 0 -) -{ - if(!compiler_prepare_request_handler(context, su, error_out, true)) - return(false); - - if(!handler) - { - if(error_out) - *error_out = compiler_missing_request_handler_message(kind, handler_name); - return(false); - } - - compiler_execute_request_handler( - context, - su, - handler, - count_request, - runtime_error_status - ); - return(true); -} - -bool compiler_invoke_render(Request* context, String file_name, String render_name, String* error_out = 0) -{ - auto su = compiler_load_shared_unit(context, file_name, "", false); - if(!su) - return(false); - - return(compiler_invoke_loaded_request_handler( - context, - su, - get_page_render_handler(su, render_name), - UnitCallMacroKind::render, - render_name, - compiler_is_request_entry_unit(context, su), - "uncaught exception during render", - error_out - )); -} - -bool compiler_invoke_component(Request* context, String file_name, String render_name, String* error_out = 0) -{ - auto su = compiler_load_shared_unit(context, file_name, "", false); - if(!su) - return(false); - - return(compiler_invoke_loaded_request_handler( - context, - su, - get_component_handler(su, render_name), - UnitCallMacroKind::component, - render_name, - false, - "uncaught exception during component render", - error_out - )); -} - -String compiler_error_page_unit(Request* context, String config_key) -{ - if(!context || !context->server) - return(""); - String configured = trim(first( - context->server->config[config_key], - context->server->config[to_upper(config_key)] - )); - if(configured == "") - return(""); - // Resolve relative to where the server was started, not the volatile - // per-unit working directory (which is also stale right after a fault). - String resolved = compiler_resolve_unit_path(context, configured, process_start_directory()); - if(resolved == "" || !file_exists(resolved)) - return(""); - return(resolved); -} - -bool compiler_render_error_page(Request* context, String config_key, s32 status_code, String status_reason, DValue error_info) -{ - if(!compiler_can_write_response(context)) - return(false); - if(!context->server || context->resources.error_page_active) - return(false); - String unit_file = compiler_error_page_unit(context, config_key); - if(unit_file == "") - return(false); - - String previous_response_code = context->response_code; - s32 previous_status = context->flags.status; - String previous_content_type = context->header["Content-Type"]; - - context->resources.error_page_active = true; - context->call["error"] = error_info; - context->set_status(status_code, status_reason); - context->header["Content-Type"] = first(context->server->config["CONTENT_TYPE"], "text/html; charset=utf-8"); - - ob_start(); - String error_message = ""; - bool rendered = compiler_invoke_render(context, unit_file, "render", &error_message); - String html = ob_get_close(); - context->resources.error_page_active = false; - - if(!rendered) - { - printf("(!) configured %s page %s failed to render: %s\n", config_key.c_str(), unit_file.c_str(), trim(error_message).c_str()); - context->response_code = previous_response_code; - context->flags.status = previous_status; - context->header["Content-Type"] = previous_content_type; - context->call.remove("error"); - return(false); - } - print(html); - return(true); -} - -bool compiler_unit_compile_pending(Request* context, String file_name) -{ - String normalized = compiler_normalize_unit_path(context, file_name); - if(normalized == "" || !file_exists(normalized)) - return(false); - if(compiler_reusable_cached_unit(context, normalized, false, false)) - return(false); - - SharedUnit probe; - setup_unit_paths(context, &probe, normalized); - auto state = inspect_shared_unit_filesystem(context, &probe); - if(!shared_unit_compile_check(state).needs_compile) - return(false); - // A persisted, still-current failure is a compiler error, not a build in - // progress: let the load path surface it. - if(compiler_failure_retry_deferred(context, &probe, state)) - return(false); - return(true); -} - -void compiler_invoke(Request* context, String file_name) -{ - printf("(i) compiler_invoke file %s\n", file_name.c_str()); - - // Entry-unit hook for the configured "compiling" page: when the unit needs - // a (re)build that the proactive compiler can deliver, answer immediately - // instead of blocking the worker on a synchronous compile. - if(context && context->stats.invoke_count == 0 && - !context->resources.is_cli && !context->resources.error_page_active && - config_bool("PROACTIVE_COMPILE_ENABLED", true) && - compiler_error_page_unit(context, "page_compiling") != "") - { - String resolved = compiler_resolve_unit_path(context, file_name, ""); - if(resolved != "" && compiler_unit_compile_pending(context, resolved)) - { - compiler_track_known_unit(context, resolved); - DValue error_info; - error_info["type"] = "compiling"; - error_info["source"] = resolved; - if(compiler_render_error_page(context, "page_compiling", 503, "UCE Unit Compiling", error_info)) - return; - } - } - - String error_message = ""; - if(!compiler_invoke_render(context, file_name, "render", &error_message) && error_message != "") - { - if(context->stats.invoke_count == 1) - context->header["Content-Type"] = "text/plain"; - print(error_message); - } -} - -void compiler_invoke_cli(Request* context, String file_name) -{ - auto su = compiler_load_shared_unit(context, file_name, "", false); - if(!su) - return; - - String error_message = ""; - if(!compiler_invoke_loaded_request_handler( - context, - su, - su->on_cli, - UnitCallMacroKind::cli, - "", - compiler_is_request_entry_unit(context, su), - "uncaught exception during cli handler", - &error_message - )) - { - if(!su->on_cli) - context->set_status(404, "CLI Entry Point Not Found"); - else - context->set_status(500, "CLI Unit Error"); - if(error_message != "") - print(error_message); - } -} - -void compiler_invoke_serve_http(Request* context, String file_name, String handler_name) -{ - auto su = compiler_load_shared_unit(context, file_name, "", false); - if(!su) - return; - - String error_message = ""; - if(!compiler_invoke_loaded_request_handler( - context, - su, - get_serve_http_handler(su, handler_name), - UnitCallMacroKind::serve_http, - handler_name, - true, - "uncaught exception during HTTP server handler", - &error_message - )) - { - context->set_status(500, "HTTP Server Handler Error"); - if(error_message != "") - print(error_message); - } -} - -void compiler_invoke_websocket(Request* context, String file_name) -{ - auto su = compiler_load_shared_unit(context, file_name, "", false); - if(!su) - return; - - if(!su->on_setup) - { - printf("internal error: %s() not defined in %s\n", UCE_SETUP_SYMBOL, file_name.c_str()); - return; - } - - if(!su->on_websocket) - { - printf("no WS() entry point in %s\n", file_name.c_str()); - return; - } - - compiler_execute_request_handler( - context, - su, - su->on_websocket, - compiler_is_request_entry_unit(context, su), - "uncaught exception during websocket handler" - ); -} - -void unit_render(String file_name) -{ - //printf("(i) unit_render(%s)\n", file_name.c_str()); - compiler_invoke(context, file_name); -} - -void unit_render(String file_name, Request& context) -{ - compiler_invoke(&context, file_name); -} - -String component_resolve(String name) -{ - return(component_resolve_path(name, context)); -} - -bool component_exists(String name) -{ - return(component_resolve(name) != ""); -} - -String component_error_banner(String message) -{ - return("
" + html_escape(message) + "
"); -} - -void component_render(String name) -{ - DValue props; - component_render(name, props, *context); -} - -void component_render(String name, Request& context) -{ - DValue props; - component_render(name, props, context); -} - -void component_render(String name, DValue props) -{ - component_render(name, props, *context); -} - -void component_render(String name, DValue props, Request& context) -{ - String file_name; - String render_name; - component_parse_target(name, file_name, render_name); - - String resolved_name = component_resolve_path(name, &context); - if(resolved_name == "") - { - print(component_error_banner("component not found: " + trim(name))); - return; - } - - RequestPropsScope props_scope(&context, props); - - String error_message = ""; - if(!compiler_invoke_component(&context, resolved_name, render_name, &error_message) && error_message != "") - print(component_error_banner(error_message)); -} - -String component(String name) -{ - DValue props; - return(component(name, props, *context)); -} - -String component(String name, Request& context) -{ - DValue props; - return(component(name, props, context)); -} - -String component(String name, DValue props) -{ - return(component(name, props, *context)); -} - -String component(String name, DValue props, Request& context) -{ - ob_start(); - component_render(name, props, context); - return(ob_get_close()); -} - -SharedUnit* unit_load(String file_name) -{ - auto su = compiler_load_shared_unit(context, file_name, "", false); - if(su && su->so_handle) - { - return(su); - } - else - { - return(0); - } -} - -DValue* unit_call(String file_name, String function_name, DValue* call_param) -{ - DValue* result = 0; - auto su = compiler_load_shared_unit(context, file_name, "", false); - if(su && su->so_handle) - { - if(!su->on_setup) - { - print("internal error: ", UCE_SETUP_SYMBOL, "() not defined in ", file_name, "\n"); - } - else - { - auto macro_target = unit_call_macro_target(function_name); - if(macro_target.kind != UnitCallMacroKind::none) - { - RequestPropsScope props_scope(context, (call_param ? *call_param : DValue())); - String error_message = ""; - - if(macro_target.kind == UnitCallMacroKind::render) - { - if(!compiler_invoke_render(context, su->file_name, macro_target.handler_name, &error_message) && error_message != "") - print("Error: unit_call() ", error_message); - } - else if(macro_target.kind == UnitCallMacroKind::component) - { - if(!compiler_invoke_component(context, su->file_name, macro_target.handler_name, &error_message) && error_message != "") - print("Error: unit_call() ", error_message); - } - else - { - request_ref_handler handler = 0; - - if(macro_target.kind == UnitCallMacroKind::once) - handler = su->on_once; - else if(macro_target.kind == UnitCallMacroKind::init) - handler = su->on_init; - else if(macro_target.kind == UnitCallMacroKind::cli) - handler = su->on_cli; - - if(!handler) - print("Error: unit_call() ", compiler_missing_request_handler_message(macro_target.kind, macro_target.handler_name)); - else if(macro_target.kind == UnitCallMacroKind::cli) - { - String prepare_error = ""; - if(!compiler_prepare_request_handler(context, su, &prepare_error, true)) - print("Error: unit_call() ", prepare_error); - else - compiler_execute_request_handler(context, su, handler, false, "uncaught exception during cli handler"); - } - else - { - UnitInvocationScope invoke_scope(context, su); - su->on_setup(context); - handler(*context); - } - } - } - else - { - auto f = (dv_call_handler)dlsym(su->so_handle, function_name.c_str()); - if(!f) - { - print("Error: unit_call() function '", function_name, "' not found"); - } - else - { - UnitInvocationScope invoke_scope(context, su); - su->on_setup(context); - result = f(call_param); - } - } - } - } - else - { - print("Error: unit_call() could not load unit file '", file_name, "'"); - } - return(result); + return(su && trim(su->compiler_messages) == "" && file_exists(su->wasm_name)); } diff --git a/src/lib/compiler.h b/src/lib/compiler.h index cc4721d..b72dbcc 100644 --- a/src/lib/compiler.h +++ b/src/lib/compiler.h @@ -13,16 +13,9 @@ String preprocess_shared_unit(Request* context, SharedUnit* su); String compiler_generated_cpp_path(Request* context, String source_file); String compiler_generated_cpp_path(SharedUnit* su); void setup_unit_paths(Request* context, SharedUnit* su, String file_name); -void load_shared_unit(Request* context, SharedUnit* su); void compile_shared_unit(Request* context, SharedUnit* su); SharedUnit* get_shared_unit(Request* context, String file_name, bool opt_so_optional = false); -void compiler_invoke(Request* context, String file_name); -void compiler_invoke_websocket(Request* context, String file_name); -void compiler_invoke_cli(Request* context, String file_name); -void compiler_invoke_serve_http(Request* context, String file_name, String handler_name = ""); -SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path = "", bool opt_so_optional = false); String compiler_error_page_unit(Request* context, String config_key); -bool compiler_render_error_page(Request* context, String config_key, s32 status_code, String status_reason, DValue error_info); bool compiler_unit_compile_pending(Request* context, String file_name); String compiler_site_directory(Request* context); StringList compiler_scan_site_units(Request* context); diff --git a/src/lib/sys.cpp b/src/lib/sys.cpp index 60ea731..8ac6e3e 100644 --- a/src/lib/sys.cpp +++ b/src/lib/sys.cpp @@ -1314,10 +1314,7 @@ StringMap make_server_settings() StringMap cfg; cfg["BIN_DIRECTORY"] = "/tmp/uce/work"; - cfg["COMPILE_SCRIPT"] = "scripts/compile"; cfg["WASM_COMPILE_SCRIPT"] = "scripts/compile_wasm_unit"; - cfg["COMPILE_WASM_UNITS"] = "0"; - cfg["WASM_BACKEND_ENABLED"] = "1"; cfg["WASM_BACKEND_VERBOSE"] = "0"; cfg["WASM_CORE_PATH"] = ""; cfg["WASM_MEMORY_LIMIT_BYTES"] = std::to_string(512ull * 1024 * 1024); diff --git a/src/lib/types.cpp b/src/lib/types.cpp index a39a374..f820763 100644 --- a/src/lib/types.cpp +++ b/src/lib/types.cpp @@ -79,12 +79,6 @@ String http_status_reason(s32 code) SharedUnit::~SharedUnit() { -#ifndef __UCE_WASM_CORE__ - if(so_handle) - { - dlclose(so_handle); - } -#endif } String nibble(String div, String& haystack) diff --git a/src/lib/types.h b/src/lib/types.h index 68d239c..6a03405 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -74,7 +74,6 @@ struct SharedUnit { String compile_output_file_name; String setup_file_name; StringList api_declarations; - std::map api_functions; String src_path; String bin_path; @@ -84,16 +83,6 @@ struct SharedUnit { String wasm_file_name; String pre_file_name; - void* so_handle = 0; - - request_handler on_setup = 0; - request_ref_handler on_render = 0; - request_ref_handler on_component = 0; - request_ref_handler on_websocket = 0; - request_ref_handler on_cli = 0; - request_ref_handler on_once = 0; - request_ref_handler on_init = 0; - String compiler_messages; String compile_status = "unknown"; String compile_error_status = ""; @@ -153,8 +142,6 @@ String nibble(String div, String& haystack); #include "dvalue.h" -void compiler_invoke(Request* context, String file_name); - struct Request { ServerState* server = 0; diff --git a/src/linux_fastcgi.cpp b/src/linux_fastcgi.cpp index cac1415..cf14321 100644 --- a/src/linux_fastcgi.cpp +++ b/src/linux_fastcgi.cpp @@ -65,6 +65,51 @@ void clear_request_output(Request& request) request.ob_start(); } +bool render_wasm_error_page(Request& request, String config_key, s32 status_code, String status_reason, DValue error_info) +{ + if(!(request.params["REQUEST_METHOD"] != "" && request.ob && request.ob_stack.size() > 0)) + return(false); + if(!request.server || request.resources.error_page_active) + return(false); + String unit_file = compiler_error_page_unit(&request, config_key); + if(unit_file == "") + return(false); + + String previous_response_code = request.response_code; + s32 previous_status = request.flags.status; + String previous_content_type = request.header["Content-Type"]; + + request.resources.error_page_active = true; + request.call["error"] = error_info; + request.set_status(status_code, status_reason); + request.header["Content-Type"] = first(request.server->config["CONTENT_TYPE"], "text/html; charset=utf-8"); + + String unit = compiler_normalize_unit_path(&request, unit_file); + if(!wasm_backend_should_handle(request, unit)) + get_shared_unit(&request, unit, true); + + ob_start(); + String wasm_error = ""; + if(wasm_backend_should_handle(request, unit)) + wasm_error = wasm_backend_serve(request, unit, "render"); + else + wasm_error = "error page wasm unit unavailable after compile: " + unit_file; + String html = ob_get_close(); + request.resources.error_page_active = false; + + if(wasm_error != "") + { + printf("(!) configured %s page %s failed to render: %s\n", config_key.c_str(), unit_file.c_str(), trim(wasm_error).c_str()); + request.response_code = previous_response_code; + request.flags.status = previous_status; + request.header["Content-Type"] = previous_content_type; + request.call.remove("error"); + return(false); + } + print(html); + return(true); +} + void render_request_failure(Request& request, String title, String details, String trace, int status_code = 500) { request.response_code = request_status_line(request, status_code, "Internal Server Error"); @@ -92,7 +137,7 @@ void render_request_failure(Request& request, String title, String details, Stri error_info["signal_name"] = signal_name((int)request_fault_signal); } error_info["trace"] = trace; - if(compiler_render_error_page(&request, "page_runtime_error", status_code, "Internal Server Error", error_info)) + if(render_wasm_error_page(request, "page_runtime_error", status_code, "Internal Server Error", error_info)) { request.err = "UCE runtime error: " + title + (details != "" ? " (" + details + ")" : ""); restore_active_request(previous_context); @@ -368,10 +413,9 @@ int handle_cli_complete(FastCGIRequest& request) // The CLI socket path installs no native fault handler, so the // wasm worker (whose traps are signal-based) can run directly. String cli_unit = compiler_normalize_unit_path(&request, script_filename); - // W7e: compile a cold/stale unit on demand instead of executing - // native. force_recompile: the compile-needed check ignores the - // .wasm (keys off .so mtime), so a missing/stale .wasm with a - // current .so must be forced to rebuild — see wasm_ready note. + // W7e: compile a cold/stale unit on demand; native execution has + // been removed, so a unit that still cannot be served by wasm is a + // request failure instead of a fallback path. if(!wasm_backend_should_handle(request, cli_unit)) get_shared_unit(&request, cli_unit, true); if(wasm_backend_should_handle(request, cli_unit)) @@ -384,7 +428,10 @@ int handle_cli_complete(FastCGIRequest& request) } } else - compiler_invoke_cli(&request, script_filename); + { + request.set_status(500, "Internal Server Error"); + print("UCE CLI wasm unit unavailable after compile: ", script_filename, "\n"); + } } } } @@ -484,27 +531,21 @@ int handle_complete(FastCGIRequest& request) { String entry_unit = compiler_normalize_unit_path(&request, request.params["SCRIPT_FILENAME"]); // W7e: every unit runs on wasm. When the artifact is missing or stale - // (cold worker, or source edited since the last compile), - // wasm_backend_should_handle() returns false; rather than fall back to - // native execution we compile the unit on demand — get_shared_unit() - // builds the .wasm side-module — and recheck. Native execution remains - // only as a safety net for a genuine wasm-compile failure (or the wasm - // backend being disabled), both of which leave should_handle() false. - // Only reached on the cold/stale path (should_handle false); warm - // requests short-circuit before this, so there is no steady-state - // recompile cost. force_recompile is needed because the compile-needed - // check keys off the .so mtime only (shared_unit_compile_check / - // compiled_time ignores the .wasm), so an uncached unit with a current - // .so but missing .wasm would otherwise not rebuild. NOTE: this does not - // cover a unit already cached in-process whose .wasm later vanished - // (compiler_reusable_cached_unit short-circuits before the recompile) — - // that synthetic case still uses native fallback and is closed properly - // in W7e stage B (make the .wasm part of the compile-freshness check). + // (cold worker, or source edited since the last compile), compile the + // unit on demand — get_shared_unit() builds the .wasm side-module — and + // recheck. Native execution has been removed, so a unit that still cannot + // be served by wasm becomes a clean 500 request failure. auto wasm_ready = [&](const String& unit) -> bool { if(!wasm_backend_should_handle(request, unit)) get_shared_unit(&request, unit, true); return(wasm_backend_should_handle(request, unit)); }; + auto fail_wasm_unavailable = [&](const String& handler) { + failure_title = "wasm unit unavailable after compile"; + failure_details = handler + " handler could not be served by wasm"; + failure_trace = "source: " + request.params["SCRIPT_FILENAME"]; + }; + if(request.params["UCE_WS"] == "1") { // A WS message the broker forwarded here: rebuild the connection @@ -529,14 +570,14 @@ int handle_complete(FastCGIRequest& request) { if(wasm_ready(entry_unit)) serve_via_wasm(entry_unit, "websocket"); else - compiler_invoke_websocket(&request, request.params["SCRIPT_FILENAME"]); + fail_wasm_unavailable("websocket"); } else if(request.resources.is_cli) { if(wasm_ready(entry_unit)) serve_via_wasm(entry_unit, "cli"); else - compiler_invoke_cli(&request, request.params["SCRIPT_FILENAME"]); + fail_wasm_unavailable("cli"); } else if(request.params["UCE_SERVE_HTTP"] == "1") { @@ -549,12 +590,12 @@ int handle_complete(FastCGIRequest& request) { serve_via_wasm(entry_unit, fn == "" ? String("serve_http") : "serve_http:" + fn); } else - compiler_invoke_serve_http(&request, request.params["SCRIPT_FILENAME"], request.params["UCE_SERVE_HTTP_FUNCTION"]); + fail_wasm_unavailable("serve_http"); } else if(wasm_ready(entry_unit)) serve_via_wasm(entry_unit, "render"); else - compiler_invoke(&request, request.params["SCRIPT_FILENAME"]); + fail_wasm_unavailable("render"); } catch(const std::exception& e) { @@ -1270,9 +1311,6 @@ void init_base_process() server_state.config["COMPILER_SYS_PATH"] = cwd_get(); printf("Compiler base path: %s\n", server_state.config["COMPILER_SYS_PATH"].c_str()); - server_state.config["COMPILE_SCRIPT"] = - server_state.config["COMPILER_SYS_PATH"] + "/" + server_state.config["COMPILE_SCRIPT"]; - if(server_state.config["FCGI_PORT"] != "") server.listen(int_val(server_state.config["FCGI_PORT"])); diff --git a/src/wasm/backend.cpp b/src/wasm/backend.cpp index 2d63df0..f9729a8 100644 --- a/src/wasm/backend.cpp +++ b/src/wasm/backend.cpp @@ -2,12 +2,8 @@ // // Included into the native server TU (src/linux_fastcgi.cpp) after uce_lib.cpp, // so it shares String/DValue/config and the UCEB1 codec. Provides a -// config-selectable page-render backend: when WASM_BACKEND_ENABLED and a wasm -// artifact exists for the entry unit, the request is served through a -// per-request wasm workspace instead of the native dlopen path. -// -// The seam is narrow on purpose — only the page render branch in -// handle_complete() changes. CLI, serve_http, and websocket stay native. +// Always-on wasm backend: every unit request is served through a per-request +// wasm workspace. The legacy native dlopen execution path has been removed. #include "../lib/wasm_trace.h" // The native server TU has the real connectors (sqlite/mysql) compiled in, so @@ -31,9 +27,7 @@ static bool g_wasm_init_attempted = false; bool wasm_backend_configured(Request* context) { - if(!context || !context->server) - return(false); - return(config_bool("WASM_BACKEND_ENABLED", false)); + return(context && context->server); } // Lazily bring up the per-process worker on first use inside a forked child @@ -90,22 +84,19 @@ static bool wasm_artifact_exists(Request* context, const String& entry_unit) struct stat wasm_st; if(stat(wasm_path.c_str(), &wasm_st) != 0 || !S_ISREG(wasm_st.st_mode)) return(false); - // The wasm path bypasses the native JIT recompile, so a source edit would - // otherwise be served from a stale .wasm. Require the artifact to be newer - // than the source; if it is stale, fall back to native — which JIT-rebuilds - // both .so and .wasm — so the next request gets a fresh artifact. + // Require the artifact to be newer than the source. If it is stale, + // dispatch compiles the unit on demand and then rechecks this predicate. struct stat src_st; - if(stat(entry_unit.c_str(), &src_st) == 0 && wasm_st.st_mtime < src_st.st_mtime) + if(stat(entry_unit.c_str(), &src_st) != 0 || !S_ISREG(src_st.st_mode)) + return(false); + if(wasm_st.st_mtime < src_st.st_mtime) return(false); return(true); } -// True if this request should be served by the wasm backend. Every unit now -// runs on wasm (W7d retired the last native-only surfaces), so this only gates -// on config, the presence of a current wasm artifact, and a healthy worker — -// the remaining native fallback exists solely to cover a cold/stale artifact -// (a unit not yet compiled, or whose source is newer than its .wasm). W7e -// removes that last fallback once cold/stale triggers an on-demand wasm compile. +// True if this request can be served by the wasm backend now. Every unit runs +// on wasm; cold/stale artifacts return false so dispatch can compile on demand +// and then recheck. bool wasm_backend_should_handle(Request& request, const String& entry_unit) { if(!wasm_backend_configured(&request)) @@ -181,9 +172,8 @@ String wasm_backend_serve(Request& request, const String& entry_unit, const Stri fcgi_forward_request(broker_socket, dispatch_params, ucb_encode(batch), 5); } - // A cli/serve_http unit that does not export the requested handler is a 404, - // matching native compiler_invoke_cli. For page render a missing handler is - // simply an empty body (native parity). + // A cli/serve_http unit that does not export the requested handler is a 404. + // For page render a missing handler is simply an empty body. if(!response.handler_present && handler != "render") { request.set_status(404, handler == "cli" ? "CLI Entry Point Not Found" : "Handler Not Found"); diff --git a/src/wasm/core.cpp b/src/wasm/core.cpp index 488b5bd..d7d99b8 100644 --- a/src/wasm/core.cpp +++ b/src/wasm/core.cpp @@ -139,32 +139,6 @@ bool unit_compile(String path) } static DValue wasm_unit_call_result; -DValue* unit_call(String file_name, String function_name, DValue* call_param) -{ - DValue request; - request["op"] = "call"; - request["file"] = file_name; - request["function"] = function_name; - if(call_param) - request["param"] = *call_param; - DValue response = wasm_units_call(request); - String output = response["output"].to_string(); - if(output != "") - print(output); - DValue* result = response.key("result"); - if(result) - { - wasm_unit_call_result = *result; - return(&wasm_unit_call_result); - } - return(0); -} - -SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path, bool opt_so_optional) -{ - (void)context; (void)file_name; (void)current_path; (void)opt_so_optional; - return(0); -} static DValue wasm_mysql_call(DValue request) { @@ -484,9 +458,9 @@ extern "C" int32_t uce_host_component_resolve( // but a single workspace can render the same component many times) static std::map wasm_component_slots; -// These mirror small page-runtime pieces of compiler.cpp, which is carved -// out of the wasm core wholesale (it is the native toolchain: parser, clang -// driver, dlopen). Kept byte-identical where possible. +// These mirror small page-runtime pieces that cannot include compiler.cpp in +// the wasm core (compiler.cpp owns parser/clang/cache bookkeeping for the host +// build). Kept byte-identical where possible. String component_normalize_path(String name) { name = trim(name); @@ -595,6 +569,62 @@ static void wasm_run_once(const String& resolved, Request& request) request.resources.current_unit_file = previous_unit; } +DValue* unit_call(String file_name, String function_name, DValue* call_param) +{ + String macro = to_upper(trim(function_name)); + String handler = ""; + if(macro == "RENDER" || macro.rfind("RENDER:", 0) == 0) + handler = "render" + (macro.length() > 7 ? ":" + trim(function_name.substr(function_name.find(":") + 1)) : String("")); + else if(macro == "COMPONENT" || macro.rfind("COMPONENT:", 0) == 0) + handler = "component" + (macro.length() > 10 ? ":" + trim(function_name.substr(function_name.find(":") + 1)) : String("")); + else if(macro == "ONCE") + handler = "once"; + else if(macro == "INIT") + handler = "init"; + + if(handler != "") + { + String resolved; + s32 slot = wasm_resolve_target(file_name, handler, &resolved); + if(!slot) + { + print("Error: unit_call() function '", function_name, "' not found"); + return(0); + } + RequestPropsScope props_scope(context, (call_param ? *call_param : DValue())); + if((handler == "render" || handler.rfind("render:", 0) == 0 || handler == "component" || handler.rfind("component:", 0) == 0) && resolved != "") + wasm_run_once(resolved, *context); + String previous_unit = context->resources.current_unit_file; + if(resolved != "") + context->resources.current_unit_file = resolved; + request_ref_handler handler_fn = (request_ref_handler)(uintptr_t)slot; + handler_fn(*context); + context->resources.current_unit_file = previous_unit; + return(0); + } + + String resolved; + s32 slot = wasm_resolve_target(file_name, "export:" + function_name, &resolved); + if(!slot) + { + print("Error: unit_call() function '", function_name, "' not found"); + return(0); + } + + String previous_unit = context->resources.current_unit_file; + if(resolved != "") + context->resources.current_unit_file = resolved; + dv_call_handler handler_fn = (dv_call_handler)(uintptr_t)slot; + DValue* result = handler_fn(call_param); + context->resources.current_unit_file = previous_unit; + if(result) + { + wasm_unit_call_result = *result; + return(&wasm_unit_call_result); + } + return(0); +} + void component_render(String name, DValue props, Request& request) { String file_name, render_name; diff --git a/src/wasm/worker.cpp b/src/wasm/worker.cpp index dfb4b92..46f564f 100644 --- a/src/wasm/worker.cpp +++ b/src/wasm/worker.cpp @@ -1046,6 +1046,9 @@ private: // __uce_[_] for a handler spec like "component:CARD" / "render". static String handler_export_symbol(const String& handler) { + String export_prefix = "export:"; + if(handler.rfind(export_prefix, 0) == 0) + return(handler.substr(export_prefix.size())); auto colon = handler.find(":"); String symbol = "__uce_" + (colon == String::npos ? handler : handler.substr(0, colon)); if(colon != String::npos) @@ -1086,6 +1089,9 @@ private: return(1); } + if(!file_exists_host(worker.unit_wasm_path(resolved)) || compiler_unit_needs_recompile(context, resolved, 0)) + get_shared_unit(context, resolved, true); + size_t unit_index = 0; String error = load_unit(resolved, unit_index); if(error != "") @@ -1791,7 +1797,7 @@ private: return(std::monostate()); })); if(mod == "env" && name == "uce_host_component_resolve") - return(add([self](Caller, Span args, Span results) -> Result { + return(add([self](Caller caller, Span args, Span results) -> Result { String target, handler, current, resolved; self->hostcall_read(args[0].i32(), args[1].i32(), target); self->hostcall_read(args[2].i32(), args[3].i32(), handler); @@ -1806,6 +1812,7 @@ private: self->hostcall_write(args[6].i32(), resolved); } results[0] = Val(slot); + caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); return(std::monostate()); }));