285166c25c
ISHIKAWA: why did exit injection still need compile-time knowledge? Because the
body-helper wrapper was only emitted when codegen already knew an exit
construct existed. The wrapper being conditional was the cause, not the wrapper
being necessary.
PREDICTIONS AND RESULTS
P1 exit becomes runtime-bindable TRUE returns 14, bound
after the build
P2 codegen shrinks TRUE 5094 -> 5044
P3 cost 5-15% from a call frame on every fn FALSE 0.37s -> 0.38s, ~3%
P4 fixpoint holds TRUE
Every fn now gets a body helper and a wrapper. It has to be unconditional:
early returns must route through something for an exit construct to observe
them, and codegen cannot know which fns will be bound after the binary exists.
Removed with the machinery: declare_exit, decorator_exit, cg_exit_target,
cg_exit_construct, and the injects_at_exit scanner branch.
Two controls failed and were rewritten rather than repaired --
no-exit-construct-emits-no-wrapper asserted the optimisation this removes, so
it is now inverted. The integration harness gained a seventh assertion: an exit
construct declared after the build replaces the result.
99/99 native, 7/7 integration, fixpoint gen2==gen3.
93 lines
3.5 KiB
Bash
Executable File
93 lines
3.5 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)"
|
|
|
|
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, $((6-FAILS)) passed, $FAILS failed"
|
|
exit $FAILS
|