repair a broken chain link: rerun cycle 18 rather than reconstruct it #167
@@ -1,6 +1,21 @@
|
||||
# async — half expressible, and the cycle that was dogma
|
||||
|
||||
**Status: measured on a branch, not merged. Two runs — the first was invalid.**
|
||||
**Status: replicated and corroborated. Three runs — the first was invalid.**
|
||||
|
||||
> **Chain of custody note, 2026-08-17.** The original measurements were produced
|
||||
> by a C stub written in `/tmp`, and that artifact was destroyed when the session
|
||||
> worktrees were removed. For a period this file asserted results with nothing
|
||||
> behind them — a claim inside an evidence record, which is the defect that turns
|
||||
> a chain into a pile. It was **rerun**, not reconstructed: reconstructing the
|
||||
> missing file would have been a fabrication with a fresh timestamp.
|
||||
>
|
||||
> The fixture now lives at `lang/tests/integration/fixtures/future.c` and the
|
||||
> harness at `lang/tests/integration/async_future.sh`, so a third party can
|
||||
> reproduce this without taking my word for it. **6/6.**
|
||||
>
|
||||
> The replication is labelled as such: the outcomes were already known when the
|
||||
> harness was written, so its expectations are not predictions committed in
|
||||
> advance. Its value is reproducibility, not foresight.
|
||||
|
||||
## The first attempt was DOGMA, not science
|
||||
|
||||
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# async_future.sh — REPLICATION of cycle 18.
|
||||
#
|
||||
# STATUS: replication, not a blind test. The outcomes were already observed on
|
||||
# 2026-08-17 before this harness existed, so the expectations below are not
|
||||
# predictions committed in advance. Its evidentiary value is that the artifact
|
||||
# lives in the repository and a third party can run it — not that it was called
|
||||
# ahead of time. The original run's artifact was written in /tmp and lost when
|
||||
# the worktrees were removed, which broke the chain; this replaces the claim
|
||||
# with something reproducible rather than reconstructing the missing file.
|
||||
#
|
||||
# CLAIM UNDER TEST: @async requires no compiler change. A future is one more
|
||||
# magic-tagged heap object, and el_seam_wrap lets a construct bound AFTER the
|
||||
# build decide whether and when to invoke the body.
|
||||
set -uo pipefail
|
||||
ELC="${1:?usage: async_future.sh <elc>}"
|
||||
LANG_DIR="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/..}"
|
||||
LANG_DIR="$(cd "$LANG_DIR" && pwd)"
|
||||
FIX="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/fixtures/future.c"
|
||||
W=$(mktemp -d); trap 'rm -rf "$W"' EXIT; F=0
|
||||
chk(){ [ "$2" = "$3" ] && printf ' ok %s\n' "$1" || { printf ' FAIL %s\n expected %s got %s\n' "$1" "$2" "$3"; F=$((F+1)); }; }
|
||||
cd "$LANG_DIR"
|
||||
SRCS=$(../scripts/el-runtime-sources.sh runtime)
|
||||
CF="-std=c11 -O2 -rdynamic -I runtime"; LF=""
|
||||
for d in /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3; do
|
||||
[ -d "$d" ] && CF="$CF -I $d/include" && LF="-L $d/lib"
|
||||
done
|
||||
LF="$LF -lcurl -lssl -lcrypto -lpthread -lm"
|
||||
|
||||
cat > "$W/p.el" <<'EOF'
|
||||
extern fn el_await(h: Int) -> Int
|
||||
|
||||
fn work(k: Int) -> Int {
|
||||
return k * 2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let h: Int = work(21)
|
||||
println("CALLER_CONTINUED")
|
||||
let r: Int = el_await(h)
|
||||
println("RESULT " + int_to_str(r))
|
||||
}
|
||||
EOF
|
||||
"$ELC" "$W/p.el" > "$W/p.c" 2>/dev/null
|
||||
cc $CF -o "$W/p" "$W/p.c" "$FIX" $SRCS $LF 2>/dev/null || { echo " FAIL probe did not build"; exit 1; }
|
||||
|
||||
out=$(cd "$W" && ./p 2>&1); rc=$?
|
||||
chk "unbound: no construct, synchronous, correct result" "0" "$rc"
|
||||
chk "unbound: el_await on a non-future passes through, no crash" "1" "$(echo "$out" | grep -c '^RESULT 42$')"
|
||||
|
||||
printf 'work async wrap defer\n' > "$W/c.txt"
|
||||
out=$(cd "$W" && EL_CONSTRUCTS=c.txt ./p 2>&1); rc=$?
|
||||
chk "bound: does not crash" "0" "$rc"
|
||||
chk "bound: the awaited result is correct" "1" "$(echo "$out" | grep -c '^RESULT 42$')"
|
||||
|
||||
wrap=$(echo "$out" | awk '/^WRAP_RETURNED/{print $2}')
|
||||
bend=$(echo "$out" | awk '/^BODY_END/{print $2}')
|
||||
caller_before_body_end=$(echo "$out" | awk '/CALLER_CONTINUED/{c=NR} /^BODY_END/{b=NR} END{print (c<b)?1:0}')
|
||||
chk "bound: the caller continues BEFORE the body finishes" "1" "$caller_before_body_end"
|
||||
chk "bound: the wrap returns in under 10ms while the body takes 50ms" "1" "$([ "${wrap:-999999}" -lt 10000 ] && [ "${bend:-0}" -gt 40000 ] && echo 1 || echo 0)"
|
||||
|
||||
echo; echo " 6 assertions, $((6-F)) passed, $F failed"; exit $F
|
||||
@@ -0,0 +1,66 @@
|
||||
/* future.c — a FUTURE as one more magic-tagged heap object.
|
||||
*
|
||||
* Fixture for tests/integration/async_future.sh. Linked into the probe but
|
||||
* never referenced from El source: everything here is reached only by binding
|
||||
* a construct AFTER the binary exists.
|
||||
*
|
||||
* The claim under test: @async needs no compiler change. el_val_t already
|
||||
* carries List, Map, Geometry, Manifold and Bin as magic-tagged heap pointers;
|
||||
* a future is one more, and el_seam_wrap hands the target the body so it can
|
||||
* decide whether and when to invoke it.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
#define EL_MAGIC_FUT 0xE1F07000u
|
||||
typedef struct { uint32_t magic; pthread_t th; el_val_t result; int done;
|
||||
el_val_t (*body)(void*); void* env; } ElFuture;
|
||||
|
||||
static long t0_us;
|
||||
static long now_us(void){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts);
|
||||
return ts.tv_sec*1000000L + ts.tv_nsec/1000; }
|
||||
|
||||
static void* fut_runner(void* v){
|
||||
ElFuture* f = (ElFuture*)v;
|
||||
printf("BODY_START %ld\n", now_us()-t0_us);
|
||||
usleep(50000); /* 50ms, so interleaving is visible */
|
||||
f->result = f->body(f->env);
|
||||
f->done = 1;
|
||||
printf("BODY_END %ld\n", now_us()-t0_us);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* wraps_body target: returns the HANDLE immediately, never the result */
|
||||
el_val_t defer(el_val_t fn, el_val_t con, el_val_t (*b)(void*), void* e){
|
||||
(void)fn; (void)con;
|
||||
t0_us = now_us();
|
||||
ElFuture* f = calloc(1,sizeof(ElFuture));
|
||||
f->magic = EL_MAGIC_FUT; f->body = b; f->env = e;
|
||||
pthread_create(&f->th, NULL, fut_runner, f);
|
||||
printf("WRAP_RETURNED %ld\n", now_us()-t0_us);
|
||||
return (el_val_t)(intptr_t)f;
|
||||
}
|
||||
|
||||
/* el_await — block on the handle and yield the real result.
|
||||
*
|
||||
* NEVER dereference to decide whether a slot is a pointer. el_val_t carries
|
||||
* integers too, so reading ->magic off an integer dereferences that integer AS
|
||||
* AN ADDRESS. The first version of this function did exactly that and
|
||||
* SIGSEGV'd on the unbound path -- sixty seconds after the same defect was
|
||||
* diagnosed elsewhere in the runtime. Check the floor and alignment first. */
|
||||
el_val_t el_await(el_val_t h){
|
||||
if (h < 0x10000) return h; /* small ints / low addresses */
|
||||
if (h & 0x7) return h; /* malloc returns 8-aligned */
|
||||
ElFuture* f = (ElFuture*)(intptr_t)h;
|
||||
if (f->magic != EL_MAGIC_FUT) return h; /* safe to read now */
|
||||
pthread_join(f->th, NULL);
|
||||
el_val_t r = f->result;
|
||||
free(f);
|
||||
return r;
|
||||
}
|
||||
Reference in New Issue
Block a user