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:
parent
cf51336873
commit
bfd6d33829
@ -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.
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 —
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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'}")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user