diff --git a/README.md b/README.md index f149711..6e1ea6b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This is in the early stages of development. Don't use this for anything importan UCE is a PHP-inspired server-side runtime that lets you build web pages and handlers in C++ using a small `.uce` preprocessor plus a FastCGI application server. -- `.uce` pages compile to shared objects on demand +- `.uce` pages compile to WebAssembly side modules on demand - normal HTTP pages expose `RENDER(Request& context)` - WebSocket pages can additionally expose `WS(Request& context)` - local CLI/admin/test entrypoints can expose `CLI(Request& context)` and are invoked through the Unix CLI socket @@ -103,7 +103,7 @@ Those are intended for sub-rendering through helpers such as `component("compone Additional lifecycle hooks are also available on ordinary `.uce` units: -- `INIT(Request& context)` runs once when a worker loads that unit's shared object into memory +- `INIT(Request& context)` runs once when a worker instantiates that unit's wasm module - `ONCE(Request& context)` runs once per request before the first `RENDER()`, `CLI()`, or `COMPONENT...` entrypoint from that file CLI units can be invoked locally with the convenience wrapper or directly over HTTP-over-Unix: @@ -330,7 +330,7 @@ Proactive compilation settings: - `SITE_DIRECTORY=site` tells the runtime which tree to scan on startup for `.uce` files when `PRECOMPILE_FILES_IN` is left empty. - `PRECOMPILE_FILES_IN=` can override that startup scan root with a different absolute or runtime-relative directory. -- `PROACTIVE_COMPILE_CHECK_INTERVAL=60` controls how often the low-priority background compiler rechecks known `.uce` files for stale or missing shared objects. +- `PROACTIVE_COMPILE_CHECK_INTERVAL=60` controls how often the low-priority background compiler rechecks known `.uce` files for stale or missing wasm modules. The runtime keeps a shared known-file registry under `BIN_DIRECTORY` and updates it as request handling discovers new `.uce` files, so proactive recompiles are not limited to the initial startup scan. diff --git a/scripts/compile b/scripts/compile deleted file mode 100755 index 4000ce6..0000000 --- a/scripts/compile +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -cd "$(dirname "$0")" -cd .. - -SRC_DIR="$1" -DEST_DIR="$2" - -SRC_FN="$3" -PP_FN="$4" -SO_FN="$5" - -LINK_OBJECTS="$6" - -#echo "Source Dir: $SRC_DIR" -#echo "Dest Dir: $DEST_DIR" -#echo "Source File: $SRC_FN" -#echo "Preprocessed File: $PP_FN" -#echo "Dest File: $SO_FN" - -mkdir -p "$DEST_DIR" > /dev/null 2>&1 - -export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH:+${CPLUS_INCLUDE_PATH}:}$SRC_DIR" - -BUILDMODE="debug" -OPT_FLAG="O0" - -COMPILER="clang++" -#COMPILER="g++" -FLAGS="-shared -g -rdynamic -w -Wall -$OPT_FLAG -std=c++20 -fpermissive -ffast-math -fPIC -Isrc/lib" - -LIBS="-ldl -lm -lpthread" -SRCFLAGS="-D PLATFORM_NAME=\"linux\"" - -# echo "Compliling executable..." -$COMPILER "$DEST_DIR/$PP_FN" $SRCFLAGS $FLAGS $LIBS -o "$DEST_DIR/$SO_FN" - -# separate .o file -#$COMPILER -c "$DEST_DIR/$PP_FN" $SRCFLAGS $FLAGS $LIBS -o "$DEST_DIR/$PP_FN.o" -#$COMPILER "$DEST_DIR/$PP_FN.o" "$LINK_OBJECTS" $SRCFLAGS $FLAGS $LIBS -o "$DEST_DIR/$SO_FN" - -if [ $? -eq 0 ] -then - # ls -lh "$DEST_DIR" - exit 0 -else - exit 1 -fi diff --git a/scripts/compile_wasm_unit b/scripts/compile_wasm_unit index 007bd00..6d6a783 100755 --- a/scripts/compile_wasm_unit +++ b/scripts/compile_wasm_unit @@ -21,9 +21,9 @@ COMMON_FLAGS=( --target=wasm32-wasip1 -fPIC -fvisibility=default -fvisibility-inlines-hidden -O1 -g -std=c++20 - # -w as in scripts/compile: warnings are not failures. The server captures - # this script's output and treats any non-empty result as a compile failure - # (then drops the .wasm), so a successful build must be silent. + # The server captures this script's output and treats any non-empty result as + # a compile failure (then drops the .wasm), so a successful build must be + # silent; warnings are intentionally suppressed. -w # must match the core build ABI: units with RTTI/EH enabled import # typeinfo/unwind symbols the -fno-rtti/-fno-exceptions core cannot provide diff --git a/scripts/wasm/build_w2_units.sh b/scripts/wasm/build_w2_units.sh index 20a3955..2117195 100755 --- a/scripts/wasm/build_w2_units.sh +++ b/scripts/wasm/build_w2_units.sh @@ -20,21 +20,12 @@ fi count=0 checked=0 skipped=0 -# Native-only units that cannot be wasm side modules (yet). -# - error-reporting.uce deliberately throws to exercise the native exception -# path; the wasm backend replaces that machinery with traps (§11.1). -# - tests/zip.uce uses try/catch around the zip library, which is carved out -# of the wasm core until it moves behind a hostcall (W4+ membrane work). -SKIP_PATTERN=${UCE_W2_SKIP:-(error-reporting|tests/zip)\.uce$} for unit in "${UNITS[@]}"; do case "$unit" in *.uce|*.ws.uce) ;; *) continue ;; esac - if [[ "$unit" =~ $SKIP_PATTERN ]]; then - continue - fi src_dir=$(dirname "$unit") base=$(basename "$unit") dest_dir="$BIN_DIR$src_dir" diff --git a/site/demo/unit-browser.uce b/site/demo/unit-browser.uce index 9641ee4..243c0ac 100644 --- a/site/demo/unit-browser.uce +++ b/site/demo/unit-browser.uce @@ -157,16 +157,16 @@ RENDER(Request& context)
Runtime Flags - loaded , stale , current + wasm , stale , current
Artifacts - +
Timestamps Source file:
- Binary:
+ Wasm:
Meta info:
diff --git a/site/doc/pages/1_INIT.txt b/site/doc/pages/1_INIT.txt index 89f21a5..568f76d 100644 --- a/site/doc/pages/1_INIT.txt +++ b/site/doc/pages/1_INIT.txt @@ -15,7 +15,7 @@ INIT(Request& context) :content Defines a worker-load hook for the current `.uce` unit. -When a worker loads the unit's compiled shared object into memory, the runtime checks whether the unit exposes `INIT(Request& context)`. If it does, the hook runs once for that load before the unit begins serving later requests from that in-memory copy. +When a worker instantiates the unit's compiled wasm module, the runtime checks whether the unit exposes `INIT(Request& context)`. If it does, the hook runs once for that instance before the unit begins serving requests from that worker-local copy. Because UCE usually loads units on demand during a request, `INIT()` still receives a valid `Request& context`. Use it for worker-local initialization, not for request-local state that should reset each request. diff --git a/site/doc/pages/3_C++ Preprocessor.txt b/site/doc/pages/3_C++ Preprocessor.txt index bb0d8e9..746e2ff 100644 --- a/site/doc/pages/3_C++ Preprocessor.txt +++ b/site/doc/pages/3_C++ Preprocessor.txt @@ -14,7 +14,7 @@ unit_call :content UCE runs a small custom source-to-source preprocessor before Clang sees a `.uce` or `.ws.uce` file. -The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, with orchestration in `src/lib/compiler.cpp`. It does not try to parse all of C++. Instead, it performs a narrow character-wise rewrite that understands literal output, inline code islands, `#load`, and `EXPORT` harvesting, then writes a generated `.cpp` file and compiles that file into a shared object. +The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, with orchestration in `src/lib/compiler.cpp`. It does not try to parse all of C++. Instead, it performs a narrow character-wise rewrite that understands literal output, inline code islands, `#load`, and `EXPORT` harvesting, then writes a generated `.cpp` file and compiles that file into a WebAssembly side module. ## Syntax @@ -32,7 +32,7 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi ## Pipeline -- The generated file starts by including the logical runtime header `uce_lib.h`; native and WASM compile scripts provide the include path. +- The generated file starts by including the logical runtime header `uce_lib.h`; the wasm unit compile script provides the include path. - It then inlines the configured setup template from `SETUP_TEMPLATE` (by default `scripts/setup.h.template`), which defines the internal hook `__uce_set_current_request(Request*)`. - It inserts `#line 1` before page code so compiler diagnostics point back to the original `.uce` file. - Each literal region is rewritten into one or more `print(R"...( ... )...");` calls using a safe raw-string delimiter selected for that literal content. @@ -47,8 +47,8 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi - Lines beginning with `RENDER:NAME(...)` are rewritten into exported `__uce_render_NAME(...)` functions. - Lines beginning with `COMPONENT:NAME(...)` are rewritten into exported `__uce_component_NAME(...)` functions for the component helpers. - The final generated source is written to `BIN_DIRECTORY + src_path + "/" + source_file + ".cpp"`. -- `scripts/compile` then compiles that generated `.cpp` into `source_file + ".so"` with `clang++ -shared -std=c++20 ...`. -- When a worker loads the compiled unit into memory, the runtime checks for `INIT(Request& context)` and calls it once for that worker-side load. +- `scripts/compile_wasm_unit` then compiles that generated `.cpp` into `source_file + ".wasm"` as a PIC WebAssembly side module. +- When a worker instantiates the compiled unit, the runtime checks for `INIT(Request& context)` and calls it once for that worker-side instance. - On each request, the first time a given unit is entered through `RENDER()`, `CLI()`, or any `COMPONENT...` handler, the runtime checks for `ONCE(Request& context)` and calls it before the selected handler. ## Generated Files @@ -56,7 +56,7 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi For a source file like `/some/path/page.uce`, the preprocessor produces: - generated C++: `BIN_DIRECTORY/some/path/page.uce.cpp` -- shared object: `BIN_DIRECTORY/some/path/page.uce.so` +- wasm side module: `BIN_DIRECTORY/some/path/page.uce.wasm` - export list: `BIN_DIRECTORY/some/path/page.uce.exports.txt` ## Examples @@ -153,7 +153,7 @@ The page template can then render `context.call["fragments"]["head"]` inside `Make dynamic websites like it's 2006.

UCE is a deliberately direct web runtime: files are pages, templates live next to control flow, - nginx fronts the whole thing, and a request can compile into a shared object on first hit. + nginx fronts the whole thing, and a request can compile into a wasm module on first hit. It is built in the image of PHP's non-architecture, but with an explicit request object, component rendering, and a built-in WebSocket broker.

@@ -177,7 +177,7 @@ RENDER(Request& context)
- +
@@ -314,7 +314,7 @@ RENDER(Request& context)
What makes it different from old PHP? -

The request object is explicit, components and named handlers are native, markdown support is built in, and WebSockets use a broker-owned connection model inside the runtime.

+

The request object is explicit, components and named handlers are first-class, markdown support is built in, and WebSockets use a broker-owned connection model inside the runtime.

diff --git a/src/lib/compiler-parser.cpp b/src/lib/compiler-parser.cpp index e80650f..8949f0c 100644 --- a/src/lib/compiler-parser.cpp +++ b/src/lib/compiler-parser.cpp @@ -567,7 +567,7 @@ String compiler_preprocess_shared_unit_char_wise(Request* context, SharedUnit* s 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)); + SharedUnit* sub_su = (resolved_unit == "" ? 0 : get_shared_unit(context, resolved_unit)); 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 426dd29..0974dcd 100644 --- a/src/lib/compiler.cpp +++ b/src/lib/compiler.cpp @@ -386,34 +386,29 @@ SharedUnit* compiler_cached_unit(Request* context, String file_name) return(it->second); } -bool compiler_cache_mode_matches(SharedUnit* su, bool opt_so_optional) -{ - return(su && su->opt_so_optional == opt_so_optional); -} - -bool compiler_cached_unit_is_reusable(Request* context, SharedUnit* su, bool opt_so_optional, bool force_recompile) +bool compiler_cached_unit_is_reusable(Request* context, SharedUnit* su, bool force_recompile) { return( !force_recompile && - compiler_cache_mode_matches(su, opt_so_optional) && + su && !shared_unit_cache_is_stale(context, su) ); } -SharedUnit* compiler_reusable_cached_unit(Request* context, String file_name, bool opt_so_optional, bool force_recompile) +SharedUnit* compiler_reusable_cached_unit(Request* context, String file_name, bool force_recompile) { auto su = compiler_cached_unit(context, file_name); - if(compiler_cached_unit_is_reusable(context, su, opt_so_optional, force_recompile)) + if(compiler_cached_unit_is_reusable(context, su, force_recompile)) return(su); return(0); } -void compiler_release_cached_unit_if_needed(Request* context, String file_name, bool opt_so_optional, bool force_recompile) +void compiler_release_cached_unit_if_needed(Request* context, String file_name, bool force_recompile) { auto su = compiler_cached_unit(context, file_name); if(!su) return; - if(force_recompile || !compiler_cache_mode_matches(su, opt_so_optional) || shared_unit_cache_is_stale(context, su)) + if(force_recompile || shared_unit_cache_is_stale(context, su)) release_shared_unit_cache_entry(context, file_name); } @@ -632,11 +627,9 @@ void setup_unit_paths(Request* context, SharedUnit* su, String file_name) su->pre_path = context->server->config["BIN_DIRECTORY"] + su->src_path; su->src_file_name = basename(file_name); - su->bin_file_name = su->src_file_name + ".so"; su->wasm_file_name = su->src_file_name + ".wasm"; su->pre_file_name = su->src_file_name + ".cpp"; - su->so_name = su->bin_path + "/" + su->bin_file_name; su->wasm_name = su->bin_path + "/" + su->wasm_file_name; su->wasm_check_file_name = su->bin_path + "/" + su->src_file_name + ".wasm-check.txt"; su->api_file_name = su->bin_path + "/" + su->src_file_name + ".exports.txt"; @@ -779,21 +772,20 @@ void compile_shared_unit(Request* context, SharedUnit* su) } } -SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name, bool opt_so_optional, bool force_recompile) +SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name, bool force_recompile) { file_name = compiler_normalize_unit_path(context, file_name); - auto cached = compiler_reusable_cached_unit(context, file_name, opt_so_optional, force_recompile); + auto cached = compiler_reusable_cached_unit(context, file_name, force_recompile); if(cached) return(cached); - compiler_release_cached_unit_if_needed(context, file_name, opt_so_optional, force_recompile); + compiler_release_cached_unit_if_needed(context, file_name, force_recompile); SharedUnit* su = new SharedUnit(); setup_unit_paths(context, su, file_name); - su->opt_so_optional = opt_so_optional; - int fdlock = compiler_open_lock_file(su->so_name + ".lock", "shared-unit:" + file_name); + int fdlock = compiler_open_lock_file(su->wasm_name + ".lock", "shared-unit:" + file_name); if(fdlock == -1) { su->compiler_messages = "could not open compile lock"; @@ -804,7 +796,7 @@ SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name return(su); } - cached = compiler_reusable_cached_unit(context, file_name, opt_so_optional, force_recompile); + cached = compiler_reusable_cached_unit(context, file_name, force_recompile); if(cached) { compiler_close_lock_file(fdlock); @@ -812,7 +804,7 @@ SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name return(cached); } - compiler_release_cached_unit_if_needed(context, file_name, opt_so_optional, force_recompile); + compiler_release_cached_unit_if_needed(context, file_name, force_recompile); auto state = inspect_shared_unit_filesystem(context, su); auto compile_check = shared_unit_compile_check(state); @@ -852,9 +844,9 @@ SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name return(su); } -SharedUnit* get_shared_unit(Request* context, String file_name, bool opt_so_optional) +SharedUnit* get_shared_unit(Request* context, String file_name) { - return(compiler_get_shared_unit_internal(context, file_name, opt_so_optional, false)); + return(compiler_get_shared_unit_internal(context, file_name, false)); } String compiler_error_page_unit(Request* context, String config_key) @@ -880,7 +872,7 @@ 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)) + if(compiler_reusable_cached_unit(context, normalized, false)) return(false); SharedUnit probe; @@ -972,7 +964,7 @@ String component(String name, DValue props, Request& context) SharedUnit* unit_load(String file_name) { String resolved = compiler_resolve_unit_path(context, file_name); - return(resolved == "" ? 0 : get_shared_unit(context, resolved, true)); + return(resolved == "" ? 0 : get_shared_unit(context, resolved)); } DValue* unit_call(String file_name, String function_name, DValue* call_param) @@ -1120,9 +1112,8 @@ DValue unit_info(String path) info["bin_path"] = su->bin_path; info["pre_path"] = su->pre_path; info["src_file_name"] = su->src_file_name; - info["bin_file_name"] = su->bin_file_name; info["pre_file_name"] = su->pre_file_name; - info["so_name"] = su->so_name; + info["wasm_file_name"] = su->wasm_file_name; info["wasm_name"] = su->wasm_name; info["wasm_exists"].set_bool(file_exists(su->wasm_name)); info["api_file_name"] = su->api_file_name; @@ -1134,7 +1125,6 @@ DValue unit_info(String path) info["error_status"] = compiler_error_status(su); info["compiler_messages"] = su->compiler_messages; info["last_compiled"] = (f64)su->last_compiled; - info["last_loaded"] = (f64)su->last_loaded; info["last_rendered"] = (f64)su->last_rendered; info["last_error"] = (f64)su->last_error; info["request_count"] = (f64)su->request_count; @@ -1164,7 +1154,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", file_exists(su->wasm_name)); + compiler_tree_set_bool(info, "wasm_available", 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); @@ -1202,6 +1192,6 @@ bool unit_compile(String 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); + auto su = compiler_get_shared_unit_internal(context, resolved_path, true); return(su && trim(su->compiler_messages) == "" && file_exists(su->wasm_name)); } diff --git a/src/lib/compiler.h b/src/lib/compiler.h index b72dbcc..16c6972 100644 --- a/src/lib/compiler.h +++ b/src/lib/compiler.h @@ -14,7 +14,7 @@ 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 compile_shared_unit(Request* context, SharedUnit* su); -SharedUnit* get_shared_unit(Request* context, String file_name, bool opt_so_optional = false); +SharedUnit* get_shared_unit(Request* context, String file_name); String compiler_error_page_unit(Request* context, String config_key); bool compiler_unit_compile_pending(Request* context, String file_name); String compiler_site_directory(Request* context); diff --git a/src/lib/functionlib.h b/src/lib/functionlib.h index a05a732..629a6f2 100644 --- a/src/lib/functionlib.h +++ b/src/lib/functionlib.h @@ -48,7 +48,7 @@ inline String to_string(SharedUnit* u) { result += String("SharedUnit( \n")+ "Source:"+(u->file_name)+"\n"+ - "SharedObject:"+(u->so_name)+"\n"+ + "Wasm:"+(u->wasm_name)+"\n"+ "API:"+(u->api_file_name)+"\n"+ to_string(u->api_declarations); diff --git a/src/lib/types.h b/src/lib/types.h index 6a03405..23e4a25 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -57,16 +57,14 @@ typedef std::ostringstream ByteStream; struct Request; struct DValue; -typedef void (*request_ref_handler)(Request& request); -typedef DValue* (*dv_call_handler)(DValue* call_param); -typedef void (*request_handler)(Request* request); +typedef void (*WasmRequestHandler)(Request& request); +typedef DValue* (*WasmDValueCallHandler)(DValue* call_param); inline String to_string(s64 v) { return(std::to_string(v)); } struct SharedUnit { String file_name; - String so_name; String wasm_name; String wasm_check_file_name; String api_file_name; @@ -79,7 +77,6 @@ struct SharedUnit { String bin_path; String pre_path; String src_file_name; - String bin_file_name; String wasm_file_name; String pre_file_name; @@ -89,7 +86,6 @@ struct SharedUnit { String runtime_error_status = ""; time_t last_compiled = 0; time_t observed_compiled_time = 0; - time_t last_loaded = 0; time_t last_rendered = 0; time_t last_error = 0; String observed_metadata_content = ""; @@ -112,8 +108,6 @@ struct SharedUnit { f64 best_render_duration = 0; f64 worst_render_duration = 0; - bool opt_so_optional = false; - ~SharedUnit(); }; diff --git a/src/linux_fastcgi.cpp b/src/linux_fastcgi.cpp index cf14321..a5309d6 100644 --- a/src/linux_fastcgi.cpp +++ b/src/linux_fastcgi.cpp @@ -86,7 +86,7 @@ bool render_wasm_error_page(Request& request, String config_key, s32 status_code String unit = compiler_normalize_unit_path(&request, unit_file); if(!wasm_backend_should_handle(request, unit)) - get_shared_unit(&request, unit, true); + get_shared_unit(&request, unit); ob_start(); String wasm_error = ""; @@ -417,7 +417,7 @@ int handle_cli_complete(FastCGIRequest& request) // 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); + get_shared_unit(&request, cli_unit); if(wasm_backend_should_handle(request, cli_unit)) { String wasm_error = wasm_backend_serve(request, cli_unit, "cli"); @@ -537,7 +537,7 @@ int handle_complete(FastCGIRequest& request) { // 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); + get_shared_unit(&request, unit); return(wasm_backend_should_handle(request, unit)); }; auto fail_wasm_unavailable = [&](const String& handler) { @@ -1218,7 +1218,7 @@ void run_proactive_compiler() if(compiler_unit_needs_recompile(&background_context, file_name, &source_missing)) { printf("(i) proactive compile %s\n", file_name.c_str()); - auto su = get_shared_unit(&background_context, file_name, false); + auto su = get_shared_unit(&background_context, file_name); if(su && su->compiler_messages == "") retry_after.erase(file_name); else diff --git a/src/wasm/backend.h b/src/wasm/backend.h index 0c9e516..0f1e440 100644 --- a/src/wasm/backend.h +++ b/src/wasm/backend.h @@ -6,8 +6,8 @@ // backend.cpp — so it does not have to compile worker.cpp + wasmtime.hh on // every build. Include only after uce_lib.h (needs Request / String). -// True if this request should be served by the wasm backend (config + artifact -// + fallback-token gate); handler-agnostic, the caller names the handler. +// True if this request can be served by the wasm backend for the named unit +// artifact; handler-agnostic, the caller names the handler. bool wasm_backend_should_handle(Request& request, const String& entry_unit); // Serve a request through a wasm workspace by invoking a named unit handler — diff --git a/src/wasm/core.cpp b/src/wasm/core.cpp index d7d99b8..adcc117 100644 --- a/src/wasm/core.cpp +++ b/src/wasm/core.cpp @@ -564,7 +564,7 @@ static void wasm_run_once(const String& resolved, Request& request) return; String previous_unit = request.resources.current_unit_file; request.resources.current_unit_file = resolved; - request_ref_handler once_handler = (request_ref_handler)(uintptr_t)once_slot; + WasmRequestHandler once_handler = (WasmRequestHandler)(uintptr_t)once_slot; once_handler(request); request.resources.current_unit_file = previous_unit; } @@ -597,7 +597,7 @@ DValue* unit_call(String file_name, String function_name, DValue* call_param) 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; + WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; handler_fn(*context); context->resources.current_unit_file = previous_unit; return(0); @@ -614,7 +614,7 @@ DValue* unit_call(String file_name, String function_name, DValue* call_param) 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; + WasmDValueCallHandler handler_fn = (WasmDValueCallHandler)(uintptr_t)slot; DValue* result = handler_fn(call_param); context->resources.current_unit_file = previous_unit; if(result) @@ -644,7 +644,7 @@ void component_render(String name, DValue props, Request& request) request.resources.current_unit_file = resolved; // a wasm function pointer is its index in the shared funcref table; the // host returned the handler's slot, so this is a plain call_indirect - request_ref_handler handler_fn = (request_ref_handler)(uintptr_t)slot; + WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; handler_fn(request); request.resources.current_unit_file = previous_unit; } @@ -680,7 +680,7 @@ void unit_render(String file_name, Request& request) String previous_unit = request.resources.current_unit_file; if(resolved != "") request.resources.current_unit_file = resolved; - request_ref_handler handler_fn = (request_ref_handler)(uintptr_t)slot; + WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; handler_fn(request); request.resources.current_unit_file = previous_unit; } @@ -706,7 +706,7 @@ void uce_wasm_invoke_entry(const char* path, size_t path_len, const char* handle 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; + WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; handler_fn(*context); context->resources.current_unit_file = previous_unit; } diff --git a/src/wasm/worker.cpp b/src/wasm/worker.cpp index 46f564f..35ec87e 100644 --- a/src/wasm/worker.cpp +++ b/src/wasm/worker.cpp @@ -1090,7 +1090,7 @@ private: } if(!file_exists_host(worker.unit_wasm_path(resolved)) || compiler_unit_needs_recompile(context, resolved, 0)) - get_shared_unit(context, resolved, true); + get_shared_unit(context, resolved); size_t unit_index = 0; String error = load_unit(resolved, unit_index); diff --git a/tests/README.md b/tests/README.md index ed1564d..64238e9 100644 --- a/tests/README.md +++ b/tests/README.md @@ -10,7 +10,7 @@ scripts/run_cli_tests.sh --include-wasm-kill scripts/run_cli_tests.sh --list ``` -`--include-wasm-kill` adds the wasm trap/loop/recurse kill cases and should be used when `WASM_BACKEND_ENABLED=1`. +`--include-wasm-kill` adds the wasm trap/loop/recurse kill cases and should be used for the normal wasm-only runtime gate. The script reads `CLI_SOCKET_PATH` from `/etc/uce/settings.cfg` unless `UCE_CLI_SOCKET` is set. diff --git a/tests/wasm_benchmark.py b/tests/wasm_benchmark.py index d2fbe3a..93b5128 100755 --- a/tests/wasm_benchmark.py +++ b/tests/wasm_benchmark.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -"""Phase 5 native/WASM benchmark harness. +"""Current wasm-runtime HTTP benchmark harness. -This runner intentionally has no third-party dependencies. It records a warmed -native HTTP baseline now and can compare against a future wasm worker by passing ---wasm-base-url. Results are written as JSON plus a small Markdown table. +This runner intentionally has no third-party dependencies. It measures one or +more HTTP endpoints served by the wasm-only UCE runtime. Optionally pass a prior +benchmark JSON as a baseline; old native baselines are treated as historical +reference data only, not as a runnable backend mode. """ from __future__ import annotations @@ -95,14 +96,14 @@ def measure_backend(backend: str, base_url: str, host_header: str, targets: list return results -def compare(results: list[Measurement]) -> list[str]: - native = {r.target: r for r in results if r.backend == "native"} +def compare(results: list[Measurement], baseline_label: str) -> list[str]: + baseline = {r.target: r for r in results if r.backend == baseline_label} lines = ["| target | backend | median ms | mean ms | budget | status |", "|---|---:|---:|---:|---|---|"] for result in results: - budget = "baseline" + budget = "baseline" if result.backend == baseline_label else "n/a" status = "PASS" if result.ok else "FAIL" - if result.backend != "native" and result.target in native: - limit = native[result.target].median_ms * 2.0 + if result.backend != baseline_label and result.target in baseline: + limit = baseline[result.target].median_ms * 2.0 budget = f"≤ {limit:.1f} ms" if result.median_ms > limit: status = "FAIL" @@ -119,33 +120,30 @@ def build_targets() -> list[Target]: def main() -> int: - parser = argparse.ArgumentParser(description="Phase 5 native/wasm benchmark harness") - parser.add_argument("--native-base-url", default="http://localhost:80") - parser.add_argument("--wasm-base-url", default="", help="optional wasm worker URL; omitted until worker exists") - parser.add_argument("--backend-label", default="native", help="label for --native-base-url measurements when running one backend at a time") - parser.add_argument("--compare-native-json", default="", help="optional prior native benchmark.json for budget comparison") + parser = argparse.ArgumentParser(description="UCE wasm-runtime HTTP benchmark harness") + parser.add_argument("--base-url", "--native-base-url", dest="base_url", default="http://localhost:80", help="runtime base URL (legacy alias --native-base-url is accepted)") + parser.add_argument("--backend-label", default="wasm", help="label for this run's measurements") + parser.add_argument("--compare-baseline-json", "--compare-native-json", dest="compare_baseline_json", default="", help="optional prior benchmark.json for budget comparison") + parser.add_argument("--baseline-label", default="native", help="backend label in the prior JSON to use as the budget baseline") parser.add_argument("--host-header", default="uce.openfu.com") parser.add_argument("--warmups", type=int, default=2) parser.add_argument("--samples", type=int, default=20) parser.add_argument("--timeout", type=float, default=10.0) - parser.add_argument("--out-dir", default="/tmp/uce/wasm-phase5") + parser.add_argument("--out-dir", default="/tmp/uce/wasm-benchmark") args = parser.parse_args() targets = build_targets() results: list[Measurement] = [] - if args.compare_native_json: - for row in json.loads(Path(args.compare_native_json).read_text()): + if args.compare_baseline_json: + for row in json.loads(Path(args.compare_baseline_json).read_text()): + row.setdefault("backend", args.baseline_label) results.append(Measurement(**row)) - results.extend(measure_backend(args.backend_label, args.native_base_url, args.host_header, targets, args.warmups, args.samples, args.timeout)) - if args.wasm_base_url: - results.extend(measure_backend("wasm", args.wasm_base_url, args.host_header, targets, args.warmups, args.samples, args.timeout)) + results.extend(measure_backend(args.backend_label, args.base_url, args.host_header, targets, args.warmups, args.samples, args.timeout)) out_dir = Path(args.out_dir) out_dir.mkdir(parents=True, exist_ok=True) (out_dir / "benchmark.json").write_text(json.dumps([asdict(r) for r in results], indent=2) + "\n") - md_lines = ["# Phase 5 benchmark report", "", *compare(results), ""] - if not args.wasm_base_url and args.backend_label == "native" and not args.compare_native_json: - md_lines.append("WASM worker URL was not provided; this report is the native baseline that future wasm runs compare against.") + md_lines = ["# UCE wasm benchmark report", "", *compare(results, args.baseline_label), ""] (out_dir / "benchmark.md").write_text("\n".join(md_lines) + "\n") print("\n".join(md_lines)) print(f"wrote {out_dir / 'benchmark.json'} and {out_dir / 'benchmark.md'}")