W7f: sweep dead/legacy/fallback leftovers after native-pipeline removal

Post-deletion cleanup (units run only on wasm):
- types.h/compiler.cpp: drop the native-era SharedUnit fields so_name,
  bin_file_name, and the opt_so_optional cache-mode plumbing (no native
  optional .so path remains). The per-unit compile lock is re-keyed from
  so_name+.lock to wasm_name+.lock (still per-unit).
- unit_info() and to_string(SharedUnit*) no longer expose .so artifact fields.
- backend.h: drop the stale "+ fallback-token gate" comment.
- Docs/comments corrected to wasm-only reality: README, tests/README,
  site/doc C++ preprocessor + error_pages + unit_info pages, site/info intro,
  site/demo/unit-browser artifact card; the Phase-5 native-vs-wasm benchmark
  harness (tests/wasm_benchmark.py) reframed for the wasm-only backend.

Audit confirmed no live references remain to so_handle, load_shared_unit,
compiler_load_shared_unit, compiler_invoke*/_cli/_websocket/_serve_http,
COMPILE_SCRIPT/COMPILE_WASM_UNITS, or the native export-symbol constants;
request_ref_handler/dv_call_handler are kept (live wasm funcref casts).

Swept via the pi agent (delegated to a gpt-5.3-codex-spark sub-model);
independently re-verified on the host: run_cli_tests --include-wasm-kill =>
87 passed, 0 failed, 0 skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root 2026-06-14 23:43:16 +00:00
parent cf51336873
commit bfd6d33829
21 changed files with 80 additions and 154 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -157,16 +157,16 @@ RENDER(Request& context)
</div>
<div class="detail-card">
<strong>Runtime Flags</strong>
<span>loaded <?= unit_flag_label(selected_info["loaded"]) ?>, stale <?= unit_flag_label(selected_info["stale"]) ?>, current <?= unit_flag_label(selected_info["current_unit"]) ?></span>
<span>wasm <?= unit_flag_label(selected_info["wasm_available"]) ?>, stale <?= unit_flag_label(selected_info["stale"]) ?>, current <?= unit_flag_label(selected_info["current_unit"]) ?></span>
</div>
<div class="detail-card">
<strong>Artifacts</strong>
<code><?= selected_info["so_name"].to_string() ?></code>
<code><?= selected_info["wasm_name"].to_string() ?></code>
</div>
<div class="detail-card">
<strong>Timestamps</strong>
<span>Source file: <?= time_format_relative(selected_info["source_mtime"].to_u64()) ?><br>
Binary: <?= time_format_relative(selected_info["compiled_mtime"].to_u64()) ?><br>
Wasm: <?= time_format_relative(selected_info["compiled_mtime"].to_u64()) ?><br>
Meta info: <?= time_format_relative(selected_info["metadata_mtime"].to_u64()) ?></span>
</div>
</div>

View File

@ -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.

View File

@ -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 `<h
- `EXPORT` harvesting only triggers when the current line starts with `EXPORT` at column 1 and is followed by whitespace.
- Relative `#load` paths are expanded against the including unit's source directory.
- `unit_render()` and `unit_call()` are runtime APIs. `#load` is a compile-time composition feature.
- `INIT()` runs when the shared object is loaded into a worker during a request-triggered load, so it still receives a valid `Request& context`.
- `INIT()` runs when the wasm unit is instantiated by a worker during a request-triggered load, so it still receives a valid `Request& context`.
- `ONCE()` is tracked per request and per resolved unit file. A file entered multiple times in one request only runs `ONCE()` once.
## Limitations

View File

@ -42,7 +42,7 @@ RENDER(Request& context)
}
```
The compiling page is only used while `PROACTIVE_COMPILE_ENABLED` is on (the default) — otherwise nothing would finish the build, and the runtime falls back to the blocking compile.
The compiling page is only used while `PROACTIVE_COMPILE_ENABLED` is on (the default) — otherwise nothing would finish the build asynchronously, and the runtime waits for the on-request wasm compile.
## page_compiler_error

View File

@ -23,7 +23,7 @@ The returned tree includes:
- request and invocation counters
- best, worst, last, and average render time
- compile counters plus best, worst, last, and average compile time
- file mtimes, stale status, ABI compatibility status, load status, and exported API declarations
- file mtimes, stale status, ABI compatibility status, wasm availability, and exported API declarations
If `path` is relative, it is resolved relative to the current executing unit.

View File

@ -166,7 +166,7 @@ RENDER(Request& context)
<h1>Make dynamic websites like it's 2006.</h1>
<p class="hero-lead">
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.
</p>
@ -177,7 +177,7 @@ RENDER(Request& context)
</div>
<div class="signal-grid">
<? render_signal("Files are routes", "A .uce page is the thing you edit and the thing nginx serves."); ?>
<? render_signal("Compile on demand", "First request builds a shared object, later requests reuse it."); ?>
<? render_signal("Compile on demand", "First request builds a wasm module, later requests reuse it."); ?>
<? render_signal("WebSockets included", "Same runtime, same page path, broker-managed connection state."); ?>
</div>
</div>
@ -314,7 +314,7 @@ RENDER(Request& context)
</details>
<details>
<summary>What makes it different from old PHP?</summary>
<p>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.</p>
<p>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.</p>
</details>
</section>
</main>

View File

@ -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");
}

View File

@ -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));
}

View File

@ -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);

View File

@ -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);

View File

@ -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();
};

View File

@ -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

View File

@ -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 —

View File

@ -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;
}

View File

@ -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);

View File

@ -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.

View File

@ -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'}")