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
|
# 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
|
## 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