W3
This commit is contained in:
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# Build the production W1 UCE WASM core from the real runtime carve-out.
|
||||
# Run on k-uce from any working directory.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
SDK=${WASI_SDK:-/opt/wasi-sdk}
|
||||
OUT=${UCE_WASM_OUT:-/tmp/uce/wasm-w1}
|
||||
mkdir -p "$OUT" bin/wasm
|
||||
|
||||
if [ ! -x "$SDK/bin/clang++" ]; then
|
||||
echo "wasi-sdk clang++ not found; set WASI_SDK" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$SDK/bin/clang++" --target=wasm32-wasip1 -mexec-model=reactor \
|
||||
-O1 -g -std=c++20 -fno-exceptions -fno-rtti \
|
||||
-D__UCE_WASM_CORE__ \
|
||||
-I. -Isrc/lib \
|
||||
src/wasm/core.cpp -o "$OUT/core.wasm" \
|
||||
-Wl,--export-all \
|
||||
-Wl,--export=__heap_base \
|
||||
-Wl,--export=__stack_pointer \
|
||||
-Wl,--import-table \
|
||||
-Wl,--allow-undefined-file=src/wasm/core_hostcalls.syms \
|
||||
-Wl,--no-entry \
|
||||
$(sed "s/^/-Wl,--export-if-defined=/" src/wasm/core_libc_exports.syms | tr "\n" " ")
|
||||
|
||||
cp "$OUT/core.wasm" bin/wasm/core.wasm
|
||||
ls -lh "$OUT/core.wasm" bin/wasm/core.wasm
|
||||
+1
-1
@@ -26,7 +26,7 @@ OPT_FLAG="O0"
|
||||
|
||||
COMPILER="clang++"
|
||||
#COMPILER="g++"
|
||||
FLAGS="-shared -g -rdynamic -w -Wall -$OPT_FLAG -std=c++20 -fpermissive -ffast-math -fPIC"
|
||||
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\""
|
||||
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
# Compile a preprocessed UCE unit into a PIC WebAssembly side module.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
cd ..
|
||||
|
||||
SRC_DIR="$1"
|
||||
DEST_DIR="$2"
|
||||
SRC_FN="$3"
|
||||
PP_FN="$4"
|
||||
WASM_FN="$5"
|
||||
|
||||
SDK=${WASI_SDK:-/opt/wasi-sdk}
|
||||
ABI_VERSION=${UCE_UNIT_ABI_VERSION:-6}
|
||||
ROOT=$(pwd)
|
||||
OBJ_FN="$DEST_DIR/$PP_FN.wasm.o"
|
||||
ABI_TMP="$DEST_DIR/$PP_FN.uce-abi.txt"
|
||||
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
|
||||
-O1 -g -std=c++20
|
||||
# must match the core build ABI: units with RTTI/EH enabled import
|
||||
# typeinfo/unwind symbols the -fno-rtti/-fno-exceptions core cannot provide
|
||||
-fno-exceptions -fno-rtti
|
||||
-D__UCE_WASM_UNIT__
|
||||
-DPLATFORM_NAME=\"wasm32-wasip1\"
|
||||
)
|
||||
|
||||
if [ ! -x "$SDK/bin/clang++" ] || [ ! -x "$SDK/bin/wasm-ld" ] || [ ! -x "$SDK/bin/llvm-objcopy" ]; then
|
||||
echo "wasi-sdk tools not found; set WASI_SDK" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOOLCHAIN_ID=$(${SDK}/bin/clang++ --version | head -n 1)
|
||||
HEADER_HASH=$(find src/lib -maxdepth 1 -name '*.h' -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum | cut -c1-16)
|
||||
FLAGS_HASH=$(printf '%s\0' "${COMMON_FLAGS[@]}" -Isrc/lib | sha1sum | cut -c1-16)
|
||||
PCH_KEY=$(printf '%s\n%s\n%s\n%s\n' "$ABI_VERSION" "$TOOLCHAIN_ID" "$HEADER_HASH" "$FLAGS_HASH" | sha1sum | cut -c1-16)
|
||||
PCH_FN="$PCH_DIR/uce_lib-wasm-unit-$PCH_KEY.pch"
|
||||
|
||||
mkdir -p "$DEST_DIR" >/dev/null 2>&1
|
||||
|
||||
build_pch_if_needed() {
|
||||
if [ "$PCH_ENABLED" = "0" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$PCH_DIR"
|
||||
if [ -s "$PCH_FN" ]; then
|
||||
return 0
|
||||
fi
|
||||
"$SDK/bin/clang++" "${COMMON_FLAGS[@]}" \
|
||||
-Isrc/lib \
|
||||
-x c++-header src/lib/uce_lib.h -o "$PCH_FN.tmp"
|
||||
mv "$PCH_FN.tmp" "$PCH_FN"
|
||||
}
|
||||
|
||||
cat > "$ABI_TMP" <<EOF
|
||||
format=uce-wasm-unit-abi-v1
|
||||
unit_abi_version=$ABI_VERSION
|
||||
toolchain=$TOOLCHAIN_ID
|
||||
source=$SRC_FN
|
||||
EOF
|
||||
|
||||
build_pch_if_needed
|
||||
PCH_FLAGS=()
|
||||
if [ "$PCH_ENABLED" != "0" ]; then
|
||||
PCH_FLAGS=(-include-pch "$PCH_FN")
|
||||
fi
|
||||
|
||||
"$SDK/bin/clang++" "${COMMON_FLAGS[@]}" \
|
||||
-I"$SRC_DIR" -I"$ROOT/src/lib" \
|
||||
"${PCH_FLAGS[@]}" \
|
||||
-c "$DEST_DIR/$PP_FN" -o "$OBJ_FN"
|
||||
|
||||
"$SDK/bin/wasm-ld" -shared --experimental-pic \
|
||||
--unresolved-symbols=import-dynamic \
|
||||
--Bsymbolic \
|
||||
"$OBJ_FN" -o "$DEST_DIR/$WASM_FN" \
|
||||
--export-if-defined=__uce_set_current_request \
|
||||
--export-if-defined=__uce_render \
|
||||
--export-if-defined=__uce_component \
|
||||
--export-if-defined=__uce_websocket \
|
||||
--export-if-defined=__uce_cli \
|
||||
--export-if-defined=__uce_serve_http \
|
||||
--export-if-defined=__uce_once \
|
||||
--export-if-defined=__uce_init
|
||||
|
||||
"$SDK/bin/llvm-objcopy" --add-section=uce.abi="$ABI_TMP" "$DEST_DIR/$WASM_FN"
|
||||
|
||||
python3 scripts/wasm/check_unit_wasm.py "$DEST_DIR/$WASM_FN" --abi-version "$ABI_VERSION" --llvm-nm "$SDK/bin/llvm-nm"
|
||||
|
||||
rm -f "$OBJ_FN" "$ABI_TMP"
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# Build the W1 host smoke driver. Run on k-uce.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
OUT=${UCE_WASM_OUT:-/tmp/uce/wasm-w1}
|
||||
WASMTIME_HOME=${WASMTIME_HOME:-/opt/wasmtime}
|
||||
WASMTIME_INCLUDE=${WASMTIME_INCLUDE:-$WASMTIME_HOME/include}
|
||||
WASMTIME_LIB=${WASMTIME_LIB:-$WASMTIME_HOME/lib}
|
||||
mkdir -p "$OUT"
|
||||
|
||||
if [ ! -d "$WASMTIME_INCLUDE" ] || [ ! -d "$WASMTIME_LIB" ]; then
|
||||
echo "Wasmtime C API not found; set WASMTIME_HOME or WASMTIME_INCLUDE/WASMTIME_LIB" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
g++ -std=c++17 -O2 -Wall -Wextra \
|
||||
-I"$WASMTIME_INCLUDE" \
|
||||
src/wasm/w1_smoke.cpp \
|
||||
-L"$WASMTIME_LIB" \
|
||||
-Wl,-rpath,"$WASMTIME_LIB" \
|
||||
-lwasmtime \
|
||||
-o "$OUT/w1_smoke"
|
||||
|
||||
ls -lh "$OUT/w1_smoke"
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
# Batch-build W2 wasm side modules for already-known/generated UCE units.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
BIN_DIR=${UCE_BIN_DIRECTORY:-/tmp/uce/work}
|
||||
KNOWN_FILE=${UCE_KNOWN_UNITS_FILE:-$BIN_DIR/known-uce-files.txt}
|
||||
MIN_UNITS=${UCE_W2_MIN_UNITS:-1}
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
UNITS=("$@")
|
||||
else
|
||||
if [ ! -f "$KNOWN_FILE" ]; then
|
||||
echo "known unit registry not found: $KNOWN_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
mapfile -t UNITS < <(grep -v '^[[:space:]]*$' "$KNOWN_FILE")
|
||||
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"
|
||||
pp_fn="$base.cpp"
|
||||
wasm_fn="$base.wasm"
|
||||
if [ ! -f "$dest_dir/$pp_fn" ]; then
|
||||
echo "missing preprocessed unit: $dest_dir/$pp_fn" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -s "$dest_dir/$wasm_fn" ] && [ "$dest_dir/$wasm_fn" -nt "$dest_dir/$pp_fn" ] && [ "$dest_dir/$wasm_fn" -nt "$unit" ]; then
|
||||
scripts/wasm/check_unit_wasm.py "$dest_dir/$wasm_fn"
|
||||
skipped=$((skipped + 1))
|
||||
else
|
||||
scripts/compile_wasm_unit "$src_dir" "$dest_dir" "$unit" "$pp_fn" "$wasm_fn"
|
||||
count=$((count + 1))
|
||||
fi
|
||||
checked=$((checked + 1))
|
||||
done
|
||||
|
||||
if [ "$checked" -lt "$MIN_UNITS" ]; then
|
||||
echo "checked only $checked wasm units, expected at least $MIN_UNITS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "W2 batch wasm units: checked=$checked compiled=$count reused=$skipped"
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# Build the W3 workspace-runtime CLI driver. Run on k-uce.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
OUT=${UCE_WASM_OUT:-/tmp/uce/wasm-w3}
|
||||
WASMTIME_HOME=${WASMTIME_HOME:-/opt/wasmtime}
|
||||
mkdir -p "$OUT"
|
||||
|
||||
g++ -std=c++20 -O1 -g -w \
|
||||
-Isrc/lib -Isrc/wasm \
|
||||
-I"$WASMTIME_HOME/include" \
|
||||
src/wasm/w3_driver.cpp \
|
||||
-L"$WASMTIME_HOME/lib" \
|
||||
-Wl,-rpath,"$WASMTIME_HOME/lib" \
|
||||
-lwasmtime -lpcre2-8 -lpthread -ldl \
|
||||
-o "$OUT/w3_driver"
|
||||
|
||||
ls -lh "$OUT/w3_driver"
|
||||
Executable
+227
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate a W2 UCE PIC unit wasm artifact.
|
||||
|
||||
Checks intentionally stay small and explicit: section walk for dylink.0,
|
||||
uce.abi, imports/exports, plus llvm-nm for allocator definitions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def read_u32leb(data: bytes, pos: int) -> tuple[int, int]:
|
||||
result = 0
|
||||
shift = 0
|
||||
while True:
|
||||
if pos >= len(data):
|
||||
raise ValueError("truncated leb128")
|
||||
b = data[pos]
|
||||
pos += 1
|
||||
result |= (b & 0x7F) << shift
|
||||
if (b & 0x80) == 0:
|
||||
return result, pos
|
||||
shift += 7
|
||||
if shift > 35:
|
||||
raise ValueError("oversized leb128")
|
||||
|
||||
|
||||
def read_name(data: bytes, pos: int) -> tuple[str, int]:
|
||||
n, pos = read_u32leb(data, pos)
|
||||
end = pos + n
|
||||
if end > len(data):
|
||||
raise ValueError("truncated name")
|
||||
return data[pos:end].decode("utf-8", "replace"), end
|
||||
|
||||
|
||||
def walk_sections(data: bytes):
|
||||
if not data.startswith(b"\0asm\x01\0\0\0"):
|
||||
raise ValueError("not a wasm v1 module")
|
||||
pos = 8
|
||||
while pos < len(data):
|
||||
section_id = data[pos]
|
||||
pos += 1
|
||||
size, pos = read_u32leb(data, pos)
|
||||
end = pos + size
|
||||
if end > len(data):
|
||||
raise ValueError("section extends past EOF")
|
||||
payload = data[pos:end]
|
||||
yield section_id, payload
|
||||
pos = end
|
||||
|
||||
|
||||
def parse_imports(payload: bytes):
|
||||
pos = 0
|
||||
count, pos = read_u32leb(payload, pos)
|
||||
imports = []
|
||||
for _ in range(count):
|
||||
module, pos = read_name(payload, pos)
|
||||
name, pos = read_name(payload, pos)
|
||||
if pos >= len(payload):
|
||||
raise ValueError("truncated import kind")
|
||||
kind = payload[pos]
|
||||
pos += 1
|
||||
# Skip type descriptors. We only need module/name/kind for W2 policy.
|
||||
if kind == 0: # func type index
|
||||
_, pos = read_u32leb(payload, pos)
|
||||
elif kind == 1: # table
|
||||
if pos >= len(payload): raise ValueError("truncated table import")
|
||||
pos += 1
|
||||
flags, pos = read_u32leb(payload, pos)
|
||||
_, pos = read_u32leb(payload, pos)
|
||||
if flags & 1: _, pos = read_u32leb(payload, pos)
|
||||
elif kind == 2: # memory
|
||||
flags, pos = read_u32leb(payload, pos)
|
||||
_, pos = read_u32leb(payload, pos)
|
||||
if flags & 1: _, pos = read_u32leb(payload, pos)
|
||||
elif kind == 3: # global
|
||||
pos += 2
|
||||
else:
|
||||
raise ValueError(f"unknown import kind {kind}")
|
||||
imports.append((module, name, kind))
|
||||
return imports
|
||||
|
||||
|
||||
def parse_exports(payload: bytes):
|
||||
pos = 0
|
||||
count, pos = read_u32leb(payload, pos)
|
||||
exports = []
|
||||
for _ in range(count):
|
||||
name, pos = read_name(payload, pos)
|
||||
if pos >= len(payload):
|
||||
raise ValueError("truncated export kind")
|
||||
kind = payload[pos]
|
||||
pos += 1
|
||||
_, pos = read_u32leb(payload, pos)
|
||||
exports.append((name, kind))
|
||||
return exports
|
||||
|
||||
|
||||
def collect(path: Path):
|
||||
data = path.read_bytes()
|
||||
customs: dict[str, list[bytes]] = {}
|
||||
imports = []
|
||||
exports = []
|
||||
for section_id, payload in walk_sections(data):
|
||||
if section_id == 0:
|
||||
name, pos = read_name(payload, 0)
|
||||
customs.setdefault(name, []).append(payload[pos:])
|
||||
elif section_id == 2:
|
||||
imports = parse_imports(payload)
|
||||
elif section_id == 7:
|
||||
exports = parse_exports(payload)
|
||||
return customs, imports, exports
|
||||
|
||||
|
||||
def dylink_has_valid_mem_info(payload: bytes) -> bool:
|
||||
pos = 0
|
||||
while pos < len(payload):
|
||||
subsection_id = payload[pos]
|
||||
pos += 1
|
||||
size, pos = read_u32leb(payload, pos)
|
||||
end = pos + size
|
||||
if end > len(payload):
|
||||
raise ValueError("dylink.0 subsection extends past section")
|
||||
if subsection_id == 1:
|
||||
mem_size, p = read_u32leb(payload, pos)
|
||||
mem_align, p = read_u32leb(payload, p)
|
||||
table_size, p = read_u32leb(payload, p)
|
||||
table_align, p = read_u32leb(payload, p)
|
||||
if p > end:
|
||||
raise ValueError("truncated dylink.0 mem_info")
|
||||
return mem_align < 32 and table_align < 32 and mem_size < (1 << 31) and table_size < (1 << 31)
|
||||
pos = end
|
||||
return False
|
||||
|
||||
|
||||
def defined_symbols(path: Path, llvm_nm: str) -> list[str]:
|
||||
proc = subprocess.run([llvm_nm, "--defined-only", str(path)], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(proc.stderr.strip() or "llvm-nm failed")
|
||||
symbols = []
|
||||
for line in proc.stdout.splitlines():
|
||||
parts = line.split()
|
||||
if parts:
|
||||
symbols.append(parts[-1])
|
||||
return symbols
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("wasm", type=Path)
|
||||
ap.add_argument("--abi-version", default="6")
|
||||
ap.add_argument("--llvm-nm", default=None)
|
||||
ap.add_argument("--verbose", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
try:
|
||||
customs, imports, exports = collect(args.wasm)
|
||||
errors = []
|
||||
dylink_payloads = customs.get("dylink.0", [])
|
||||
if not dylink_payloads:
|
||||
errors.append("missing dylink.0 custom section")
|
||||
elif not any(dylink_has_valid_mem_info(payload) for payload in dylink_payloads):
|
||||
errors.append("dylink.0 missing valid mem_info subsection")
|
||||
abi_payloads = customs.get("uce.abi", [])
|
||||
if not abi_payloads:
|
||||
errors.append("missing uce.abi custom section")
|
||||
else:
|
||||
abi_text = abi_payloads[-1].decode("utf-8", "replace")
|
||||
required = ["format=uce-wasm-unit-abi-v1", f"unit_abi_version={args.abi_version}", "toolchain="]
|
||||
for needle in required:
|
||||
if needle not in abi_text:
|
||||
errors.append(f"uce.abi missing {needle!r}")
|
||||
export_names = {name for name, _ in exports}
|
||||
forbidden_exports = {"uce_alloc", "uce_free"}
|
||||
for name in sorted(export_names & forbidden_exports):
|
||||
errors.append(f"forbidden allocator export {name}")
|
||||
import_map = {(module, name): kind for module, name, kind in imports}
|
||||
required_imports = {
|
||||
("env", "memory"): 2,
|
||||
("env", "__memory_base"): 3,
|
||||
}
|
||||
# units without indirect calls / stack spills / table needs
|
||||
# legitimately omit these; if present, the kind must be right
|
||||
optional_imports = {
|
||||
("env", "__indirect_function_table"): 1,
|
||||
("env", "__stack_pointer"): 3,
|
||||
("env", "__table_base"): 3,
|
||||
}
|
||||
for key, kind in required_imports.items():
|
||||
if import_map.get(key) != kind:
|
||||
errors.append(f"missing required import {key[0]}.{key[1]}")
|
||||
for key, kind in optional_imports.items():
|
||||
if key in import_map and import_map[key] != kind:
|
||||
errors.append(f"wrong kind for import {key[0]}.{key[1]}")
|
||||
for module, name, kind in imports:
|
||||
if module.startswith("wasi_") or module == "wasi_snapshot_preview1":
|
||||
errors.append(f"forbidden WASI import {module}.{name}")
|
||||
if module not in {"env", "GOT.mem", "GOT.func"} and not module.startswith("GOT."):
|
||||
errors.append(f"unexpected import module {module}.{name}")
|
||||
if module.startswith("GOT.") and kind != 3:
|
||||
errors.append(f"GOT import is not a global: {module}.{name}")
|
||||
llvm_nm = args.llvm_nm or shutil.which("llvm-nm") or "/opt/wasi-sdk/bin/llvm-nm"
|
||||
if Path(llvm_nm).exists():
|
||||
bad_prefixes = ("_Znwm", "_Znam", "_ZdlPv", "_ZdaPv", "_ZdlPvm", "_ZdaPvm")
|
||||
for sym in defined_symbols(args.wasm, llvm_nm):
|
||||
if sym in {"uce_alloc", "uce_free"} or sym.startswith(bad_prefixes):
|
||||
errors.append(f"forbidden allocator definition {sym}")
|
||||
else:
|
||||
errors.append("llvm-nm not found; cannot verify allocator definitions")
|
||||
if errors:
|
||||
for e in errors:
|
||||
print(f"ERROR: {e}", file=sys.stderr)
|
||||
return 1
|
||||
if args.verbose:
|
||||
print(f"UCE W2 unit check PASS: {args.wasm}")
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user