Trim compiled unit export surface

This commit is contained in:
udo 2026-07-18 10:11:06 +00:00
parent 69e8051fd3
commit eda124b15f
6 changed files with 107 additions and 11 deletions

View File

@ -131,6 +131,15 @@ __uce_<base>[_<sanitize(suffix)>]
| `exists` | probe only — resolves the unit, loads nothing |
`sanitize_symbol_suffix()` keeps `[A-Za-z0-9_]` (mirrors `ascii_safe_name`).
Unit code is hidden by default so the linker can discard loaded helpers that the
unit does not use. Handler macros and the explicit `EXPORT` directive are the
only application symbols promoted to default visibility; generated request
binding and constructor exports remain part of the runtime ABI. Debug sections
are removed after linking while the wasm name section is retained for runtime
traces. This keeps each side module's code and public ABI local to its actual
handler responsibility instead of duplicating every helper from a loaded app
library.
`wasm_resolve_target(unit, handler)` (`src/wasm/core.cpp`) resolves the source
path and looks up the export's funcref slot; `exists` lets callers probe a unit
without instantiating it.

View File

@ -20,7 +20,7 @@ PCH_ENABLED=${UCE_WASM_UNIT_PCH:-1}
PCH_DIR=${UCE_WASM_PCH_DIR:-/tmp/uce/wasm-w2/pch}
COMMON_FLAGS=(
--target=wasm32-wasip1
-fPIC -fvisibility=default -fvisibility-inlines-hidden
-fPIC -fvisibility=hidden -fvisibility-inlines-hidden
-O1 -g -std=c++20
# 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
@ -80,6 +80,7 @@ fi
-c "$DEST_DIR/$PP_FN" -o "$OBJ_FN"
"$SDK/bin/wasm-ld" -shared --experimental-pic \
--gc-sections \
--unresolved-symbols=import-dynamic \
--Bsymbolic \
"$OBJ_FN" -o "$WASM_TMP" \
@ -92,7 +93,7 @@ fi
--export-if-defined=__uce_once \
--export-if-defined=__uce_init
"$SDK/bin/llvm-objcopy" --add-section=uce.abi="$ABI_TMP" "$WASM_TMP"
"$SDK/bin/llvm-objcopy" --strip-debug --add-section=uce.abi="$ABI_TMP" "$WASM_TMP"
python3 scripts/check_unit_wasm.py "$WASM_TMP" --abi-version "$ABI_VERSION" --llvm-nm "$SDK/bin/llvm-nm"
mv "$WASM_TMP" "$DEST_DIR/$WASM_FN"

View File

@ -76,5 +76,6 @@ if [[ "$action" == "run" ]]; then
scripts/test_log_timeliness.sh
scripts/test_raw_http_request_log.sh
scripts/test_component_resolution_ttl.sh
scripts/test_unit_export_surface.sh
scripts/test_socket_activation.sh
fi

View File

@ -0,0 +1,79 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
test_name="unit-export-surface-test-$$"
site_directory="${UCE_TEST_SITE_DIRECTORY:-site}"
bin_directory="${UCE_TEST_BIN_DIRECTORY:-/tmp/uce/work}"
if [[ -r /etc/uce/settings.cfg ]]; then
if [[ -z "${UCE_TEST_SITE_DIRECTORY:-}" ]]; then
configured_site_directory=$(awk -F= '/^[[:space:]]*HTTP_DOCUMENT_ROOT[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' /etc/uce/settings.cfg)
site_directory="${configured_site_directory:-$site_directory}"
fi
if [[ -z "${UCE_TEST_BIN_DIRECTORY:-}" ]]; then
configured_bin_directory=$(awk -F= '/^[[:space:]]*BIN_DIRECTORY[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' /etc/uce/settings.cfg)
bin_directory="${configured_bin_directory:-$bin_directory}"
fi
fi
source_dir="$site_directory/$test_name"
artifact_dir=""
cleanup() {
rm -rf "$source_dir"
if [[ -n "$artifact_dir" ]]; then
rm -rf "$artifact_dir"
fi
}
trap cleanup EXIT
mkdir -p "$source_dir"
absolute_source_dir=$(realpath "$source_dir")
artifact_dir="$bin_directory$absolute_source_dir"
printf '%s\n' \
'String visibility_used() { return("private-used"); }' \
'String visibility_unused() { return("uce-private-unused-marker-8f61d2"); }' \
'EXPORT String visibility_shared() { return("shared"); }' \
'CLI(Request& context) { print(visibility_shared(), ":", visibility_used(), ":", component("named:NAMED", context)); }' \
>"$source_dir/entry.uce"
printf '%s\n' \
'String named_used() { return("private-named-used"); }' \
'String named_unused() { return("uce-named-unused-marker-4ae973"); }' \
'EXPORT String named_shared() { return("named-export"); }' \
'COMPONENT:NAMED(Request& context) { print(named_shared(), ":", named_used()); }' \
>"$source_dir/named.uce"
output=$(scripts/uce-cli "/$test_name/entry.uce")
if [[ "$output" != "shared:private-used:named-export:private-named-used" ]]; then
echo "unit export surface runtime failed: $output" >&2
exit 1
fi
entry_wasm="$artifact_dir/entry.uce.wasm"
named_wasm="$artifact_dir/named.uce.wasm"
python3 - "$entry_wasm" "$named_wasm" <<'PY'
import sys
from pathlib import Path
from scripts.check_unit_wasm import collect
cases = [
(Path(sys.argv[1]), {"__wasm_call_ctors", "__uce_set_current_request", "__uce_cli", "visibility_shared"}, b"uce-private-unused-marker-8f61d2"),
(Path(sys.argv[2]), {"__wasm_call_ctors", "__uce_set_current_request", "__uce_component_NAMED", "named_shared"}, b"uce-named-unused-marker-4ae973"),
]
for path, allowed, unused_marker in cases:
data = path.read_bytes()
_, imports, exports = collect(path)
names = {name for name, _ in exports}
unexpected = names - allowed
missing = allowed - names
if unexpected or missing:
raise SystemExit(f"{path}: unexpected exports={sorted(unexpected)} missing={sorted(missing)} all={sorted(names)}")
if unused_marker in data:
raise SystemExit(f"{path}: retained unused private code marker")
if len(imports) >= 40:
raise SystemExit(f"{path}: retained {len(imports)} imports (expected fewer than 40)")
if path.stat().st_size >= 1024 * 1024:
raise SystemExit(f"{path}: artifact is {path.stat().st_size} bytes (expected under 1 MiB)")
PY
echo "unit export surface passed"

View File

@ -16,7 +16,7 @@
namespace {
const u64 UCE_UNIT_ABI_VERSION = 8;
const u64 UCE_UNIT_ABI_VERSION = 9;
struct SharedUnitFilesystemState
{

View File

@ -1,13 +1,19 @@
#pragma once
#define RENDER(X) extern "C" void __uce_render(X)
#define COMPONENT(X) extern "C" void __uce_component(X)
#define ONCE(X) extern "C" void __uce_once(X)
#define INIT(X) extern "C" void __uce_init(X)
#define WS(X) extern "C" void __uce_websocket(X)
#define CLI(X) extern "C" void __uce_cli(X)
#define SERVE_HTTP(X) extern "C" void __uce_serve_http(X)
#define EXPORT extern "C"
#if defined(__UCE_WASM_UNIT__)
#define UCE_UNIT_EXPORT __attribute__((visibility("default")))
#else
#define UCE_UNIT_EXPORT
#endif
#define RENDER(X) extern "C" UCE_UNIT_EXPORT void __uce_render(X)
#define COMPONENT(X) extern "C" UCE_UNIT_EXPORT void __uce_component(X)
#define ONCE(X) extern "C" UCE_UNIT_EXPORT void __uce_once(X)
#define INIT(X) extern "C" UCE_UNIT_EXPORT void __uce_init(X)
#define WS(X) extern "C" UCE_UNIT_EXPORT void __uce_websocket(X)
#define CLI(X) extern "C" UCE_UNIT_EXPORT void __uce_cli(X)
#define SERVE_HTTP(X) extern "C" UCE_UNIT_EXPORT void __uce_serve_http(X)
#define EXPORT extern "C" UCE_UNIT_EXPORT
String preprocess_shared_unit(Request* context, SharedUnit* su);
String compiler_generated_cpp_path(Request* context, String source_file);