bc2f26ddfc
ISHIKAWA: why did wraps_body need compile-time knowledge? Because the wrapper
called the target directly. If the wrapper calls through the seam instead, the
seam can call the body itself, and a construct bound after the build decides
how and whether to invoke it.
PREDICTIONS AND RESULTS
P1 wrap becomes runtime-bindable TRUE body x3 -> 21,
never invoked -> 111
P2 codegen shrinks TRUE 5042 -> 4977
P3 cost 5-10% from an indirect call on every fn TRUE 0.36s -> 0.39s, ~8%
P4 zero-param fns break on the empty struct TRUE empty struct is a GNU
extension, empty init
is C23. Fixed with a
char field.
P5 fixpoint holds TRUE
PROCESS FAILURE worth recording: my first patch silently did not apply because
I dropped the assert on the string replacement. The build then failed with
"undeclared identifier __thunk_noargs", which I nearly attributed to the
empty-struct prediction. The guard that would have caught it existed and I
removed it -- the same shape as every other defect found tonight.
Removed: declare_wrap, decorator_wrap, cg_wrap_target, cg_wrap_construct,
params_to_call_args, and the wraps_body scanner branch.
prohibits_outside is now the ONLY construct kind left at compile time, and it
cannot move: a #error has no runtime.
98 lines
3.7 KiB
Bash
Executable File
98 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# seam_binding.sh — integration control for the runtime construct seam.
|
|
#
|
|
# The seam's whole claim is that a construct declared AFTER a binary exists
|
|
# applies to that already-built program. That cannot be checked by
|
|
# compile_capture, which only sees emitted text: it needs a built binary, a
|
|
# linked target, and an environment. Hence a harness rather than a unit test.
|
|
#
|
|
# usage: seam_binding.sh <elc-binary> [lang-dir]
|
|
# exit 0 = all assertions held; non-zero = number of failures
|
|
set -uo pipefail
|
|
ELC="${1:?usage: seam_binding.sh <elc-binary> [lang-dir]}"
|
|
LANG_DIR="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
|
FAILS=0
|
|
|
|
ok() { printf ' ok %s\n' "$1"; }
|
|
fail() { printf ' FAIL %s\n expected: %s\n actual: %s\n' "$1" "$2" "$3"; FAILS=$((FAILS+1)); }
|
|
check(){ [ "$2" = "$3" ] && ok "$1" || fail "$1" "$2" "$3"; }
|
|
|
|
SRCS=$("$LANG_DIR/../scripts/el-runtime-sources.sh" "$LANG_DIR/runtime")
|
|
CFLAGS="-std=c11 -O2 -rdynamic -I $LANG_DIR/runtime"
|
|
for d in /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3; do
|
|
[ -d "$d" ] && CFLAGS="$CFLAGS -I $d/include" && LDFLAGS="-L $d/lib"
|
|
done
|
|
LDFLAGS="${LDFLAGS:-} -lcurl -lssl -lcrypto -lpthread -lm"
|
|
|
|
# A construct target that is LINKED but never referenced from El source.
|
|
cat > "$WORK/targets.c" <<'EOF'
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
typedef int64_t el_val_t;
|
|
el_val_t observe(el_val_t fn, el_val_t con, el_val_t r){
|
|
printf("SEEN %s/%s\n", (const char*)(intptr_t)fn, (const char*)(intptr_t)con);
|
|
return r; /* zero = do not refuse */
|
|
}
|
|
el_val_t double_result(el_val_t fn, el_val_t con, el_val_t r){
|
|
(void)fn; (void)con; return r * 2; /* exit: replace the result */
|
|
}
|
|
el_val_t refuse(el_val_t fn, el_val_t con, el_val_t r){
|
|
(void)fn; (void)con; (void)r; return 42; /* non-zero = short-circuit */
|
|
}
|
|
EOF
|
|
|
|
# A program with NO construct anywhere in its source.
|
|
cat > "$WORK/prog.el" <<'EOF'
|
|
fn work() -> Int {
|
|
return 7
|
|
}
|
|
|
|
fn main() {
|
|
println(int_to_str(work()))
|
|
}
|
|
EOF
|
|
|
|
"$ELC" "$WORK/prog.el" > "$WORK/prog.c" 2>/dev/null
|
|
cc $CFLAGS -o "$WORK/prog" "$WORK/prog.c" "$WORK/targets.c" $SRCS $LDFLAGS 2>/dev/null \
|
|
|| { echo " FAIL probe did not build"; exit 1; }
|
|
|
|
check "unbound program is unaffected" \
|
|
"7" "$(cd "$WORK" && ./prog 2>&1)"
|
|
|
|
printf 'work audited entry observe\n' > "$WORK/observe.txt"
|
|
check "a construct declared AFTER the build applies" \
|
|
"SEEN work/audited
|
|
7" "$(cd "$WORK" && EL_CONSTRUCTS=observe.txt ./prog 2>&1)"
|
|
|
|
printf 'work denied entry refuse\n' > "$WORK/refuse.txt"
|
|
check "a construct declared after the build can REFUSE" \
|
|
"42" "$(cd "$WORK" && EL_CONSTRUCTS=refuse.txt ./prog 2>&1)"
|
|
|
|
printf 'work ghost entry no_such_symbol_anywhere\n' > "$WORK/ghost.txt"
|
|
check "an unlinked target is skipped, not fatal" \
|
|
"7" "$(cd "$WORK" && EL_CONSTRUCTS=ghost.txt ./prog 2>&1)"
|
|
|
|
printf 'other_fn x entry refuse\n' > "$WORK/other.txt"
|
|
check "a binding for a different fn does not fire" \
|
|
"7" "$(cd "$WORK" && EL_CONSTRUCTS=other.txt ./prog 2>&1)"
|
|
|
|
printf 'work a entry observe\nwork b entry observe\n' > "$WORK/two.txt"
|
|
check "two constructs compose on one crossing" \
|
|
"SEEN work/a
|
|
SEEN work/b
|
|
7" "$(cd "$WORK" && EL_CONSTRUCTS=two.txt ./prog 2>&1)"
|
|
|
|
cat >> "$WORK/targets.c" <<'TGT'
|
|
el_val_t thrice(el_val_t fn, el_val_t con, el_val_t (*b)(void*), void* e){
|
|
(void)fn; (void)con; return b(e) + b(e) + b(e); /* wrap: invoke N times */
|
|
}
|
|
TGT
|
|
printf 'work doubler exit double_result\n' > "$WORK/exit.txt"
|
|
check "an EXIT construct declared after the build replaces the result" \
|
|
"14" "$(cd "$WORK" && EL_CONSTRUCTS=exit.txt ./prog 2>&1)"
|
|
|
|
echo
|
|
echo " 7 assertions, $((7-FAILS)) passed, $FAILS failed"
|
|
exit $FAILS
|